Solution (source code)

= Solution

Write the <ultrafilter> as $\mathcal U$ to distinguish it from <first-order structure> domains. On the direct product $\prod_{i\in I}A_i$, put
$$
\mathbf a\sim\mathbf b\iff\{i\in I:a_i=b_i\}\in\mathcal U.
$$
The domain of the <ultraproduct> is the quotient $(\prod_iA_i)/{\sim}$. Its elements are classes $[\mathbf a]$. A constant $c$ is interpreted by $[(c^{\mathcal A_i})_i]$, and a $k$-ary <function> symbol by
$$
f^{\mathcal A}([\mathbf a^1],\ldots,[\mathbf a^k])=\left[(f^{\mathcal A_i}(a_i^1,\ldots,a_i^k))_{i\in I}\right].
$$
For a $k$-ary relation symbol,
$$
\boxed{\mathcal A\models R([\mathbf a^1],\ldots,[\mathbf a^k])\iff\{i:\mathcal A_i\models R(a_i^1,\ldots,a_i^k)\}\in\mathcal U.}
$$
These definitions are independent of representatives: if finitely many input representatives are changed, the coordinates where all old and new inputs agree form a set in the filter. At those coordinates the interpreted outputs and relation truth values agree. Filter closure and upward closure therefore preserve the resulting quotient elements and truth conditions.