The Hour Angle

The hour angle is measured between the face of the dial and the plane containing the sun ray and the gnomon.
from analemma.algebra import frame, render, util, result
The calculation proceeds by defining the shadow bivector \(S = s \wedge g\), the plane containing the sun ray and the gnomon. Its magnitude is the (negative) cosine of the angle between \(s\) and \(g\).
In anticipation of setting \(\delta =0\), I will apply \(\delta\) as a subscript to these quantities for now.
s = frame.sunray()
gn = frame.gnomon("n", zero_decl=False)
g = util.project_vector(gn, target_frame=frame.base("n"), render_frame=frame.surface())
S_delta = result.shadow_bivector(s, g)
render.align(r"S_\delta", S_delta)
\[\displaystyle \begin{align} S_\delta & = \sin{\left (\alpha \right )} \sin{\left (\iota \right )} \sin{\left (\sigma \right )} \sin{\left (\theta \right )} \cos{\left (\delta \right )} + \sin{\left (\alpha \right )} \sin{\left (\sigma \right )} \cos{\left (\iota \right )} \cos{\left (\theta \right )} - \sin{\left (\delta \right )} \sin{\left (\iota \right )} \sin{\left (\psi \right )} \sin{\left (\sigma \right )} \cos{\left (\alpha \right )} - \sin{\left (\delta \right )} \sin{\left (\iota \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} - \sin{\left (\iota \right )} \sin{\left (\psi \right )} \cos{\left (\delta \right )} \cos{\left (\sigma \right )} \cos{\left (\theta \right )} + \sin{\left (\iota \right )} \sin{\left (\sigma \right )} \cos{\left (\alpha \right )} \cos{\left (\delta \right )} \cos{\left (\psi \right )} \cos{\left (\theta \right )} + \sin{\left (\psi \right )} \sin{\left (\theta \right )} \cos{\left (\iota \right )} \cos{\left (\sigma \right )} - \sin{\left (\sigma \right )} \sin{\left (\theta \right )} \cos{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\psi \right )} e_1 \wedge e_2 \nonumber \\ &\sin{\left (\alpha \right )} \sin{\left (\delta \right )} \sin{\left (\iota \right )} \sin{\left (\psi \right )} \cos{\left (\sigma \right )} - \sin{\left (\alpha \right )} \sin{\left (\iota \right )} \cos{\left (\delta \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} \cos{\left (\theta \right )} + \sin{\left (\alpha \right )} \sin{\left (\theta \right )} \cos{\left (\iota \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} + \sin{\left (\iota \right )} \sin{\left (\theta \right )} \cos{\left (\alpha \right )} \cos{\left (\delta \right )} \cos{\left (\sigma \right )} + \cos{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\sigma \right )} \cos{\left (\theta \right )} e_1 \wedge e_3 \nonumber \\ &\sin{\left (\alpha \right )} \sin{\left (\delta \right )} \sin{\left (\iota \right )} \sin{\left (\psi \right )} \sin{\left (\sigma \right )} - \sin{\left (\alpha \right )} \sin{\left (\iota \right )} \sin{\left (\sigma \right )} \cos{\left (\delta \right )} \cos{\left (\psi \right )} \cos{\left (\theta \right )} + \sin{\left (\alpha \right )} \sin{\left (\sigma \right )} \sin{\left (\theta \right )} \cos{\left (\iota \right )} \cos{\left (\psi \right )} + \sin{\left (\iota \right )} \sin{\left (\sigma \right )} \sin{\left (\theta \right )} \cos{\left (\alpha \right )} \cos{\left (\delta \right )} + \sin{\left (\sigma \right )} \cos{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\theta \right )} e_2 \wedge e_3& \nonumber \end{align}\]
The magnitude of \(S_\delta\) is \(\sin(\Xi_\delta)\) where \(\Xi_\delta\) is the angle between sun ray and gnomon:
cosXi_delta = result.shadow_bivector_magnitude_angle_cos(s, g)
render.expression(r"\cos(\Xi_\delta)", cosXi_delta)
\[\displaystyle
\begin{equation}
\cos(\Xi_\delta) = \left(- \sin{\left (\delta \right )} \sin{\left (\iota \right )} \cos{\left (\psi \right )} - \sin{\left (\iota \right )} \sin{\left (\psi \right )} \cos{\left (\delta \right )} \cos{\left (\theta \right )} + \sin{\left (\psi \right )} \sin{\left (\theta \right )} \cos{\left (\iota \right )}\right) \sin{\left (\sigma \right )} + \left(- \sin{\left (\alpha \right )} \sin{\left (\iota \right )} \sin{\left (\theta \right )} \cos{\left (\delta \right )} - \sin{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\theta \right )} + \sin{\left (\delta \right )} \sin{\left (\iota \right )} \sin{\left (\psi \right )} \cos{\left (\alpha \right )} - \sin{\left (\iota \right )} \cos{\left (\alpha \right )} \cos{\left (\delta \right )} \cos{\left (\psi \right )} \cos{\left (\theta \right )} + \sin{\left (\theta \right )} \cos{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\psi \right )}\right) \cos{\left (\sigma \right )} \nonumber
\end{equation}
\]
The hour angle \(\mu_\delta\) between \(S\) and \(M\) is given by
\[\cos(\mu_\delta) = \frac{-S_\delta\cdot M}{\sqrt{-S_\delta^2}\sqrt{-M^2}} = \frac{-S_\delta\cdot M}{\sin(\Xi_\delta)}\]
given that \(M^2=-1\).
M = frame.meridian_plane()
_, sinXi_cos_mu_delta = result.hour_angle_sincos(S_delta, M, zero_decl=False)
render.expression( r"\sin(\Xi_\delta) \cos(\mu_\delta)", sinXi_cos_mu_delta )
\[\displaystyle
\begin{equation}
\sin(\Xi_\delta) \cos(\mu_\delta) = - \sin{\left (\alpha \right )} \sin{\left (\iota \right )} \cos{\left (\delta \right )} \cos{\left (\sigma \right )} \cos{\left (\theta \right )} + \sin{\left (\alpha \right )} \sin{\left (\theta \right )} \cos{\left (\iota \right )} \cos{\left (\sigma \right )} + \sin{\left (\iota \right )} \sin{\left (\psi \right )} \sin{\left (\sigma \right )} \sin{\left (\theta \right )} \cos{\left (\delta \right )} + \sin{\left (\iota \right )} \sin{\left (\theta \right )} \cos{\left (\alpha \right )} \cos{\left (\delta \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} + \sin{\left (\psi \right )} \sin{\left (\sigma \right )} \cos{\left (\iota \right )} \cos{\left (\theta \right )} + \cos{\left (\alpha \right )} \cos{\left (\iota \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} \cos{\left (\theta \right )} \nonumber
\end{equation}
\]
While \(\sin(\Xi)\cos(\mu)\) is available in closed form, I have not been able to factorize \(\sin(\Xi)\sin(\mu) = \sqrt{1-\sin^2(\Xi)\cos^2(\mu)}\). While we can always proceed numerically, there is a nice factorization available in the case where the gnomon's declination angle \(\delta\) is zero, so that it remains in the \(n_1 \wedge n_3\) plane, so I will work in that case from here.
The Gnomon
gn = frame.gnomon("n", zero_decl=True)
render.expression("g_n", gn)
\[\displaystyle
\begin{equation}
g_n = - \sin{\left (\iota \right )} \boldsymbol{n}_{1} + \cos{\left (\iota \right )} \boldsymbol{n}_{3} \nonumber
\end{equation}
\]
g = frame.gnomon("e", zero_decl=True)
render.expression("g", g)
\[\displaystyle
\begin{equation}
g = \left ( - \sin{\left (\alpha \right )} \cos{\left (\iota - \theta \right )} - \sin{\left (\iota - \theta \right )} \cos{\left (\alpha \right )} \cos{\left (\psi \right )}\right ) \boldsymbol{e}_{1} - \sin{\left (\psi \right )} \sin{\left (\iota - \theta \right )} \boldsymbol{e}_{2} + \left ( - \sin{\left (\alpha \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\psi \right )} + \cos{\left (\alpha \right )} \cos{\left (\iota - \theta \right )}\right ) \boldsymbol{e}_{3} \nonumber
\end{equation}
\]
The Shadow Plane
Setting \(\delta = 0\) in the shadow plane \(S = s \wedge g\) (the plane containing the sunshine vector and the gnomon) gives a more manageable expression.
S = result.shadow_bivector(s, g)
Sn = result.shadow_bivector_explicit()
render.simplification("S", [S, Sn], as_lines=[True, False])
\[\displaystyle \begin{align} S & = \sin{\left (\alpha \right )} \sin{\left (\sigma \right )} \cos{\left (\iota - \theta \right )} - \sin{\left (\psi \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\sigma \right )} + \sin{\left (\sigma \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\alpha \right )} \cos{\left (\psi \right )} e_1 \wedge e_2 \nonumber \\ &- \sin{\left (\alpha \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} + \cos{\left (\alpha \right )} \cos{\left (\sigma \right )} \cos{\left (\iota - \theta \right )} e_1 \wedge e_3 \nonumber \\ &- \sin{\left (\alpha \right )} \sin{\left (\sigma \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\psi \right )} + \sin{\left (\sigma \right )} \cos{\left (\alpha \right )} \cos{\left (\iota - \theta \right )} e_2 \wedge e_3 \nonumber \\ & = - \sin{\left (\Xi \right )} \sin{\left (\iota \right )} \sin{\left (\mu \right )} \boldsymbol{n}_{1}\wedge \boldsymbol{n}_{2} + \sin{\left (\Xi \right )} \cos{\left (\mu \right )} \boldsymbol{n}_{1}\wedge \boldsymbol{n}_{3} - \sin{\left (\Xi \right )} \sin{\left (\mu \right )} \cos{\left (\iota \right )} \boldsymbol{n}_{2}\wedge \boldsymbol{n}_{3}& \nonumber \end{align}\]
Apply the \(\delta=0\) assumption to the cosine of the angle \(\Xi\) between sun ray and gnomon:
cosXi = result.shadow_bivector_magnitude_angle_cos(s, g)
render.expression(r"\cos(\Xi)", cosXi)
\[\displaystyle
\begin{equation}
\cos(\Xi) = - \left(\sin{\left (\alpha \right )} \cos{\left (\iota - \theta \right )} + \sin{\left (\iota - \theta \right )} \cos{\left (\alpha \right )} \cos{\left (\psi \right )}\right) \cos{\left (\sigma \right )} - \sin{\left (\psi \right )} \sin{\left (\sigma \right )} \sin{\left (\iota - \theta \right )} \nonumber
\end{equation}
\]
The Hour Angle
Apply \(\delta=0\) to the hour angle:
sinXi_sin_mu, sinXi_cos_mu = result.hour_angle_sincos(S, M)
render.expression(r"\sin(\Xi) \sin(\mu)", sinXi_sin_mu)
\[\displaystyle
\begin{equation}
\sin(\Xi) \sin(\mu) = \sin{\left (\psi \right )} \cos{\left (\alpha \right )} \cos{\left (\sigma \right )} - \sin{\left (\sigma \right )} \cos{\left (\psi \right )} \nonumber
\end{equation}
\]
render.expression(r"\sin(\Xi) \cos(\mu)", sinXi_cos_mu)
\[\displaystyle
\begin{equation}
\sin(\Xi) \cos(\mu) = - \sin{\left (\alpha \right )} \sin{\left (\iota - \theta \right )} \cos{\left (\sigma \right )} + \sin{\left (\psi \right )} \sin{\left (\sigma \right )} \cos{\left (\iota - \theta \right )} + \cos{\left (\alpha \right )} \cos{\left (\psi \right )} \cos{\left (\sigma \right )} \cos{\left (\iota - \theta \right )} \nonumber
\end{equation}
\]
The ratio gives \(\tan(\mu)\) as in the paper:
tan_mu = result.hour_angle_tan(S, M)
render.expression(r"\tan(\mu)", tan_mu)
\[\displaystyle
\begin{equation}
\tan(\mu) = \frac{\sin{\left (\psi \right )} \cos{\left (\alpha \right )} - \cos{\left (\psi \right )} \tan{\left (\sigma \right )}}{\left(\sin{\left (\psi \right )} \tan{\left (\sigma \right )} + \cos{\left (\alpha \right )} \cos{\left (\psi \right )}\right) \cos{\left (\iota - \theta \right )} - \sin{\left (\alpha \right )} \sin{\left (\iota - \theta \right )}} \nonumber
\end{equation}
\]
In The Shadow Angle, we calculate a unit vector in the dial face parallel to the shadow interms of the hour angle, and track its progress across the dial face.