My main specialty is universal algebra; current projects focus on lattice theory, computational complexity, and universal algebraic approaches to constraint satisfaction problems. Other research interests include logic, category theory, type theory, functional programming, computer-aided mathematical proof and formalization of mathematics.
My research statement summarizes some of my research projects. Some of that information can also be found in this post.
Photo: Ralph Freese describes something about the 3-generated free modular lattice to JB Nation as Peter Mayr looks on. (Hawaii, May, 2018.)
I am fascinated by the connections between programming languages and mathematics, and my most recently initiated research program aims to develop a library of all core definitions and theorems of universal algebra in the Lean proof assistant and programming language. The title of this project is Formal Foundations for Informal Mathematics Research and a detailed project description is available in the document demeo_informal_foundations.pdf.
About a year ago I began collaborating with Peter Mayr (CU Boulder) and Nik Ruskuc (University of St. Andrews) who were interested in knowing when a homomorphism $\varphi \colon \mathbf{F} \to \mathbf{L}$ from a finitely generated free lattice $\mathbf{F}$ onto a finite lattice $\mathbf L$ has a kernel $\ker \varphi$ that is a finitely generated sublattice of $\mathbf{F}^2$. We conjectured that this could be characterized by whether or not the homomorphism is bounded. (See the book by Freese, Jezek and Nation for the definition of a bounded lattice homomorphism.) and I presented a proof of one direction of this conjecture at the Algebras and Lattices in Hawaii conference earlier this year. Last month we proved the converse and thus confirmed our conjecture. All along Mayr and Ruskuc have had in mind an application for the fact that our new result is equivalent to a characterization of \emph{fiber products} of lattices. With the proof of our new characterization theorem complete, we expect to have a manuscript ready for submission by January 2019.
In 2015, I joined a group of 8 other scientists to form a universal algebra research group and secure a 3-year NSF grant for the project, Algebras and Algorithms, Structure and Complexity Theory. Our focus is on fundamental problems at the confluence of logic, algebra, and computer science, and our main goal is to deepen understanding of how to determine the complexity of certain types of computational problems. We focus primarily on classes of algebraic problems whose solutions yield new information about the complexity of CSPs. These include scheduling problems, resource allocation problems, and problems reducible to solving systems of linear equations. CSPs are theoretically solvable, but some are not solvable efficiently. Our work provides procedures for deciding whether a given instance of a CSP is tractable or intractable, and we develop efficient algorithms for finding solutions in the tractable cases.
My work on this project culminated in a 50-page manuscript co-authored with Cliff Bergman and entitled Universal Algebraic Methods for Constraint Satisfaction Problems. This was recently accepted for publication in the journal Logical Methods in Computer Science (LMCS); a draft of the paper resides at arXiv cs.LO 1611.02867.
My teaching experience is extensive, and I love encouraging and mentoring budding mathematicians and computer scientists. I've held academic appointments at four institutions and in each location I started active seminars or research groups.
I have also successfully introduced Lean proof assistant in the classroom, as described in my teaching statement.
Here is a list of the courses I have taught at various universities, along with links to some of the course web sites.
(as Burnett Meyer Instructor)
(as Visiting Assistant Professor)
(as Postdoctoral Associate)}
(as Visiting Assistant Professor)
(as Graduate Student Instructor)
The online books that I'm developing are linked to below. Please note that these are works in progress.
]]>Install the required software.
sage -i beautifulsoup sage -pip install git+https://github.com/vbraun/ExportSageNB.git sudo apt install python3-sagenb-export sage -i rst2ipynb
(I had first tried pip install git+https://github.com/vbraun/ExportSageNB.git
in place of the second command above, but it didn't work.)
Export the experiments.sws
file in two steps (sws -> rst -> ipynb)
sage -sws2rst experiments.sws experiments.rst sage -rst2ipynb experiments.rst experiments.ipynb
Alternatively, I think this would have worked:
sage -sws2rst experiments.sws experiments.rst sagenb-export --list sagenb-export --ipynb=experiments.ipynb admin:0
(where admin:0
is the unique handle to experiments.rst that showed up in the list).
Load the experiments notebook in the new format.
Launch Sage while loading the experiments notebook as follows:
sage --notebook=jupyter experiments.ipynb
Install the required software (as above).
Export the MAA-DeMeo-DeMo.sws
file in two steps (sws -> rst -> ipynb)
sage -sws2rst MAA-DeMeo-DeMo.sws MAA-DeMeo-DeMo.rst sage -rst2ipynb MAA-DeMeo-DeMo.rst MAA-DeMeo-DeMo.ipynb
Load the MAA-DeMeo-DeMo notebook in the new format
Launch Sage while loading the MAA-DeMeo-DeMo notebook as follows:
sage -n jupyter MAA-DeMeo-DeMo.ipynb
The symbols $\mathbb{N}$, $\omega$, and nat
are used interchangeably; they all denote the set of natural numbers.
If $m$ is a natural number, we write $m \colon \mathbb N$ and say "$m$ has type $\mathbb N$." (For the reader unfamiliar with type theory, it's safe in the beginning to think of this as meaning $m\in \mathbb N$.)
For $m \colon \mathbb N$, we denote and define $\underline{m} = \{0, 1, \dots, m-1\}$. Let $a = (a_0, a_1, \dots, a_{m-1})$ be an mtuple of elements from $A$.
(As explained in the post on composition of operations, the tuple $a$ may be identified with a function of type $\underline{m} \to A$, where $a(i) = a_i$, for each $i<m$.)
If $h \colon A \to A$, then $h\circ a : \underline{m} \to A$ is the function whose $i$-th coordinate is $(h\circ a)(i) = h(a(i)) = h(a_i)$, and we may formally identify the function $h \circ a : \underline{m} \to A$ with its "image tuple" $(h(a_0), h(a_1), \dots, h(a_{m-1}))$.
A signature $S = (F, \rho)$ consists of a set $F$ of operation symbols and a function $\rho \colon F \to \mathbb{N}$. We call $\rho f$ the arity of the symbol $f$.
If $A$ is a set and $f$ is a $\rho f$-ary operation on $A$, then we may write $f \colon A^{\rho f} \to A$. On the other hand, as the natural number $\rho f$ denotes the set $\{0, 1, \dots, \rho f -1\}$, a function $a \colon \rho f \to A$ can be identified with its graph, which is simply a $\rho f$-tuple of elements from $A$; that is, $a i : A$, for each $i: \rho f$. Then, by identifying the $\rho f$-th power $A^{\rho f}$ with the type $\rho f \to A$ of functions from $\{0, 1, \dots, \rho f -1\}$ to $A$, we thus identify the function type $A^{\rho f} \to A$ with the type $(\rho f \to A) \to A$.
Examples.
a. If $g \colon (\underline{m} \to A) \to A$ is an $\underline{m}$-ary operation on $A$ and if $a : \underline{m} \to A$, then $g a = g(a_0, a_1, \dots, a_{m-1})$ has type $A$.
b. If $f \colon (\rho f \to B) \to B$ is a $\rho f$-ary operation on $B$, if $a \colon \rho f \to A$ is a $\rho f$-tuple on $A$, and if $h \colon A \to B$, then of course $h \circ a \colon \rho f \to B$, so $f (h \circ a)$ has type $B$.
Generalized composition. We summarize in a single sentence the final result of our previous post on general composition. If $f\colon (\underline n \to A) \to A$ and if $g : \Pi_{i:\underline{n}} (\underline{k_i} \to A) \to A$ and $a : \Pi_{i : \underline{n}}(\underline{k_i} \to A)$, then $\mathbf{eval}\circ \mathbf{fork}(g)(a)$ has type $\underline{n} \to A$, which is the domain type of $f$; therefore, $f \circ (\mathbf{eval}\circ \mathbf{fork}(g) (a))$ has type $A$, as desired. See the post on general composition for details.
We summarize in a single sentence the final result of our previous post on general composition. If $f\colon (\underline n \to A) \to A$ and if $g : \Pi_{i:\underline{n}} (\underline{k_i} \to A) \to A$ and $a : \Pi_{i : \underline{n}}(\underline{k_i} \to A)$, then $\mathbf{eval}\circ \mathbf{fork}(g)(a)$ has type $\underline{n} \to A$, which is the domain type of $f$; therefore, $f \circ (\mathbf{eval}\circ \mathbf{fork}(g) (a))$ has type $A$, as desired. See the post on general composition post for details.
Let $F$ be an endofunctor on Set and let $(A, f^A)$ and $(B, f^B)$ be $F$-algebras. (The F-algebras post explains what this means.)
Let $g$ and $h$ be $F$-homomorphisms from $(A, f^A)$ to $(B, f^B)$. That is $g \circ f^A = f^B \circ F g$ (similarly with $h$ in place of $g$). Let $E(g,h) = \{ a : A \mid g(a) = h(a) \}$. Then
a. $E(g,h)$ is a subuniverse of $(A, f^A)$.
b. If $X \subseteq A$ and $X$ generates $(A, f^A)$ and $g|_X= h|_X$, then $g = h$.
c. If $A$ and $B$ are finite sets and $X$ generates $(A, f^A)$, then $|\mathrm{Hom}((A, f^A),(B, f^B))| \leq |B|^{|X|}$.
Proof. Suppose there are $m$ operation symbols in the similarity type of the two algebras, and assume the $i$-th operation symbol has arity $k_i$. Then $F A : \coprod_{i=0}^{m-1}(\underline{k_i} \to A)$.
a. Fix arbitrary $0\leq i< m$ and $a : \underline{k_i} \to E(g,h)$. We wish to show that $g (f^A (\inji a)) = h (f^A (\inji a))$, as this will show that $E(g,h)$ is closed under the $i$-th operation of $(A, f^A)$. (Since $i$ was arbitrary this will complete the proof.) But this is trivial since, by definition of an $F$-algebra homomorphism, we have $$(g \circ f^A)(\inji a) = (f^B \circ F g)(\inji a) = (f^B \circ F h)(\inji a) = (h \circ f^A)(\inji a).$$
b. Suppose the subset $X \subseteq A$ generates $(A, f^A)$ and suppose $g|_X = h|_X$. Fix an arbitrary $a : A$. We show $g(a) = h(a)$. Since $X$ generates $(A, f^A)$, there exists a term $t$ and a tuple $x : \rho t \to X$ of generators such that $a = t^A x$. Therefore, since $F g = F h$ on $X$, we have $$g(a) = g(t^A x) = (t^B \circ F g)(x) = (t^B \circ F h)(x) = h(t^A x) = h(a).$$
c. By b, a homomorphism is uniquely determined by its restriction to a generating set. If $X$ generates $(A, f^A)$, then, since there are exactly $|B|^{|X|}$ functions from $X$ to $B$, we have $|\mathrm{Hom}((A, f^A),(B, f^B))| \leq |B|^{|X|}$.
Suppose $g$ and $h$ are homomorphisms from $(A, f^A)$ to $(B, f^B)$ and from $(A, f^A)$ to $(C,f^C)$, respectively. Assume $g$ is surjective, and $\ker g \subseteq \ker h$. Then there exists a homomorphism $k$ from $(B, f^B)$ to $(C, f^C)$ such that $h = k \circ g$.
Proof. Define $k\colon B \to C$ as follows: for each $b\in B$, choose (by Axiom of Choice!) $a_0\in g^{-1}\{b\}$ and let $k(b) = h(a_0)$. (Since $g$ is surjective, such an $a_0$ exists for each $b\in B$.) Fix $a \in A$. We show $h(a) = k g(a)$. Let $a_0$ be the element of $g^{-1}\{g(a)\}$ that we chose when defining $k$ at $b = g(a)$. That is, $k(b) = h(a_0)$. Then, $g(a_0) = b = g(a)$, so $(a_0, a) \in \ker g\subseteq \ker h$, so $h(a) = h(a_0) = k(b) = k g(a)$, as desired.
To see that $k$ is a homomorphism, let there be $m$ operation symbols and let $0\leq i< m$ be arbitrary. Fix $b : \underline{k_i} \to B$ and $a : \underline{n} \to A$ be the respective representatives of the $g$-kernel classes $g^{-1}\{b(i)\}$ that we chose when defining $k$. Then, $$(f^C \circ F k) (b) = (f^C \circ F k) ((F g) a) = (f^C \circ F h) (a) = (h \circ f^A)(a) = (k g \circ f^A)(a).$$ $$(k \circ (g \circ f^A))(a) = (k \circ (f^B \circ F g))(a) = (k \circ f^B) ((F g)(a)) = (k \circ f^B)(b).$$
Subalgebra generation
Clones
Let $A$ be a set and $S = (F, \rho)$ a signature and suppose each $f\in F$ is a $(\rho f)$-ary operation on $A$. Define $$\begin{aligned} F_0 &= \operatorname{Proj}}}(A);\ F_{n+1} &= F_n \cup \{ f g \mid f \in F, g \colon \rho f \to (F_n \cap (\rho g \to A)) \}, \text{ for } n < \omega. \end{aligned}$$ Then $\operatorname{Clo}}}^A(F) = \bigcup_n F_n$.
Terms and Free Algebras
Let $\rho$ be a similarity type.
a. $\mathbf{T}_\rho(X)$ is generated by $X$.
b. For every algebra $(A, f^A)$ of type $\rho$ and every function $h\colon X \to A$ there is a unique homomorphism $g\colon \mathbf{T}\rho(X) \to (A, f^A)$ such that ${{ \left.\kern-\nulldelimiterspace g \vphantom{\big|} \right|{X} }} = h$.
Proof.* The definition of $\mathbf{T}_\rho(X)$ exactly parallels the construction in Theorem 1.14 (Bergman 2012). That accounts for (1). For (2), define $g(t)$ by induction on $|t|$. Suppose $|t| = 0$. Then $t \in X \cup {\mathcal{F}}}_0$. If $t \in X$ then define $g(t) = h(t)$. For $t \in {\mathcal{F}}}_0$, $g(t) = t^{(A, f^A)}$. Note that since $(A, f^A)$ is an algebra of type $\rho$ and $t$ is a nullary operation symbol, $t^{(A, f^A)}$ is defined.
For the inductive step, let $|t| = n + 1$. Then $t = f(s_1, \dots, s_k)$ for some $f \in {\mathcal{F}}}_k$ and $s_1, \dots, s_k$ each of height at most $n$. We define $g(t) = f^{(A, f^A)}(g(s_1), \dots, g(s_k))$.
By its very definition, $g$ is a homomorphism. Finally, the uniqueness of $g$ follows from Exercise 1.16.6 in Bergman (2012).
Let $(A, f^A)$ and $(B, f^B)$ be algebras of type $\rho$.
a. For every $n$-ary term $t$ and homomorphism $g\colon (A, f^A) \to (B, f^B)$, $g(t^{(A, f^A)}(a_1,\dots, a_n)) = t^{(B, f^B)}(g(a_1),\dots, g(a_n))$.
b. For every term $t \in T_\rho(X_\omega)$ and every $\theta \in \operatorname{Con}((A, f^A))}}$, $(A, f^A)\equiv_\theta (B, f^B)\implies t^{(A, f^A)}((A, f^A)) \equiv_\theta t^{(A, f^A)}((B, f^B))$.
c. For every subset $Y$ of $A$, $$\operatorname{Sg}^{(A, f^A)}(Y)}} = \{ t^{(A, f^A)}(a_1,\dots, a_n) : t \in T(X_n), a_i \in Y, i \leq n < \omega\}.$$
Proof. The first statement is an easy induction on $|t|$. The second statement follows from the first by taking $(B, f^B) = (A, f^A)/\theta$ and $g$ the canonical homomorphism. For the third statement, again by induction on the height of $t$, every subalgebra must be closed under the action of $t^{(A, f^A)}$. Thus the right-hand side is contained in the left. On the other hand, the right-hand side is clearly a subalgebra containing the elements of $Y$ (take $t = x_1$) from which the reverse inclusion follows.
Let $\rho$ be a similarity type. An identity of type $\rho$ is an ordered pair of terms, written $p \approx q$, from $T_\rho(X_\omega)$. Let $(A, f^A)$ be an algebra of type $\rho$. We say that $(A, f^A)$ satisfies $p\approx q$ if $p^{(A, f^A)} = q^{(A, f^A)}$. In this situation, we write $(A, f^A) \models p \approx q$. If $\mathcal{K}$ is a class of algebras of type $\rho$, we write $\mathcal{K} \models p \approx q$ if $\forall (A, f^A) \in \mathcal{K}$, $(A, f^A) \models p \approx q$. Finally, if $\Sigma$ is a set of equations, we write $\mathcal{K} \models \Sigma$ if every member of $\mathcal{K}$ satisfies every member of $\Sigma$.
Let $\mathcal{K}$ be a class of algebras and $\Sigma$ a set of equations, each of similarity type $\rho$. We define $\operatorname{Id}(\mathcal{K}) = \{p \approx q : \mathcal{K} \models p \approx q\}$ and $\operatorname{Mod}(\Sigma) = \{ (A, f^A) : (A, f^A) \models \Sigma \}$. Classes of the form $\operatorname{Mod}(\Sigma)$ are called equational classes, and $\Sigma$ is called an equational base or an axiomatization of the class. $\operatorname{Mod}(\Sigma)$ is called the class of models of $\Sigma$. Dually, a set of identities of the form $\operatorname{Id}(\mathcal{K})$ is called an equational theory.
For every class $\mathcal{K}$, each of the classes $\mathbf{S}(\mathcal{K})$, $\mathbf{H}(\mathcal{K})$, $\mathbf{P}(\mathcal{K})$, and $\mathbf{V}(\mathcal{K})$ satisfies exactly the same identities as does $\mathcal{K}$.
(exercise)
$\mathcal{K} \models p \approx q$ if and only if for every $(A, f^A) \in \mathcal{K}$ and every $h\in \operatorname{Hom}(\mathbf{T}(X_\omega),(A, f^A))}}$, we have $h(p) = h(q)$.
Proof. First assume that $\mathcal{K} \models p\approx q$. Pick $(A, f^A)$ and $h$ as in the theorem. Then $(A, f^A) \models p\approx q \implies p^{(A, f^A)} = q^{(A, f^A)} \implies p^{(A, f^A)}(h(x_1), \dots, h(x_n)) = q^{(A, f^A)}(h(x_1), \dots, h(x_n))$. Since $h$ is a homomorphism, we get $h(p^{(A, f^A)}(x_1, \dots, x_n)) = h(q^{(A, f^A)}(x_1, \dots, x_n))$, i.e., $h(p) = h(q)$.
To prove the converse we must take any $(A, f^A) \in \mathcal{K}$ and $a_1, \dots, a_n \in A$ and show that $p^{(A, f^A)}(x_1, \dots, x_n) = q^{(A, f^A)}(x_1, \dots, x_n)$. Let $h_0 \colon X_\omega \to A$ be a function with $h_0(x_i) = a_i$ for $i\leq n$. By TheoremÂ 4.21 (Bergman, 2012), $h_0$ extends to a homomorphism $h$ from $\mathbf{T}(X_\omega)$ to $(A, f^A)$. By assumption $h(p) = h(q)$. Since $h(p) = h(p^{(A, f^A)}(x_1, \dots, x_n)) = p^{(A, f^A)}(h(x_1), \dots, h(x_n)) = p^{(A, f^A)}(a_1,\dots, a_n)$ (and similarly for $q$) the result follows.
Let $\mathcal{K}$ be a class of algebras and $p \approx q$ an equation. The following are equivalent.
a. $\mathcal{K} \models p\approx q$.
b. $(p,q)$ belongs to the congruence $\lambda_{\mathcal{K}}$ on $\mathbf{T}(X_\omega)$.
c. $\mathbf{F}{\mathcal{K}}(X\omega) \models p\approx q$.
Proof. We shall show (a) $\implies$ (c) $\implies$ (b) $\implies$ (a). Throughout the proof we write $\mathbf{F}$ for $\mathbf{F}{\mathcal{K}}(X\omega)$, $\mathbf{T}$ for $\mathbf{T}(X_\omega)$ and $\lambda$ for $\lambda_{\mathcal{K}}$.
Recall that $\mathbf{F} = \mathbf{T}/\lambda \in \mathbf{S}\mathbf{P}(\mathcal{K})$. From (a) and LemmaÂ 4.36 (Bergman, 2012) we get $\mathbf{S}\mathbf{P}(\mathcal{K}) \models p \approx q$. Thus (c) holds.
From (c), $p^{\mathbf{F}}(\bar{x}_1,\dots, \bar{x}_n) = q^{\mathbf{F}}(\bar{x}_1,\dots, \bar{x}_n)$ where $\bar{x}i = x_i/\lambda$. From the definition of $\mathbf{F}$, $p^{\mathbf{T}}(x_1,\dots, x_n) \equiv\lambda q^{\mathbf{T}}(x_1,\dots, x_n)$ from which (b) follows since $p = p^{\mathbf{T}}(x_1,\dots, x_n)$ and $q = q^{\mathbf{T}}(x_1,\dots, x_n)$.
Finally assume (b). We wish to apply LemmaÂ 4.37. Let $(A, f^A) \in \mathcal{K}$ and $h \in \operatorname{Hom}(\mathbf{T},(A, f^A))}}$. Then $\mathbf{T}/\ker h \in \mathbf{S}((A, f^A)) \subseteq \mathbf{S}(\mathcal{K})$ so $\ker h \supseteq \lambda$. Then (b) implies that $h(p) = h(q)$ hence (a) holds, completing the proof.
Remark. The last result tells us that we can determine whether an identity is true in a variety by consulting a particular algebra, namely $\mathbf{F}(X_\omega)$. Sometimes it is convenient to work with algebras free on other generating sets besides $X_\omega$. The following corollary takes care of that for us.
Let $\mathcal{K}$ be a class of algebras, $p$ and $q$ $n$-ary terms, $Y$ a set and $y_1, \dots, y_n$ distinct elements of $Y$. Then $\mathcal{K} \models p \approx q$ if and only if $p^{\mathbf{F}{\mathcal{K}}(Y)}(y_1, \dots, y_n) = q^{\mathbf{F}{\mathcal{K}}(Y)}(y_1, \dots, y_n)$. In particular, $\mathcal{K} \models p \approx q$ if and only if $\mathbf{F}_{\mathcal{K}}(X_n)\models p \approx q$.
Proof. Since $\mathbf{F}{\mathcal{K}}(Y)\in \mathbf{S}\mathbf{P}(\mathcal{K})$, the left-to-right direction uses the same argument as in TheoremÂ 4.38 (Bergman, 2012) (Result 3 in this section). So assume that $p^{\mathbf{F}{\mathcal{K}}(Y)}(y_1, \dots, y_n) = q^{\mathbf{F}_{\mathcal{K}}(Y)}(y_1, \dots, y_n)$. To show that $\mathcal{K} \models p \approx q$, let $(A, f^A) \in \mathcal{K}$ and $a_1$, $\dots$, $a_n \in A$. We must show $p^{(A, f^A)}(a_1, \dots, a_n) = q^{(A, f^A)}(a_1, \dots, a_n)$.
There is a homomorphism $h\colon \mathbf{F}{\mathcal{K}}(Y) \to (A, f^A)$ such that $h(y_i) = a_i$ for $i \leq n$. Then \begin{align*} p^{(A, f^A)}(a_1, \dots, a_n) &= p^{(A, f^A)}(h (y_1), \dots, h (y_n)) = h(p^{\mathbf{F}{\mathcal{K}}(Y)}(y_1, \dots, y_n))\ &= h(q^{\mathbf{F}_{\mathcal{K}}(Y)}(y_1, \dots, y_n)) = q^{(A, f^A)}(h(y_1), \dots, h(y_n))\ &= q^{(A, f^A)}(a_1, \dots, a_n).\end{align*} It follows from LemmaÂ 4.36 (Result 1 of this section) that every equational class is a variety. The converse is Birkhoffâs Theorem.
Theorem (Bergman, Thm 4.41) Every variety is an equational class.
Proof. Let $\mathcal{W}$ be a variety. We must find a set of equations that axiomatizes $\mathcal{W}$. The obvious choice is to use the set of all equations that hold in $\mathcal{W}$. To this end, take $\Sigma = \operatorname{Id}(\mathcal{W})$. Let $\overline{\mathcal{W}} = \operatorname{Mod}(\Sigma)$. Clearly, $\mathcal{W} \subseteq \overline{\mathcal{W}}$. We shall prove the reverse inclusion.
Let $(A, f^A) \in \overline{\mathcal{W}}$ and $Y$ a set of cardinality $\max(|A|, |\omega|)$. Choose a surjection $h_0\colon Y \to A$. By TheoremÂ [thm:4.21], $h_0$ extends to a (surjective) homomorphism $h \colon \mathbf{T}(Y) \to (A, f^A)$. Furthermore, since $\mathbf{F}{\mathcal{W}}(Y) = \mathbf{T}(Y)/\Theta{\mathcal{W}}$, there is a surjective homomorphism $g \colon \mathbf{T}(Y) \to \mathbf{F}_{\mathcal{W}}$.
We claim that $\ker g \subseteq \ker h$. If the claim is true then by LemmaÂ [ex:1.26.8] there is a map $f\colon \mathbf{F}{\mathcal{W}}(Y) \to (A, f^A)$ such that $f {\circ}}g = h$. Since $h$ is surjective, so is $f$. Hence $(A, f^A) \in \mathbf{H}(\mathbf{F}{\mathcal{W}}(Y)) \subseteq \mathcal{W}$ completing the proof.
Let $u,v \in T(Y)$ and assume that $g(u) = g(v)$. Since $\mathbf{T}(Y)$ is generated by $Y$, by TheoremÂ [thm:4.21], there is an integer $n$, terms $p, q \in T(X_n)$, and $y_1$, $\dots$, $y_n \in Y$ such that $u = p^{\mathbf{T}(Y)}(y_1,\dots, y_n)$ and $v = q^{\mathbf{T}(Y)}(y_1,\dots, y_n)$, by TheoremÂ [thm:4.32]. Applying the homomorphism $g$, $$p^{\mathbf{F}{\mathcal{W}}(Y)}(y_1,\dots, y_n) = g(u) = g(v) = q^{\mathbf{F}{\mathcal{W}}(Y)}(y_1,\dots, y_n).$$ Then by Result 4 above (CorollaryÂ 4.39, Bergman, 2012), $\mathcal{W} \models p \approx q$, hence $(p \approx q) \in \Sigma$. Since $(A, f^A) \in \overline{\mathcal{W}} = \operatorname{Mod}(\Sigma)$, we get $(A, f^A) \models p \approx q$. Therefore, $$h(u) = p^{(A, f^A)}(h_0(y_1), \dots, h_0(y_n)) = q^{(A, f^A)}(h_0(y_1), \dots, h_0(y_n)) = h(v),$$ as desired.
]]>The symbols $\mathbb{N}$, $\omega$, and nat
are used interchangeably; they all denote the set of natural numbers.
If $m$ is a natural number, we write $m \colon \mathbb N$ and say "$m$ has type $\mathbb N$." (For the reader unfamiliar with type theory, it's safe in the beginning to think of this as meaning $m\in \mathbb N$.)
For $m \colon \mathbb N$, we denote and define $\underline{m} = \{0, 1, \dots, m-1\}$. Let $a = (a_0, a_1, \dots, a_{m-1})$ be an mtuple of elements from $A$.
(As explained in the post on composition of operations, the tuple $a$ may be identified with a function of type $\underline{m} \to A$, where $a(i) = a_i$, for each $i<m$.)
If $h \colon A \to A$, then $h\circ a : \underline{m} \to A$ is the function whose $i$-th coordinate is $(h\circ a)(i) = h(a(i)) = h(a_i)$, and we may formally identify the function $h \circ a : \underline{m} \to A$ with its "image tuple" $(h(a_0), h(a_1), \dots, h(a_{m-1}))$.
A signature $S = (F, \rho)$ consists of a set $F$ of operation symbols and a function $\rho \colon F \to \mathbb{N}$. We call $\rho f$ the arity of the symbol $f$.
If $A$ is a set and $f$ is a $\rho f$-ary operation on $A$, then we may write $f \colon A^{\rho f} \to A$. On the other hand, as the natural number $\rho f$ denotes the set $\{0, 1, \dots, \rho f -1\}$, a function $a \colon \rho f \to A$ can be identified with its graph, which is simply a $\rho f$-tuple of elements from $A$; that is, $a i : A$, for each $i: \rho f$. Then, by identifying the $\rho f$-th power $A^{\rho f}$ with the type $\rho f \to A$ of functions from $\{0, 1, \dots, \rho f -1\}$ to $A$, we thus identify the function type $A^{\rho f} \to A$ with the type $(\rho f \to A) \to A$.
Examples.
a. If $g \colon (\underline{m} \to A) \to A$ is an $\underline{m}$-ary operation on $A$ and if $a : \underline{m} \to A$, then $g a = g(a_0, a_1, \dots, a_{m-1})$ has type $A$.
b. If $f \colon (\rho f \to B) \to B$ is a $\rho f$-ary operation on $B$, if $a \colon \rho f \to A$ is a $\rho f$-tuple on $A$, and if $h \colon A \to B$, then $h \circ a \colon \rho f \to B$, so $f (h \circ a)$ has type $B$.
Let $F$ be an endofunctor on Set and let $(A, f^A)$ and $(B, f^B)$ be $F$-algebras each with $m$ operation symbols. Let $k_i$ be the arity of the $i$-th operation symbol. Then $F A : \coprod_{i=0}^{m-1}(\underline{k_i} \to A)$. (See the F-algebras post.)
Let $g$ and $h$ be homomorphisms from $(A, f^A)$ to $(B, f^B)$. That is, $g \circ f^A = f^B \circ F g$ (similarly with $h$ in place of $g$).
Define the equalizer of $g$ and $h$ as follows: $E(g,h) = \{ a : A \mid g(a) = h(a) \}$.
Fact 1. $E(g,h)$ is a subuniverse of $(A, f^A)$.
Proof. Fix arbitrary $0\leq i< m$ and $a : \underline{k_i} \to E(g,h)$. We wish to show that $g (f^A (\inji a)) = h (f^A (\inji a))$, as this will show that $E(g,h)$ is closed under the $i$-th operation of $(A, f^A)$. But this is trivial since, by definition of an $F$-algebra homomorphism, we have $$(g \circ f^A)(\inji a) = (f^B \circ F g)(\inji a) = (f^B \circ F h)(\inji a) = (h \circ f^A)(\inji a).$$
Fact 2. If $X \subseteq A$ and $X$ generates $(A, f^A)$ and $g|_X= h|_X$, then $g = h$.
Proof. Suppose the subset $X \subseteq A$ generates $(A, f^A)$ and suppose $g|_X = h|_X$. Fix an arbitrary $a : A$. We show $g(a) = h(a)$.
Since $X$ generates $(A, f^A)$, there exists a term $t$ and a tuple $x : \rho t \to X$ of generators such that $a = t^A x$. Therefore, since $F g = F h$ on $X$, we have $$g(a) = g(t^A x) = (t^B \circ F g)(x) = (t^B \circ F h)(x) = h(t^A x) = h(a).$$
Fact 3. If $A$ and $B$ are finite sets and $X$ generates $(A, f^A)$, then $|\mathrm{Hom}((A, f^A),(B, f^B))| \leq |B|^{|X|}$.
Proof. By Fact 2, a homomorphism is uniquely determined by its restriction to a generating set. If $X$ generates $(A, f^A)$, then since there are exactly $|B|^{|X|}$ functions from $X$ to $B$ we have $|\mathrm{Hom}((A, f^A),(B, f^B))| \leq |B|^{|X|}$.
Fact 4. Suppose $g$ and $h$ are homomorphisms from $(A, f^A)$ to $(B, f^B)$ and from $(A, f^A)$ to $(C,f^C)$, respectively. Assume $g$ is surjective, and $\ker g \subseteq \ker h$. Then there exists a homomorphism $k : (B, f^B)\to (C, f^C)$ such that $h = k \circ g$.
Proof. We define $k : (B, f^B)\to (C, f^C)$ constructively, as follows:
Fix $b\colon B$. Since $g$ is surjective, the set $g^{-1}\{b\} \subseteq A$ is nonempty, and since $\ker g \subseteq \ker h$, we see that every element of $g^{-1}\{b\}$ is mapped by $h$ to a single element of $C$. Label this element $c_b$. That is, $h(a) = c_b$, for all $a : g^{-1}\{b\}$. We define $k(b) = c_b$. Since $b$ was arbitrary, $k$ is defined on all of $B$ in this way.
Now it's easy to see that $k g = h$ by construction. Indeed, for each $a \in A$, we have $a \in g^{-1}\{g(a)\}$, so $k(g(a)) = h(a)$ by definition.
To see that $k$ is a homomorphism, let there be $m$ operation symbols and let $0\leq i< m$ be arbitrary. Fix $b \colon \underline{k_i} \to B$. Since $g$ is surjective, for each $i \colon \underline{k_i}$, the subset $g^{-1}\{b(i)\}\subseteq A$ is nonempty and is mapped by $h$ to a single point of $C$ (since $\ker g \subseteq \ker h$. Label this point $c_i$ and define $c \colon \underline{k_i} \to C$ by $c(i) = c_i$.
We want to show $(f^C \circ F k) (b) = (k \circ f^B)(b).$ The left hand side is $f^C c$, which is equal to $(h \circ f^A)(a)$ for some $a\colon \underline{k_i} \to A$, since $h$ is a homomorphism. Therefore, $$(f^C \circ F k) (b) = (h \circ f^A) (a) = (k \circ g \circ f^A)(a) = (k \circ f^B \circ F g)(a) = (k \circ f^B)(b).$$
]]>This page is basically a teaser for the lean-ualib package and its detailed documentation (tutorial/textbook) that we are currently developing to formalize important parts of universal algebra in the Lean theorem proving language.
The Lean Universal Algebra Library (lean-ualib) is work in progress. We haven't implemented very much yet. Nonetheless, you can view some detailed documentation by clicking here and the lean-ualib software repository by clicking here.
]]>If $n$ is a natural number, we write $n \colon \mathbb N$ and say "$n$ has type $\mathbb N$." (For the reader unfamiliar with type theory, it's safe in the beginning to think of this as meaning $n\in \mathbb N$.)
For $n \colon \mathbb N$, we denote and define $\underline{n} = \{0, 1, \dots, n-1\}$.
For $m \colon \mathbb N$, denote and define the $\mathrm{mtuple}$ functor on Set as follows:
on objects: if $A$ is a Set, then $\mathrm{mtuple} A = \{(a_{0}, \dots, a_{m-1}) \mid a_{i} \colon A\}$
on arrows: if $f \colon A \to B$ is a function from the set $A$ to the set $B$, then $\mathrm{mtuple} f \colon \mathrm{mtuple}A \to \mathrm{mtuple}B$ is defined for each $(a_{0}, \dots, a_{m-1})$ of type $\mathrm{mtuple}A$ as follows: $$\mathrm{mtuple}f (a_0, \dots, a_{m-1}) = (f a_0, \dots, f a_{m-1}),$$ which inhabits the type $\mathrm{mtuple} A$.
Notice that $\mathbf a$ has type $\mathrm{mtuple} A$ iff we can represent $\mathbf a$ as a function of type $\underline{m} \to A$; that is, iff we can represent the mtuple $(a_0, \dots, a_{m-1})$ as a function, $\mathbf a$, where $\mathbf a(i) = a_i$ for each $0\leq i < n$. Thus, we have the following equivalence of types: $\mathrm{mtuple} A \cong \underline{m} \to A$.
Let $\mathbf m = (m_0, \dots, m_{n-1}) \colon \mathrm{ntuple} \mathbb N$. Define the $\mathbf{mtuple}$ functor as follows:
on objects: if $A$ is a Set, then $$\mathbf{mtuple} A = \{((a_{00}, \dots, a_{0(m_1-1)}), \dots, (a_{(n-1)0}, \dots, a_{(n-1)(m_n-1)})) \mid a_{ij} \colon A\}$$ (We may write $\mathbf a_i$ in place of $(a_{i0}, \dots, a_{i(k-1)})$, if $k$ is clear from context.)
on arrows: if $f$ is a function from the set $A$ to the set $B$, then $\mathbf{mtuple} f \colon \mathbf{mtuple}A \to \mathbf{mtuple}B$ is defined for each $(\mathbf a_1, \dots, \mathbf a_n)$ in $\mathbf{mtuple}A$ as follows: \begin{align*}\mathbf{mtuple} f (\mathbf a_1, \dots, \mathbf a_n) &= (\mathrm{m_1tuple}f \mathbf a_1, \dots, \mathrm{m_ntuple}f \mathbf a_n)\ &= ((f a_{11}, \dots, f a_{1m_1}), \dots, (f a_{n1}, \dots, f a_{nm_n})).\end{align*}
Notice that $\mathbf a_i$ has type $\mathrm{m_ituple} A$ iff it can be represented as a function of type $\underline{m_i} \to A$; that is, iff the tuple $(a_{i0}, \dots, a_{i(m_i-1)})$ is (the graph of) the function defined by $\mathbf a_i(j) = a_{ij}$ for each $0\leq j < m_i$. Thus, if $\mathbf m = (m_0, \dots, m_{n-1}) \colon \mathrm{ntuple} \mathbb N$, then $\mathbf{mtuple} A$ is the dependent function type, $$\prod_{i \colon \underline{n}} (\underline{m_i} \to A).$$
Define $\mathrm{fork} : (A \to B)\to (A \to C) \to A \to (B \times C)$ as follows: if $f \colon A \to B$, $g \colon A \to C$, and $a \colon A$, then $\mathrm{fork} (f) (g) (a) = (f (a), g (a))$.
(A more standard definition of fork might take the domain to be $(A \to B)\times (A \to C)$, whereas we have described a "curried" version in order to support partial application.)
The fork function generalizes easily to dependent function types. Let $A$ be a type and for each $a \colon A$ let $B_a$ and $C_a$ be types. Define the dependent fork, denoted by $$\mathbf{fork} : \prod_{a : A} B_a\to \prod_{a : A} C_a \to \prod_{a : A}(B_a \times C_a),$$ as follows: if $f \colon \Pi_{a : A} B_a$, $g \colon \Pi_{a : A} C_a$, and $a \colon A$, then $\mathbf{fork} (f) (g) (a) = (f (a), g (a))\colon B_a \times C_a$. Since we use a curried definition, we can partially apply $\mathbf{fork}$ and obtain the expected typing relations, viz., $$\mathbf{fork} (f) \colon \prod_{a:A} C_a \to \prod_{a:A} (B_a \times C_a)\quad \text{ and } \quad \mathbf{fork} (f) (g) \colon \prod_{a:A} (B_a \times C_a).$$
Next, let $\mathbf{eval} \colon (A \to B) \times A$ denote function application; that is, $\mathbf{eval} (f, a) = f a$, if $f \colon A \to B$ and $a \colon A$. Thus, if $h \colon \prod_{a : A}(C_a \to D)$, $k \colon \prod_{a : A}C_a$, and $a\colon A$, then $$\mathbf{fork} (h)(k)(a) = (h(a), k(a)) \colon (C_a \to D) \times C_a, \text{ and }$$ $$\mathbf{eval} \circ \mathbf{fork} (h)(k)(a) = h(a)k(a) \colon D.$$
In universal algebra we deal mainly with finitary operations on sets. By an $n$-ary operation on the set $A$ we mean a function $f \colon A^n \to A$, that takes $n$ inhabitants of the type $A$ and returns an element of type $A$.
By the equivalence of the $\mathrm{ntuple}$ type and the function type $\underline{n} \to A$, we may represent the type of $n$-ary operations on $A$ by $(\underline{n} \to A) \to A$. Evaluating such an $f \colon (\underline{n} \to A) \to A$ at a tuple $a \colon \underline{n} \to A$ is simply function application, expressed by the usual rule (sometimes called "implication elimination" or "modus ponens").
If we let $a_i$ denote the value of $a$ at $i$, and if we identify $a$ with it's graph (the tuple $(a_0, \dots, a_{n-1})$), then $f a = f(a_0, \dots, a_{n-1})$.
Denote and define the collection of all finitary operations on $A$ by $$\operatorname{Op}A = \bigcup_{n<\omega} (A^n \to A)\cong \bigcup_{n<\omega} ((\underline{n} \to A) \to A).$$
We will now try to develop a formulation of general function composition that is more elegant and computationally practical than the standard formulation. Let us first briefly review the standard formulation of function composition. Let $f \colon (\underline{n} \to A) \to A$ be an $n$-ary operation on $A$, and suppose for each $0\leq i < n$ we have an operation $g_i \colon (\underline{k_i} \to A) \to A$. Then we define $f \circ (g_0, \dots, g_{n-1})$ in the following standard way: for each $$((a_{00}, \dots, a_{0(k_0-1)}), \dots, (a_{(n-1)0}, \dots, a_{(n-1)(k_{n-1}-1)}))\colon A^{k_0} \times \cdots \times A^{k_{n-1}},$$ \begin{align*}(f\circ &(g_0, \dots, g_{n-1}))((a_{00}, \dots, a_{0(k_0-1)}), \dots, (a_{(n-1)0}, \dots, a_{(n-1)(k_{n-1}-1)}))\ &= f(g_0(a_{00}, \dots, a_{0(k_0-1)}), \dots, g_{n-1}(a_{(n-1)0}, \dots, a_{(n-1)(k_{n-1}-1)})).\end{align*}
Not only is this notation tedious, but also it lends itself poorly to computation. To improve upon it, let us first consider the ntuple $(g_0, \dots, g_{n-1})$. This is an ntuple of operations from $\operatorname{Op}A$. If we denote by $g$ the function from $\underline{n}$ to $\operatorname{Op}A$ given by $g i = g_i$ for each $0\leq i < n$, then $g$ inhabits the following dependent function type: $$\prod_{i : \underline{n}} ((\underline{k_i} \to A) \to A).$$
Next, define the function $a$ as follows: $a i \colon \underline{k_i} \to A$ for each $0\leq i < n$ and for each $j\colon \underline{k_i}$, $a i j = a_{ij}$. Then the ntuple of arguments in the expression above can be identified with the tuple $a = (a 0, \dots, a (n-1))$ of functions. Thus $a$ has dependent function type $\prod_{i : \underline{n}} (\underline{k_i} \to A)$, and for each $i\colon \underline{n}$, we have $a i j = a_{ij}$.
Now, looking back at the section above, where we defined the fork and eval functions, we can see how to perform general composition using dependent types. If $g \colon \Pi_{i : \underline{n}} ((\underline{k_i} \to A) \to A)$, and $a \colon \Pi_{i : \underline{n}}(\underline{k_i} \to A)$, then $$\mathbf{fork} (g) (a) (i) = (g(i), a(i)) : ((\underline{k_i}\to A) \to A) \times (\underline{k_i}\to A)$$ and $\mathbf{eval} (\mathbf{fork} (g) (a) (i)) = g(i) a(i)$ has type $A$. Observe that the codomain $A$ does not depend on $i$, so the types $\Pi_{i:\underline{n}} A$ and $\underline{n} \to A$ are equivalent. Therefore, $\mathbf{eval} \circ \mathbf{fork} (g) (a)$ has type $\underline{n} \to A$. On the other hand, we have $$\mathbf{eval}\circ \mathbf{fork} (g) : \prod_{i : \underline{n}} (\underline{k_i} \to A) \to (\underline{n} \to A).$$ Thus, if we take an $n$-ary operation, $f\colon (\underline{n} \to A) \to A$, and an $n$-tuple of operations, $g\colon \Pi_{i : \underline{n}} ((\underline{k_i} \to A) \to A)$, then we can define the composition of $f$ with $g$ as follows: $$f [g] := f \circ (\mathbf{eval}\circ \mathbf{fork}(g)) : \prod_{i : \underline{n}}(\underline{k_i} \to A) \to A.$$ Indeed, if $a \colon \Pi_{i : \underline{n}}(\underline{k_i} \to A)$, then $\mathbf{eval}\circ \mathbf{fork}(g)(a)$ has type $\underline{n} \to A$, which is the domain type of $f$; therefore, $f (\mathbf{eval}\circ \mathbf{fork}(g) (a))$ has type $A$, as desired.
]]>A group is an $\FGrp$-algebra where $\FGrp A = 1 + A + A \times A$.
To see how to interpret the standard definition of a group, $(A, e, ^{-1}, \circ)$, in this language, observe that an element of the coproduct $\FGrp A$ has one of three forms,
We define the group operations $f \colon \mathbf{F} A \to A$ by pattern matching as follows:
For example, in Lean the implementation would look something like this:
def f : 1 + â + (â Ă â) â â | inâ 1 := e | inâ x := xâťÂš | inâ x y := x â y
Let $(A, f)$ and $(B, g)$ be two groups (i.e., $\FGrp$-algebras).
A homomorphism from $(A, f)$ to $(B, g)$, denoted by $h\colon (A, f)\to (B, g)$, is a function from $A$ to $B$ that satisfies the identity $$h \circ f = g \circ \FGrp h$$
To make sense of this identity, we must know how the functor $\FGrp$ acts on arrows (i.e., homomorphisms, like $h$). It does so as follows:
Equivalently,
So, in this case, the indentity $h \circ f = g \circ \FGrp h$ reduces to
which are precisely the conditions we would normally verify when checking that $h$ is a group homomorphism.
]]>The UACalc_CLI github repository has instructions for configuring your machine to support a command line interface (CLI) with the Universal Algebra Calculator (UACalc).
This post gives an example showing how to exploit the Jython interface to the UACalc to study properties of large collections of algebraic structures.
I created this set of four computer laboratory assignments to teach my students how to do linear algebra in Sage.
I assigned these to my undergraduate linear algebra class at Iowa State University in the spring of 2016.
Each lab was completed during one 50-minute class held in the computer lab in Carver Hall.
]]>Part 1. Sign up for an account on the Math Department Sage server and then demonstrate that you know how to compute $2\times 2$ (more detailed instructions are below; see Part 1).
Have the instructor sign your paper to record completion of Part 1.
Completing Part 1 is worth 1 point. If you are not interested in completing Part 2 of the assignment, you may turn in your paper and leave the lab after completing Part 1.
Make sure your name is on your paper and it was signed by the instructor.
Part 2. Complete the exercises in Part 2 following the instructions provided below, then save your Sage worksheet as a .sws file and upload this file on Blackboard.
Finishing. When you have finished working or it is 2pm (whichever comes first):
stop your Sage worksheet (Action $\rightarrow$ Save and quit worksheet; or use Stop button),
sign out of Sage
logout of your computer
write your name on your paper
hand it in to the instructor
Login to any machine in Carver 449, open up a browser (preferably Chrome) and navigate to https://sage.math.iastate.edu/
You should arrive at a page that looks like this:
Using your Iowa State username and a password supplied by your instructor, login to your Sage account.
Once you are logged in, select the New Worksheet link and name the worksheet Lab1.
If all went well, you will now see a page that looks like this:
\
Click somewhere inside the wide rectangular box (or âcellâ) and type the expression 2*2. Then type Shift+Enter (hold down the Shift key and press Enter; or simply click âevaluateâ).
If a 4 appears in the worksheet, congratulations on completing Part 1. Ask the instructor to check your work and sign below, then do Part 2 (or Save & quit if you want to stop here):
Instructor signature: (1 point)
In this part we will use Sage to help solve the following exercise from the textbook:
By solving a system of equations, write the vector $$\mathbf{b} = \begin{bmatrix*}[r] 3 \ 0 \ -2 \end{bmatrix*}$$ as a linear combination of the vectors $$\mathbf{v}_1 = \begin{bmatrix*}[r] 1 \ 0 \ -1 \end{bmatrix*}, \quad \mathbf{v}_2 = \begin{bmatrix*}[r] 0 \ 1 \ 2 \end{bmatrix*}, \quad \text{ and } \quad \mathbf{v}_3 = \begin{bmatrix*}[r] 2 \ 1 \ 1 \end{bmatrix*}.$$
To write the vector ${{\mathbf{b}}}$ as a linear combination of the vectors ${{\mathbf{v}}}_1$, ${{\mathbf{v}}}_2$, and ${{\mathbf{v}}}_3$, we must find coefficients $x_1, x_2, x_3$ such that ${{\mathbf{b}}}= x_1 {{\mathbf{v}}}_1 + x_2 {{\mathbf{v}}}_2 + x_3 {{\mathbf{v}}}_3$. As we discussed in lecture, this is equivalent to finding a vector ${{\mathbf{x}}}= (x_1, x_2, x_3)$ such that $A{{\mathbf{x}}}= {{\mathbf{b}}}$, where $A$ is the matrix whose columns are the vectors ${{\mathbf{v}}}_1$, ${{\mathbf{v}}}_2$, and ${{\mathbf{v}}}_3$.
In Sage, we will construct the matrix $A$ and then augment this matrix with the vector ${{\mathbf{b}}}$. Finally we will solve for ${{\mathbf{x}}}$ by putting the augmented matrix in echelon form. We'll make Sage do all the tedious work, but it will be up to us to interpret Sage's output and write down a correct solution.
First, we need to learn how to enter the matrix $$A = \begin{bmatrix*}[r] 1 &0&2\ 0&1&1\-1&2&1\end{bmatrix*}$$ in Sage. In your Lab1 Sage worksheet, click the [Help]{} link at the top right. A help window should appear (possibly in a pop-up window). On the Help page, navigate through the following links:
Reference Manual $\rightarrow$ modules $\rightarrow$ m $\rightarrow$ sage.matrix.constructor
From the help page that appears, you should be able to discern how to input the matrix $A$ in Sage. (Hint: you should start with [A = matrix([[1,0,2],[]{}...)
After entering the matrix, type Shift+Enter to evaluate your input. To confirm that Sage correctly interpreted your input, enter the single character [A]{} in a new cell and then Shift+Enter.
Next, input the vector ${{\mathbf{b}}}= (3, 0, -2)$ using the following syntax:
b = vector([3,0,-2])
At this point, you are half way done with Part 2. Ask the instructor to check your work.
Instructor signature: (1/2 point)
Next, we want to construct the augmented matrix $$[A|{{\mathbf{b}}}] = \left[\begin{array}{@{}*{3}{r}|rr@{}} 1 &0&2&3\ 0&1&1&0\-1&2&1&-2 \end{array}\right].$$ Using a search engine (e.g., Google) search for the phrase "sage augmented matrix." The first result will probably be to the Sage Reference Manual page matrices/sage/matrix/matrix1.html.
Read a bit of this page and see if you can figure out how to use the augment()
function (or method) of the matrix object A
. You will use b
as the input to the augment()
method.
Don't forget to give a name to the augmented matrix, e.g., Ab = augment...
and then check that Sage correctly interpreted your input (by typing Ab
then Shift+Enter
.)
Next, put your augmented matrix in echelon form. Again, try to use the Sage help pages or a Google search to figure out how to do this. If you need help, ask the instructor.
Finally, interpret the Sage output of the echelon_form
command and write down a solution.
The solution vector is $$\mathbf{x}=$$ ...so $\mathbf{b}= (3, 0, -2)$ can be written as the following linear combination: $$\mathbf{b}= \text{\underline{\phantom{XXX}}}\mathbf{v}_1 + \text{\underline{\phantom{XXX}}} \mathbf{v}_2+ \text{\underline{\phantom{XXX}}} {{\mathbf{v}}}_3.$$ You have now completed Part 2. By the way, you can also use Sage to check your final answer. One way to do this is to type your solution vector into Sage (with x = vector(...)
and then compute A*x
. Does this result in the vector $(3, 0, -2)$? Alternatively, you could simply ask Sage to solve the system for you with the solve_right
command!
A tutorial describing how to do more linear algebra in Sage is available at http://doc.sagemath.org/html/en/tutorial/tour_linalg.html
Instructor signature: (1/2 point)
(More detailed instructions are below.)
Sign up for an account on the Math Department Sage server
Demonstrate that you know how to compute $2\times 2$.
When you finish Part 1, have the instructor sign your paper to record completion.
Completing Part 1 is worth 1 point. If you are not interested in completing Part 2 of the assignment, you may turn in your paper and leave the lab after completing Part 1.
Make sure your name is on your paper and it was signed by the instructor.
(More detailed instructions are below.)
Complete the exercises in Part 2 following the instructions provided below, then save your Sage worksheet as a .sws file and upload this file on Blackboard.
When you have finished working or it is 2pm (whichever comes first):
stop your Sage worksheet (Action $\rightarrow$ Save and quit worksheet; or use Stop button),
sign out of Sage
logout of your computer
write your name on your paper
hand it in to the instructor
Login to any machine in Carver 449, open up a browser (preferably Chrome) and navigate to https://sage.math.iastate.edu/
You should arrive at a page that looks like this:
Using your Iowa State username and a password supplied by your instructor, login to your Sage account.
Once you are logged in, select the New Worksheet link and name the worksheet Lab1.
If all went well, you will now see a page that looks like this:
Click somewhere inside the wide rectangular box (or âcellâ) and type the expression 2*2. Then type Shift+Enter (hold down the Shift key and press Enter; or simply click âevaluateâ).
If a 4 appears in the worksheet, congratulations on completing Part 1. Ask the instructor to check your work and sign below, then do Part 2 (or Save & quit if you want to stop here):
Instructor signature: (1 point)
In this part we will use Sage to help solve the following exercise from the textbook:
By solving a system of equations, write the vector $\mathbf b = \begin{bmatrix} 3 \\ 0 \\ -2 \end{bmatrix}$ as a linear combination of the vectors $$\mathbf v_1 = \begin{bmatrix} 1 \\ 0 \\ -1 \end{bmatrix}, \quad \mathbf{v}_2 = \begin{bmatrix} 0 \\ 1 \\ 2 \end{bmatrix}, \quad \text{ and } \quad \mathbf v_3 = \begin{bmatrix} 2 \\ 1 \\ 1 \end{bmatrix}.$$
To write the vector $\mathbf{b}$ as a linear combination of the vectors $\mathbf{v}_1$, $\mathbf{v}_2$, and $\mathbf{v}_3$, we must find coefficients $x_1, x_2, x_3$ such that $\mathbf{b}= x_1 \mathbf{v}_1 + x_2 \mathbf{v}_2 + x_3 \mathbf{v}_3$. As we discussed in lecture, this is equivalent to finding a vector $\mathbf{x}= (x_1, x_2, x_3)$ such that $A\mathbf{x}= \mathbf{b}$, where $A$ is the matrix whose columns are the vectors $\mathbf{v}_1$, $\mathbf{v}_2$, and $\mathbf{v}_3$.
In Sage, we will construct the matrix $A$ and then augment this matrix with the vector $\mathbf{b}$. Finally we will solve for $\mathbf{x}$ by putting the augmented matrix in echelon form. We'll make Sage do all the tedious work, but it will be up to us to interpret Sage's output and write down a correct solution.
First, we need to learn how to enter the matrix $$A = \begin{bmatrix} 1 &0&2\\ 0&1&1\\-1&2&1\end{bmatrix}$$ in Sage. In your Lab1 Sage worksheet, click the Help link at the top right. A help window should appear (possibly in a pop-up window). On the Help page, navigate through the following links:
Reference Manual $\rightarrow$ modules $\rightarrow$ m $\rightarrow$ sage.matrix.constructor
From the help page that appears, you should be able to discern how to input the matrix $A$ in Sage. (Hint: you should start with A = matrix([[1,0,2],...
After entering the matrix, type Shift+Enter to evaluate your input. To confirm that Sage correctly interpreted your input, enter the single character A
in a new cell and then Shift+Enter.
Next, input the vector $\mathbf{b}= (3, 0, -2)$ using the following syntax: b = vector([3,0,-2])
At this point, you are half way done with Part 2. Ask the instructor to check your work.
Instructor signature: (1/2 point)
Next, we want to construct the augmented matrix $$[A|\mathbf{b}] = \left[\begin{array}{rrr|r} 1&0&2&3\\ 0&1&1&0\\-1&2&1&-2\end{array}\right].$$ Using a search engine (e.g., Google) search for the phrase "sage augmented matrix." The first result will probably be to the Sage Reference Manual page matrices/sage/matrix/matrix1.html.
Read a bit of this page and see if you can figure out how to use the augment()
function (or "method") of the matrix object A
. You will use b
as the input to the augment()
method.
Don't forget to give a name to the augmented matrix, e.g., Ab = augment...
and then check that Sage correctly interpreted your input (by typing Ab
then Shift
+Enter
.)
Next, put your augmented matrix in echelon form. Again, try to use the Sage help pages or a Google search to figure out how to do this. If you need help, ask the instructor.
Finally, interpret the Sage output of the echelon_form
command and write down a solution.
The solution vector is $\mathbf{x}=$
...so $\mathbf{b}= (3, 0, -2)$ can be written as the following linear combination: $\mathbf{b}= \underline{\phantom{XXX}}\mathbf{v}_1 + \underline{\phantom{XXX}} \mathbf{v}_2+ \underline{\phantom{XXX}} \mathbf{v}_3$ (fill in the blanks).
You have now completed Part 2.
You should take this opportunity to use Sage to check your final answer. One way to do this is to type your solution vector into Sage (with x = vector(...)
and then compute A*x
. Does this result in the vector $(3, 0, -2)$? Alternatively, you could simply ask Sage to solve the system for you with the solve_right
command!
A tutorial describing how to do more linear algebra in Sage is available at http://doc.sagemath.org/html/en/tutorial/tour_linalg.html
Instructor signature: (1/2 point)
To complete this part, you must login to your account on the Math Department Sage server and demonstrate that you know how to input a matrix, augment a matrix with a vector, and compute the echelon form of an (augmented) matrix.
Completing Part 1 is worth 1 point. If you are not interested in completing Part 2 of the assignment, you may turn in your paper and leave the lab after completing Part 1.
Make sure your name is on your paper and it was signed by the instructor.
To complete Part 2, you must first download the Sage worksheet file Math317-Lab2.sws from either Blackboard or GitHub, load this worksheet into your Sage session and follow the instructions provided. You must then save your completed Sage worksheet to a file and upload the resulting sws file to Blackboard.
When you have finished working or it is 2pm (whichever comes first):
stop your Sage worksheet (Action $\rightarrow$ Save and quit worksheet; or use Stop button),
sign out of Sage,
logout of your computer,
write your name on your paper,
hand it in to the instructor.
In Homework 3, we proved that the vector ${{\mathbf{b}}}= (1, -1, 1, -1)$ is as a linear combination of the vectors ${{\mathbf{v}}}_1 = (1 , 0 , 1 , -2)$, ${{\mathbf{v}}}_2 = (0 , -1 , 0 , 1)$, and ${{\mathbf{v}}}_3 = (1, -2 , 1 , 0)$.
Do you remember how we did this?
That's right! We recognized that if the matrix $A$ has first column ${{\mathbf{v}}}_1$, second column ${{\mathbf{v}}}_2$, and third column ${{\mathbf{v}}}_3$, then writing ${{\mathbf{b}}}$ as a linear combination of the given vectors is the same as solving the system $A{{\mathbf{x}}}= {{\mathbf{b}}}$. That is, if we find ${{\mathbf{x}}}= (x_1, x_2, x_3)$ that solves this system, then ${{\mathbf{b}}}= x_1{{\mathbf{v}}}_1+x_2{{\mathbf{v}}}_2+x_3{{\mathbf{v}}}_3$ is the desired linear combination.
In this first part of this lab assignment, we will simply re-solve the above problem, but this time we will make Sage do the tedious work. Carry out the following steps:
Login to any machine in Carver 449, open up a browser (preferably Chrome) and navigate to https://sage.math.iastate.edu/
Login to your account on the Math Department Sage server using the login information you were given for Lab 1. Once you are logged in, create a new worksheet and name it Lab2.
In the Lab2 worksheet, click the first cell and type the following:
b = vector([1, -1, 1, -1]); print b
Then type Shift
+Enter
or click evaluate
.
Did Sage print what you expect? If so, move on to number 3.
If not, try again and/or ask the instructor for help.
Click somewhere inside the next cell and enter the following expression:
A = matrix(4, 3, [1, 0, 1, 0, -1, -2, 1, 0, 1, -2, 1, 0]) print A
What did Sage print?
Is it a matrix with three columns, $\mathbf{v}_1$, $\mathbf{v}_2$, $\mathbf{v}_3$? If not, try again and/or your neighbor for help.
Can you decipher the syntax we used to input this matrix $A \in \mathbb{R}^{4\times 3}$? If so, move on to number 4.
If not, think a litter harder and/or discuss it with your neighbor.
Next, have Sage augment the matrix $A$ with the vector $b$ by entering the following into the next worksheet cell:
Ab = A.augment(b, subdivide=True) print Ab
Ask Sage to put your augmented matrix in reduced-row echelon form:
Ab.rref()
You should now see two pivots (both equal to 1). Let the free variable $x_3 = s$ and write the vector $\mathbf{b}$ as a linear combination of the vectors $\mathbf{v}_1$, $\mathbf{v}_2$, $\mathbf{v}_3$ involving $s$.
$$\mathbf{b}= \begin{bmatrix} 1 \\ -1 \\ 1 \\ -1\end{bmatrix}= \underline{\phantom{XXX}}\mathbf{v}_1 + \underline{\phantom{XXX}} \mathbf{v}_2+ \underline{\phantom{XXX}} \mathbf{v}_3.$$
If you want to stop here, ask the instructor to check your work and sign below then save and quit. Otherwise, move on to Part 2
Instructor signature: (1 point)
Load the Math317-Lab2.sws file into your Sage session and complete the exercises.
]]>When you have finished working or it is 2pm (whichever comes first):
Ask the professor to check your work.
Save your worksheet and name it Lab03.sws.
Submit your Lab04.sws file on Blackboard
Stop your Sage worksheet (Action $\rightarrow$ Save and quit worksheet; or use Stop button).
Sign out of your Sage account and log off the computer.
The purpose of the first part of this lab is simply to recapitulate some of the things we learned about Sage in previous computer lab assignments, in case you are a bit rusty. Although this part will not be graded, you are strongly encouraged to try out all of the examples yourself (by entering them in a Sage worksheet).
Sage knows a wide variety of sets of numbers. These are known as "rings" or "fields," but we may call them "number fields" or "scalar fields." (We need not worry about the differences for now.) Examples include the integers and the rational numbersâthese sets are denoted by $\mathbb{Z}$ and $\mathbb{Q}$, respectivelyâas well as the real and complex number fields, denoted $\mathbb{R}$ and $\mathbb{C}$, respectively. Sage has special notation for each of these sets of numbers, and these are listed in the table below:
Set | Description | standard notation | sage notation |
---|---|---|---|
Integers | $\mathbb{Z} := {\dots, -1, 0, 1, 2, \dots}$ | ZZ | |
Rational numbers | $\mathbb Q := {\frac{p}{q} : p, q \in \mathbb{Z}, q \neq 0}$ | QQ | |
Real numbers | $\mathbb{R} := (-\infty, \infty)$ | RR | |
Complex numbers | $\mathbb{C} := {a + ib : a, b \in \mathbb{R}}$ | CC |
Thus, RR
denotes (Sage's representation of) the set of real numbers (up to "reasonable" precision), and CC is (Sage's representation of) the complex numbers (up to "reasonable" precision). In any computer system, there are complications surrounding the inability of digital arithmetic to accurately represent all real (or complex) numbers. In contrast, Sage can represent rational numbers exactly as the quotient of two (perhaps very large) integers. So, for now at least, we will use QQ
as our main number system so we can concentrate on understanding the key concepts and ignore the fact that computers have trouble faithfully representing real and complex numbers.
Sage is largely "object-oriented," which means many functions are associated with a specific "object" and are invoked using the "dot" notation. For example a matrix A
is represented in Sage as a "matrix object," and the command A.nrows()
returns the number of rows of the matrix A
.
sage: A = matrix(QQ, 2, 3, [[1,2,3],[4,5,6]]) sage: print A [1 2 3] [4 5 6] sage: A.nrows(), A.ncols() (2, 3) sage: A.base_ring() Rational Field sage: A.parent() Full MatrixSpace of 2 by 3 dense matrices over Rational Field
Vectors in Sage are built, manipulated and interrogated in much the same way as matrices. However as simple lists, they are simpler to construct and manipulate. Sage will print a vector across the screen, even if we wish to interpret it as a column vector. It will be delimited by parentheses ((,)) which allows us to distinguish a vector from a matrix with just one row, if we look carefully. The number of slots in a vector is not referred to in Sage as rows or columns, but rather by "degree." Here are some examples:
sage: v = vector(QQ, 4, [1, 1/2, 1/3, 1/4]) sage: print v (1, 1/2, 1/3, 1/4) sage: v.degree() 4 sage: v.parent() Vector space of dimension 4 over Rational Field sage: v[2] 1/3 sage: w = vector([1, 2, 3, 4, 5, 6]) sage: print w (1, 2, 3, 4, 5, 6) sage: w.degree() 6 sage: w.parent() Ambient free module of rank 6 over the principal ideal domain Integer Ring sage: w[3] 4
Notice that if you use commands like .parent()
you will sometimes see references to "free modules." This is a technical generalization of the notion of a vector, which is beyond the scope of this course, so just mentally convert to vectors when you see this term.
The zero vector is super easy to build, but be sure to specify what number system your zero is from.
sage: z = zero_vector(QQ, 5) sage: print z (0, 0, 0, 0, 0)
ex:vectors Login to your Sage account, open a new worksheet and name it Lab03. Then try out all of the examples above. (Enter what appears after the sage:
Â prompt in a worksheet cell and click
evaluate
; alternatively, you can evaluate a worksheet cell with the keyboard shortcut Shift
+Enter
.) Also note, you can put multiple lines in a single worksheet cell and evaluate them all at once.
One of Sage's strengths is the ability to create infinite sets, such as the span of a set of vectors, from finite descriptions. In other words, we can take a finite set with just a handful of vectors and Sage will create the set that is the span of these vectors, which is an infinite set. Here we learn how to do this for the example vector space $\mathbb{Q}^4 = {(x_1, x_2, x_3, x_4) : x_i \in \mathbb{Q}}$, which Sage denotes by QQ^4
. The key command is the vector space method .span()
.
Continuing with the worksheet (named Lab03) that you created in ExerciseÂ [ex:vectors] above, try out each example below.
sage: V = QQ^4 sage: v1 = vector(QQ, [1,1,2,-1]) sage: v2 = vector(QQ, [2,3,5,-4]) sage: W = V.span([v1, v2]) sage: print W Vector space of degree 4 and dimension 2 over Rational Field Basis matrix: [ 1 0 1 1] [ 0 1 1 -2] sage: x = 2*v1 + (-3)*v2 sage: print x (-4, -7, -11, 10) sage: x in W True sage: y = vector(QQ, [3, -1, 2, 2]) sage: y in W False sage: u = vector(QQ, [3, -1, 2, 5]) sage: u in W True sage: W <= V True
Most of the above should be fairly self-explanatory, but a few comments are in order. The span, W
, is constructed with the .span()
method, which accepts a list of vectors and is called using the "dot" syntax (since it is a method of a vector space object). You can see the number system (Rational Field) and the number of entries in each vector (degree 4).
Notice that the span (and the .span()
command) is a function that takes as input a finite set of vectors and generates as output the span of these vectors. As we know, the result in an infinite collection of vectors. Of course, Sage does not literally construct all of the vectors in the span at once, since itâs impossible to fit all of them in the computers finite physical memory. However, Sage provides us with a means of accessing any (finite) number of vectors in the span, and, importantly, Sage can check whether any given vector belongs to the span.^{1}
In the example above, we know the vector x
will be in the span W
since we built it as a linear combination of v1
and v2
. The vectors y
and u
are a bit more mysterious, but Sage can answer
the membership question easily for both of these vectors as well. In fact, from your experience in class and previous computer labs, you already know how to use Sage to prove that y
and u
belong to
W
.
If possible, use Sage to write y
as a linear combination of the vectors v1
and v2
. Do the same for u
. That is, find the coefficients $c_1, c_2, d_1, d_2$, so that $\mathbf{y}= c_1\mathbf{v}_1 +c_2 \mathbf{v}_2$ and $\mathbf{u}= d_1\mathbf{v}_1 +d_2 \mathbf{v}_2$. Write these coefficients in the blanks spaces provided below. If it's not possible, explain why not.
$$\mathbf{y}= \begin{bmatrix} 3 \\ -1 \\ 2 \\ 2\end{bmatrix}= \underline{\phantom{XXX}}\mathbf{v}_1 + \underline{\phantom{XXX}} \mathbf{v}_2, \qquad \qquad \mathbf{u}= \begin{bmatrix} 3 \\ -1 \\ 2 \\ 5\end{bmatrix}= \underline{\phantom{XXX}}\mathbf{v}_1 + \underline{\phantom{XXX}} \mathbf{v}_2.$$
The Null Space $\mathrm{N}(A)$
Sage can compute the null space of a matrix. Again, this may seem rather remarkable, since the null space is very often an infinite set! The null space command is quite powerful since, as we have learned, there is a lot of theory associated with the null space; e.g., if we know the null space of a given matrix, then we know a lot about (among other things) systems of linear equations associated with that matrix.
One way to get Sage to build the null space of a matrix is with the .right_kernel()
command. We experiment with this command below and, to ensure that the presentation of results is consistent with our
previous work and the textbook, we will always use the option basis="pivot"
. (Also, for reasons that are not important right now, we must continue to define matrices over the rational numbers.)
sage: A = matrix(QQ, [[ 1, 4, 0, -1, 0, 7, -9], [ 2, 8, -1, 3, 9, -13, 7], [ 0, 0, 2, -3, -4, 12, -8], [-1, -4, 2, 4, 8, -31, 37]]) sage: NA = A.right_kernel(basis="pivot") # (name this whatever you want) sage: print NA Vector space of degree 7 and dimension 4 over Rational Field User basis matrix: [-4 1 0 0 0 0 0] [-2 0 -1 -2 1 0 0] [-1 0 3 6 0 1 0] [ 3 0 -5 -6 0 0 1]
(Stuff that appears after a hashtag symbol #
is called a comment; Sage ignores all comments.)
We can test membership in NA
,
sage: x = vector(QQ, [3, 0, -5, -6, 0, 0, 1]) sage: x in NA True sage: vector(QQ, [-4, 1, -3, -2, 1, 1, 1]) in NA True # (we needn't name a vector to test its membership in NA) sage: vector(QQ, [1, 0, 0, 0, 0, 0, 2]) in NA False
We can also verify that [NA` is an infinite set,
sage: NA.is_finite() False
As we also know, sometimes the null space is finite. When does this occur?
[ex:null-space-oper] Build your own matrix (call it F
) such that the following command returns True
.
sage: F.right_kernel(basis=âpivotâ).is_finite()
If you're having trouble with ExerciseÂ [ex:null-space-oper], here's a hint: Since the null space of F
is finite, we can list its vectors. For example, if our matrix happens to have two columns, then we will observe
sage: F.right_kernel(basis="pivot").list() [(0, 0)]
The Column Space $\operatorname{C}(A)$
Using span, we can expand our techniques for checking the consistency of a linear system $A\mathbf{x}= \mathbf{b}$. Recall that the system is consistent if and only if $\mathbf{b}$ is a linear combination of the columns of $A$. So consistency of a system is equivalent to the membership of $\mathbf{b}$ in the span of the columns of $A$.
We make use of the matrix method columns()
to get all of the columns into a list at once.
sage: A = matrix(QQ, [[33, -16, 10, -2], [99, -47, 27, -7], [78, -36, 17, -6], [-9, 2, 3, 4]]) sage: column_list = A.columns()
Let b = vector(QQ, [-27, -77, -52, 5])
. Use the Sage commands columns
and (QQ^4).span
to verify that the system Ax = b
is consistent. (Hint: write a line in Sage that tests for membership
of b
in the column space of A
and returns True
.)
Although the above works perfectly well, Sage has its own column space command, called .column_space()
. It's very convenient, so let's try it out.
sage: A = matrix(QQ, [[ 1,-1, 0], [ 2, 3, 9], [ 0, -3, -4], [-1, 4, 8]]) sage: CA = A.column_space() sage: CA == (QQ^4).span(A.columns()) # check we get the same answer either way True
That the equality test in the last line above returns True
provides evidence that our means of computing the column space of A
is equivalent to Sage's build-in column_space()
command.
Now if, say, b = vector(QQ, [-16, 43, -10, 126])
, then we can check that the system Ax = b
is consistent as follows:
sage: b = vector(QQ, [-16, 43, -10, 126]) sage: b in A.column_space() True
Knowing that b
is in the column space of A
, we can now ask Sage to solve for x
in the system Ax = b
, without fear of producing an error.
sage: A.solve_right(b) (-46, -30, 25)
To gain more familiarity with Sage, continue exploring on your own. Here are some suggestions:
Construct a right-hand side vector for which the system above is inconsistent, then look up what Sage commands are available for least squares solutions and/or projections.
Apply these to the example you just constructed to find the vector in the column space of $A$ that is closest to your right-hand side vector.
If you have studied computer science or programming, then you have probably heard of recursion, streams, and the like, and you should not be surprised that a computer can represent and compute with infinite sets, despite the fact that a computer is a finite entity with finite amount of physical memory. For example, a "stream" is basically just an infinite list that gives us access to its elements as needed... as many as we need, the stream will generate for us... but we are not allowed to ask for the entire list at once!
In this lab we will learn how to create a linear transformation in Sage, and try out some of the many operations we can perform on such objects. As in the previous lab assignment, rather than work over the real or complex numbers as our base number field, we will work (at least initially) over $\mathbb{Q}$, the field of rational numbers, as this gives us better insight into the theory we have been studying.^{1}
Let $V = \mathbb{Q}^3$ and $W = \mathbb{Q}^4$, and consider the linear transformation $T: V \to W$ defined as follows:
$$T(\mathbf{x}) = T \begin{bmatrix}x_1\\x_2\\x_3\end{bmatrix} =\begin{bmatrix}-x_1 + 2x_3\\x_1 + 3x_2 + 7x_3\\x_1 + x_2 + x_3\\2x_1 + 3x_2 + 5x_3\end{bmatrix} \qquad\qquad (\mathrm{eq1})$$ To create this linear transformation in Sage, we will create a "symbolic" function. But this requires some symbolic variables, which we will name x1
, x2
and x3
.
This is done as follows:
x1, x2, x3 = var(âx1, x2, x3â) # (eq 2)
Now we give a name to the transformation mentioned in (eq1), and define it (symbolically) as follows:
Tsymb(x1, x2, x3) = [-x1+2 *x3, x1+3*x2+7*x3, x1+x2+x3, 2*x1+3*x2+5*x3] # (eq 3)
Define and experiment with Tsymb
, using the following steps to guide you:
a. Create a new Sage worksheet if you haven't already, and name it something like Lab4
.
Enter the above expressions (eq 2)
and (eq 3)
in a worksheet cell and then evaluate the cell.
b. Experiment with the Tsymb
object.
For example, you could evaluate the function at a triple of rational numbers, say, Tsymb (3, -1, 2/3)
.
Alternatively, you could try something a little more exotic, like calculating the partial derivative of Tsymb
with respect to x3
.
To see what methods are available to the Tsymb
object, try the following:
In a new cell of your worksheet, type Tsymb.
(making sure to include the trailing dot).
Then, with the cursor immediately after the dot, hit the Tab
key.
You should see a drop-down list of all the functions that can be applied to the object Tsymb
.
Some of the entries in the list begin with the string Tsymb.is_
followed by the name of some property. Such commands allow us to interrogate Tsymb
to find out whether it has the given property.
c. Find out whether Tsymb
is immutable.
If you don't know what "immutable" means, enter the expression Tsymb.is_immutable?
and evaluate it.
By appending a question mark to a command, we are asking Sage to display the documentation page for that command.
The question mark is a very useful feature of Sage that you should keep in mind for future reference.
d. Another command that should have appeared in the drop-down list when you typed Tsymb.
and then Tab
is called derivative
.
You probably have an idea of what this function does, but just to be sure, evaluate Tsymb.derivative?
in a worksheet cell, then compute the partial derivative of Tsymb
with respect to x3
.
At this point, our Tsymb
object is a vector of mathematical expressions that involve symbolic variables. We want to tell Sage to convert it into a linear transformation, as this will prompt Sage to
make available a whole new set of functions that we can apply to Tsymb
.
We will use the linear_transformation()
constructor, which requires us to carefully specify the domain and codomain, as vector spaces over the rational number field $\mathbb{Q}$.
V= QQ^3 W = QQ^4 T = linear_transformation(V, W, Tsymb)
Now, we can try out our newly constructed linear transformation T
by evaluating it on some input vector, say, ${{\mathbf{x= (3,-1,2)$,
x = vector(QQ, [3, -1, 2]) T(x)
Enter these commands in a Sage worksheet cell and make sure you get the answer (1, 14, 4, 13)
.
Recall the linear transformation from Homework Exercise 4.3.7, $$T(\mathbf{x} = T\begin{bmatrix} x_1\\x_2\\x_3 \end{bmatrix} =\begin{bmatrix}-x_1 + 2x_2 + x_3\\x_2 + 3x_3\\x_1 - x_2 + x_3\end{bmatrix}.$$
Use Sage to find the matrix representation of $T$ by following these steps.
a. In your Sage worksheet, construct the linear transformation described by (eq2). (Use the what you learned in Part 1.)
V = QQ^3 Tsymb = #...(fill in the rest here) T = linear_transformation ... (be careful to specify the right domain and codomain)
b. By now you have a lot of experience with linear transformations, and with just a glance at Equation (eq:2) you can surely write down a matrix that represents $T$. (Do it!)
Of course, Sage has a command that will do this for you. Try evaluating T.matrix()
in your Sage worksheet.
Is the output of this command what you expected?
If not, maybe there is an option that makes the matrix()
function produce a more satisfying output. Try to find this option and use it.
(Remember, you can get help with the matrix
function by entering T.matrix?
.)
c. In this part, we will use Sage to find the matrix representation $[T]_{\mathcal B}$ of $T$ with respect to the basis $$\mathcal{B} = \left\{ \begin{bmatrix} 1\\ 0 \\ -1 \end{bmatrix}, \begin{bmatrix} 0\\2\\3 \end{bmatrix}, \begin{bmatrix} 1\\1\\1 \end{bmatrix} \right\}.$$
We will do this in two different ways and then check that both methods yield the same result.
Method 1: Define the three basis vectors in $\mathcal B$ using the vector
constructor:
b1 = vector(QQ, [1,0,-1]) # etc.
Construct the change-of-basis matrix $P$, and then compute $P^{-1} T P$.
One way to construct $P$ is with the command matrix(QQ, [b1,b2,b3]).transpose()
. Don't forget to apply the matrix
method to $T$ before using it in matrix computations, as in
TB1 = P.inverse() * T.matrix(side="right") * P
Method 2: Let V = QQ^3
and define VB = V.subspace_with_basis([b1, b2, b3])
.
Use the linear_transformation
constructor, as you did above, but this time replace each occurrence of V
with VB
, since VB
is the vector space QQ^3
endowed with the new basis. That is, compute
TB2 = linear_transformation(VB, VB, Tsymb)
Finally, check that the two methods give the same result:
TB1 == TB.matrix(side=ârightâ) # (this should return True)
Given a linear transformation $T: V \to W$, we may want to find a basis that is in some sense optimal for representing $T$.
Specifically, we often seek a matrix representation of $T$ that is as close to diagonal as possible.
Let $A$ be the standard basis representation of $T$, that is, $A = [T]_{\mathcal E}$.
We learned that a necessary and sufficient condition for diagonalizability of $A$ is that the algebraic multiplicity of each eigenvalue of $A$ must be the same as the geometric multiplicity.
We can interpret this in a few equivalent ways:
$A$ is similar to a diagonal matrix, $D = P^{-1} A P$.
There is a change-of-basis that diagonalizes $A$.
There is a basis $\mathcal B$ with respect to which the $\mathcal B$-basis representation $[T]_{\mathcal B}$ is a diagonal matrix.
These statements all say the same thing. Importantly, when these conditions hold, the basis $\mathcal B$ consists of eigenvectors of $A$, and $D$ has the eigenvalues of $A$ along the main diagonal.
Example 5 on Page 273 of our textbook involves two matrices, $$A =\begin{bmatrix}-1&4&2 \\ -1&3 &1 \\ -1&2 &2 \end{bmatrix} \qquad \text{ and } \qquad B =\begin{bmatrix} 0&3&1 \\ -1&3 &1 \\ 0&1 &1 \end{bmatrix}.$$
Use Sage to compute the characteristic polynomial and the eigenvalues/vectors of $A$.
Check that $A$ is diagonalizable and perform the similarity transformation that gives the diagonalization.
Use the steps below as a guide. Later, come back and do the same $B$. What goes wrong?
a. Construct the matrix $A$ with the command
A = matrix(QQ, [[-1,4,2], [-1,3,1], [-1,2,2]])
Use the following commands to compute its characteristic polynomial.
p = A.characteristic_polynomial() p.factor()
b. Compute the roots of the characteristic polynomial with p.roots()
.
The output should be a list of two ordered pairs.
Can you make sense of this output?
c. You computed the eigenvalues of $A$ indirectly in the last step. Now compute them directly with the Sage command A.eigenvalues()
.
Compare the output to the result from part b.
d. You found eigenvalues in the last two steps; name them ev1
and ev2
.
Compute the eigenvectors of $A$ using the right_kernel
command that we learned in Lab 3.
Recall that, for each eigenvalue $\lambda$, the eigenspace is the null space of $A - \lambda I$.
Here's the first one:
I = identity_matrix(3) E1 = (A-ev1 * I).right_kernel(basis='pivot')
Finally, compute the change-of-basis matrix $P$ whose columns are the eigenvectors of $A$ and check that $P^{-1}A P$ gives a diagonal matrix with the eigenvalues of $A$ along the main diagonal.
Here's one way to do it:
b12 = E1.matrix().transpose() # we want the eigenvectors as columns, b3 = E2.matrix().transpose() # not rows, so we apply transpose() P = b12.augment(b3); print P
After checking that the columns of P
are exactly the eigenvectors you found above, you can compute the diagonalization of $A$ as usual: P.inverse()*A*P
.
To experiment more with change-of-basis, see if you can come up with an example of the more general version of the change-of-basis theorem---Theorem 4.2, page 231 of our textbook---and try it out in Sage.
That is, given a linear transformation $T: V \to W$, and two bases $\mathcal V$, $\mathcal V'$ for $V$, and two bases $\mathcal W$, $\mathcal W'$ for $W$, use the formula $$[T]_{\mathcal V', \mathcal W'} = Q^{-1}[T]_{\mathcal V,\mathcal W} P$$ to compute the $(\mathcal V',\mathcal W')$-basis representation of $T$.
You will have to generalize one of the two methods described in Exercise 2.c. above, but by now it should be clear how to do this.
If it isn't clear, then check out the examples on page 127 (Section MR) and page 138 (Section MRCB) of the document fcla-2.22-sage-4.7.1-preview.pdf.
Of course, Sage has its own built-in commands for computing eigenvectors and eigenspaces.
Here are some commands you could try in the context of Exercise 3.d.: A.eigenvectors_right()
, A.eigenspaces_right()
.
Compare the results of these commands with the results produced by the right_kernel
that we used above.
See also Section EE (p. 95) of the document fcla-2.22-sage-4.7.1-preview.pdf for more details about computing eigenvalues in Sage.
In particular, we want to work over a field that contains the roots of the characteristic polynomial. (In Exercise 3, the eigenvalues were real numbers so this was not an issue.)
Working over $\mathbb{Q}$ makes it possible for Sage to perform exact arithmetic, which avoids the round-off errors associated with finite precision arithmetic over the real or complex numbers. This makes our demonstration of the theory much cleaner and clearer. In applications, we donât always have the luxury of working over $\mathbb{Q}$. Typically the results we observe when applying linear algebra âin the real-worldâ are not as elegant as the results presented in a first course in linear algebra. Nonetheless, the theoretical principles that we highlight (using exact arithmetic) in this lab assignment apply equally well in practical situations, even those that require real and complex numerical computation.
<div class="left" id="main-content"> <div class="section"> <video title="William DeMeo's Slideshow" width="400" height="200" controls>
</div> </div> </div>
\subsection{Algebras and Algorithms, Structure and Complexity}
\label{sec:csp}
In 2015, I joined a group of 8 other scientists to form a universal algebra research group and secure a 3-year \textsc{nsf} grant for the project, Algebras and Algorithms, Structure and Complexity Theory.'' Our focus is on fundamental problems at the confluence of logic, algebra, and computer science, and our main goal is to deepen understanding of how to determine the complexity of certain types of computational problems. We focus primarily on classes of algebraic problems whose solutions yield new information about the complexity of \csps. These include scheduling problems, resource allocation problems, and problems reducible to solving systems of linear equations. \csps are theoretically solvable, but some are not solvable efficiently. Our work provides procedures for deciding whether a given instance of a \csp is tractable or intractable, and we develop efficient algorithms for finding solutions in the tractable cases. My work on this project culminated in a 50-page manuscript, co-authored with Cliff Bergman, entitled
Universal Algebraic Methods for Constraint Satisfaction Problems'' ~\cite{Bergman-DeMeo:2016}. This was recently accepted for publication in the journal \textit{Logical Methods in Computer Science} \textsc{(lmcs)}; a draft is at~\href{https://arxiv.org/abs/1611.02867}{arXiv [cs.LO] 1611.02867}.
(More exams coming soon.)
Year | Spring | Fall | |
---|---|---|---|
1991 | $\mathbb C$ | $\mathbb C$ Â Â $\mathbb R$ | |
1992 | Rng | ||
1993 | Grp | ||
1994 | $\mathbb R$ | ||
1995 | $\mathbb C$Â Â Rng | Rng | --> |
1996 | Rng | ||
1998 | $\mathbb R$ | ||
1999 | GrpÂ Â Rng | ||
2000 | $\mathbb R$Â Â GrpÂ Â Rng | ||
2001 | $\mathbb C$Â Â $\mathbb R$Â Â Rng | ||
2002 | $\mathbb C$Â Â Grp | ||
2003 | $\mathbb C$Â Â Rng | $\mathbb C$Â Â GrpÂ Â Rng | |
2004 | $\mathbb C$Â Â $\mathbb R$ | GrpÂ Â Rng | |
2006 | Grp | $\mathbb C$Â Â Grp | |
2007 | $\mathbb C$Â Â Grp | $\mathbb C$Â Â $\mathbb R$ | |
2008 | GrpÂ Â Rng Â (Jan); | ||
GrpÂ Â Rng Â (April) |
(Key: $\mathbb C$ = complex analysis, $\mathbb R$ = real analysis, Grp = group theory, Rng = ring theory)
]]>This page describes some of the steps I took to install and configure Ubuntu Linux 18.04 on a Lenovo X1 Yoga (3rd Generation) laptop.
Purpose of this page. I post this information so that I may easily refer to it in the future, if/when I forget how to do this stuff next time I configure a machine.
Disclaimer. If you make use of the information on this page, then you do so at your own risk. I am not responsible for any damages or injuries sustained as a result of following the instructions below.
On the other hand, if you follow these instructions and something doesn't work for you, I'd like to hear about it. In that case, please send me an email!
Download Ubuntu 18.04 and make a startup iso image on a blank usb drive. (Use a utility like the Ubuntu Startup Disk Creator app.)
Boot the laptop and hit Enter
when the Lenovo splash screen appears to enter the BIOS setup utility. (Be very careful to not change too many BIOS settings. You can brick the machine if you mess around too much with the BIOS.)
As suggested here, modify the EFI BIOS settings and disable "Secure Boot" feature. This setting is found in the "Security > Secure Boot" menu of the BIOS configuration program.
In the "Startup" BIOS menu item,
Boot into Windows, and follow the repartitioning instructions given at this site
Here is a summary of those instructions.
Assuming your machine comes with a single partition on which Windows 10 has been installed, you will need to shrink the Windows partition to make room for Ubuntu. (N.B. This can be done without damaging the Windows 10 installation so that, in the end, you will have a dual boot machine---i.e., you can choose at boot time whether to run windows or linux.)
First, boot up Windows 10 and get to the DOS command line (CL) by openning a Command Prompt window.
At the DOS CL, invoke the diskmgmt.msc
command to open the Disk Management utility.
Right click on the Windows partition (usually C:
volume) and select the Shrink Volume
option in order to reduce the partition size.
Wait for the system to collect partition size data, enter the desired amount of space you want to shrink, and hit in Shrink button.
After the shrink process completes, new free space will appear on your drive. This free space is where Ubuntu will be installed.
When prompted for the type of installation, I recommend choosing the minimal option to avoid installing too much bloatware. You can easily add additional software later using apt install <program name>
.
(apt is the Debian package manager, which provides the standard means of installing and remove software on Debian based distributions, such as Ubuntu.)
The additional software packages that I like to install when setting up a new Ubuntu machine are listed in the sections below, along with commands required to install them.
The remaining sections of this page describe things to do after installing Ubuntu. These sections describe how to implement the specific configuration and customizations that I like, as well as fix some hardware issues specific to the Lenovo X1.
(I could add many more details about installing Ubuntu here, but this is covered extensively elsewhere.)
sudo apt update sudo apt upgrade
The resolution on the latest X1s can reach 2560x1440, which is incredibly hight for a 14"-diagonal screen. As a result, upon login, the window manager defaults to 200% zoom, which makes everything appear too big, as if the machine has pathetically low resolution.
On the other hand, 100% zoom (i.e., no zoom) requires a very good pair of eyes or a very good pair of binoculars (or both) to see what's on the screen. To solve this, set the zoom to 100% and then use the command line to achieve a comfortable fractional zoom level of, say, 140% (i.e., 1.4 zoom factor), as follows:
Hit Windows key, then type settings
and hit Enter.
In the "Devices > Displays" section, set the zoom level to 100% (no zoom).
If everything is too small (but too big at 200% zoom), then invoke the following command to obtain a 1.4x zoom factor:
gsettings set org.gnome.desktop.interface text-scaling-factor 1.4
To make this fix easier to use in the future, I recommend creating a file called zoom.sh, in a directory in your search path, with the following contents:
#!/bin/bash gsettings set org.gnome.desktop.interface text-scaling-factor $1
Then make the file executable and create a link to it with a simpler name; e.g., if you put the file zoom.sh
in the $HOME/bin
directory, do the following:
chmod a+x zoom.sh ln -s $HOME/bin/zoom.sh $HOME/bin/zoom
and make sure the $HOME/bin
directory is in your search path (invoke echo $PATH
to see your search path).
Then, to execute this new shell script, applying a scaling factor of, say, 1.2, you would simply enter zoom 1.2
on the command line.
See my linux utils repo, especially the dotfiles-setup subdirectory.
(N.B. There is some overlap between the dotfiles-setup procedure and what's described below; e.g., in both places I suggest installing emacs.)
Generate ssh keys and and post the public key on your github and bitbucket account pages.
ssh-keygen # Hit Enter (to select default filename) # Hit Enter twice (to select/confirm empty passphrase) cat $HOME/.ssh/id_rsa.pub
Copy all of the output that the previous command produces including ssh-rsa
and your usename@hostname
and past it into the input windows on the "ssh keys" pages at github and bitbucket. (Login to account at github.com and bitbucket.org and find the Settings -> SSH
page.)
settings
and hit Enter.Alt-Tab
to set the switch-window behavior to the old-style.Backspace
key to disable it.+
to add a new keyboard shortcut; in the "Name" field, type Launch terminal left
; in the command field, type gnome-terminal --geometry 140x75+0+0
and select "Set shortcut..." and type Alt+Ctrl+T
.+
again; in the "Name" field, type Launch terminal right
; in the command field, type gnome-terminal --geometry 140x75-0+0
and select "Set shortcut..." and type Shift+Alt+T
. (N.B. the - instead of + in "140x75 - 0+0".)Install gnome-tweak-tool
with the command
sudo apt install gnome-tweak-tool
Launch gnome-tweak-tool by typing gnome-tweaks
at the command prompt.
In the "Keyboard & Mouse" section,
In the "Top Bar" section, set "Battery Percentage" and "Clock > Date" to ON.
Install Numix theme:
sudo add-apt-repository ppa:numix/ppa
sudo apt-get update
sudo apt-get install numix-gtk-theme numix-icon-theme-circle
(See this page for more detailed instructions about installing the Numix theme.)
Launch gnome-tweaks
and select the "Appearance" settings and adjust the "Themes" settings as desired.
(I like the following:
Applications: Adwaita-dark Cursor: Redglass Icons: Numix-Circle
The following is a list of apps I install from the command line using sudo apt install <name of package>
(The command used to install all packages at once appears below.)
Name | Description |
---|---|
emacs | GNU Emacs editor (metapackage) |
coq | the Coq proof assistant |
proofgeneral | a generic frontend (IDE) for proof assistants |
prooftree | proof-tree visualization for Proof General |
git | fast, scalable, distributed revision control system |
mg | microscopic GNU Emacs-style editor |
nemo | File manager and graphical shell for Cinnamon |
build-essential | Informational list of build-essential packages |
cmake | cross-platform, open-source make system |
dconf-tools | transitional dummy package |
libgmp-dev | Multiprecision arithmetic library developers tools |
pm-utils | utilities and scripts for power management |
Okular | Universal document viewer |
djview4 | djvu document viewer |
texlive | TeX Live: A decent selection of the TeX Live packages |
texlive-bibtex-extra | TeX Live: BibTeX additional styles |
texlive-generic-extra | TeX Live: transitional dummy package |
texlive-latex-extra | TeX Live: LaTeX additional packages |
texlive-latex-recommended | TeX Live: LaTeX recommended packages |
texlive-publishers | TeX Live: Publisher styles, theses, etc. |
texlive-science | TeX Live: Mathematics, natural sciences, computer science packages |
texlive-xetex | TeX Live: XeTeX and packages |
cargo | Rust package manager |
To install all of these at once, copy-and-paste the following into a terminal window:
sudo apt install emacs coq proofgeneral prooftree git mg nemo build-essential cmake dconf-tools pm-utils okular djview4 texlive texlive-latex-extra texlive-xetex texlive-science texlive-latex-recommended texlive-publishers texlive-generic-extra texlive-bibtex-extra cargo
After installing the above, apt
recommends installing more packages, which I do as follows:
sudo apt install tcl-tclreadline python-pygments icc-profiles libfile-which-perl libspreadsheet-parseexcel-perl texlive-latex-extra-doc dot2tex prerex texlive-pictures-doc vprerex texlive-publishers-doc texlive-science-doc
View a brief description of these packages with the command:
dpkg -l tcl-tclreadline python-pygments icc-profiles libfile-which-perl libspreadsheet-parseexcel-perl texlive-latex-extra-doc dot2tex prerex texlive-pictures-doc vprerex texlive-publishers-doc texlive-science-doc
We will need some extra packages, mainly for creating nice documentation; these are installed as follows:
sudo apt install ttf-bitstream-vera fonts-linuxlibertine texlive-lang-french dvipng latexmk sphinx-doc sgml-base-doc debhelper docutils-doc fonts-linuxlibertine libjs-mathjax fonts-mathjax-extras fonts-stix
sudo apt install python python-pip python-sphinxcontrib.bibtex-doc python-jinja2-doc python-latexcodec-doc python-pybtex-doc python-pybtex-docutils-doc python-sortedcontainers-doc
sudo apt install python3 python3-pip python3-sphinx python3-sphinxcontrib.bibtex python3-sphinx-rtd-themedocutils-doc python3-venv
To see a brief description of each of these, do
dpkg -l ttf-bitstream-vera fonts-linuxlibertine texlive-lang-french dvipng latexmk sphinx-doc sgml-base-doc debhelper docutils-doc fonts-linuxlibertine libjs-mathjax fonts-mathjax-extras fonts-stix python python-pip python-sphinxcontrib.bibtex-doc python-jinja2-doc python-latexcodec-doc python-pybtex-doc python-pybtex-docutils-doc python-sortedcontainers-doc python3 python3-pip python3-sphinx python3-sphinxcontrib.bibtex python3-sphinx-rtd-theme docutils-doc
Here's (an abridged, reordered version of) the output.
Name | Description |
---|---|
debhelper | helper programs for debian/rules |
docutils-doc | text processing system for reStructuredText - documentation |
dvipng | convert DVI files to PNG graphics |
fonts-linuxlibertine | Linux Libertine family of fonts |
fonts-mathjax-extras | JavaScript display engine for LaTeX and MathML (extra fonts) |
fonts-stix | Scientific and Technical Information eXchange fonts |
latexmk | Perl script for running LaTeX the correct number of times |
libjs-mathjax | JavaScript display engine for LaTeX and MathML |
sphinx-doc | documentation generator for Python projects - documentation |
python-jinja2-doc | documentation for the Jinja2 Python library |
python-latexcodec-doc | LaTeX lexer and codec library for Python (docs) |
python-pybtex-doc | documentation for pybtex |
python-pybtex-docutils-doc | documentation for pybtex-docutils |
python-sortedcontainers-doc | sorted container types: SortedList, SortedDict, and SortedSet |
python-sphinxcontrib.bibtex-doc | documentation for sphinxcontrib-bibtex |
python3 | interactive high-level object-oriented language (default python |
python3-sphinx | documentation generator for Python projects (implemented in Pyt |
python3-sphinx-rtd-theme | sphinx theme from readthedocs.org (Python 3) |
python3-sphinxcontrib.bibtex | Sphinx extension for BibTeX style citations |
python3-venv | pyvenv-3 binary for python3 (default python3 version) |
sgml-base-doc | Documentation for sgml-base |
texlive-lang-french | TeX Live: French |
ttf-bitstream-vera | The Bitstream Vera family of free TrueType fonts |
sudo apt update sudo apt upgrade
sudo apt install cargo cmake curl jq libgmp-dev npm
dpkg -l cargo cmake curl jq libgmp-dev npm
Name | Description |
---|---|
cargo | Rust package manager |
cmake | cross-platform, open-source make system |
curl | command line tool for transferring data with URL syntax |
jq | lightweight and flexible command-line JSON processor |
libgmp-dev:amd64 | Multiprecision arithmetic library developers tools |
npm | package manager for Node.js |
pip
is the python installation program
These programs are useful for creating nice documentation using sphinx.
pip install https://bitbucket.org/gebner/pygments-main/get/default.tar.gz#egg=Pygments pip install sphinx pip install sphinxcontrib-bibtex pip install sphinxcontrib-proof
Launch emacs and invoke M-x package-list-packages
. From the list, choose to install the following:
App Name | Description | url |
---|---|---|
Enpass | password manager | https://www.enpass.io/ |
MEGAsync | synch between computer and MEGA cloud storage account | http://mega.nz |
atom | modern extensible editor | https://atom.io |
Chrome | web browser | https://www.google.com/chrome |
IntelliJ IDEA | IDE I use for programming in Scala | https://www.jetbrains.com/idea/download |
Lean | Interactive theorem prover and fp lang | https://leanprover.github.io |
VS Code | IDE I use for programming in Lean | https://code.visualstudio.com/ |
JDK | Java Development Kit | oracle javase overview & download page |
Acroread | pdf viewer | See these instructions |
Enpass. To install Enpass, do the following:
sudo su # (enter password if/when prompted)
echo "deb https://apt.enpass.io/ stable main" > /etc/apt/sources.list.d/enpass.list
wget -O - https://apt.enpass.io/keys/enpass-linux.key | apt-key add -
Then
apt update; apt install enpass
If necessary, do
apt --fix-broken install; apt install enpass; exit
MEGAsync. Go to http://mega.nz and use the hamburger menu on the right to select
"Apps" > "sync client" and download the appropriate .deb package. Once the download
finishes, click on the downloaded file and you should be asked whether to open
the file with the Ubuntu App Installer. Select "yes" and install MEGAsync.
Alternatively, enter sudo dpkg -i megasync*.deb
on the command line. Finally, launch
the Megasync program and start syncing!
Atom. Go to https://atom.io and download atom-amd64.deb
. Once the download
finishes, click on the downloaded file and you should be asked whether to open
the file with the Ubuntu App Installer. Select "yes" and install Atom.
Alternatively, enter sudo dpkg -i atom-*.deb
on the command line. Finally, launch
Atom from the command line with the command atom &
and start editing!
Chrome. (see instructions for atom, except use https://www.google.com/chrome)
IntelliJ IDEA. Go to JetBrains download page and login, indicate your academic status, and get a license to use the full version for free. Download the file ideaIU-2018.1.4.tar.gz
(or similar) and extract it.
tar xvzf ideaIU-*.gz mkdir -p $HOME/bin ln -s $HOME/opt/IntelliJ/idea-IU-181.5087.20/bin/idea.sh $HOME/bin/idea
Then make sure $HOME/bin
is in your search $PATH
; e.g., input
export PATH="$HOME/bin":$PATH
at the command line, and also put this line in your $HOME/.bash_profile
file to make it permanent.
Lean
Warning: Installing Lean from source takes quite a while, say, 30 minutes. If you're not sure whether you need to compile Lean, consider trying the precompiled binaries.
sudo apt install git libgmp-dev cmake mkdir -p $HOME/git/PROGRAMMING/LEAN cd $HOME/git/PROGRAMMING/LEAN git clone git@github.com:leanprover/lean.git
cd lean mkdir -p build/release cd build/release cmake ../../src make
VS Code and vscode-lean
Go to https://code.visualstudio.com/ and download the vscode .deb file. Once download finishes, open a terminal window, go to the directory where you downloaded the vscode .deb file, and install it with the command sudo dpkg -i code_*.deb
.
Next, clone the vscode-lean
repository with either
mkdir -p $HOME/git; cd $HOME/git git clone git@github.com:leanprover/vscode-lean.git
or
mkdir -p $HOME/git; cd $HOME/git git clone https://github.com/leanprover/vscode-lean
Next, install the node package manager.
sudo apt install npm
Next, install vscode-lean
.
cd $HOME/git/vscode-lean npm install
Finally, launch vscode by entering code
on the command line, then open vscode-lean
and start developing (F5 starts the debugger).
JDK Go to the oracle javase overview & download page and download the deb
file of the version of the Java Development Kit (JDK) you wish to install, if such a file exists. (For earlier versions of Java, there are no .deb installation files available. In that case, download the appropriate .tar.gz file.)
For example,
Setup Java 8 with the following commands:
tar xvzf jdk-8u221-linux-x64.tar.gz; sudo mkdir -p /usr/lib/jvm; sudo mv jdk1.8.0_221 /usr/lib/jvm/jdk-8.0.221; sudo chown -R root:root /usr/lib/jvm/jdk-8.0.221; sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk-8.0.221/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk-8.0.221/bin/javac" 1; sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/lib/jvm/jdk-8.0.221/bin/javaws" 1; sudo update-alternatives --install "/usr/bin/jcontrol" "jcontrol" "/usr/lib/jvm/jdk-8.0.221/bin/jcontrol" 1;
Setup Java 11 and 12 similarly. (Sadly, these more recent versions of Java no longer come with javaws
and jcontrol
commands.)
sudo apt install jdk-11.0.4_linux-x64_bin.deb sudo chown -R root:root /usr/lib/jvm/jdk-11.0.4; sudo apt install jdk-12.0.2_linux-x64_bin.deb sudo chown -R root:root /usr/lib/jvm/jdk-12.0.2; sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk-11.0.4/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk-11.0.4/bin/javac" 1; sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk-12.0.2/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk-12.0.2/bin/javac" 1;
Let's make sure all the Java programs are executable. (This may be unnecessary, but it probably doesn't hurt.)
sudo chmod a+x /usr/bin/java; sudo chmod a+x /usr/bin/javac; sudo chmod a+x /usr/bin/javaws; sudo chmod a+x /usr/bin/jcontrol;
To set the default Java version, invoke the command sudo update-alternatives --config java
and make a selection. Do the same for each Java program we wish to install. (These commands require feedback from the user, so they must be invoked separately.)
sudo update-alternatives --config java
sudo update-alternatives --config javac
The next two are probably unnecessary, since we only have Java 8 versions, but it doesn't hurt to check.
sudo update-alternatives --config javaws
sudo update-alternatives --config jcontrol
See also this post for more details about this setup.
UACalc. To install the Universal Algebra Calculator,
go to uacalc.org and download one of the jnlp files (depending on how much RAM you have). (For instance, I downloaded uacalcbig8.jnlp since I have 16Gb of RAM.)
Next, we will make a desktop application launcher for UACalc.
In a terminal window, change the working directory to wherever you downloaded the uacalc*.jnlp file, then do
mimetype uacalcbig8.jnlp
to find out how linux refers to this file type. The output should be application/x-java-jnlp-file
. Become the superuser,
sudo su # (enter password when prompted)
and then invoke the following command:
echo "application/x-java-jnlp-file=javaws" >> /usr/share/applications/defaults.list
Open your favorite editor and create a file called UACalc.desktop
which you should save in your ~/Desktop
directory. The file ~/Desktop/UACalc.desktop
should have the following contents:
#!/usr/bin/env xdg-open [Desktop Entry] Version=1.0 Type=Application Terminal=false Exec=javaws /home/williamdemeo/opt/uacalcbig8.jnlp Name=UACalc Comment=UACalc Icon=/usr/share/icons/Numix-Circle-Light/48/apps/javaws.svg
You can choose a different value for the Icon
field, especially if you didn't install the Numix theme, as described in the section on Emacs shortcuts, Ctrl position, Top bar, and theme.
Right click on the new file you just created on your desktop and select Properties > Permissions
and check the box next to "Allow executing file as program".
Double-click on the new desktop file and select "Run" or "Okay" when confronted with "untrusted application" and other such warnings. The UACalc GUI should launch at this point.
If these instructions don't work, I'd like to know about it, so please send me a message.
curl https://sh.rustup.rs -sSf | sh
Note: you will probably get an error if Rust is already installed on your system.
Reference: https://www.rust-lang.org/tools/install
(I did snap install --edge zola
.)
Brother printer drivers. For our printer, the appropriate link is this one.
Download the appropriate file (for me it is linux-brprinter-installer-2.2.1-1.gz), extract and run it and then follow the onscreen instructions.
For me, the appropriate commands are:
gunzip linux-brprinter*.gz sudo su bash linux-brprinter-installer-* DCP-7065DN
Launch Atom by typing atom&
at the command line. Select "Edit > Preferences" from the menu, then select "-Install". Search for and install the following:
Lauch vscode by typing code &
at the command prompt. From the "View" menu, select "Extensions". A search box should appear in the left pane. Type each of the names of each of extensions in the list below, hitting enter after each name. Each time a list of candidates should appear. When you find the one you want, select "install."
Alternatively, you could install these (and others of my favorite extensions) on the linux command line as follows:
code --install-extension alefragnani.project-manager; code --install-extension bungcip.better-toml; code --install-extension codezombiech.gitignore; code --install-extension donjayamanne.git-extension-pack; code --install-extension donjayamanne.githistory; code --install-extension eamodio.gitlens; code --install-extension gerane.Theme-Zenburn; code --install-extension GuidoTapia2.unicode-math-vscode; code --install-extension jroesch.lean; code --install-extension lextudio.restructuredtext; code --install-extension lfs.vscode-emacs-friendly; code --install-extension ms-python.python; code --install-extension pomber.git-file-history; code --install-extension streetsidesoftware.code-spell-checker; code --install-extension yzhang.markdown-all-in-one; code --install-extension ziyasal.vscode-open-in-github;
The list of commands above was produced by entering
code --list-extensions | xargs -L 1 echo code --install-extension
on the command line (source: https://stackoverflow.com/a/49398449).
To my surprise, the first time I tried to use the X1Y3 to give a talk, the "mirror displays" option had disappeared from its usual place in the Settings > Devices > Displays menu. It took me a while to find the solution to this, but thanks to Franck Pommereau's brilliant blog post, the solution is straight-forward.
See Franck Pommereau's post for details, but here's the gist. The command
xrandr -q
outputs information about the resolution of the connected displays. We want to add a resolution mode to the e-DPI display (the laptop display) that agrees with one of the resolution modes of the HDMI connected display (i.e., the projector). For me, the following command did the trick:
xrandr --addmode eDP-1 1400x1050 xrandr --addmode eDP-1 1680x1050 xrandr --addmode eDP-1 1920x1080i
Now the "mirror displays" option appears again in the Settings > Devices > Displays menu, and that menu also shows the 3 resolution modes (specified in the above commands) for me to choose from.
Note. When I give a presentation on a different projector, I will probably want to run the xrandr -q
command again to determine the best resolution to set up for that projector.
The fix described in this section are no longer necessary as of Ubuntu release 18.04.2.
(to find out which release you're using: Settings --> Details)
Following the advice given in this answer, and on this page,
N.B. The instructions in this section assume you have the mg
(microscopic Emacs) editor installed. You should either install mg
before following these instructions (e.g., with sudo apt install mg
), or replace occurrences of mg
with the name of your favorite text editor.
Make sure the xserver-xorg-input-libinput
package is installed.
sudo apt-get install xserver-xorg-input-libinput
Edit the file /etc/udev/rules.d/10-trackpoint.rules
,
sudo -i mg /etc/udev/rules.d/10-trackpoint.rules
so that it includes the following lines:
ACTION=="add", SUBSYSTEM=="input", ATTR{name}=="TPPS/2 IBM TrackPoint", ATTR{device/sensitivity}="132", ATTR{device/speed}="158", ATTR{device/inertia}="6", ATTR{device/press_to_select}="0"
Finally, change the acceleration of the cursor.
sudo mg /usr/share/X11/xorg.conf.d/90-libinput.conf
Replace the first section with the following code:
Section "InputClass" Identifier "libinput pointer catchall" MatchIsPointer "on" MatchDevicePath "/dev/input/event*" Driver "libinput" Option "AccelSpeed" "-0.40" EndSection
Save the file with C-x C-c
.
Finally, reboot and test the trackpoint and touchpad.
The steps in this section are probably unecessary, especially if you selected "minimal" when installing ubuntu.
Delete "favorites" icons
Delete "favorites" apps
Also, it is advisable to invoke the command sudo apt autoremove
every once in a while to get rid of obsolete packages that may be lingering on your machine.
Reference: this lifehacker article was useful.
My machine was dual-boot capable (Linux & Windows 10), but I decided to wipe Windoze off my machine for two reasons.
I deleted my Windows partition by following these steps.
At this point, we could enlarge the existing Linux partition(s), or simply make some new partitions. I chose the latter since I wanted to make a large swap partition in order to get "hibernate" to work.
(Enlarging existing linux partitions is described in the lifehacker article mentioned above.
When finished, the hard drive will only have Linux on it. However, the boot menu may still have some Windows entries. These shouldn't cause any problems, but the command sudo update-grub
may succeed in removing obsolete menu entries.
Good references for this topic are this page and this page.
On my machine, I did swapon --show
and found a swapfile called /swapfile
with only 2G of swap space.
I added a new swap partition with gparted
(in the new space that became available when I deleted my windows partition), making note of the UUID that was assigned to the swap space.
Then I edited the file /etc/fstab
, commenting out the line for /swapfile
and insert the following new line:
UUID=b9f55147-75a2-4420-9ff6-191c1210eda4 none swap sw 0 0
Reference: https://help.ubuntu.com/16.04/ubuntu-help/power-hibernate.html
Test if hibernate works
Save all work before hibernating in case something goes wrong and open applications cannot be recovered when the computer comes out of hibernation.
Use the command line to test if hibernate works:
Open the Terminal to get a command line (CL) by typing Ctrl+Alt+t
Type sudo systemctl hibernate
âľ at the CL. (Enter your password when prompted.)
If your machine goes into (and stays in) hibernation, then switch it back on with the power button and verify that all the previous open applications re-open. Skip to Step 2 below.
If your machine only stayed in hibernation for a second or two, then it's likely that you don't have at least as much swap space as you have ram on your machine.
(The hibernate
operation will save the state of your RAM in a swapfile, so you must have a swapfile that is at least as large as your amount of RAM.)
See this page or this page for information about adding or adjusting swap space. See what I did on my machine in section on adjust amount of swap space.
Enable hibernate-on-lid-close. Assuming the hibernate test works, edit the file /etc/systemd/logind.conf
.
sudo -H gedit /etc/systemd/logind.conf
Set the parameter HandleLidSwitch
equal to hibernate
by adding the following line to the /etc/systemd/logind.conf
file:
HandleLidSwitch=hibernate
Next we need the UUID of our swap partition, which must be at least as large as the amount of RAM present on the machine.
Launch the gparted
program. (If you don't have it, do sudo apt install gparted
.)
Right click on the swap partition, select "Information" from the menu, and copy the UUID that appears in the resulting dialog box.
Edit the file /etc/default/grub
and add resume=UUID=b9f55147-75a2-4420-9ff6-191c1210eda4
to the end of the GRUB_CMDLINE_LINUX_DEFAULT
parameter definition. For example,
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash psmouse.synaptics_intertouch=1 resume=UUID=b9f55147-75a2-4420-9ff6-191c1210eda4"
Save the file and then invoke the command sudo update-grub
.
After that, reboot the machine.
Important note. I have turned Off the "suspend on lid close" setting in the "Power" menu of the gnome-tweaks
gui. (See screenshot below.)
Problem. When I close the lid of the laptop (without shutting it down), and then open it again, everything seems to work except for the touchpad. Controlling the pointer via the touchpad becomes impossible since the pointer jumps around the screen erratically.
Solution. This problem is now SOLVED using the approach described in the section "Hibernate on lid close".
Some of the steps I took in my first (unsuccessful) attempt to fix this are mentioned below in the section on failed attempt to fix wake-from-suspend problem.
Old Partial Solution. (reference: https://askubuntu.com/a/1083546/100671)
Here are some notes about a previous approach I used to try to fix the wake-from-suspend problem. Unfortunately, this approach only worked temporarily.
The following solution seemed to work at first, but then when I got home and took my (suspended) laptop out of my bag, it was very warm, and when I opened the lid, the pointer only worked for a minute or two, then froze.
Invoke the following command from the linux CL.
sudo -H gedit /lib/systemd/system-sleep/touchpad
Insert the following lines in the touchpad
file by copying and pasting them into the gedit window you just openned:
#!/bin/bash if [[ $1 == post ]]; then modprobe -r psmouse modprobe psmouse fi
Save the file, exit gedit, and make the touchpad
file executable by invoking the following on the CL.
chmod a+x /lib/systemd/system-sleep/touchpad
REBOOT the system now. When you restart, closing the laptop lid should suspend the machine (assuming you have that option set; see below) and when you re-open the lid, the touchpad should work normally.
This assumes you have the configuration options set as shown in the screenshot below.
This sections mentions some of the issues I haven't been able to resolve yet.
Problem. When connecting to public wifi, the login ("Acceptable Use Policy", "captive portal") page does not appear.
Solution Hints.
References:
What I tried first:
Create file /etc/NetworkManager/conf.d/20-connectivity-debian.conf
using the command
sudo -H gedit /etc/NetworkManager/conf.d/20-connectivity-debian.conf
and add the following lines to this file:
[connectivity] uri=http://network-test.debian.org/nm response=NetworkManager is online interval=300
Restart the network manager with sudo service network-manager restart
.
Hibernating instead of powering down, as described in section Hibernate on lid close above, is a suitable work-around. (When waking from hibernation, the X session is in the same state as it was before hibernation.)
Here's what I tried, but it doesn't have the intended effect of picking up where I left off... :(
Install dconf-tools
sudo apt install dconf-tools
Then run the command dconf-editor
When dconf-editor starts, select org > gnome > gnome-session, then check the box next to auto-save-session
Problem. When I close the lid of the laptop (without shutting it down), and then open it again, everything seemed to work except for the touchpad. When I touch the touchpad, the pointer jumps around the screen erratically and makes it impossible to do anything with the touchpad (or the red trackpoint stick in the middle of the keyboard).
Hibernating instead of suspending, as described in section Hibernate on lid close above, is a suitable work-around, although waking from hibernation takes significantly longer than wake-from-suspend should. On the plus side, no power is used while hibernating.
Some of the steps I took in my first (unsuccessful) attempt to fix this are mentioned in this section.
WARNING: The steps below bricked my machine. Do not follow these steps!!! (I'm leaving these steps posted here as a reminder, in case some other website suggests them, these steps can be fatal. Do not proceed!!!)
Below is how I tried to fix the weird mouse behaviour observed after waking from suspend, but the consequences of this attempt were disasterous.
Do not follow the steps in this section!
Do NOT do the following!!! (You have been warned.)
(DO NOT) Enable Thunderbolt 3 compatibility mode in the BIOS
(DO NOT) Disable internal card reader in BIOS (under security settings)
(DO NOT) Add kernel flag: acpi.ec_no_wakeup=1
(DO NOT) Do this by adding the following line to the file /etc/default/grub
GRUB_CMDLINE_LUNUX="acpi.ec_no_wakeup=1"
And then (DO NOT) run sudo update-grub
Bill Lampe pointed out that the main result here has been known for a long time. However, our proof seems new and simpler to us.
The note also describes closure operators and reviews some useful facts about them. Finally, we recall a theorem from Berman's thesis that relates congruence lattices of partial algebras with those of total algebras.
The main purpose of the note is to describe some tools that we plan to exploit in our quest to represent every finite lattice as the congruence lattice of a finite algebra.
]]>I started a GitHub repository called UniversalAlgebra/Conferences in an effort to keep up with the vast number of meetings in these areas and help make summer travel plans accordingly.
The UniversalAlgebra/Conferences repository is simply a list of links to the conferences listed.
Here are some other more general conference listings that may be updated more frequently than mine:
]]>This post details the steps I use to update or install the latest version of the Java Development Kit (JDK) on my Linux systems.
(These notes are mainly for my own reference, but also come in handy when I need to something to point to when a colleague or student needs to know how to do this.)
http://www.oracle.com/technetwork/java/javase/downloads/index.html
I'll assume we're using Linux on an x64 machine, so we would, for example, download a file with a name like
jdk-9.0.1_linux-x64_bin.tar.gz
(As of Dec 6, 2018, the direct link to this file is here)
I'll assume we downloaded the tar.gz file into the directory ~/opt/Java
tar xvzf jdk-9.0.1_linux-x64_bin.tar.gz
/usr/lib/jvm
directorycreate the directory /usr/lib/jvm
sudo mkdir -p /usr/lib/jvm
If you already have directory named /usr/lib/jvm/jdk-9.0.1
, move it out of the way:
sudo mv /usr/lib/jvm/jdk-9.0.1{,.orig}
Move your newly unpacked jdk directory to /usr/lib/jvm
and rename it jdk1.9.0
:
sudo mv ~/opt/Java/jdk-9.0.1 /usr/lib/jvm/jdk1.9.0
Use the update-alternatives
program for this (see also: notes on configuring JDK 1.7 on Ubuntu):
This first block of 9 commands can be copy-and-pasted to the command line all at once:
sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.9.0/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.9.0/bin/javac" 1; sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/lib/jvm/jdk1.9.0/bin/javaws" 1; sudo update-alternatives --install "/usr/bin/jcontrol" "jcontrol" "/usr/lib/jvm/jdk1.9.0/bin/jcontrol" 1; sudo chmod a+x /usr/bin/java; sudo chmod a+x /usr/bin/javac; sudo chmod a+x /usr/bin/javaws; sudo chmod a+x /usr/bin/jcontrol; sudo chown -R root:root /usr/lib/jvm/jdk1.9.0;
The following commands are interactive and should be invoked individually:
sudo update-alternatives --config java sudo update-alternatives --config javac sudo update-alternatives --config javaws sudo update-alternatives --config jcontrol
Check which version of Java your system is currently using with the command java -version
.
The link to the version of the course that I took is https://class.coursera.org/reactive-002.
General information about the course and possible future sessions is found at https://www.coursera.org/course/reactive.
This is the first assignment. The goal is to familiarize yourself with the infrastructure and the tools required during this class. The grade in this assignment will be excluded from your final grade for the course.
Before anything else, we need to make sure that all tools are correctly installed. Take a look at the Tools Setup page and verify that all of the listed tools work.
(If you already did all this for the progfun course, you don't need to do anything more.)
Download the example.zip handout archive file and extract it somewhere on your machine.
During this class we will always launch the Scala REPL (the interactive Scala console) through sbt, so we don't need to install the Scala distribution---having sbt is enough. (In case you prefer to have the scala command, you can download the Scala distribution from the scala-lang.org website.)
Note that sbt
can only be started inside a project directory, so we first
navigate to the example directory created in Part 1, then enter sbt
.
In the future, we can type console
to get the Scala REPL, but for now, just type
> eclipse
at the sbt prompt to generate the files needed to load the project into
Eclipse. See next section for more details. You can exit sbt with ctrl-c
.
(If something goes wrong with sbt, open the Sbt Tutorial page and follow the first steps until "Running the Scala Interpreter.")
To work on the source code of the project, we must import it into Eclipse.
IMPORTANT Before importing the project into Eclipse, we must first create the Eclipse project from the sbt command line as mentioned above.
$ sbt > eclipse ...
Once the Eclipse project has been created from sbt, you can import it in Eclipse. Follow these steps to work on the project using the Scala IDE:
In the folder src/main/scala
, open the package example
and double-click the
file Lists.scala
. There are two methods in this file that need to be
implemented (sum
and max
).
When working on an assignment, it is important that you don't change any existing method, class or object names or types. Doing so will prevent our automated grading tools from working and you have a high risk of not obtaining any points for your solution.
To learn how to use the Scala IDE, we recommend you to watch the official tutorial video which is available here: http://scala-ide.org/docs/current-user-doc/gettingstarted/index.html. This website also contains a lot more useful information and handy tricks that make working with eclipse more efficient.
You can easily execute the test suites directly inside eclipse. Simply navigate
to source file of the test suite in src/test/scala
, right-click on it and select
"Run As" - "JUnit Test".
The JUnit window will display the result of each individual test.
Once you start writing some code, you might want to run your code on a few examples to see if it works correctly. We present two possibilities to run the methods you implemented.
In the sbt console, start the Scala REPL by typing console
.
> console [info] Starting scala interpreter... scala>
The classes of the assignment are available inside the REPL, so you can for instance import all the methods from object Lists:
scala> import example.Lists._ import example.Lists._ scala> max(List(1,3,2)) res1: Int = 3
Another way to run your code is to create a new Main object that can be executed by the Java Virtual Machine.
example
in src/main/scala
and select "New" - "Scala Object"Main
as the object name (any other name would also work)In order to make the object executable it has to extend the type App
. Change the
object definition to the following:
object Main extends App { println(Lists.max(List(1,3,2))) }
Now the Main
object can be executed. In order to do so in eclipse:
Main.scala
You can also run the Main
object in the sbt console by simply using the command run
.
Throughout the assignments of this course we will require you to write unit tests for the code that you write. Unit tests are the preferred way to test your code because unlike REPL commands, unit tests are saved and can be re-executed as often as required. This is a great way to make sure that nothing breaks when you have go back later to change some code that you wrote earlier on.
We will be using the ScalaTest testing framework to write our unit tests. In
eclipse, navigate to the folder src/test/scala
and open the file
ListsSuite.scala
in package example
. This file contains a step-by-step tutorial
to learn how to write and execute ScalaTest unit tests.
The only way to submit your solution is through sbt.
In order to submit, you need to have your coursera username and your submission password. Note that the submission password is a special password you must generate using a button on the assignments page.
Important: Open a terminal, change to your project directory, and start the sbt console. That is, you must start sbt from within your project directory.
> submit your.email@domain.com submissionPassword [info] Connecting to coursera. Obtaining challenge... [info] Computing challenge response... [info] Submitting solution... [success] Your code was successfully submitted: Your submission has been accepted and will be graded shortly. [success] Total time: 2 s, completed Aug 30, 2012 4:30:10 PM >
You are allowed to resubmit a maximum of 5 times! Once you submit your solution, you should see your grade and a feedback about your code on the Coursera website within 10 minutes. If you want to improve your grade, just submit an improved solution. The best of all your submissions will count as the final grade.
]]>Suppose the lattice shown below is a congruence lattice of an algebra.
Conjecture: If the three $\alpha_i$'s pairwise permute, then all pairs in the lattice permute.
Whether or not this claim is true is a simplified version of a question left open by PĂĄlfy and Saxl at the end of their 1990 paper. Below is a more formal statement of the problem, and a link to my notes describing a proposed method of solution. There remains one gap in the proof, that I'm not yet sure how to fill, but I am hopeful that the overall strategy will work.
In an attempt to prove the claim above and its generalization, I apply an idea described in Heinrich Werner's paper called graphical composition.
Before giving a more precise statement of the problem, let us recall a couple of basic definitions. Given two equivalence relations $\alpha$ and $\beta$ on a set $X$, the relation
$$\alpha \circ \beta = {(x,y) \in X^2: (\exists z)(x ; \alpha ; z ; \beta ; y)}$$
is called the composition of $\alpha$ and $\beta$, and if $\alpha \circ \beta = \beta \circ \alpha$ then $\alpha$ and $\beta$ are said to permute.
Problem. $\def\bA{\bf A} \def\bB{\bf B}$ Let $\bA$ be a finite algebra with $\operatorname{Con} \bA$ isomorphic to $M_n$, for some $n\geq 4$. If three nontrivial congruences of $\bA$ pairwise permute, does it follow that every pair of congruences of $\bA$ permute?
My GitHub repository contains the following:
]]>It is not hard to see that 3-SAT reduces to the problem of deciding whether all coatoms in a certain partition lattice are contained in the union of a collection of certain principal filters. Therefore, the latter problem, which we will call the covered coatoms problem (CCP), is NP-complete. In this post we simply define CCP. Later we check that 3-SAT reduces to CCP, and then develop some ideas about constructing a feasible algorithm to solve CCP.
Consider the relational structure $\mathbf B = \langle \{0, 1\}, R\rangle$ where $R$ is the ternary relation $\{0, 1\}^3 - \{(0,0,0), (1,1,1)\}$. That is,
$$ R = {(0,0,1), (0,1,0), (0,1,1), (1,0,0), (1,0,1), (1,1,0)}.$$
We now describe the constraint satisfaction problem $\operatorname{CSP}(\mathbf B)$.
By an "instance" of $\operatorname{CSP}(\mathbf B)$, we mean a (finite) relational structure $\mathbf A = \langle A, S \rangle$ with a single ternary relation $S$.
The constraint satisfaction problem $\operatorname{CSP}(\mathbf B)$ is the following:
Given an instance $\mathbf A = \langle A, S \rangle$, does there exist a relational homomorphism from $\mathbf A$ to $\mathbf B$? That is, does there exist a function $f: A \rightarrow \{0,1\}$ such that $(f(a), f(b), f(c)) \in R$ whenever $(a, b, c) \in S$?
The kernel of a function $f$ with codomain $\{0,1\}$ has two equivalence classes, or "blocks"---namely, $f^{-1}\{0\}$ and $f^{-1}\{1\}$.
If one of these classes is empty, then $f$ is constant in which case it cannot be a homomorphism into the relational structure $\langle \{0, 1\}, R\rangle$ (since $(0,0,0)\notin R$ and $(1,1,1)\notin R$).
Therefore, the kernel of every homomorphism $f : \mathbf A \rightarrow \mathbf B$ has two nonempty blocks.
Now, given a partition of $A$ into two blocks, $\pi = |A_1|A_2|$, there are exactly two functions of type $A \rightarrow \{0,1\}$ with kernel $\pi$. One is $f(x) = 0$ iff $x \in A_1$ and the other is $1-f$. It is obvious that either both $f$ and $1-f$ are homomorphisms or neither $f$ nor $1-f$ is a homomorphism.
Indeed, both are homomorphisms if and only if for all tuples $(a,b,c) \in S$ we have $\{a,b,c\} \nsubseteq A_1$ and $\{a,b,c\} \nsubseteq A_2$.
Neither is a homomorphism if and only if there exists $(a,b,c) \in S$ with $\{a,b,c\} \subseteq A_1$ or $\{a,b,c\} \subseteq A_2$.
Now, for each tuple $s = (a,b,c) \in S$, we let $\langle s\rangle$ denote the equivalence relation of $A$ generated by $a$, $b$, and $c$.
It is clear that a function $f: A \rightarrow \\{0, 1\\}$ is a homomorphism from $\mathbf A$ to $\mathbf B$ if and only if for all $s \in S$ the relation $\langle s\rangle$ does not belong to the kernel of $f$. Therefore, a solution to the instance $\mathbf A = \langle A, S \rangle$ of $\operatorname{CSP}(\mathbf B)$ exists if and only if there is at least one coatom in the lattice of equivalence relations of $A$ that is not contained in the union $ \bigcup_{s\in S} \, ^\uparrow\langle s \rangle $ of principal filters.The Covered Coatoms Problem is the following: Given a set $A$ and a list $s_1 = (a_1, b_1, c_1), s_2=(a_2, b_2, c_2), \dots, s_n =(a_n, b_n, c_n)$ of 3-tuples with elements drawn from $A$, decide whether all of the coatoms of the lattice $\prod_A$ of partitions of $A$ are contained in the union $ \bigcup_{i=1}^n , ^\uparrow\langle s_i \rangle $ of principal filters.
If we find an algorithm that decides this in polynomial time (in the size of the set $A$), or if we prove that no such algorithm exists, then this would answer the P versus NP question.
]]>Contents
Given a relational structure, $\mathbb{R} = \langle X, \Gamma \rangle$, that is, set $X$ along with a collection $\Gamma \subseteq \bigcup_{n>0} \mathcal{P}(X^n)$ of relations on $X$, we associate with $\mathbb{R}$ a constraint satisfaction problem denoted by $\operatorname{CSP}(\mathbb{R})$. This is a decision problem that is solved by finding an algorithm or program that does the following: take as input any
and decide (output "yes" or "no") if there is or is not a
If there is such an algorithm that takes at most a power of $n$ operations to process an input $\mathbb{S}$ of size $n$ (bits of memory required to encode $\mathbb{S}$), then we say that $\operatorname{CSP}(\mathbb{R})$ is tractable. Otherwise, we call it intractable.
Equivalently, if we define $\operatorname{CSP}(\mathbb{R})= \{\mathbb{S} \mid \text{ there is a homomorphism } h : \mathbb{S} \to \mathbb{R} \}$, then the CSP problem described above is simply the membership problem for the set $\operatorname{CSP}(\mathbb{R})$. That is, our algorithm must take as input a relational structure of the same signature as $\mathbb{R}$ and decide whether it belongs to the set $\operatorname{CSP}(\mathbb{R})$.
Let $X$ be a set, let $Op(X)$ denote the set of all operations on $X$, and $Rel(X)$ the set of all relations on $X$.
Given $\Gamma\subseteq Rel(X)$, define the set of all operations that leave the relations in $\Gamma$ "fixed" as follows:
$$\operatorname{Fix}(\Gamma) = \{f \in Op(X) \mid f \text{ respects every } \gamma \in \Gamma\}.$$
By "$f$ respects every $\gamma$" we mean that each $\gamma$ is a subalgebra of a (finite) power of the algebra $\langle X, \{ f \} \rangle$.
If $\mathbb{R} = \langle X, \Gamma \rangle$ is a relational structure, then the set $\operatorname{Fix}(\Gamma)$ is sometimes called the set of polymorphisms of $\mathbb{R}$.
Going the other way, starting with a collection $F\subseteq Op(X)$, define the set of all relations left "invariant" by the functions in $F$ as follows:
$$\operatorname{Inf}(F) = \{\gamma \in Rel(X) \mid \gamma \text{ is respected by every } f \in F\}.$$
It is easy to see that $\Gamma \subseteq \operatorname{Inv}(\operatorname{Fix}(\Gamma))$ and $F \subseteq \operatorname{Fix}(\operatorname{Inv}(F))$.
Let $\mathbf{A}(\mathbb{R})$ denote the algebraic structure with universe $X$ and operations $\operatorname{Fix}(\Gamma)$. Then every $\gamma \in \Gamma$ is a subalgebra of a power of $\mathbf{A}(\mathbb{R})$.
Clearly $\operatorname{Inv}(\operatorname{Fix}(\Gamma)) = \operatorname{S}\operatorname{P}_{fin}(\mathbf{A}(\mathbb{R}))$, the set of all subalgebras of finite powers of $\mathbf{A}(\mathbb{R})$.
The reason this Galois connection is useful is due to the following fact that Peter Jeavons first observed in the late 1990's (see [J]):
Theorem. If $\langle X, \Gamma \rangle$ is a finite relational structure and if $\Gamma'\subseteq \operatorname{Inv}(\operatorname{Fix}(\Gamma))$ is finite, then $\operatorname{CSP}\langle X, \Gamma'\rangle$ is reducible in polynomial time to $\operatorname{CSP}\langle X, \Gamma \rangle$.
In particular, the tractability of a CSP depends only on its associated algebra $\mathbf{A}(\mathbb{R}) := \langle X, \operatorname{Fix}(\Gamma)\rangle$.
What has become known as the CSP-dichotomy conjecture now boils down to the following (quoted terms are defined below):
Conjecture: The CSP associated with a finite idempotent algebra $\mathbf A$ is tractable iff $\mathbf A$ has a "Taylor" (or "WNU," or "cyclic," or "Siggers") term operation.
Let $\mathbf A$ be a finite idempotent algebra and let $V(\mathbf A)$ denote the variety generated by $\mathbf A$. Then, the following are equivalent:
It is known that if a CSP is tractable, then the associated algebra $\mathbf A$ must satisfy the equivalent conditions above. (See the section Tractable only if Taylor term below.) The converse is open. That is, it is not known whether each of the equivalent conditions above is sufficient to prove tractability of the associated CSP.
Walter Taylor proved in [T] that a variety V satisfies some nontrivial Malcev condition iff it satisfies the following one: for some $n$, V has an $n$-ary term $t$ such that, for all $i$ between 1 and $n$ there is an identity $$t(\ast, \cdots, \ast, x, \ast, \cdots, \ast) \approx t(\ast, \cdots, \ast, y, \ast, \cdots, \ast)$$ true in V where different variables $x\neq y$ appear in the $i$-th position on either side of the identity. (Such a term $t$ is called a "Taylor term".)
Hobby and McKenzie proved in [HM] that a finite algebra A has a Taylor term iff 1 does not belong to the set of TCT-types of $V(\mathbf A)$. (We say $V(\mathbf A)$ "omits type 1" in this case.)
Freese and Valeriote proved in [FV] that, for a finite idempotent algebra $\mathbf A$, $V(\mathbf A)$ omits type 1 iff $\mathbf A$ has no "trivial divisors." Stated more precisely, in the contrapositive,
*The TCT-type set of $V(\mathbf A)$ contains 1 iff there is a subalgebra $\mathbf B$ of $\mathbf A$, and a congruence $\eta$, such that $\mathbf{B}/\eta$ is a two element algebra with only the trivial projection operations. (That is, $\mathbf B/\eta$ is a "trivial divisor" of $\mathbf A$.) *
If the algebra B, consisting of the set $\{0, 1\}$ along with trivial projection operations, occurs in the variety V(A), then the associated $\operatorname{CSP}(\langle A, \operatorname{Inv}(\mathbf{A})\rangle)$ is NP-complete.
To see this, note that the problem $\operatorname{CSP}(\mathbb{S})$ for the relational structure $\mathbb{S} = \langle \{0, 1\}, T\rangle$, where $T = \{0, 1\}^3 - \{(0,0,0), (1,1,1)\}$, is NP-complete, and $\operatorname{CSP}(\mathbb{S})\leq \operatorname{CSP}(\langle A, \operatorname{Inv}(\mathbf{A})\rangle)$.
It follows that
*Tractability of $\operatorname{CSP}(\langle A, \operatorname{Inv}(\mathbf{A})\rangle)$ implies $V(\mathbf A)$ has a Taylor term operation.
Maroti and McKenzie proved in [MM] that a finite idempotent algebra has a Taylor term iff it has a weak near-unanimity (WNU) term operation, that is, an idempotent term $w(x_1, \dots, x_k)$ such that $$w(y,x,\dots,x) \approx w(x,y,x,\dots,x) \approx \cdots \approx w(x,\dots,x,y).$$
Barto and Kozik proved in [BK] that a finite idempotent algebra has a WNU term iff it has a special type of WNU term called a cyclic term. A cyclic term is a term $c(x_1, \dots, x_k)$ that satisfies $$c(x_1, x_2, x_3, \dots, x_k)\approx c(x_2, x_3, x_4, \dots, x_1)$$
Note that the above term conditions place no bounds on the value of $k$, that is, the arity of the term operations involved in the given identities.
Siggers proved in [S] that the above term conditions are equivalent to one involving a term of bounded arity--namely, a six-place term operation. Kearnes, Markovic, and McKenzie in [KMM] improved this to a 4-place term $t(x_1,x_2,x_3,x_4)$ satisfying $t(x,y,x,z)\approx t(y,x,z,y)$.
[J] Jeavons, Peter, On the algebraic structure of combinatorial problems Theoretical Computer Science 200 (1998).
[T] Taylor, Varieties obeying homotopy laws. Canad. J. Math. 29 (1977).
[HM] Hobby and McKenzie, The structure of finite algebras. Contemporary Mathematics, AMS 76 (1988).
[FV] Freese and Valeriote, On the complexity of some Maltsev conditions. Internat. J. Algebra Comput. 19 (2009).
[MM] Maroti and McKenzie, Existence theorems for weakly symmetric operations. Algebra Universalis 59 (2008).
[BK] Barto and Kozik, Absorbing subalgebras, cyclic terms, and the constraint satisfaction problem. Log. Methods Comput. Sci. 8 (2012).
[S] Siggers, A strong Malcev condition for locally finite varieties omitting the unary type. Algebra Universalis 64 (2010).
[KMM] Kearnes, Markovic, and McKenzie, Optimal strong Mal'cev conditions for omitting type 1 in locally finite varieties. Algebra Universalis 72 (2014).
This page collects some basic information about Coq, and is mainly intended for my research students interested in learning how to use Coq.
What is Coq? Coq is a proof assistant and a functional programming language.
What is a proof assistant? A proof assistant is a tool that automates the more routine aspects of building proofs while depending on human guidance for more difficult aspects. Coq is one such tool. It provides a rich environment for interactive development of machine-checked formal reasoning.
How to get started? There are a number of ways to get started with Coq. Here I'll first give an overview of my experience, and then, further on down the page, I'll provide some more details.
I got started with Coq by watching Andrej Bauer's series of YouTube tutorials. These videos give some nice introductory lessons which show exactly how one interacts with Coq. For example, in the video How to use Coq with Proof General, Andrej shows how to use Coq to prove that Pierce's Law is equivalent to the Law of Excluded Middle.
(Proof General is one way to interact with Coq, which is the great for those accustomed to the Emacs editor. For those who aren't into Emacs, an alternative interface to Coq exists, called CoqIDE, about which I know absolutely nothing. I installed Coq and Proof General on Ubuntu 14.04 very easily using the Synaptic package manager; no custom configuration required.)
How to continue? Again, more details about how to really get going with Coq and how to use it for theorem proving and functional programming, but if you want to stop reading here, then I highly recommend reading (and solving the exercises in) the first 5 or 6 chapters of the book, Software Foundations.
(At the time of this writing, the link above points to the url https://softwarefoundations.cis.upenn.edu/lf-current/toc.html. However, the url of the Software Foundations book occasionally changes. If the link is broken, you can find the book by simply googling "Software Foundations Pierce".)
The Software Foundations book is an outstanding resource. Not only is it well written and accessible, even to non-computer scientists, but also it's fun to read because of the many engaging exercises sprinkled throughout the book that test your understanding of the material.
With some prior programming experience---and especially with some functional programming experience---you can probably proceed quickly through SF and learn a lot about Coq by solving most, if not all, of the exercises. I would guess that an intelligent undergraduate, with a little background in elementary logic, math, and programming, could get through a few chapters of SF per week.
This section collects some notes on basic commands used to interact with Coq using [Proof General]. We will begin with Andrej Bauer's YouTube tutorials, and then move on to the Basics section of the book Software Foundations.
To get started using Coq with Proof General, I recommend the YouTube tutorial by Andrej Bauer.
Andrej has a collection of nice tutorials; the video embedded above concerns Proof General.
I will not reiterate everything that Andrej covers in his tutorials. Rather, the main point here is to describe, and record for my own reference, a few emacs commands that are most useful when using Proof General with Coq (since this isn't covered in the Software Foundations book).
Let's assume Coq and Proof General have been installed in Ubuntu, using, e.g.,
sudo apt-get install proofgeneral coq
Startup emacs with a new file called pierce_lem.v
:
emacs pierce_lem.v
You should see the Proof General welcome screen for a few seconds, then an
emacs buffer containing the (empty) contents of the new pierce_lem.v
file.
Add the following line to the pierce_lem.v
file by typing it in the buffer window:
Definition pierce := forall (p q : Prop), ((p -> q) -> p) -> p.
Then enter the emacs control sequence: C-c C-n
(that is, hold down the control
key while typing c
, then hold down control while typing n
).
If you see a new emacs buffer window labeled *goals* containing the text "pierce is defined" and if the Definition appears in color, then Coq has accepted the new definition.
Now, on the next line, type the definition of the law of excluded middle:
Definition lem := forall (p : Prop), p \/ ~p.
and type C-c C-n
, and then the theorem we want to prove:
Theorem pierce_equiv_lem : pierce <-> lem.
After entering C-c C-n
at the end of this line, Coq shows the goals and subgoals
that we need to establish in order to prove the theorem.
(Andrej mentions that by default Proof General complains if you try to edit
lines that have already been accepted by Coq. It seems this is no longer the
case---you can freely edit any line, and it will revert to unchecked status.
However, if your installation complains when you try to edit lines that have
already been accepted, you can manually uncheck them using the command C-c C-u
.)
Comments may be added between starred parens, like this:
(* this is a comment *)
In Andrej's Proof General YouTube tutorial, he only mentions a couple of Proof General commands, such as:
C-c C-n
proof-assert-next-command-interactiveC-c C-u
proof-undo-last-successful-commandC-c C-p
show goalsSome other useful commands are
C-c C-RET
proof-goto-pointC-c C-b
proof-process-bufferC-c C-r
proof-retract-bufferThe first of these evaluates the proof script up to the point (where cursor is located), the second processes the whole buffer, and the third retracts the whole buffer (i.e., clears all definitions).
These and other commands are mentioned in Section 2.6 of the Proof General documentation.
Here is the link to the main Proof General documentation page and the section on Coq.
After following along with Andrej's first tutorial on Proof General, I recommend proceeding with the fourth in his series:
Download and extract the lf.tgz file for the Software Foundations (volume 1) book as follows.
mkdir -p ~/git; mkdir ~/git/Coq; cd ~/git/Coq wget https://softwarefoundations.cis.upenn.edu/lf-current/lf.tgz tar xvzf lf.tgz rm lf.tgz
Remarks
You may of course choose a directory other than ~/git/Coq, but the commands shown below assume we are working in the ~/git/Coq directory.
If you use Git, you may wish to create a new repository with the Software Foundation book's files, so you can modify them as you work through them without worrying if you break them. Do this as follows:
git init git add lf/* git commit -m "initial commit of sf book files"
To be continued...
]]>The questions below appeared on an online test administered by Jane Street Capital Management to assess whether a person is worthy of a phone interview.
Suppose we choose four numbers at random without replacement from the first 20 prime numbers. What is the probability that the sum of these four numbers is odd?
Solution By definition, an even number is an integer multiple of 2; that is, even numbers have the form $2n$, for some integer $n$. An odd number is one more than an even number; that is, odd numbers have the form $2n+1$ for some integer $n$.
If all four numbers are odd, then the sum is even. This follows from the fact that the sum of two odd numbers is even: $(2n+1) + (2k+1) = 2(n+k) + 2 = 2(n+k+1)$. And the sum of two even numbers is again even: $2n+2k = 2(n+k)$. Therefore, in order for the sum of the four primes chosen to be odd, the only even prime number, 2, must be among the four numbers we selected.
Here are two ways to compute the probability that 2 is one of the four numbers selected: First, consider the probability of not selecting 2: $$ P(\text{2 is not among the chosen primes}) = \frac{19}{20} \frac{18}{19} \frac{17}{18} \frac{16}{17} = \frac{16}{20} = \frac{4}{5}. $$
Then the probability that 2 is among the four chosen prime numbers is $1 - \frac{4}{5}$, or $\frac{1}{5}$.
Alternatively, we could count the number of four-tuples that have a 2. This is the same as taking 4 times the number of ways to choose the other three numbers: $19\cdot 18\cdot 17$. (The factor of 4 appears since we could put the 2 in any of four positions). Now divide this by the total number of four-tuples, $20 \cdot 19\cdot 18\cdot 17$, to arrive at the same answer as above:
$$ \frac{4\cdot 19\cdot 18\cdot 17} {20 \cdot 19\cdot 18\cdot 17} = \frac{4}{20} = \frac{1}{5}. $$
Suppose you have an 8 sided die with sides labeled 1 to 8. You roll the die once and observe the number $f$ showing on the first roll. Then you have the option of rolling a second time. If you decline the option to roll again, you win $f$ dollars. If instead you exercise the option to roll a second time, and if $s$ shows on the second roll, then you win $f + s$ dollars if $f+s < 10$ and 0 dollars if $f+s \geq 10$. What is the best strategy?
Solution
A strategy is given by specifying what to do in response to each possible first roll, $f$. The best strategy will maximize the expected winnings.
The expected winnings if we roll just once is simply the number that appears, $f$.
Let $S$ be the random variable representing the value showing on the second roll. This is uniformly distributed with $P(S=s) = 1/8$. Therefore, if we observe $f$ and then decide to roll twice, the expected winnings are
$$ E(W | f, \mathrm{roll\ twice}) = \sum_{s=1}^8 (f+s)\chi_{{f+s < 10}} P(S=s) = \frac{1}{8}\sum_{s=1}^8 (f+s)\chi_{{f+s < 10}} $$
where $\chi$ denotes the characteristic function, that is, $\chi_{\mathrm{True}}=1$ and $\chi_{\mathrm{False}}=0$.
If we let $N= \min{8, 10 - f -1}$, then the expected value of rolling twice is
$$ E(W | f, \mathrm{roll\ twice}) = \frac{1}{8}\sum_{s=1}^{N} (f+s) $$
If we observe $f=1$ on the first roll, then
$$ E(W | f=1, \mathrm{roll\ twice}) = \frac{1}{8} \sum_{s=1}^8 (1+s)= \frac{11}{2}. $$
Since this is greater than 1, we should certainly exercise the option to roll twice if 1 shows up on the first roll.
Continuing this way, we have
$$ E(W | f=2, \mathrm{roll\ twice}) = \frac{1}{8}\sum_{s=1}^{7} (2+s) = \frac{1}{8}(2\cdot7 + 1 + 2+ 3+ \cdots + 7) = 6. $$
$$ E(W | f=3, \mathrm{roll\ twice}) =\frac{1}{8}\sum_{s=1}^{6} (3+s) = \frac{1}{8}(3\cdot 6 +1 + 2+ 3+ \cdots + 6) = \frac{39}{8}. $$
$$ E(W | f=4, \mathrm{roll\ twice}) = \frac{1}{8}\sum_{s=1}^{5} (4+s) = \frac{1}{8}(4\cdot 5+1 + 2+ 3+ \cdots + 5) = \frac{35}{8}. $$
Since this is larger than 4, we should roll twice when observing 4 on the first roll.
However, if $f = 5$, then
$$ E(W | f=5, \mathrm{roll\ twice}) = \frac{1}{8}\sum_{s=1}^{4} (5+s) = \frac{1}{8}((5)(4)+1 + 2+ 3+ \cdots + 4) = \frac{30}{8}. $$
Since this is less than 5, we should not roll a second time when a 5 is observed on the first roll.
General strategy: Exercise the option to roll a second time if the first results in 1, 2, 3, or 4. Otherwise, decline the second roll and take the value shown on the first roll.
(The original question was for 3 coins, with respective probabilities 0.5, 0.3, and 0.2 of coming up heads. This version is a generalization to $n$ coins with arbitrary probabilities that sum to 1.)
Suppose you have a bag with $n$ coins, $C_1, C_2, \dots, C_n$, and coin $C_i$ has probability $p_i$ of coming up heads when flipped. Assume $p_1 + p_2 + \cdots p_n = 1$. Suppose you draw a coin from the bag at random and flip it and it comes up heads. If you flip the same coin it again, what is the probability it comes up heads?
Denote by $H_i$ the event that heads turns up on the $i$-th flip, and by a slight abuse of notation, let $C_i$ denote the event that we are flipping coin $C_i$. Then
$$ P(H_2 | H_1) = \sum_{i=1}^n P(H_2| C_i, H_1) P(C_i|H_1) = \sum_{i=1}^n p_i P(C_i|H_1) $$
Applying Bayes' Theorem,
$$ P(C_i|H_1) = \frac{P(C_i,H_1)}{P(H_1)} = \frac{P(H_1|C_i) P(C_i)}{P(H_1)}. $$
Assuming all coins are equally likely to have been drawn from the bag, $P(C_i)=\frac{1}{n}$. Therefore,
$$ P(H_1) = \sum_{i=1}^n P(H_1| C_i) P(C_i) = \frac{1}{n}\sum_{i=1}^n p_i = \frac{1}{n}, $$
whence,
$$ P(C_i|H_1) = \frac{P(H_1|C_i) P(C_i)}{P(H_1)} = p_i \frac{1/n}{1/n} = p_i. $$
Therefore,
$$ P(H_2 | H_1) = \sum_{i=1}^n p_i^2. $$
For the special case given in the original problem, we have
$$ P(H_2 | H_1) = (0.5)^2 + (0.3)^2 + (0.2)^2 = 0.25 + 0.09 + 0.04 = 0.38. $$
Suppose you have two urns that are indistinguishable from the outside.
One of the urns contains 3 one-dollar coins and 7 ten-dollar coins.
The other urn contains 5 one-dollar coins and 5 ten-dollar coins.
Suppose you choose an urn at random and draw a coin from it at random.
You find that it is a $10 coin. Now you are given the option to draw
again (without replacing the first coin) from either the same urn or
the other urn. Should you draw from the same urn or switch?
Solution
The problem is uninteresting if we assume the draws are made with replacement. Clearly we should not switch urns in this case. Assume the second draw is made without replacing the first coin.
Let $X$ be a random variable denoting the value of the second draw. We compute the expected values $E(X | \text{ stay})$ and $E(X | \text{ switch})$ and whichever is higher determines our strategy.
Let us call the urn that starts with 7 ten-dollar coins urn $A$, and the urn that starts with 5 ten-dollar coins urn $B$. Let $A_1$ denote the event that urn $A$ is chosen for the first draw and let $B_1$ denote the event that urn $B$ is chosen for the first draw. Let $T$ denote the event that the first draw produces a ten-dollar coin.
The probability that a ten-dollar coin appears on the first draw is
$$ P(T) = P(T| A_1) P(A_1)+ P(T| B_1) P(B_1) = \frac{7}{10}\frac{1}{2} +\frac{5}{10}\frac{1}{2} = \frac{3}{5}. $$
Given that $T$ occurred, the probability we were drawing from $A$ is, by Bayes' Theorem,
$$ P(A_1 | T) = \frac{P(T|A_1) P(A_1)}{P(T)} = \frac{(7/10)(1/2)}{3/5} = \frac{7}{12}. $$
Similarly the probability we were drawing from $B$, given $T$, is
$$ P(B_1 | T) = \frac{P(T|B_1) P(B_1)}{P(T)} = \frac{(5/10)(1/2)}{3/5} = \frac{5}{12}. $$
Now, if we decide to draw again from the same urn and that urn happens to be $A$, there will be 6 ten-dollar coins remaining, so our expected value of drawing again from $A$ is
$$ E(X | A_1, \text{ same}) = 10\cdot \frac{6}{9} + 1 \cdot \frac{3}{9} = \frac{21}{3} = 7. $$
If we decide to draw from the same urn and it happens to be $B$, there will be 4 ten-dollar coins remaining, so
$$ E(X | B_1, \text{ same}) = 10\cdot \frac{4}{9} + 1 \cdot \frac{5}{9} = \frac{45}{9} = 5. $$
So, if we don't switch urns, the expected value of the second draw is
$$ E(X | \text{ same}) = E(X | A_1, \text{ same}) P(A_1)
Suppose instead we decided to switch urns. In the event $A_1$ that the first draw was from urn $A$, then urn $B$ will contain its original contents: 5 ten-dollar coins and 5 one-dollar coins. Therefore,
$$ E(X | A_1, \text{ switch}) = 10\cdot \frac{5}{10} + 1 \cdot \frac{5}{10} = 6.5. $$
If we switch in the event $B_1$, then we choose from a pristine urn $A$ having 7 ten-dollar coins and 3 one-dollar coins. Thus,
$$ E(X | B_1, \text{ switch}) = 10\cdot \frac{7}{10} + 1 \cdot \frac{3}{10} = 7.3. $$
Therefore,
$$ E(X | \text{ switch}) = E(X | A_1, \text{ switch}) P(A_1)+ E(X | B_1, \text{ switch}) P(B_1) = 6.5 \frac{7}{12} + 7.3 \frac{5}{12}. $$
So, the expected value of the second draw if we switch urns is a little more than 6.83.
Conclusion: The best strategy is to switch urns before drawing the second coin.
]]>The TypeFunc list of resources (about type theory, functional programming, and related subjects) is my most popular github repository.
]]>update-alternatives
and some examples demonstrating its
use. The purpose of the update-alternatives utility program is to manage,
on a single machine, serveral versions of programs that all provide the same
functionality.
Excerpts from the update-alternatives man page:
It is possible for several programs fulfilling the same or similar functions to be installed on a single system at the same time. Debian's alternatives system aims to manage this situation. A generic name in the filesystem is shared by all files providing interchangeable functionality. The alternatives system and the system administrator together determine which actual file is referenced by this generic name.
The generic name is not a direct symbolic link to the selected alternative. Instead, it is a symbolic link to a name in the alternatives directory, which in turn is a symbolic link to the actual file referenced. This is done so that the system administrator's changes can be confined within the /etc directory.
When each package providing a file with a particular functionality is installed, changed or removed, update-alternatives is called to update information about that file in the alternatives system.
Eclipse seems idosyncratic and tempremental to me, and I have found various versions working well sometimes and poorly other times. Also, there are useful plugins that are only available for some versions and not others.
I currently have three versions of Eclipse installed---a version from TypeSafe and two simultaneous releases: Kepler and Luna (a developer build). (The version from TypeSafe is just a Kepler release with the Scala IDE plugin preinstalled. Note that you can manually install the Scala IDE plugin with other versions Eclipse.) This section describes how to get these multiple versions of Eclipse to happily coexist on the same machine.
If you don't already have any Eclipse versions installed, see the appendix for some installation links and tips.
Create startup scripts This step may become optional in the future, but it seems for now it provides a workaround to deal with bugs in Ubuntu and/or Eclipse that effect the Eclipse menus.
For each version of Eclipse, create a startup script that includes any options that
would otherwise be added to the command line when launching Eclipse.
For example, in the file $HOME/opt/Eclipse/kepler/eclipse-kepler
, put
#!/bin/bash export UBUNTU_MENUPROXY=0 $HOME/opt/Eclipse/kepler/eclipse -vmargs -Xmx4096M
The option -vmargs -Xmx4096M
sets the available memory to 4096 Mb. You can
either adjust or remove this parameter to suit your hardware. In fact, this
seems to cause problems with some versions of the IDE, so it may be safer to
leave it off unless you run into memory problems without it.
See this page for more about the vmargs command line uption.
The line export UBUNTU_MENUPROXY=0
was added to work around
a bug
that causes problems with Eclipse's menus.
Now, make the file you just created executable; e.g.,
chmod a+x $HOME/opt/Eclipse/kepler/eclipse-kepler
To summarize, here are three example startup scripts I use for three different versions of Eclipse:
In the file $HOME/opt/Eclipse/luna/eclipse-luna
,
#!/bin/bash export UBUNTU_MENUPROXY=0 $HOME/opt/Eclipse/luna/eclipse
In the file $HOME/opt/Eclipse/kepler/eclipse-kepler
,
#!/bin/bash export UBUNTU_MENUPROXY=0 $HOME/opt/Eclipse/kepler/eclipse
In, the file $HOME/opt/Eclipse/scala/eclipse-scala
#!/bin/bash export UBUNTU_MENUPROXY=0 $HOME/opt/Eclipse/scala/eclipse
Assuming each of your versions of Eclipse is stored in it own directory under
$HOME/opt/Eclipse
(as described in the appendix),
you can now configure these various versions of Eclipse by invoking the
following at the command line:
sudo update-alternatives --install /usr/bin/eclipse eclipse $HOME/opt/Eclipse/scala/eclipse-scala 400 sudo update-alternatives --install /usr/bin/eclipse eclipse $HOME/opt/Eclipse/luna/eclipse-luna 300 sudo update-alternatives --install /usr/bin/eclipse eclipse $HOME/opt/Eclipse/kepler/eclipse-kepler 200
The numbers 200, 300, 400 indicate the respective priorities, and since scala has the highest number, it will be the default program (but we can change the default later).
You can remove any alternative that you added accidentally. For example,
sudo update-alternatives --remove eclipse $HOME/opt/Eclipse/scala/eclipse-scala
The command update-alternatives --query eclipse
should now print something
like this:
Name: eclipse Link: /usr/bin/eclipse Status: auto Best: /home/williamdemeo/opt/Eclipse/scala/eclipse-scala Value: /home/williamdemeo/opt/Eclipse/scala/eclipse-scala Alternative: /home/williamdemeo/opt/Eclipse/kepler/eclipse-kepler Priority: 200 Alternative: /home/williamdemeo/opt/Eclipse/luna/eclipse-luna Priority: 300 Alternative: /home/williamdemeo/opt/Eclipse/scala/eclipse-scala Priority: 400
Now, or later, you can change the priority of these three alternative versions of
Eclipse with the command update-alternatives --config eclipse
, which
should output the following:
There are 3 choices for the alternative eclipse (providing /usr/bin/eclipse). Selection Path Priority Status ------------------------------------------------------------ * 0 /home/williamdemeo/opt/Eclipse/scala/eclipse-scala 400 auto mode 1 /home/williamdemeo/opt/Eclipse/kepler/eclipse-kepler 200 manual mode 2 /home/williamdemeo/opt/Eclipse/luna/eclipse-luna 300 manual mode 3 /home/williamdemeo/opt/Eclipse/scala/eclipse-scala 400 manual mode
If you want to create an application launcher for the Unity dash for any of these alternatives, or any program for that matter, do so as follows:
mkdir -p ~/.local/share/applications gnome-desktop-item-edit --create-new ~/.local/share/applications/
Click on the icon in the dialog box that pops up, and choose, for
example, $HOME/opt/Eclipse/scala/icon.xpm
. Fill in the rest of the dialog box
as appropriate. For example, you want the Command field to point to one of the bash
scripts we created above---like $HOME/opt/Eclipse/scala/eclipse-scala
---and
not directly to an eclipse
command.
After downloading JDK 7 from the Oracle JDK 7 downloads page. I unpacked it and moved it to a directory called /usr/lib/jvm/jdk1.7.0/. I then made it the default Java on my machine using the commands below. (The first block of 9 commands can be copy-and-pasted to the command line all at once.)
sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.7.0/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.7.0/bin/javac" 1; sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/lib/jvm/jdk1.7.0/bin/javaws" 1; sudo update-alternatives --install "/usr/bin/jcontrol" "jcontrol" "/usr/lib/jvm/jdk1.7.0/bin/jcontrol" 1; sudo chmod a+x /usr/bin/java; sudo chmod a+x /usr/bin/javac; sudo chmod a+x /usr/bin/javaws; sudo chmod a+x /usr/bin/jcontrol; sudo chown -R root:root /usr/lib/jvm/jdk1.7.0;
The following commands are interactive and should be invoked individually:
sudo update-alternatives --config java sudo update-alternatives --config javac sudo update-alternatives --config javaws sudo update-alternatives --config jcontrol
This example comes from an old forum post
# install ruby1.8 & friends with priority 500 # so this will be the default "auto" choice update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.8 500 \ --slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \ /usr/share/man/man1/ruby.1.8.gz \ --slave /usr/bin/ri ri /usr/bin/ri1.8 \ --slave /usr/bin/irb irb /usr/bin/irb1.8 # install ruby1.9 & friends with priority 400 update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9 400 \ --slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \ /usr/share/man/man1/ruby.1.9.gz \ --slave /usr/bin/ri ri /usr/bin/ri1.9 \ --slave /usr/bin/irb irb /usr/bin/irb1.9 # choose your interpreter # changes symlinks for /usr/bin/ruby , # /usr/bin/irb, /usr/bin/ri and man (1) ruby update-alternatives --config ruby
Make a directory under your home directory called ~/opt/Eclipse
.
Download the versions of Eclipse you want to
try---e.g., Luna, Kepler, the version from TypeSafe---and
place the tar.gz files in ~/opt/Eclipse
.
Untar each tar.gz file, and rename it appropriately. For example,
tar xvzf eclipse-standard-luna-M6-linux-gtk-x86_64.tar.gz mv eclipse luna
That is, each time you untar one of these files, the resulting directory will be called eclipse. You should change the name eclipse to the name of the version so that they don't conflict with one another.
In the end you should have, say, three directories:
~/opt/Eclipse/luna ~/opt/Eclipse/kepler ~/opt/Eclipse/scala
each one containing a different version of Eclipse. Now you can follow the instructions above to get them configured as alternatives in Linux.
If you are getting out of memory erros when running your programs in Eclipse, check out this page.
]]>I took four Coursera courses in functional programming.
Functional Programming Principles in Scala by Martin Odersky.
Verified Certificate earned 17 Nov 2016 (grade: 100%)
Functional Program Design in Scala by Odersky, Meijer and Kuhn.
Verified Certificate earned 6 Aug 2016 (grade: 100%)
Parallel Programming in Scala
Verified Certificate earned 27 Jun 2016 (grade: 100%)
Big Data Analysis with Scala and Spark Verified Certificate earned 24 Nov 2017 (grade: 93.4%)
The pages progfun and reactive collect some of my own notes about these courses, and are intended merely for my own future reference.
]]>The link to the version of the course that I took is https://class.coursera.org/progfun-004.
General information about the course and possible future sessions is here: https://www.coursera.org/course/progfun.
In 2015 I took a follow up course called Principles of Reactive Programming by Martin Odersky, Erik Meijer, Roland Kuhn. My notes from that course are [here]({{ root_url }}/scala/reactive).
See this page
(I have upgraded to version 0.13.1, which was the latest as of March 2014.)
Unpack the archive to a directory of your choice.
tar xvzf sbt.tgz
Add the bin/
directory to the PATH
environment variable:
echo "export PATH=/PATH/TO/YOUR/sbt/bin:$PATH" >> ~/.bash_profile source ~/.bash_profile
in an editor (create it if it doesn't exist) and add the following line export Verify that sbt is installed correctly: Open a new terminal (to apply the changed .bashrc) and type sbt -h, you should see a help message from sbt. If you have problems installing sbt, ask for help on the forums.
The key point of this lecture is
If we want to implement high-level concepts following their mathematical theories, there's no place for mutation.
Avoid mutations; get new ways to abstract and compose functions.
Outside resource Cited:
YouTube: O'Reilly OSCON Java 2011: "Working Hard to Keep It Simple"
Main Ideas
def
x++
using substituion evaluation.def loop: Int = loop
def constOne(x: Int, y: => Int) = 1
Here, the line constOne(1+2, loop)
would terminate because the second
argument is call-by-name, whereas constOne(loop,1+2)
would not terminate
because the first argument is call-by-value.
true && e
evaluates to e
, false && e
evaluates to false
,true || e
evaluates to true
, false || e
evaluates to e
,if (b) e1 else e2
as b && e1 || e2
.def
are by-name; the right-hand-side is evaluated on each use.val
are by-value; the right-hand-side is evaluated
immediately and only once. For example,val x = 2 val y = square(x)
Since the right-hand side of a val
is evaluated at the point of definition,
y
has the value 4, not the value square(x)
.
Example:
def loop: Boolean = loop
This defines loop as a function that is supposed to return a Boolean,
but simply calls itself. Now, def x = loop
sets x equal to the loop function,
which is no problem, but val x = loop
crashes because it attempts to evaluate
immediately and gets caught in the infinite recursive call. In the REPL:
scala> def loop: Boolean = loop loop: Boolean scala> def x = loop x: Boolean scala> val x = loop
and
and or
function without using &&
and ||
.def and(x: Boolean, y: Boolean) = if(x) y else false def or(x: Boolean, y: Boolean) = if(x) true else y
Now suppose the second argument of and
is nonterminating, say,
and(false, loop)
Even though and
should short circuit because of the false first argument,
this doesn't work because the second argument is call-by-value. We can fix
this by modifying the argument to be call-by-name as follows:
def and(x: Boolean, y: => Boolean) = if(x) y else false
First Scala program in Eclipse!
Ctrl-Shift-f
nicely formats (indents) all code in the current buffer(easy stuff)
(easy stuff)
def f(x1, ..., xn) = B; ... f(v1, ..., vn)
could be written
def f(x1, ..., xn) = B; ... [v1/x1, ..., vn/xn]B
Here, the notation [v1/x1, ..., vn/xn]B
means
"v1 for x1, ..., vn for xn in B", that is,
substitute the value vi for xi in the function body B.
Tail Recursion
If a function simply calls itself as its last action, the function's
stack frame can be reused. This is called tail recursion.
A tail recursive function can execute in constant stack space, so
tail recursive functions are iterative processes.
A tail recursive function is the functional programmer's loop---it excutes just as efficiently as a loop in an imperative program.
More generally, if the last action of a function consists of calling a single function (which may be the same function), one stack frame would be sufficient for both functions. Such calls are called tail-calls.
Here is a non-example (i.e., a recursive function that is not tail recursive):
def factorial( n: Int ): Int = if (n==0) 1 else n * factorial( n-1 )
Notice that after the recursive call to factorial, there is still work to be done.
We will rewrite factorial in a tail-recursive way. However, we pause to ask:
Should every recursive function be reformulated to be tail recursive?
No. Tail recursion is used to avoid very deep recursive chains. Most JVM implementations limit the maximal depth of recursion to a couple of thousands of stack frames.
The factorial function is not succeptible to this because the values computed by factorial grow so quickly that they exceed the range of integers before stack overflow. In such cases, use the simpler function definitions and heed Knuth:
"Premature optimization is the root of all evil." --Donald Knuth
To define a tail recursive version of factorial, we use an accumulator:
def factorial( n: Int , acc: Int ): Int = if ( n == 0 ) acc else factorial( n-1, acc*n )
We could sum the integers between a and b (inclusive) as follows:
def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a+1, b)
Then sum the squares of integers between a and b (inclusive):
def sumSquares(a: Int, b: Int): Int = if (a > b) 0 else a*a + sumSquares(a+1, b)
Sum the cubes or sum the factorials, etc. But this can be done more generally and elegantly if we simply pass the functions---e.g., square, cube, factorial---as arguments:
def sumFunc(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumFunc(f, a+1, b)
Then sumSquares
could be implemented as
def sumSquares(a: Int, b: Int): Int = sumFunc(square, a, b) def square(a: Int): Int = a * a
But even that's not great because then we end up defining all these little auxiliary functions. Since functions are first class objects, we use anonymous functions as "function literals" just as we use string literals as follows:
println("abc")
Instead of the also correct, but more tedious,
def str = "abc" println(str)
The syntax for anonymous functions in Scala is as follows:
(x: Int) => x * x (x: Int, y: Int) => x + y
Every anonymous function can be expressed using def instead. That is,
(x1: T1, ..., xn: Tn) => E
can be implemented as follows:
def f(x1: T1, ..., xn: Tn) => E; f
But sometimes we need brackets so the name f doesn't get confused with another function:
{def f(x1: T1, ..., xn: Tn) => E; f}
So, returning to the example above, we could have defined sumSquares as:
def sumSquares(a: Int, b: Int): Int = sumFunc((x: Int) => x*x, a, b)
or, more simply,
def sumSquares(a: Int, b: Int): Int = sumFunc(x => x*x, a, b)
The sum function above uses linear recursion. We can write a tail recursive version. (Here the tail recursion is actually useful, unlike in the factorial case.)
def sum(f: Int => Int, a: Int, b: Int): Int = { def sumAux(a: Int, acc: Int): Int = { if (a > b) acc else sumAux(a+1, acc + f(a)) } sumAux(a, 0) } sum(x => x*x, 0,3) //> res4: Int = 14
Another version of the sum function could employ Currying.
First, we could do this simply as follows:
def sum(f: Int => Int)(a: Int, b: Int): Int = { def sumAux(a: Int, acc: Int): Int = { if (a > b) acc else sumAux(a+1, acc + f(a)) } sumAux(a, 0) }
Then, the expression
sum(x => x*x)_
is valid. It is a "partially applied" function.
Alternatively, we could get rid of the a and b parameters:
def sum(f: Int => Int): (Int, Int) => Int = { def sumAux(a: Int, b: Int): Int = { if (a > b) 0 else f(a) + sumAux(a+1, b) } sumAux }
This gives us a function that returns, not an Int
, but a function
of type (Int, Int) => Int
. So now we could define sumSquares as follows:
def sumSquares = sum(x => x*x) sumSquares(0, 3) //> res3: Int = 14
Or avoid the middle man and use the sum function directly, as in
sum(x=> x*x*x)(0, 3) //> res3: Int = 36
This is the same as
(sum(x=> x*x*x))(0, 3)
So we see that function application associates to the left.
Products
We can define a product function in a similar way. We just need to abstract
out the identity and the binary operation (instead of 0 we have 1, and instead of + we have *).
def product(f:Int => Int)(a: Int, b: Int): Int = { if (a>b) 1 else f(a) * product(f)(a+1, b) } //> product: (f: Int => Int)(a: Int, b: Int)Int
We can define factorial in terms of this product function.
def factorial(b: Int): Int = product(a=>a)(1,b) //> factorial: (b: Int)Int // Test it: factorial(3) //> res0: Int = 6
MapReduce We can simultaneously generalize sum and product. Since we are
providing a mapping (with f), and then reducing (with the binary op), we should call
the generalized version mapReduce
.
def mapReduce(map: Int => Int, reduce: (Int, Int) => Int, unit: Int)(a: Int, b: Int): Int = { if(a > b) unit else reduce(map(a), mapReduce(map, reduce, unit)(a+1, b)) } // Test it: mapReduce(x => x*x, (a, b) => a+b, 0)(0, 3) //> res1: Int = 14
At about 3'30" of the video lecture, the (Java) package called week3 and a new scala worksheet called rationals.sc are created.
abstract class -- may contain members that are not implemented. You cannot instantiate abstract classes. You must implement them with a class that extends the abstract class.
Example: IntSet (abstract), Empty extends IntSet, NonEmpty extends IntSet. The classes that extend (implement) the abstract class implement all the unimplemented members of the abstract class. They can also implement members that were already implmented in the abstract class, but then you need to use override.
superclasses, subclasses. Every class extends another class. If no explicit class is given, then the standard Java Object class is assumed.
The direct or indirect superclasses of a class are called the base classes.
Standalone applications. Hello world program. Create this in Eclipse not as a Scala worksheet, but as a Scala Object.
The implementation of the union operation for IntSet is a really nice example of functional object oriented programming. The union is implemented recursively. For the Empty object, union obviously returns the argument
def union(other: IntSet) = other
For the NonEmpty class, the union is implemented as follows:
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet { def union(other: IntSet): IntSet = ((left union right) union other) incl elem }
How cool is that?!
How is this done in an object oriented language?
Using the dynamic method dispatch model. This means that the code invoked by a
method call depends on the runtime type of the object that contains the method.
Dynamic dispatch is analogous to calls to higher-order functions in a purely functional language.
Classes and objects are organized in packages. Packages are imported with the
import
statement. Some entities are imported automatically in every Scala
program. These are
You can explore the standard Scala library using the scaladoc web pages at
www.scala-lang.org/api/current/index.html
Traits In Scala, like Java, a class can only have one super class. That is, Scala is a single inheritance language. But sometimes, we want a class to inherit properties from several super entities. To do this we use traits. For example,
trait Planar { def height: Int def width: Int def surface = height * width }
Classes can inherit from at most one class but from arbitrarily many traits. For example,
class Square extends Shape with Planar with Movable
Thus, Square has one superclass called Shape, but it also inherits traits Planar and Movable.
Main distinction Traits do not have val members.
Special types in the hierarchy: At the top, there is the Any
class and its
subclasses, AnyVal
and AnyRef
. At the bottom, there is the Nothing
class
which is a subclass of Null
.
Null is a subclass of all the types that are reference types. If, for example, a String is expected, then you can pass a null value.
val x = null val y: String = x
However, val z: Int = null
doesn't work because Null is not a subclass of
subclasses of AnyVal.
Consider the line
if (true) 1 else false //> res1: AnyVal = 1
The result is of type AnyVal, because the type checker picks a type that is the least upper bound of the types involved in the expression. In this example, we have 1 and false, which are Int and Boolean, resp. The "least" type that contains both is AnyVal.
Errors
To immediately halt execution of the program and output an error message:
def error(msg: String) = throw new Error(msg)
Type parameterization Running example: the immutable list
Cons-List
Nil -- the empty list
Cons -- a cell containing the first element of the list and a pointer to the
rest of the list
In Eclipse, create a Scala Trait called List
. (at 5'55" of Lecture 3.3)
Generics
In Scala, you can put field definitions in parameter lists, instead of in the body of the class. For example, the following:
class Cons[T](val head: T, val tail: List[T]) extends List[T] { def isEmpty = false }
is equivalent to
class Cons[T](_head: T, _tail: List[T]) extends List[T] { def isEmpty = false val head = _head val tail = _tail }
Type Erasure Type parameters do not affect evaluation in Scala. We can assume that ll type parameters and type arguments are removed before evaluating the program. Scala shares this with Java, Haskell, ML and OCaml.
Two basic principles of polymorphism are subtyping and generics.
(See page 54 of Scala by Example, Odersky.)
If we have a function that takes any subtype of IntSet and returns something of that same type, the interface could be something like this:
def assertAllPos(r: IntSet): IntSet = ...
But this isn't very precise. Instead, we could specify that the return type is the same as follows:
def assertAllPos[S <: IntSet](r: S): S = ...
Here, "<: IntSet" is an upper bound on the type parameter S. It means S can be instantiated only to types that conform to IntSet. I believe this means that once we are passed the parameter r of a specific type S, then the return type must also be of type S. (It's not clear whether this allows return type to be a subtype of type S. We should check this.)
Generally, the notation
S <: T // means S is a subtype of T S >: T // means S is a supertype of T
With the >: operation we can be even more precise:
def assertAllPos[S >: NonEmpty <: IntSet](r: S): S = ...
would restrict S to be of a type between NonEmpty and IntSet.
Question: How do we specify that S must be of a specific type and not of a subtype? Perhaps as follows?
def assertAllPos[S >: IntSet <: IntSet](r: S): S = ...
Important: Arrays are not covariant in Scala, unlike in Java, because they are mutable. (Lists in Scala are immutable, so they are covariant.) For example, in Java, one would have
NonEmpty[] <: IntSet[]
This causes problems and leads to runtime errors. (See Lecture 4.4 on variance for more details.)
Liskov Substitution Principle If A <: B, and if q(x) is a property provable for objects x:B, then q(y) should be provable for objects y:A.
This is an important lecture.
After explaining how to implement Boolean as a class instead of a primitive type (see also page 37 of Scala by Example), Odersky explains how to implement the type Nat (the Peano numbers) in Scala.
Suppose we want to write a small interpreter for arithmetic expressions. Let's restrict to just numbers and sums of numbers. We will have an expression class Expr with two subclasses, Number and Sum. We could have, outside our Expr class, an eval method that tests whether the input is a number or a sum and then evaluates it. First solution, use type tests and type casts. This is low level and potentailly unsafe. Here's a better, object oriented solution:
trait Expr { def eval: Int } class Number(n: Int) extends Expr { def eval: Int = n } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval }
But what if we decide later to have other subclasses? Then we need to implement an eval method for each. It is tedious to have to modify all the subclasses of Expr. Also, what if we now want a show method to print out the string representation of Expr subclass objects. Worse than that, what if we want to simplify expressions? A given expression might be a composition of sums and products and it's not clear how to implement a simplify method. The solution is pattern matching, which we take up in the next lecture.
(See page 43 of Scala by Example.)
Pattern matching allows us to find a general and convenient way to access objects in an extensible class hierarchy.
The three solutions we have seen all have shortcomings. They were:
Case Classes the sole purpose of test and accessor functions is to reverse the construction process. This is automated by pattern matching.
trait Expr case class Number(n: Int) extends Expr case class Sum(e1: Expr, e2: Expr) extends Expr
We get added functionality by adding case. The compiler implicitly adds companion objects:
object Number { def apply(n: Int) = new Number(n) } object Sum { def apply(e1: Expr, e2: Expr) = new Sum(e1, e2) }
We saw earlier that you can manually do this. Put a def apply
method in your
class and then you can instantiate the class without the new keyword, as in
Number(2)
instead of new Number(2)
.
Pattern matching is a generalization of switch from C or Java. It is expressed in Scala using the keyword match. For example,
def eval(e: Expr): Int = e match { case Number(n) => n case Sum(e1, e2) => eval(e1) + eval(e2) }
Patterns are constructed from:
Note: variables always begin with a lowercase letter; constants begin with a capital letter (except for reserved words like null, true, false).
As an alternative to the above implementation of eval, we could make eval a member method of the Expr trait:
trait Expr { def eval: Int = this match { case Number(n) => n case Sum(e1, e2) => e1.eval + e2.eval } }
Fold and reduce combinators (*) stands for ((x,y) => x*y)
Vector
This lecture has some crucial tips on when to use List
and when to use
Vector
. Vector
has more evenly balanced access patterns than List
. So,
why would you ever want to use List.
The List
type is most useful in
recursive algorithms where you are repeatedly acting on the head (a constant
time access), and then passing the tail to the recursion. With Vector
,
accessing the leading element might require going down a few levels in the (albeit shallow)
tree, and splitting off the "tail" of a vector is not as clean as with a list.
val nums = Vector(1, 2, 3, -88) val people = Vector("Bob", "Jim")
This type supports the same operations as List
except concat ::. Instead of
x :: xs
, there is x +: xs
and 'xs :+ x`.
A common base class of List
and Vector
is Seq
, which (along with Set
and Map
types) is a subclass of Iterable
. Array
and String
are
also sequence like classes. The usual operations on sequences like map
and
filter
and take while
and fold
can be applied to all of these types.
Most of the instructions here are aimed at Linux users. If you use another operating system, feel free to use these notes as a guide.
The standard way to use the UACalc is through its graphical user interface. This requires Java. There are many ways to get the Java Runtime Environment working on a Linux machine, but here we describe how to install the full Oracle Java Development Kit (JDK). This is a reasonable option, especially if you plan to venture beyond the GUI, and write some Java or Jython or Scala programs that call UACalc Java packages.
Here is one way to install Java on Linux (Ubuntu 13.10). It is not the only way, but it seems to work. (Alternative instructions for installing the JDK on Linux are here.)
Download the Java Development Kit
As of this writing (March, 2014) the latest version of the JDK is 1.7, which is available at the
For example, I'm now using jdk-7u51-linux-x64.tar.gz, but you should pick the tarball that is most appropriate for your hardware. If the link above doesn't work, try the following:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
Unpack the jdk tarball
Go to the directory where you downloaded the jdk in the previous step
and invoke the command
tar xvzf jdk-7u*-linux-x64.tar.gz
Create the jvm directory
sudo mkdir -p /usr/lib/jvm
Move the jdk directory
If you already have directory named /usr/lib/jvm/jdk1.7.0, move it out of the
way:
sudo mv /usr/lib/jvm/jdk1.7.0{,.orig}
Now move your newly unpacked jdk directory (e.g. jdk1.7.0_51) to /usr/lib/jvm and rename it jdk1.7.0:
sudo mv jdk1.7.0_* /usr/lib/jvm/jdk1.7.0
Make jdk1.7.0 the default Java
We will use the update-alternatives
program for this
(see also: notes on configuring JDK 1.7 on Ubuntu):
This first block of 9 commands can be copy-and-pasted to the command line all at once:
sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.7.0/bin/java" 1; sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.7.0/bin/javac" 1; sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/lib/jvm/jdk1.7.0/bin/javaws" 1; sudo update-alternatives --install "/usr/bin/jcontrol" "jcontrol" "/usr/lib/jvm/jdk1.7.0/bin/jcontrol" 1; sudo chmod a+x /usr/bin/java; sudo chmod a+x /usr/bin/javac; sudo chmod a+x /usr/bin/javaws; sudo chmod a+x /usr/bin/jcontrol; sudo chown -R root:root /usr/lib/jvm/jdk1.7.0;
The following commands are interactive and should be invoked individually:
sudo update-alternatives --config java sudo update-alternatives --config javac sudo update-alternatives --config javaws sudo update-alternatives --config jcontrol
(Note: the jcontrol
command is not mentioned in
the notes cited above,
but we will need it below.)
You can check which version of Java your system is currently using with the commandjava -version
.
(As of March 2014, the Java security certificate for the UACalc has been renewed, so it shouldn't be necessary to follow all of the steps in this section. After installing Java as described above, and then following steps 1 and 2 below, the UACalc gui should run fine. However, I'll leave the information in this section as is, in case Ralph decides it's not worth renewing the security certificate in the future.)
In an ideal world, assuming you successfully installed Java as described in
the previous step, you would now be able to go to uacalc.org
and click a Launch
button. However, the world is not idea, and launching
UACalc for the first time now requires an extra step.
We must first tell Java that we trust the site www.uacalc.org.
(This used to be a simple matter of checking a box, but Oracle has
recently made the procedure for accepting security certificates even
more annoying.)
Try to launch the UACalc gui (and probably fail)
In a terminal window, go to the directory where you downloaded the file
in the previous step and try to launch UACalc with the following
command:
javaws uacalcbig4.jnlp
If UACalc starts up, you're good to go! More than likely, however, you will get an annoying dialog box like the following:
Where is the checkbox on this dialog so that I can accept the risk and
proceed at my own peril? It's gone. So we have no choice but to
select the OK
button to abort launch and follow the steps below.
Launch the Java Control Panel
At the command line, type jcontrol
You should see a window that looks like this:
If you get an error, try typing /usr/lib/jvm/jdk1.7.0/bin/jcontrol
Add uacalc.org to the Exception Site List
Click on the Security
tab, and click the Edit Site List
button.
You should see a dialog box that looks like this:
Click the Add
button and type http://www.uacalc.org and click OK
.
You will get a warning. Click Continue
.
If your Java Control Panel now looks like the one below, click OK
.
Now, when you invoke
javaws ~/Desktop/uacalc/uacalcbig4.jnlp
at the command line, you should see a less futile and pointless window than the one we saw in Step 2.
Accept the risks and click OK
and you should finally see the
UACalc gui, which looks like this:
Here we describe how to read in a bunch of algebras and test whether the varieties they generate have certain properties.
We have some files in the UACalc/Algebra directory called Alg1.ua
, Alg2.ua
,..., Alg9.ua
each one containing a finite idempotent algebra, and we want to check whether the varieties generated by these algebras are congruence distributive or congruence permutable.
Freese and Valeriote [1] discovered polynomial time algorithms to test for these properties, and implemented them in the UACalc.
Here's how one could use Jython to call the UACalc methods that perform these tests:
from org.uacalc.alg.Malcev import isCongruenceDistIdempotent from org.uacalc.alg.Malcev import cpIdempotent from org.uacalc.io import AlgebraIO homedir = "/home/williamdemeo/git/UACalc/" outfile1 = open(homedir+"isCD.txt", 'w') outfile2 = open(homedir+"isCP.txt", 'w') for k in range(1,10): algname = "Alg"+str(k) algfile = homedir+"Algebras/"+algname+".ua" A = AlgebraIO.readAlgebraFile(algfile) outfile1.write(algname+" "+str(isCongruenceDistIdempotent(A, None))+"\n") outfile2.write(algname+" "+str((cpIdempotent(A, None)==None))+"\n") outfile1.close() outfile2.close()
The output will be stored in the isCD.txt and isCP.txt files, and will look something like this:
Alg1 True Alg2 True Alg3 False Alg4 True...
with True in the isCD.txt (resp, isCP.txt) file meaning the algebra generates a variety that is congruence distributive (resp, permutable).
[1] Ralph Freese and Matthew Valeriote, On the complexity of some Maltsev conditions, Intern. J. Algebra & Computation, 19(2009), 41-77.
]]>I made this version for my own reference, while working through the tutorial, making some revisions and additions, and a few corrections. You may prefer the original.
Table of Contents
Copyright (c) 2013, Liam O'Connor-Davis
Welcome to Learn You an Agda and Achieve Enlightenment! If you're reading this, you're probably curious as to what Agda is, why you want to learn it, and in general what the big deal is about dependently typed, purely functional programming.
Inspired by BONUS, the writer of Learn You a Haskell, I decided that I should write an approachable Agda tutorial that would introduce dependently typed programming to ordinary people rather than Ivory Tower Academics. Of course, seeing as I am one of those Ivory Tower Academics, this might not be easy. I am, however, prepared to give it a try. Learning Agda was a very rewarding but very difficult process for me. It is my hope that, by writing this tutorial, it will become a little bit easier for everyone else.
(The original tutorial suggested that Haskell should be learned before Agda. However, if one already knows some functional programming, then Agda should not be very hard to learn. This should be especially true for those with some background in logic and experience with other dependently typed languages.)
This tutorial is not aimed at those who are completely new to functional programming. Agda is similar on a basic level to typed functional languages such as Haskell and ML, and so knowing a language in the ML family will certainly make learning Agda a great deal easier.
If you don't know a statically typed functional language, I recommend that you learn Haskell, as Agda has a close relationship with the Haskell ecosystem. If you're looking for a good Haskell tutorial, look no further than this book's companion, Learn You a Haskell.
If you don't know how purely functional programming works, learn a little of it before trying to tackle Agda.
Understanding of imperative and object oriented programming (C, Java, Ruby..) isn't necessary. In fact, trying to apply skills learned from these languages might even be harmful when you're trying to learn Agda.
The moral of the story is: keep an open mind. A lot of Agda's power comes from features that are at first difficult to understand. It took a long time for everything in Agda to fall into place in my head. Agda is hard. After some time, though, Agda's inherent awesomeness comes to the fore, and it all just clicks. If you encounter obstacles in your Agda learning, don't be discouraged! Keep working, and eventually you will be a master of Agda fu.
Agda is a programming language, but not a programming language like Java. It's not even very much like Haskell, although it's a lot more like Haskell than Java.
Agda is a programming language that uses dependent types. Many of you would be familiar with types from imperative languages such as Java or C++, and if you're reading up to this point, you should also have a familiarity with types from Haskell.
Types in these languages essentially annotate expressions with a tag. At a simple level,
an expression's type might just be a concrete type, like Bool
or Int
. Java (through
generics), C++ (through templates) and Haskell all support polymorphic types as well,
such as List a
or Map k v
.
But, if List a
is a type, then what exactly is just List
(without the parameter)?
Haskell calls it a "type constructor", but really it's a function at the type
level. List
takes in a type, say Int
, and returns a new type, List Int
. Haskell (with appropriate extensions) even supports arbitrary functions on
the type level, that don't necessarily have to construct a type term, and
instead can simply refer to existing ones.
So, Haskell has type-level functions, even type-level types (kinds). It almost seems like an entirely new language, overlaid over Haskell, that operates at compile time, manipulating type terms.
In fact, you could think of any type system this way. In C++, people exploit the Turing-completeness of their type system to perform compile-time analysis and computation. While such type level work is very powerful, I fear that such type machinery is very often difficult to understand and manipulate. Even in Haskell, applications that make extensive use of type-level computation are very often substantially harder to comprehend. The type-level "language" is almost always substantially more complicated to work with than the value-level "language."
In Agda, the distinction between types and values does not exist. Instead, the language you use to manipulate type terms is exactly the same language that you use to manipulate values.
This means that you can actually include values inside a type. For example, the List
type constructor can be parameterized by both the type of its contents and the length of
the list in question (we'll be doing this later). This allows the compiler to check for you
to make sure there are no cases where you attempt to call head
on a
potentially empty list, for example. Being able to include values inside a type,
and use all the same value-level operations on them, is what makes Agda
dependently typed - Not only can values have a type, but types can have a value.
In fact, seeing as the language of values and the language of types are the same, any property that you can express about a value can be expressed statically in its type, and machine checked by Agda. We can statically eliminate any error scenario from our program.
If I can come up with a function of type Foo -> Bar
(and Agda says that it's
type correct) that means that I've written not only a program, but also a proof
by construction that, assuming some premise Foo
, the judgment Bar
holds. (We'll touch more on proofs later; I don't want to get bogged down in
details just yet.)
Seeing as our Foo
and Bar
can be as expressive as we like, this lets us
prove anything we want about our program simply by exploiting this
correspondence between proofs and programs - called the
Curry-Howard Correspondence,
discovered by two brilliant logicians in the sixties.
The validity of formal verification of software is often hotly contested by programmers who usually have no experience in formal verification. Often testing methodologies are presented as a more viable alternative.
While formal verification is excessive in some situations where bugs are acceptable, I hardly think that testing could replace formal verification completely. Here are three reasons for this:
Of course, proofs are not for every scenario, but I think they should be far more widely used than they currently are.
Thanks to Curry-Howard, Agda can also be used as a proof language, as opposed to a programming language. You can construct a proof not just about your program, but about anything you like.
In fact, Curry-Howard shows us that the fundamentals of functional programming (Lambda Calculus), and the fundamentals of mathematical proof (Logic) are in fact the same thing (isomorphic). This means that we can structure mathematical proofs in Agda as programs, and have Agda check them for us. It's just as valid as a standard pen-and-paper mathematical proof (probably more so, seeing as Agda doesn't let us leave anything as "an exercise for the reader" - and Agda can check our proof's correctness automatically for us. We'll be doing this later by proving some basic mathematical properties on Peano natural numbers.
So, Agda is a language that really lives the dream of the Curry-Howard correspondence. An Agda program is also a proof of the formula represented in its type.
At the time of writing, it is only really feasible to edit Agda code using Emacs. GNU Emacs or XEmacs are both fine. However, you don't need a great deal of Emacs proficiency to edit Agda code.
(If you are using Ubuntu Linux, you may wish to skip to the next section, Installing on Ubuntu Linux.)
You'll need GHC, a Haskell compiler, and an assortment of tools and libraries that make up the Haskell Platform. It is the best way to get started using Haskell, and it's also the easiest way to get Agda.
Once you have Haskell and Emacs, there are three things you still need to do:
cabal-install
tool to download, compile, and set up Agda.cabal install agda
PATH
):agda-mode setup
agda-mode compile
By then you should be all set. To find out if everything went as well as expected, head on over to the next section.
On a Ubuntu Linux system, instead of installing Agda using cabal as above, one could alternatively use the following two commands, which take a few minutes to run:
sudo apt-get install agda-mode sudo apt-get install agda-stdlib
That's it! Now, when you launch Emacs and edit a file with the .agda extension,
it should switch to agda-mode
or agda2-mode
. If not, you can switch manually
by invoking one of the following (in Emacs, of course): M-x agda-mode
or
M-x agda2-mode
or Esc-x agda-mode
. (An easy way to find out which agda
modes are available is to type M-x agda
and then hit tab a couple of times to
see the possible completions.)
Unlike the previous section, this section will actually involve some coding in Agda.
Most language tutorials start with the typical "Hello, World" example, but this is not really appropriate for a first example in Agda. Unlike other languages, which rely on a whole lot of primitive operations and special cases for basic constructs, Agda is very minimal - most of the "language constructs" are actually defined in libraries.
Agda doesn't even have numbers built in, so the first thing we're going to do is
define them---specifically natural numbers. Natural numbers are nonnegative
integers, that is, the whole numbers starting with zero and going
up. Mathematics uses the symbol $\mathbb N$ to represent natural numbers, so we're going
to borrow that for our example (Another thing that sets Agda apart from other
languages is its extensive use of unicode to make mathematical constructs more
natural). To enter â into emacs, type \bn
. To enter the unicode arrow (â),
type \->
. I'm going to demonstrate this line by line, so bear with me.
First, open a file named LearnYouAn.agda in Emacs and type the following:
module LearnYouAn where data â : Set where
The data
keyword means we're defining a type---in this case, â
.
In this example, we're specifying that the type â
is of type Set
(that's
what the colon means).
If you recall the introduction, I mentioned that in Agda, types and values are treated the same way. Since values are given types, types are given types as well. Types are merely a special group of language terms, and in Agda, all terms have types.
Even Set
(the type of our type â
) has a type: Setâ
, which has a type
Setâ
, going on all the way up to infinity. We'll touch more on what these
Set
types mean later, but for now you can think of Set
as the type we give
to all the data types we use in our program.
This infinite hierarchy of types provides an elegant resolution of
Russell's Paradox.
For example, Setâ
cannot contain Setâ
or Setâ
, only Set
, so Russell's
problematic set (that contains itself) cannot exist.
Okay, so, we've defined our type, but now we need to fill the type with values. While a type with no values does have its uses, a natural numbers type with no values is categorically wrong. So, the first natural number we'll define is zero:
zero : â
Here we are simply declaring the term zero
to be a member of our new type
â
. We could continue to define more numbers this way:
zero : â one : â two : â
But we'd quickly find our text editor full of definitions and we'd be no closer to defining all the natural numbers than when we started. So, we should instead refer to a strict mathematical definition.
(The notation I'm using here should be familiar to anyone who knows set theory and/or first-order logic. Don't panic if you don't know these things, we'll be developing models for similar things in Agda later, so you will be able to pick it up as we go along.)
This is called an inductive definition of natural numbers. We call it inductive because it consists of a base rule, where we define a fixed starting point, and an inductive rule that, when applied to an element of the set, induces the next element of the set. This is a very elegant way to define infinitely large sets. This way of defining natural numbers was developed by a mathematician named Giuseppe Peano, and so they're called the Peano numbers.
We will look at inductive proof in the coming sections, which shares a similar structure.
For the base case, we've already defined zero to be in $\mathbb{N}$ by saying:
zero : â
. To define the natural numbers inductively, let's recall the
induction step of first order logic. This can be written as follows:
Given a natural number n
, the constructor suc
will return
another natural number. In other words, suc
could be considered a
function that, when given a natural number, produces the next natural
number. In Agda, we define the constructor suc
like so:
data â : Set where zero : â suc : â â â
Now we can express the number one as suc zero
, and the number two as suc (suc zero)
, and the number three as suc (suc (suc zero))
, and so on.
Incidentally, this definition of natural numbers corresponds to the Haskell data type:
data Nat = Zero | Suc Nat
If you load that into GHCi and ask it what the type of Suc
is, it
(unsurprisingly) will tell you: Nat -> Nat
. This is a good way to get an
intuition for how to define constructors in Agda.
Also, GHC supports an extension, Generalized Algebraic Data Types or GADTs, which allows you to define data types Agda style:
data Nat :: * where Zero :: Nat Suc :: Nat -> Nat
It's worth noting that GADTs are not exactly the same as Agda data definitions, and Haskell is still not dependently typed, so much of what you learn in this book won't carry over directly to extended Haskell.
Now we're going to define some arithmetic operations on our natural numbers. Let's try addition, first.
_+_ : â â â â â
Here I'm declaring a function. To start with, I give it a type^{2}---it takes two natural numbers, and returns a natural number.
Unlike Haskell which has only prefix functions (ordinary functions) and infix functions (operators), Agda supports mixfix syntax. This allows you to declare functions where the arguments can appear anywhere within a term. You use underscores to refer to the "holes" where the arguments are meant to go.
So, an if-then-else construct in Agda can be declared with:^{3}
if_then_else_ : â { a } â Bool â a â a â a
This can be used with great flexibility: You can call this function with if a then b else c
, which Agda interprets as if_then_else_ a b c
. This syntactic
flexibility delivers great expressive power, but be careful about using it too
much, as it can get very confusing!
Now, let's implement and check the sum function by structural recursion.^{4}
_+_ : â â â â â zero + m = m (suc n) + m = suc (n + m)
Normally we'd run the program at this point to verify that it works, but in Agda we check our code. This checks that all our proof obligations have been met:
Proof obligations of a program can only be machine-checked if the program terminates, but checking that any program terminates is in general undecidable (see The Halting Problem). To circumvent this dilemma, Agda runs its checker only on structural recursion with finite data structures, and warns that it can't check proof obligations in which non-structural recursion is used. We will discuss this more in later sections, but all of the examples in the early part of this tutorial can be proved by Agda to terminate.
At this point, the contents of the LearnYouAn.agda file should be as follows:
module LearnYouAn where data â : Set where zero : â suc : â â â _+_ : â â â â â zero + m = m (suc n) + m = suc (n + m)
Make sure you get the indentation right! In particular, the constructors
for zero
and suc
start in the 5th
column (below the t in data
), whereas all three lines in the definition of
_+_
begin in column 3.
To check the program, type C-c C-l
in Emacs (or choose Load from the Agda
menu). If your program checks correctly, there will be no error messages, no hole markers
(yellow highlighting) and no orange-highlighted non-terminating sections. Also,
the words (Agda: Checked)
should appear in the Emacs mode line, and you should
notice that the colors of characters have changed.
Right now, our checks aren't all that meaningful---the only thing they prove is that our addition function does indeed take any natural number and produce a natural number, as the type suggests. Later on, when we encode more information in our types, our checks can mean a lot more---even more than running and testing the program.
To evaluate an expression (just to verify that it truly does work), we can type
C-c C-n
into emacs, or select "Evaluate term to normal form" from the Agda
menu. Then, in the minibuffer, we can type an expression for 3 + 2:
(suc (suc (suc zero))) + (suc (suc zero))
This produces the following representation of the number 5:
(suc (suc (suc (suc (suc zero)))))
In this section we have examined the Peano natural numbers, and defined some basic functions and data types in Agda. In the next section, we'll look at propositional logic, and how to encode logical proofs in Agda using this system.
If you are having trouble getting the First program to work, make sure you have the indentation right. It might help to download the LearnYouAn.agda file, just to be sure.
Now that we've defined the natural numbers, we're going to do some simple example proofs of some basic mathematical properties. We'll first discuss logic and logic specification in natural deduction, and as we go, we'll discuss the application to Agda.
At a fundamental level, a logic is a system of judgments. Judgments are statements in a mathematical language that may be proven, or unproven. A language is usually described as a set of strings, which make up every term in the language, but this is a simplification: a language can be made up of arbitrary data structures. We just use strings to represent these structures because any data structure can be represented in some string form.
For our example, we will define a very simple logic based on the language of natural numbers $\mathbb{N}$ we used earlier.
We're going to have just one type of judgment, of the form $\mathbb{N}
\textbf{even}$, which is provable only when the given number is even.
A logic consists of a set of axioms and a set of rules. Axioms are the foundation of the logic: they're the basic, simple statements that are assumed to be true. Rules describe how to produce new theorems from existing ones. A theorem is a proposition that has been proved. Thus, the rules tell us how to construct proofs of statements using proofs of other statements. We can formally specify these axioms and rules in a meta-logic called natural deduction, by writing them in the form of inference rules, which look like this:
$$\frac{P_1 \quad P_2\quad\cdots\quad P_n}{C} \text{(name of rule)}$$
This says that if we can prove all of the premises $P_1 \cdots P_n$, then we can prove the conclusion $C$.
For our purposes, we have just one axiom, that the number zero is even. Axioms are written as inference rules with no premises:
$$\frac{}{\mathtt{zero}\ \textbf{even}} {\rm Z\scriptsize ERO}$$
Then, based on the inductive reasoning we used earlier, the rules for our logic should express that if some number $m$ is even, then $m + 2$ is also even. We do this by writing an inference rule schema, which describes a set of rules, by including one or more metavariables in an inference rule.
If we have some metavariable $x$ in a rule schema, we can substitute any term in the language for $x$, and the result is a valid rule. For example,
$$\frac{x\ \textbf{even}}{\mathtt{suc}\ (\mathtt{suc}\ x)\ \textbf{even}} {\rm S\scriptsize TEP}$$
If we want to show that four is even, we apply this rule twice (once where $x$ is two, and once when $x$ is zero), leaving the obligation $\mathtt{zero}\ \textbf{even}$ which is shown by the axiom ${\rm Z\scriptsize ERO}$.
We can write this proof using natural deduction in a "proof tree" format:
$$ \frac{\large \frac{\LARGE \frac{}{\mathtt{zero} \ \textbf{even}}}{\mathtt{suc} (\mathtt{suc}\ \mathtt{zero}) \ \textbf{even}}}{\mathtt{suc}\ (\mathtt{suc}\ (\mathtt{suc}\ (\mathtt{suc}\ \mathtt{zero})))\ \textbf{even}}$$
When proving a theorem, we work from the bottom of this tree upwards, applying rules that fit the form of the goal as we go. When reading the proof, we work downwards, reasoning from known axioms to the theorem that we want.
Agda's types correspond to judgments. If we can construct a value, or "inhabitant," of a certain type, we have simultaneously constructed a proof that the theorem encoded by that type holds.
As types are judgments, and values are theorems, data constructors for a type correspond to inference rules for the corresponding proposition.
Let's encode the judgment $\textbf{even}$ in Agda, based on our definition in
natural deduction.
We'll use the mix-fix name _even
here rather than just even
so that we can
use the judgment in post-fix form. As our judgment is over the language of
natural numbers, we index the type constructor for our judgment by the type â
.
data _even : â â Set where
Then we can define the axiom ${\rm Z\scriptsize ERO}$ as a
constructor for the type zero even
as follows:
ZERO : zero even
The ${\rm S\scriptsize TEP}$ axiom is a little more complicated, due to the presence of the metavariable $x$. If we just write the rule as-is,
STEP : x even â suc (suc x) even
Agda responds with the following:
/home/username/LearnYouAn.agda:14,12-13 Not in scope: x at /home/username/LearnYouAn.agda:14,12-13 when scope checking x
To resolve this, we let STEP
depend on a parameter, or variable. We name
this variable $x$, and specify that $x$ must be of type â, as follows:
STEP : (x : â) â x even â suc (suc x) even
This is an example of a dependent type, which provides a means of defining a
rule schema (a collection of rules parameterized by a variable).
Here, for example, STEP zero
refers to the rule zero even â suc (suc zero) even
.
Thus STEP
has the same substitution semantics for metavariables that we
described above.
At this point, our full program looks as follows:
module LearnYouAn where data â : Set where zero : â suc : â â â _+_ : â â â â â zero + n = n (suc n) + m = suc (n + m) data _even : â â Set where ZERO : zero even STEP : (x : â) â x even â suc (suc x) even
You should type this program into your Emacs buffer, save it as the file
LearnYouAn.agda, and check it with C-c C-l
Before we go on to use our program to actually prove something, we make
one more observation. In the current version of our program, the type of x
can be inferred from its context, since we use it with _even
, which takes a
instance of type â
as an argument. Therefore, so we can use the special â
symbol to introduce x
and omit the type. So, our final definition of _even
is
data _even : â â Set where ZERO : zero even STEP : â x â x even â suc (suc x) even
(This change will appear in the file LearnYouAn2.agda below.)
Now we use our Agda program to prove that four is even. Add the following lines
to your LearnYouAn.agda file, and then type C-c C-l
(use \_1
to type the subscript symbol â):
proofâ : suc (suc (suc (suc zero))) even proofâ = ?
Agda will convert the question mark into the symbols { }0
, which is Agda's
notation for a hole in the program (i.e., a hole in the proof).
proofâ : suc (suc (suc (suc zero))) even proofâ = { }0
The following will appear in a separate Emacs buffer:
?0 : suc (suc (suc (suc zero))) even
This tells us that our proof obligation at hole ?0
is
suc (suc (suc (suc zero))) even
.
Next, put your cursor into the hole, and type STEP ? ?
, and then type C-c C-space
. This splits the hole in two more holes.
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP { }1 { }2
?1 : â ?2 : suc (suc zero) even
The obligation ?1
, corresponding to hole { }1
, is the number we must provide
for x
when applying the STEP
constructor.
Hole { }2
corresponds to proof obligation ?2
, which is the obligation to
show that two is even. In this case, there is only one constructor that fits
the obligation---namely, STEP
Move the cursor inside hole { }2
and type C-c C-r
. Agda splits the hole
into another STEP
call for us, resulting in two more holes, { }3
and { }4
.
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP { }1 (STEP { }3 { }4)
?1 : â ?3 : â ?4 : zero even
Another C-c C-r
in hole { }4
will fill it with ZERO
.
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP { } (STEP { } ZERO)
?1 : â ?3 : â
The remaining obligations, ?1
and ?3
, can also be fulfilled automatically based
on the surrounding context. We can see what constraints on the holes are known
to Agda by using the "Show Constraints" option, or by typing
C-c C-=
. For the present example, this prints the following:
?1 := suc (suc zero) ?3 := zero
Agda will fill in the holes for us if we use the "Solve Constraints" option, or
C-c C-s
, and our final proof becomes
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP (suc (suc zero)) (STEP zero ZERO)
Another feature worth mentioning is Agsy, an automatic proof searcher for
Agda. Simply type C-c C-a
in any hole and Agsy will search for an appropriate
term to fill it. It's not guaranteed to find anything, but it can be useful.
(In the example above, it works well.)
It can be annoying, though, to have to pass in those numbers to STEP
explicitly, when Agda already knows from surrounding context exactly what they
are. In these situations, you can use a single underscore (_
) to indicate that
you wish Agda to infer the value in this position during type-checking.
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP _ (STEP _ ZERO)
If Agda cannot infer the value of the implicit underscore, an "unsolved metavariable" will be shown in the goals window, and the underscore will be highlighted in yellow.
For this particular judgment, however, it is almost always obvious from known constraints what the value of those numbers should be. In these cases, it's common to use an implicit parameter when declaring the type:
data _even : â â Set where ZERO : zero even STEP : â {x} â x even â suc (suc x) even -- long form { x : â } is also fine
Note that here we have added braces around the variable x
in our definition of
STEP
. This lets us omit the number entirely when writing our proof:
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP (STEP ZERO)
If Agda cannot, for whatever reason, infer the value of the implicit parameter, yellow highlighting and an unsolved metavariable will be added, as before. In those scenarios, you can manually specify the value of the implicit parameter by using braces:
proofâ : suc (suc (suc (suc zero))) even proofâ = STEP {suc (suc zero)} (STEP {zero} ZERO)
For _even
, this is not usually necessary, but if you find yourself specifying
implicit parameters frequently, you may wish to consider making it explicit.
In Agda, an implication proposition corresponds to a function type.
When we prove an implication, say A â B
, we assume the premise A
, and
derive the conclusion B
. In terms of proof by construction, this
is the same as implementing a function---a function that, when given an argument
of type A
, constructs an return value of type B
.
In other words, given a proof (by construction) of some proposition
A
, our function produces a proof of proposition B
.
By writing such a function (and having Agda check it), we have constructed a
proof of A â B
.
Consider the simple tautology A â A
.
Let's prove this in Agda!
First, we prove it for the proposition that natural numbers exist, that is, "if natural numbers exist, then natural numbers exist."
proofâ : â â â proofâ Î˝ = Î˝ -- type \mathbb Nu for Î˝.
So, proof of this tautology corresponds to the identity function. The reason for
this is fairly clear: Given a proof of A
, there's one obvious way to produce
a proof of A
: present the same proof you were just given!
It would be nice though, to make the above proof about all propositions, not merely the proposition that natural numbers exist---after all, the proof is the same regardless of the proposition involved!
To do this, we have to exploit Agda's flexible type system a little. We make our identity function take an additional parameter---a type. Given a type, we then return an identity function, instantiated for that type.
proofââ˛ : (A : Set) â A â A proofââ˛ _ x = x
The new type signature here means: Given some value of type Set
(i.e., a type),
called A
, return a function from A
to A
. We take this to be equivalent to
the logical statement, "For any proposition A
, A â A
."
In logic, the universal quantifier symbol â
means "for any" or "for all".
So, the above type signature could be stated as, $(\forall A) (A \Rightarrow A)$.
Making propositions about all members of a set (or universe) is called
universal quantification, and it corresponds to parametric polymorphism
(including Java generics and C++ templates) in type system lingo.
Now we can implement our special case proof, proofâ
, in terms of the more
general proofââ˛
, as follows:
proofâ : â â â proofâ = proofââ˛ â
In other words, "to prove â â â, apply proofââ˛
to type â."
In the next section we will start a new Agda program so, before moving on, we list the full contents of our first Agda program, which resides in the file LearnYouAn2.agda.
module LearnYouAn2 where data â : Set where zero : â suc : â â â _+_ : â â â â â zero + n = n (suc n) + m = suc (n + m) data _even : â â Set where ZERO : zero even STEP : â x â x even â suc (suc x) even proofâ : suc (suc (suc (suc zero))) even proofâ = STEP (suc (suc zero)) (STEP zero ZERO) proofâ : â â â proofâ Î˝ = Î˝ proofââ˛ : (A : Set) â A â A proofââ˛ _ x = x
Unlike universal quantification or implication, conjunction and disjunction do not correspond to built-in types in Agda, however they are fairly straightforward to define.
The conjunction of the propositions $P$ and $Q$ is denoted by $P\wedge Q$. Informally, to prove the conjunction $P\wedge Q$, we prove each of its components. If we have a proof each component, then we automatically have a proof of their conjunction. Thus conjunction corresponds to a pair or a tuple (more formally known as a product type) in Agda.
More formally, to give meaning to conjunction, we must say how to introduce the judgment $P \wedge Q $. A verification of $P \wedge Q$ requires a proof of $P$ and a proof of $Q$. Thus, the introduction rule for conjunction is,
$$\frac{P \quad Q }{P \wedge Q }\quad \wedge \mathrm{I}$$
Let's introduce conjunction in Agda. Create a file called IPL.agda
containing the following (and check it with C-c C-l
):
module IPL where data _â§_ (P : Set) (Q : Set) : Set where â§-intro : P â Q â (P â§ Q)
(The symbol â§
is produced by typing \and or \wedge.)
Here we've defined a new data type, this time it is parameterized by two propositions, which make up the components of the conjunction. Conjunction itself is also a proposition, and we give it the type Set.
Notice how the â§-intro
constructor can only produce a proof of P â§ Q
if it
is passed both a proof of P
and a proof of Q
. This is how conjunction is
demonstrated by construction---it is impossible to construct a conjunction that
is not supported by proofs of each component.
We now prove some simple properties about conjunctions, such as P â§ Q â P
.
proofâ : {P Q : Set} â (P â§ Q) â P proofâ (â§-intro p q) = p
This is the elimination rule sometimes called "and-elim-1" or "and-elim-left."
If we define a similar rule for "and-elim-2", we have the following program:
module IPL where data _â§_ (P : Set) (Q : Set) : Set where â§-intro : P â Q â (P â§ Q) proofâ : {P Q : Set} â (P â§ Q) â P proofâ (â§-intro p q) = p proofâ : {P Q : Set} â (P â§ Q) â Q proofâ (â§-intro p q) = q
Now that we have defined conjunction and implication, we can define a notion of
logical equivalence. Two propositions are equivalent if both propositions
can be considered to be the same. This is defined as: if one is true, the other
is also true. In logic, this is called bijection and is written as
A â B
. Bijection can be expressed simply as a conjunction of two implications:
If A is true then B is true, and if B is true then A is true.
_â_ : (P : Set) â (Q : Set) â Set a â b = (a â b) â§ (b â a)
(The symbol â
is produced by typing <=>.)
We can write programs (i.e. proofs) of the algebraic properties of conjunction. The commutative property says that $A \wedge B \Leftrightarrow B \wedge A$. Let's prove it:
â§-commâ˛ : {P Q : Set} â (P â§ Q) â (Q â§ P) â§-commâ˛ (â§-intro p q) = â§-intro q p â§-comm : {P Q : Set} â (P â§ Q) â (Q â§ P) â§-comm = â§-intro (â§-commâ˛ {P} {Q}) (â§-commâ˛ {Q} {P}) -- implicits provided for clarity only.
Remove the implicits and have Agda check the following:
â§-commâ˛ : {P Q : Set} â (P â§ Q) â (Q â§ P) â§-commâ˛ (â§-intro p q) = â§-intro q p â§-comm : {P Q : Set} â (P â§ Q) â (Q â§ P) â§-comm = â§-intro â§-commâ˛ â§-commâ˛
Let's also prove associativity.
â§-assocâ : { P Q R : Set } â ((P â§ Q) â§ R) â (P â§ (Q â§ R)) â§-assocâ (â§-intro (â§-intro p q) r) = â§-intro p (â§-intro q r) â§-assocâ : { P Q R : Set } â (P â§ (Q â§ R)) â ((P â§ Q) â§ R) â§-assocâ (â§-intro p (â§-intro q r)) = â§-intro (â§-intro p q) r â§-assoc : { P Q R : Set } â ((P â§ Q) â§ R) â (P â§ (Q â§ R)) â§-assoc = â§-intro â§-assocâ â§-assocâ
If conjunction is a pair, because it requires both proofs to hold, then
disjunction is a sum type (also known as an Either
type), because it only
requires one proof in order to hold. In order to model this in Agda, we add
two constructors to the type, one for each possible component of the
disjunction.
data _â¨_ (P Q : Set) : Set where â¨-introâ : P â P â¨ Q â¨-introâ : Q â P â¨ Q
Using this, we can come up with some interesting proofs. The simplest one to
prove is disjunction elimination, which is the rule
âA B C â ((A â C) â§ (B â C) â§ (A â¨ B))â C
.
In other symbols,
$$\frac{A \vee B \quad A \Rightarrow C \quad B \Rightarrow C}{C}$$
In words, if $A$ implies $C$ and $B$ implies $C$, and $A$ is true or $B$ is true, then if follows that $C$ is true.
â¨-elim : {A B C : Set} â (A â C) â (B â C) â (A â¨ B) â C â¨-elim ac bc (â¨-introâ a) = ac a â¨-elim ac bc (â¨-introâ b) = bc b
We can also prove the algebraic properties of disjunction, such as commutativity:
â¨-commâ˛ : {P Q : Set} â (P â¨ Q) â (Q â¨ P) â¨-commâ˛ (â¨-introâ p) = â¨-introâ p â¨-commâ˛ (â¨-introâ q) = â¨-introâ q â¨-comm : {P Q : Set} â (P â¨ Q) â (Q â¨ P) â¨-comm = â§-intro â¨-commâ˛ â¨-commâ˛
The associativity proof is left as an exercise for the reader.
You have probably noticed if you're familiar with boolean logic that I've avoided mentioning false throughout this entire section. Unlike boolean logic, Agda's intuitionistic logic does not have a well-defined notion of "false". In classical and boolean logics, all propositions are considered to be either true or false. Intuitionistic logic, by contrast, is purely constructive. You can either construct a proof for a proposition, making it true, or you can fail to construct a proof, making you feel bad.
The only "false" values that exist in intuitionistic logic, therefore, are
values for which there can exist no proof. In Agda, this corresponds to a type
that contains no values. We call this type âĽ
, pronounced "bottom". We define
it like so:
data âĽ : Set where
That's right. No, it's not a mistake. There are no constructors for âĽ
. It is a
type for which it is impossible to produce a value. Having such a value allows
us to define negation (ÂŹA
) as true if A
being true would mean bottom is true
(which is impossible). Or, in more formal terms: ÂŹA â (A â âĽ)
ÂŹ : Set â Set -- for ÂŹ type \mathbb Neg ÂŹ A = A â âĽ
This section has taught you how to encode propositional logic into Agda's type system. The correspondences discussed here between disjunction and sum types, conjunction and product types, functions and implication, and propositions and types are the fundamentals behind the Curry-Howard Correspondence. Using these tools, you can encode any constructive proof in Agda, which covers a vast range of possible proofs, including the vast majority of proofs encountered in program verification.
Future sections will introduce relational equality, and begin proving some theorems about the Peano numbers we introduced in the previous section.
(This section did not appear in the original version of the tutorial. It is essentially independent of the rest of the tutorial, and could be read immediately after Section 1.)
Let's get some practice creating some "much needed" gaps in our proofs and then filling them in. As we learned above, Agda calls such gaps "holes".
Open a file called HoleFilling.agda and put the following code in it:
module HoleFilling where data Bool : Set where false : Bool true : Bool
Above we saw how to implement a general conjunction, but here we will implement a simpler
(post-fix) conjunction for the Bool
type in order to demonstrate the creation and removal of holes.
To implement conjunction for Bool
, we merely have to give the value of the conjunction for each
of the four possible pairs of Bool
values. We begin by entering the following
(put this outside the indentation block of data Bool
):
â§ : Bool â Bool â Bool â§ a b = ?
Note that Bool â Bool â Bool
indicates the types of arguments â§
should expect
(namely, â§
is a function from Bool
type to Bool â Bool
type). Since Agda knows what to expect,
it can write much of the function for us. As usual, use C-c C-l
to type-check the program, and
Agda creates a hole where we had a question mark. Our program should now look like this
module HoleFilling where data Bool : Set where false : Bool true : Bool â§ : Bool â Bool â Bool â§ a b = {!!}
Now, with the cursor in the hole (i.e., at the braces { }), and with the hole highlighted,
type C-c C-,
and Agda should respond with a list of goals that we must accomplish in order to give a
valid definition of â§
for type Bool
.
Goal: Bool ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ b : Bool a : Bool
With the cursor still in the hole, hit C-c C-c
and Agda responds with
"pattern variables to case", which prompts us to introduce a case for the first variable.
Next to the phrase enter the variable b
, to which Agda responds with
â§ : Bool â Bool â Bool â§ a false = {!!} â§ a true = {!!}
Put the cursor in the first of the two new holes and again type C-c C-c
but this time
enter a in response to the "pattern variables to case" prompt.
â§ : Bool â Bool â Bool â§ false false = {!!} â§ true false = {!!} â§ a true = {!!}
With the cursor in the hole, type C-c C-c
and again enter a.
â§ : Bool â Bool â Bool â§ false false = {!!} â§ true false = {!!} â§ false true = {!!} â§ true true = {!!}
Finally, we fill in the right hand sides to comport with conjunction:
â§ : Bool â Bool â Bool â§ false false = false â§ true false = false â§ false true = false â§ true true = true
Instead of filling in the holes in the end manually, you could
place the cursor in each of the holes and hit C-c C-a
.
The result will be four false
's, which wouldn't be a very
useful definition of conjunction... so change the last false
to true
!
Below is a list of the Emacs commands we encountered above. For a more complete list, visit the Agda Wiki.
Command | Key binding |
---|---|
Load | C-c C-l |
Evaluate term | C-c C-n |
Show goals | C-c C-? |
Next goal | C-c C-f |
Split obligation | C-c C-space |
Split again | C-c C-r |
Show Constraints | C-c C-= |
Solve Constraints | C-c C-s |
Agsy find proof term | C-c C-a |
suc
standing for successor.
^{2}: Unlike Haskell, type declarations are mandatory.
^{3}: Don't worry if you're scared by that â
sign, all will be explained in time.
^{4}: Don't be scared by the term - structural recursion is when a recursive function follows the structure of a recursive data type - it occurs very frequently in functional programs.
The GroupSound project is about harmonic analysis on finite groups. Classical dsp filtering algorithms can be implemented as operations involving functions (e.g., audio signals) defined on a finite group. That is, the group serves as the domain, or "index set," of the functions. In this project, we explore the idea of using the finite group as an adjustable parameter of a digital audio filter.
Underlying many digital signal processing (dsp) algorithms, in particular those used for digital audio filters, is the convolution operation, which is a weighted sum of translations $f(x-y)$. Most classical results of dsp are easily and elegantly derived if we define our functions on $\mathbb{Z}/n\mathbb{Z}$, the abelian group of integers modulo n. If we replace this underlying "index set" with a nonabelian group, then translation may be written $f(y^{-1}x)$, and the resulting audio filters arising from convolution naturally produce different effects than those obtained with ordinary (abelian group) convolution.
By listening to samples produced using various nonabelian groups, we try to get a sense of the "acoustical characters" of finite groups.
Please visit the GroupSound project webpage for more details.
]]>The IEPost GitHub repository contains a draft of the article Interval enforceable properties of finite groups, to appear in Communications in Algebra.
The paper has been accepted and is in the final review stage. It should appear in print sometime in 2014. The original submission is available in the file DeMeo-IEProps-rev1.pdf. The latest revision is in the tex directory.
For questions, comments, or suggestions please submit an issue.
Thanks for your interest in this work!
]]>Consider two finite algebras that are the same except for a relabeling of the operations. Our intuition tells us that these are simply different names for the same mathematical structure. Formally, however, not only are these distinct mathematical objects, but also they are nonisomorphic.
Isotopy is another well known notion of "sameness" that is broader than isomorphism. However, I recently proved a result that shows why isotopy also fails to fully capture algebraic equivalence.
This post provides a little bit of background and motivation, with a couple of examples of the problems that arise when trying to decide whether two algebraic objects are "equivalent."
Let us consider an example of two algebras that are the same except for the naming of their operations. (This is an elaboration on Exercises 5.1 in Algebras, Lattices, Varieties.)
Let $ \mathbf{A}$ be an algebra of size 4 and label the elements of the universe $\{1,2,3,4\}$. Suppose $ \mathbf{A}$ has two unary operations, $f^ \mathbf{A}$ and $g^ \mathbf{A}$, where $f^ \mathbf{A}$ is the permutation $(1,2)(3,4)$ and $g^ \mathbf{A}$ is the permutation $(1,3)(2,4)$.
Thus, the operations permute the elements as follows:
The congruence lattice of $ \mathbf{A}$ is $M_n$ whose universe is the set $\{ x_A , \theta_1, \theta_2, \theta_3, 1_A \}$. By abuse of notation, we identify the congruences with their corresponding partitions as follows:
$$ 1_A = \mid 1,2,3,4 \mid $$
$$ \theta_1 = \mid 1, 2 \mid 3, 4 \mid \quad \theta_2 = \mid 1, 3 \mid 2, 4 \mid \quad \theta_3 = \mid 1, 4 \mid 2, 3 \mid $$
$$0_A = \mid 1 \mid 2 \mid 3 \mid 4 \mid $$
Now consider the algebras $\mathbf{A}/\theta_1$, $\mathbf{A}/\theta_2$, and $\mathbf{A}/\theta_3$ which have universes $\{ \{1, 2\}, \{3, 4\}\}$, $\{ \{1, 3\}, \{2, 4\}\}$, and $\{ \{1, 4\}, \{2, 3\}\}$$, respectively.
It is clear that $ \mathbf{A} \cong \mathbf{A}/\theta_1 \times \mathbf{A}/\theta_2 \cong \mathbf{A}/\theta_1 \times\mathbf{A}/\theta_3 \cong \mathbf{A}/\theta_2 \times\mathbf{A}/\theta_3.$
Also, no two factors are isomorphic. This is because of the naming of their respective operations.
The algebra $\mathbf{A}/\theta_1$ has operations $f^{ \mathbf{A}/\theta_1}$ and $g^{ \mathbf{A}/\theta_1}$, where $g^{ \mathbf{A}/\theta_1}$ permutes the blocks $\{1, 2\}\longleftrightarrow \{3, 4\}$ and $f^{ \mathbf{A}/\theta_1}$ leaves them fixed.
On the other hand, the algebra $ \mathbf{A}/\theta_2$ has operations $f^{ \mathbf{A}/\theta_2}$ and $g^{ \mathbf{A}/\theta_2}$, where $f^{ \mathbf{A}/\theta_2}$ permutes the blocks $\{1, 3\} \longleftrightarrow \{2, 4\}$ and $g^{ \mathbf{A}/\theta_2}$ leaves them fixed.
Thus, the operation in $ \mathbf{A}/\theta_1$ corresponding to the symbol $f$ is the identity, while in $ \mathbf{A}/\theta_2$ the $f$ symbol corresponds to the nonidentity permutation. But apart from this trivial relabeling, these two algebraic structures are the same.
This may be viewed as a minor technical issue that could be overcome by simply viewing the two algebras as nonindexed algebras (see, e.g., PolĂĄk), and we can certainly say that up to a relabeling of the operation symbols, the two algebras are isomorphic.
However, it would be preferable to have a notion of algebraic equivalence that did not hinge on operation alignment.
The example above was a simple, almost trivial illustration of how the notion of isomorphic (indexed) algebras fails to capture our intuitive understanding of what it means for two algebras to be structurally the same. But there are examples that go deeper and are not as easy to side step.
Consider the Oates-Powell Theorem 1 which states that the variety generated by a finite group has a finite equational basis.
In a 1982 paper Roger Bryant proved that if we merely single out an element of the group and giving it a label, then the resulting algebra (called a pointed group) generates a variety that need not be finitely based.
Any reasonable notion of algebraic equivalence should at least respect congruences lattice structure. The congruence relations of algebras are the kernels of homomorphisms, and the congruence lattice is a basic "algebraic" feature of an algebra. Certainly we cannot accept a notion of "sameness" that equates two algebras with different congruence lattices (puting aside for now what it means for two congruence lattices to be "different").
Isotopy provides a notion of "sameness" of algebras that is less demanding than isomorphism, so there is some hope that it provides a reasonably intuitive notion of algebraic equivalence.
But by the foregoing remark, if isotopy is to serve as a notion of algebraic equivalence, we should at least demand that two isotopic algebras have the "same" congruence lattices, and for two lattices to be the "same" in any sense, they must have the same number of elements so that we can at least find a bijection between them (even if it is not a "structure preserving" bijection).
However, I recently proved that, for any number $N \in \mathbb{N}$, it's easy to construct a pair of isotopic algebras with congruence lattices that differ in size by more than $N$ elements. We see from this that there is no hope for isotopy as a reasonable notion of algebraic equivalence. (A link to my short article describing the construction appears below.)
Later, we will explore Voevodsky's univalence principle and try to write down the definitions of universal algebras as higher types so that we can exploit the ideas of homotopy type theory in order to possibly arrive at a more useful, intuitionistic definition of algebraic equivalence.
The Isotopy GitHub repository contains my article in which I present isotopic algebras with congruence lattices that are vastly different in size.
The published version of the paper is in the file DeMeo-Expansions-AU-2013.pdf, and is also available at springer.com.
In the file gap2uacalc.g is a GAP program that can be used on its own to convert GAP groups and G-sets into UACalc .ua files, which can then be imported into the Universal Algebra Calculator. See universalalgebra.org for more information about gap2uacalc.g.
For questions, comments, or suggestions please submit an issue.
Thanks for your interest in this work!
]]>I started a GitHub repository called TypeFunc in an effort to get my head around the massive amount of online resources for learning about type theory, functional programming, category theory, $\lambda$-calculus, and connections between topology and computing.
At this point, the TypeFunc repository is mostly a collection of links, but it also includes notes and software resources that I have found helpful.
]]>This post describes the proof as it was presented by Bauer. These notes are rough and intended for my own reference. Please see FranĂ§ois Dorais' blog post for a nice discussion of this topic.
The Axiom of Choice (AC) states that if $\mathcal{S}$ is a collection of nonempty sets, then there is a choice function $f$ that can be used to select an element from each set in $\mathcal{S}$.
Law of the Excluded Middle (LEM) states that $P$ is a proposition, then $P \bigvee \neg P$.
Diaconescu's Theorem: AC $\rightarrow$ LEM.
Proof: Assume AC. Let $P$ be any proposition. We will prove $P \bigvee \neg P$.
Define the set $\mathbf{2} = \{0, 1\} = \{x \mid x = 0 \bigvee x= 1\}$.
Define the following sets:
$A = \{x \mid (x = 0) \bigvee P\}$
$B = \{y \mid (y = 1) \bigvee P\}$
Note that $P \Rightarrow A = B = \mathbf{2}$. Therefore, $A \neq B \Rightarrow \neg P$.
Both of the sets $A$ and $B$ are nonempty, since 0 belongs to $A$ and 1 belongs to $B$.
Therefore, $\{A, B\}$ is a set of nonempty sets, so by AC we have a choice function,
$$f : \{A, B\} \rightarrow A \cup B, \text{ and note that } A\cup B = \{0, 1\}.$$
Now, because equality on $\mathbb{N}$ is decidabile (which can be proved by induction on $\mathbb{N}$), we can consider cases:
If $f(A) = 0 = f(B)$, then $0 \in B$, so $P$.
If $f(A) = 1 = f(B)$, then $1 \in A$, so $P$.
If $f(A) \neq f(B)$, then $A \neq B$ so $P$ cannot hold. (Recall, $P \Rightarrow A = B = \mathbf{2}$.)
We have covered all cases and found that $P \bigvee \neg P$ holds. â
Proofs of Diaconescu's Theorem in Coq
I am a postdoc in the Mathematics Department at University of Colorado, Boulder. My official title is Burnett Meyer Instructor. Peter Mayr is my postdoc supervisor.
I have worked in mathematics departments at the University of Hawaii with Ralph Freese (2016--2017), Iowa State University with Cliff Bergman (2014-2016), and the University of South Carolina with George McNulty (2012--2014).
My main specialty is universal algebra; current projects focus on lattice theory, computational complexity, and universal algebraic approaches to constraint satisfaction problems. Other research interests include logic, category theory, type theory, functional programming, computer-aided mathematical proof and formalization of mathematics.
cv Â Â Â Â Â github Â Â Â Â google scholar Â Â Â microsoft academic
Tom Ramsey and Wayne Smith helped me learn how to solve problems in real and complex analysis. Matt Chasse, Mike Joyce, and Yoshi found typos in earlier drafts of solutions to these problems. If any typos or mistakes remain, I blame the reader (who has failed to report them). Joking aside, in exchange for offering you these study materials for free, please do me the favor of emailing any mistakes or typos to williamdemeo at gmail.
Please send comments, suggestions, and corrections to williamdemeo@gmail.com.
]]>Bill Lampe, Ralph Freese, and JB Nation taught me how to solve problems in groups and rings (among other things). Nonetheless, I take responsibility for most of the mistakes and typos in the problems and solutions posted here. For the others, I blame readers who fail to report them to me.
Joking aside, in exchange for offering you these study materials for free, please do me the favor of emailing any mistakes or typos to williamdemeo at gmail.
Please send comments, suggestions, and corrections to williamdemeo@gmail.com.
Tom Ramsey and Wayne Smith helped me learn how to solve problems in real and complex analysis. Matt Chasse, Quinn Culver, Mike Joyce, and Geoff Patterson found multiple typos and one error in earlier drafts of solutions to these problems. If any typos or mistakes remain, I blame the reader (who has failed to report them). Joking aside, in exchange for offering you these study materials for free, please do me the favor of emailing any **mistakes or typos to [williamdemeo at gmail]the favor of emailing any mistakes or typos to williamdemeo at gmail.
Please send comments, suggestions, and corrections to williamdemeo@gmail.com.
Bill Lampe, Ralph Freese, and JB Nation taught me how to solve problems in groups and rings (among other things). Nonetheless, I take responsibility for most of the mistakes and typos in the problems and solutions posted here. For the others, I blame readers who fail to report them to me.
Joking aside, in exchange for offering you these study materials for free, please do me the favor of emailing any mistakes or typos to williamdemeo at gmail.
Please send comments, suggestions, and corrections to williamdemeo@gmail.com.
(a) State the Schwarz lemma.
(b) Suppose $f$ is holomorphic in $D = \{z: |z|< 1\}$ with $f(D) \subseteq D$. Let $f_n$ denote the composition of $f$ with itself $n$ times $(n= 2, 3, \dots)$. Show that if $f(0) = 0$ and $|f'(0)| < 1$, then ${f_n}$ converges to 0 locally uniformly
on $D$.
Exhibit a conformal mapping of the region common to the two disks $|z|<1$ and $|z-1|<1$ onto the region inside the unit circle $|z| = 1$.
Let $\{f_n\}$ be a sequence of functions analytic in the complex plane $\mathbb C$, converging uniformly on compact subsets of $\mathbb C$ to a polynomial $p$ of positive degree $m$. Prove that, if $n$ is sufficiently large, then $f_n$ has at least $m$ zeros, counting multiplicities. (Do not simply refer to Hurwitz's theorem; prove this version of it.)
Let $(X, d)$ be a metric space.
(a) Define what it means for a subset $K\subseteq X$ to be compact.
(b) Using your definition in (a), prove that $K\subseteq X$ is compact implies that $K$ is both closed and bounded in $X$.
(c) Give an example that shows the converse of the statement in (b) is false.
Notation. $\mathbb R$ is the set of real numbers and $\mathbb R^n$ is $n$-dimensional Euclidean space. Denote by $m$ Lebesgue measure on $\mathbb R$ and $m_n$ $n$-dimensional Lebesgue measure. Be sure to give a complete statement of any theorems from analysis that you use in your proofs below.
Let $K$ be a compact subset in $\mathbb R^3$ and let $f(x) = \mbox{dist}(x,K)$.
(a) Prove that $f$ is a continuous function and that $f(x) = 0$ if and only if $x\in K$.
(b) Let $g = \max \{1-f, 0\}$ and prove that $\lim_{n\to \infty} \iiint g^n$ exists and is equal to $m_3(K)$.
Let $E$ be a Borel subset of $\mathbb R^2$.
(a) Explain what this means.
(b) Suppose that for every real number $t$ the set $E_t = \{(x,y) \in E \mid x=t\}$ is finite. Prove that $E$ is a Lebesgue null set.
Let $\mu$ and $\nu$ be finite positive measures on the measurable space $(X,\mathcal A)$ such that $\nu \ll \mu \ll \nu$, and let $d\nu/d(\mu + \nu)$ denote the Radon-Nikodym derivative of $\nu$ with respect to $\mu+\nu$. Show that $$0 < \frac{d\nu}{d(\mu + \nu)} < 1 \quad \text{a.e.} [\mu].$$
Suppose for all $n\in \mathbb N$ that the function $f_n$ is holomorphic in $D$ and satisfies $|f_n(z)|<1$ for all $z \in D$. Also suppose that $\lim_{n\to \infty} \mathrm{Im}\ f_n(x) = 0$ for all $x\in (-1,0)$.
(a) Prove: $\lim_{n\to \infty} \mathrm{Im}\ f_n(1/2) = 0$.
(b) Give a complete statement of the convergence theorem that you use in part (a).
Use the residue theorem to evaluate $\int_{-\infty}^{\infty} \frac{1}{1+x^4}\ dx$.
Present a function $f$ that has all of the following properties:
(i) $f$ is one-to-one and holomorphic on $D$.
(ii) $\{f(z): z\in D\} = \{w \in \mathbb C: \mathrm{Re}\ w > 0, \ \mathrm{Im}\ w > 0\}$.
(iii) $f(0) = 1+i$.
(a) Prove: If $f: D \rightarrow D$ is holomorphic and $f(1/2) = 0$, then $|f(0)| \leq 1/2$.
(b) Give a complete statement of the maximum modulus theorem that you use in part (a).
Prove: If $G$ is a connected open subset of $\mathbb C$, any two points of $G$ can be connected by a parametric curve in $G$.
(a) State the Schwarz lemma.
(b) Suppose that $f\in H(\Pi^+)$ and that $|f(z)|<1$ for all $z\in \Pi^+$. If $f(i)=0$ how large can $|f'(i)|$ be? Find the extremal functions.
(cf. '95 Apr #6)
(a) State Cauchy's theorem and its converse.
(b) Suppose that $f$ is a continuous function defined on the entire complex plane. Assume that
(i) $f\in H(\Pi^+ \cup \Pi^-)$
(ii) $f(\bar{z}) = \overline{f(z)}$ all $z\in \mathbb C$.
Prove that $f$ is an entire function.
Prove or disprove:
(a) For $1 \leq p < \infty$, let $\ell^p = \left\{ \mathbf{x} = \{x_k\} \bigm\vert \| \mathbf x \|_p = \left(\sum_{k=1}^\infty|x_k|^p\right)^{1/p} < \infty \right\}$. Then for $p \neq 2$, $\ell^p$ is a Hilbert space.
(b) Let $X = (C[0,1], \| \cdot \|_1)$, where the linear space $C[0,1]$ is endowed with the $L^1$-norm: $\|f\|_1 = \int_0^1 |f(x)|\ dx$. Then $X$ is a Banach space.
(c) Every real, separable Hilbert space is isometrically isomorphic to $\ell^2$.
Let $f, g \in L^1(\mathbb R)$. Give a precise statement of some version of Fubini's theorem that is valid for non-negative functions, and then prove the following:
(a) $h(x) = \int_\mathbb R f(x-t)g(t)\ dt$ exists for almost all $x\in \mathbb R$;
(b) $h\in L^1(\mathbb R)$ and $\|h\|_1 \leq \|f\|_1 \|g\|_1$.
(a) State the Radon-Nikodym theorem.
(b) Let $(X, \mathcal B, \mu)$ be a complete measure space, where $\mu$ is a positive measure defined on $\mathcal B$, a $\sigma$-algebra of subsets of $X$. Suppose $\mu(X) < \infty$ and $S$ is a closed subset of $\mathbb R$. Let $f\in L^1(\mu)$, where $f$ is an extended real-valued function defined on $X$. Prove: If for every $E\in \mathcal B$ with $\mu(E) > 0$ we have $$A_E(f) = \frac{1}{\mu(E)}\int_E f\ d\mu \in S,$$ then $f(x)\in S$ for almost all $x\in X$.
Give two quite different proofs of the Fundamental Theorem of Algebra that if a polynomial with complex coefficients has no complex zero, then it is constant. You may use independent, well-known theorems and principles such as Liouville's Theorem, the Argument Principle, the Maximum Principle, Rouche's Theorem, and/or the Open Mapping Theorem.
(a) State and prove the Casorati-Weierstrass Theorem concerning the image of any punctured disk about a certain type of isolated singularity of an analytic function. You may use the fact that if a function $g$ is analytic and bounded in the neighborhood of a point $z_0$, then $g$ has a removable singularity at $z_0$.
(b) Verify the Casorati-Weierstrass Theorem directly for a specific analytic function of your choice, with a suitable singularity.
(a) Define $\gamma : [0,2\pi] \rightarrow \mathbb C$ by $\gamma(t) = \sin (2t) + 2i \sin (t)$. This is a parametrization of a "figure 8" curve, traced out in a regular fashion. Find a meromorphic function $f$ such that $\int_\gamma f(z) \ dz = 1$. Be careful with minus signs and factors of $2\pi i$.
(b) From the theory of Laurent expansions, it is known that there are constants $a_n$ such that, for $1<|z|<4$, $$\frac{1}{z^2 - 5z + 4} = \sum_{n=-\infty}^\infty a_n z^n.$$ Find $a_{-10}$ and $a_{10}$ by the method of your choice.
Do as many problems as you can. Complete solutions to five problems would be considered a good performance.
a. State the inverse function theorem.
b. Suppose $L\colon \mathbb R^3 \rightarrow \mathbb R^3$ is an invertible linear map and that
$g\colon \mathbb R^3 \rightarrow \mathbb R^3$ has continuous first order partial derivatives and satisfies
$|g(x)| \leq C|x|^2$ for some constant $C$ and all $x\in \mathbb R^3$. Here $|x|$ denotes the usual Euclidean norm on $\mathbb R^3$. Prove that $f(x) = L(x) + g(x)$ is locally invertible near $0$.
Let $f$ be a differentiable real valued function on the interval $(0,1)$, and suppose the derivative of $f$ is bounded on this interval. Prove the existence of the limit $L = \lim_{x\rightarrow 0^+} f(x)$.
Let $f$ and $g$ be Lebesgue integrable functions on $[0,1]$, and let $F$ and $G$ be the integrals $$F(x) = \int_0^x f(t) \ dt, \quad G(x) = \int_0^x g(t) \ dt.$$ Use Fubini's and/or Tonelli's Theorem to prove that $$\int_0^1 F(x)g(x) \ dx = F(1) G(1) - \int_0^1 f(x)G(x) \ dx.$$ Other approaches to this problem are possible, but credit will be given only to solutions based on these theorems.
Let $(X, A, \mu)$ be a finite measure space and suppose $\nu$ is a finite measure on $(X, A)$ that is absolutely continuous with respect to $\mu$. Prove that the norm of the Radon-Nikodym derivative $f = \left[\frac{d\nu}{d\mu}\right]$ is the same in $L^\infty (\mu)$ as it is in $L^\infty(\nu)$.
Suppose that $\{f_n\}$ is a sequence of Lebesgue measurable functions on $[0,1]$ such that $\lim_{n\rightarrow \infty} \int_0^1 |f_n|\ dx = 0$ and there is an integrable function $g$ on $[0,1]$ such that $|f_n|^2 \leq g$, for each $n$. Prove that $\lim_n \int_0^1 |f_n|^2\ dx =0$.
Denote by $\mathcal P_e$ the family of all even polynomials. Thus a polynomial $p$ belongs to $\mathcal P_e$ if and only if $p(x) = \frac{p(x) + p(-x)}{2}$ for all $x$. Determine, with proof, the closure of $\mathcal P_e$ in $L^1[-1,1]$. You may use without proof the fact that continuous functions on $[-1,1]$ are dense in $L^1[-1,1]$.
Suppose that $f$ is real valued and integrable with respect to Lebesgue measure $m$ on $\mathbb R$ and that there are real numbers $a<b$ such that $$a \cdot m(U) \leq \int_U f \ dm \leq b \cdot m(U),$$ for all open sets $U$ in $\mathbb R$. Prove that $a \leq f(x) \leq b$ a.e.
a. State the Sylow theorems (existence, conjugacy and number). b. Deduce that every group $G$ of order 140 contains a normal subgroup of index 4. Is $G'$ necessarily abelian?
Let $G$ be a group and $H$ a subgroup of $G$, $K$ a normal subgroup of $G$. Prove that $HK$ is a subgroup of $G$, that $H\cap K$ is a normal subgroup of $H$, and $HK/K \cong H/H\cap K$.
a. State the "class equation" for a finite group $G$ and prove its validity. b. Prove that if a prime $p$ divides the order of $G$, then $G$ contains an element of order $p$. c. Let $|C(x)|$ denote the order of the conjugacy class of the centralizer of $x \in G$, and $k(G)$ denote the number of conjugacy classes of $G$. Prove that $\sum_{x \in G}|C(x)| = k(G) \cdot |G|$.
a. Define "solvable" group. b. Prove (only) one direction of the following statement: If $N$ is a normal subgroup of $G$, then $G$ is solvable if and only if $N$ and $G/N$ are solvable. c. Prove that every group of order 200 is solvable.
List (do not just count) a complete set of representatives of the isomorphism classes of abelian groups of order $p^5$, where $p$ is any prime.
Recall, for prime $p$, a group every element of which has order some power ($\geq 0$) of $p$ is called a $p$-group. If $H$ is a $p$-group and $H < G$, then $H$ is called a $p$-subgroup. A maximal $p$-subgroup of $G$---that is, a $p$-subgroup of $G$ contained in no larger $p$-subgroup---is called a Sylow $p$-subgroup of $G$
First Sylow theorem. Let $G$ be a group of order $p^n m$, with $p$ prime, $n\geq 1$ and $(p,m) = 1$. Then for each $1\leq k < n$, $G$ contains a subgroup of order $p^k$ and every subgroup of $G$ of order $p^k$ ($k < n$) is normal in some subgroup of order $p^{k+1}$.
Second sylow theorem. If $H$ is a $p$-subgroup of a finite group $G$, and if $P$ is any Sylow $p$-subgroup of $G$, then there exists $x\in G$ such that $H\leq xPx^{-1}. In particular, any two Sylow $p$-subgroups of $G$ are conjugate.
Third Sylow theorem. If $G$ is a finite group and $p$ a prime, then the number of Sylow $p$-subgroups of $G$ divides $|G|$ and is of the form $kp +1$ for some $k \geq 0$.
The number of Sylow $7$-subgroups must be congruent to $1$ modulo $7$ and divide $140$, hence must be $1$. Call this subgroup $H$. $H$ is then normal in $G$, by the Second Sylow Theorem. Similary, the number of Sylow $5$-subgroups must be congruent to $1$ modulo $5$ and divide $140$, hence must be $1$. Call this (normal) subgroup $K$. The nonidentity elements of $H$ all have order $7$ and the nonidentity elements of $K$ all have order $5$. As $H$ and $K$ are normal in $G$, $HK$ is a subgroup and as $H\cap K = (e)$, $|HK| = 35$. Thus, by Lagrange's Theorem, $HK$ is a subgroup of index $|G:HK| = |G|/|HK| = 4$. If $g \in G$, then $gHKg^{-1}= gHg^{-1}gKg^{-1}\subseteq HK$, since $H$ and $K$ are both normal in $G$. Thus, $HK$ is also normal in $G$.
Every commutator $[a, b]$, where either $a$ or $b$ is in $HK$, is equal to the identity. As $|HK| = 35$, the commutator subgroup $G'$ is of order $1$, $2$, or $4$. Therefore the commutator subgroup is abelian as all groups of such orders are abelian.
Let $hk \in HK$. We show $(hk)^{-1} = k^{-1}h^{-1} \in HK$. Since $K$ is normal, $hk^{-1}h^{-1} =k'\in K$. Therefore, $k^{-1}h^{-1}= h^{-1}k' \in HK$.
Let $hk \in HK$, and $h_1k_1 \in HK$. We show $hkh_1k_1\in HK$. Since $K$ is normal in $G$, there exist $k_2$ and $k_3$ such that $hkh_1k_1= hkh_1k_1h_1^{-1}h_1= h kk_2h_1 = h h_1h_1^{-1}kk_2h_1 = hh_1k_3$, which is clearly in $HK$. Therefore, $HK$ is a subgroup of $G$.
$K$ is normal in $G$ so $N_G(K) = G$. $H \subseteq N_G(H)$ by definition. So $H\cap K \in N_G(H),$ i.e., $H\cap K$ is normal in $H$. Since $K$ is normal in $G$, the quotient group $HK/K$ is well-defined. Define a map $\phi \colon H \to HK/K$ by $\phi(h) = hK$. Then $\phi$ is a homomorphism since for all $h_1, h_2\in H$, we have $\phi(h_1h_2) = h_1K h_2K = h_1 h_2 K$ because as $K\triangleleft G$. The kernel of $\phi$ consists of all elements $h\in H$ such that $hK =K$; that is, $\ker h = H\cap K$. For any element $hk \in HK$, $\phi(hk) = hK$ so $\phi$ is surjective. Therefore, by the first isomorphism theorem for groups, and $H/H\cap K \cong HK/K$.
Theorem. (Class Equation) Suppose $G$ is a group. Then $$|G| = |Z| + \sum_{g\in T} |G : C(g)|,$$ where $Z$ denotes the center of $G$, $C(g):= \{x \in G: xg = gx\}$ is the centralizer of $g$, and $T$ contains one element from each non trivial conjugacy class of $G$.
Corollary. Let $p$ be a prime number. Any finite group of prime power order has a nontrivial center (i.e., $Z\neq (e)$).
Sorry, not available yet.
Sorry, not available yet.
Sorry, not available yet.
Sorry, not available yet.
Sorry, not available yet.
Sorry, not available yet.
Let $f(z) = \sum_{n=0}^\infty a_n z^n$ be analytic and one-to-one on $|z|<1$.
Suppose that $|f(z)|<1$ for all $|z|<1$.
(a) Prove that $\sum_{n=1}^\infty n |a_n|^2\leq 1$.
(b) Is the constant 1 the best possible?
Let $u(z)$ be a nonconstant, real valued, harmonic function on $\mathbb C$. Prove there exists a sequence ${z_n}$ with $|z_n|\rightarrow \infty$ for which $u(z_n)\rightarrow 0$.
Find an explicit conformal mapping of the semidisk $H = \{z : |z| < 1, \mathrm{Real}\ z > 0\}$ onto the unit disk.
(cf. '89 Apr #3, '06 Nov #2).
Suppose $f(z)$ is a holomorphic function on the unit disk which satisfies $|f(z)|<1$ for all $|z|<1$.
(a) State the Schwarz lemma, as applied to $f$.
(b) If $f(0)=\frac{1}{2}$, how large can $|f'(0)|$ be?
(cf. '06 Nov #3)
Where does the function $f(z) = z\ \mathrm{Real} z + \bar{z}\ \mathrm{Imag} z + \bar{z}$ have a complex derivative? Compute the derivative wherever it exists.
(a) Prove that any nonconstant polynomial with complex coefficients has at least one root.
(b) From (a) it follows that every nonconstant polynomial $P$ has the factorization $$P(z) = a \prod_{n=1}^N (z - \lambda_n),$$ where $a$ and each root $\lambda_n$ are complex constants. Prove that if $P$ has only real coefficients, then $P$ has a factorization $$P(z) = a \prod_{k=1}^K (z - r_k) \prod_{m=1}^M (z^2 - b_m z + c_m),$$ where a and each $r_k, b_m, c_m$ are real constants.
Use complex residue methods to compute the integral $$\int_0^\pi \frac{1}{5 + 3 \cos \theta} \ d\theta.$$
Suppose that $f$ is analytic in the annulus $1< |z|<2$, and that there exists a sequence of polynomials converging to $f$ uniformly on every compact subset of this annulus. Show that $f$ has an analytic extension to all of the disk $|z|<2$. (See also 1996 April (8).)
Let $f$ be analytic in $|z|<2$, with the only zeros of $f$ being the distinct points $a_1, a_2, \ldots, a_n$, of multiplicities $m_1, m_2, \dots, m_n$, respectively, and with each $a_j$ lying in the disk $|z|<1$. Given that $g$ is analytic in $|z|<2$, what is $$\int_{|z|=1} \frac{f'(z) g(z)}{f(z)}\ dz \quad \text{?}$$ (Verify your answer.)
Let $\{f_n\}$ be a sequence of analytic functions in the unit disk $D$, and suppose there exists a positive constant $M$ such that $$\int_C |f_n(z)|\ |dz| \leq M$$ for each $f_n$ and for every circle $C$ lying in $D$. Prove that $\{f_n\}$ has a subsequence converging uniformly on compact subsets of $D$.
Let $X$ be a Hausdorff topological space, let $K$ be a compact subset of $X$, and let $x$ be a point of $X$ not in $K$. Show that there exist open sets $U$ and $V$ such that $$K \subset U, ; x\in V, ; U\cap V = \emptyset.$$
A topological space $X$ satisfies the second axiom of countability. Prove that every open cover of $X$ has a countable subcover.
Let $X$ be a topological space, and let $U$ be a subset of $X$.
(a) Show that if an open set intersects the closure of $Y$ then it intersects $Y$.
(b) Show that if $Y$ is connected and if $Y\subset Z \subset \bar{Y}$, then $Z$ is connected.