Skip to content

algebra.frame

Key vectors, bivectors, and frames defining the location, orientation and geometry of a sundial.

See Setup and Definitions for the algebraic form taken by these objects.

base(base_symbol) cached

The set of basis vectors from the Geometric Algebra (GA) with basis vectors identified by base_symbol

See also Fixed Stars and Earth Frames

Earth's orientation and orbit.

Parameters:

Name Type Description Default
base_symbol str

Symbol to identify the basis vectors, eg supplying '\(e\)' gives \(e_1, e_2, e_3\)

required

Returns:

Type Description
Tuple[Mv]

A 3-tuple of galgebra.mv.Mv objects each representing a basis vector

Source code in src/analemma/algebra/frame.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@lru_cache(maxsize=_cache_max_size)
def base(base_symbol: str) -> Tuple[mv.Mv]:
    """
    The set of basis vectors from the Geometric Algebra (GA) with basis vectors identified by `base_symbol`

    See also [Fixed Stars and Earth Frames](../nb/sundial_setup.md#fixed-stars-and-earth-frames)

    ![Earth's orientation and orbit](https://raw.githubusercontent.com/russellgoyder/analemma/main/docs/paper/figs/MainArena.png "Earth's orientation and orbit.").

    Parameters:
        base_symbol: Symbol to identify the basis vectors, eg supplying '$e$' gives $e_1, e_2, e_3$

    Returns:
        A 3-tuple of galgebra.mv.Mv objects each representing a basis vector
    """
    return space_algebra(base_symbol).mv()

base_bivec(base_symbol) cached

The set of basis bivectors from the Geometric Algebra (GA) with basis vectors identified by base_symbol

Parameters:

Name Type Description Default
base_symbol str

Symbol to identify the basis bivectors, eg supplying 'e' gives \(e_1\wedge e_2,\; e_1\wedge e_3,\; e_2\wedge e_3\)

required

Returns:

Type Description
Tuple[Mv]

A 3-tuple of galgebra.mv.Mv objects each representing a basis bivector

Source code in src/analemma/algebra/frame.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@lru_cache(maxsize=_cache_max_size)
def base_bivec(base_symbol: str) -> Tuple[mv.Mv]:
    r"""
    The set of basis bivectors from the Geometric Algebra (GA) with basis vectors identified by `base_symbol`

    Parameters:
        base_symbol: Symbol to identify the basis bivectors, eg supplying 'e' gives $e_1\wedge e_2,\; e_1\wedge e_3,\; e_2\wedge e_3$

    Returns:
        A 3-tuple of galgebra.mv.Mv objects each representing a basis bivector

    """
    v1, v2, v3 = base(base_symbol)
    return v1 ^ v2, v1 ^ v3, v2 ^ v3

dial(incl_symbol=i, decl_symbol=d) cached

The dial frame

This vector frame is aligned with the face of the sundial such that \(m_3\) is perpendicular pointing up. It is formed by applying the rotor \(R_dR_i\) to the surface frame, where

\(R_i = \exp( - n_1 \wedge n_3 \frac{1}{2} i )\)

and

\(R_d = \exp( - n_1 \wedge n_2 \frac{1}{2} d )\)

See also Dial Face and Gnomon

.

Parameters:

Name Type Description Default
incl_symbol Symbol

The inclination angle of the dial face relative to the surface frame

i
decl_symbol Symbol

The declination angle of the dial face relative to the surface frame

d

Returns:

Type Description
Tuple[Mv]

A 3-tuple of vectors forming the dial frame

Source code in src/analemma/algebra/frame.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
@lru_cache(maxsize=_cache_max_size)
def dial(
    incl_symbol: sp.Symbol = i,
    decl_symbol: sp.Symbol = d,
) -> Tuple[mv.Mv]:
    r"""
    The dial frame

    This vector frame is aligned with the face of the sundial such that $m_3$ is perpendicular pointing up. It is formed
    by applying the rotor $R_dR_i$ to the [surface][analemma.algebra.frame.surface] frame, where

    $R_i = \exp( - n_1 \wedge n_3 \frac{1}{2} i )$

    and

    $R_d = \exp( - n_1 \wedge n_2 \frac{1}{2} d )$

    See also [Dial Face and Gnomon](../nb/sundial_setup.md#dial-face-and-gnomon)

    ![](https://raw.githubusercontent.com/russellgoyder/analemma/main/docs/paper/figs/DialFrame.png "Frame embedded in the sundial's face.").

    Parameters:
        incl_symbol: The inclination angle of the dial face relative to the surface frame
        decl_symbol: The declination angle of the dial face relative to the surface frame

    Returns:
        A 3-tuple of vectors forming the dial frame
    """
    n1, n2, n3 = base("n")
    m1 = util.rotate(
        util.rotate(n1, incl_symbol, n1 ^ n3), decl_symbol, n1 ^ n2
    ).trigsimp()
    m2 = util.rotate(util.rotate(n2, incl_symbol, n1 ^ n3), decl_symbol, n1 ^ n2)
    m3 = util.rotate(
        util.rotate(n3, incl_symbol, n1 ^ n3), decl_symbol, n1 ^ n2
    ).trigsimp()
    return m1, m2, m3

dialface() cached

The dial face as a unit bivector

Given \(m_1\) and \(m_2\) from the dial frame, form:

\(m_1 \wedge m_2\)

See also Dial Face and Gnomon

Returns:

Type Description
Mv

A bivector representing the face of the sundial

Source code in src/analemma/algebra/frame.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
@lru_cache(maxsize=_cache_max_size)
def dialface() -> mv.Mv:
    r"""
    The dial face as a unit bivector

    Given $m_1$ and $m_2$ from the [dial frame][analemma.algebra.frame.dial], form:

    $m_1 \wedge m_2$

    See also [Dial Face and Gnomon](../nb/sundial_setup.md#dial-face-and-gnomon)

    Returns:
        A bivector representing the face of the sundial
    """
    m1, m2, _ = dial()
    return (m1 ^ m2).trigsimp()

gnomon(base_symbol='n', zero_decl=True, incl_symbol=iota, decl_symbol=delta) cached

Form the gnomon on the sundial

The gnomon (silent g) is the part of a sundial that casts the shadow. Here we assume it is a rod or stick of unit length, and calculate it by rotating the surface vector \(n_3\) (pointing overhead) by two angles via \(g = R_\delta R_\iota n_3 \tilde{\)\iota}\tilde{R\delta}$ where

\(R_\iota = \exp( - n_1 \wedge n_3 \frac{1}{2} \iota )\)

and

\(R_\delta = \exp( - n_1 \wedge n_2 \frac{1}{2} \delta )\)

See also Dial Face and Gnomon

.

Parameters:

Name Type Description Default
base_symbol str

Symbol identifing the basis on which to project the gnomon

'n'
zero_decl bool

Indicates whether declination is assumed to be zero, which gives simpler results

True
incl_symbol Symbol

The inclination angle of the gnomon relative to the surface frame

iota
decl_symbol Symbol

The declination angle of the gnomon relative to the surface frame

delta

Returns:

Type Description
Mv

A vector representing the gnomon

Source code in src/analemma/algebra/frame.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
@lru_cache(maxsize=_cache_max_size)
def gnomon(
    base_symbol: str = "n",
    zero_decl: bool = True,
    incl_symbol: sp.Symbol = iota,
    decl_symbol: sp.Symbol = delta,
) -> mv.Mv:
    r"""
    Form the gnomon on the sundial

    The gnomon (silent g) is the part of a sundial that casts the shadow. Here we assume it is a rod or stick of unit
    length, and calculate it by rotating the [surface][analemma.algebra.frame.surface] vector $n_3$ (pointing overhead)
    by two angles via $g = R_\delta R_\iota n_3 \tilde{$_\iota}\tilde{R_\delta}$ where

    $R_\iota = \exp( - n_1 \wedge n_3 \frac{1}{2} \iota )$

    and

    $R_\delta = \exp( - n_1 \wedge n_2 \frac{1}{2} \delta )$

    See also [Dial Face and Gnomon](../nb/sundial_setup.md#dial-face-and-gnomon)

    ![](https://raw.githubusercontent.com/russellgoyder/analemma/main/docs/paper/figs/Gnomon.png "The gnomon.").

    Parameters:
        base_symbol: Symbol identifing the basis on which to project the gnomon

            * '`e`' selects the [frame of the fixed stars][analemma.algebra.frame.base]
            * '`n`' selects the [surface frame][analemma.algebra.frame.surface]
        zero_decl: Indicates whether declination is assumed to be zero, which gives simpler results
        incl_symbol: The inclination angle of the gnomon relative to the surface frame
        decl_symbol: The declination angle of the gnomon relative to the surface frame

    Returns:
        A vector representing the gnomon
    """
    if base_symbol == "n":
        return _form_gnomon(zero_decl, incl_symbol, decl_symbol)
    elif base_symbol == "e":
        gn = _form_gnomon(zero_decl, incl_symbol, decl_symbol)
        return util.update_coeffs(
            util.project_vector(gn, target_frame=base("n"), render_frame=surface())
        )
    else:
        raise Exception("Base symbol must be either 'n' or 'e'")

meridian_plane() cached

The meridian plane encoded as a bivector

The meridian plane contains a line of longitude and is perpendicular to all lines of latitude. It is therefore parallel to North-South lines and is formed as \(n_1\wedge n_3\). Note that while \(n_1\) and \(n_3\) are both functions of latitude, this dependency cancels in the meridian plane and it depends on the planet parameters (axis tilt and rotation angles) only. See also Orbit Rotor and Meridian Plane

Returns:

Type Description
Mv

Bivector representing the meridian plane

Source code in src/analemma/algebra/frame.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
@lru_cache(maxsize=_cache_max_size)
def meridian_plane() -> mv.Mv:
    r"""
    The meridian plane encoded as a bivector

    The meridian plane contains a line of longitude and is perpendicular to all lines of latitude. It is therefore
    parallel to North-South lines and is formed as $n_1\wedge n_3$. Note that while $n_1$ and $n_3$ are both functions
    of latitude, this dependency cancels in the meridian plane and it depends on the
    [planet][analemma.algebra.frame.planet] parameters (axis tilt and rotation angles) only. See also
    [Orbit Rotor and Meridian Plane](../nb/sundial_setup.md#orbit-rotor-and-meridian-plane)

    Returns:
        Bivector representing the meridian plane
    """
    n1, _, n3 = surface()
    return (n1 ^ n3).trigsimp()

planet(axis_tilt_symbol=alpha, rotation_symbol=psi) cached

A vector frame embedded in a planet

This frame accounts for the tilt of the planet's axis of rotation relative to the plane of its orbit around a star (via axis_tilt_symbol), and encodes the rotation itself via rotation_symbol. The frame is calculated by forming the two rotors

\(R_\alpha = \exp(-e_1 \wedge e_3\frac{1}{2}\alpha)\)

\(R_\psi = \exp(-R_\alpha e_1 \tilde{R}_\alpha \wedge e_2\frac{1}{2}\psi)\)

and applying them together as \(R_\psi R_\alpha\) to the fixed \(\{e_i\}\) frame from analemma.algebra.frame.base.

Earth's orientation and orbit.

See also Fixed Stars and Earth Frames

Parameters:

Name Type Description Default
axis_tilt_symbol Symbol

Symbol denoting the angle of tilt of the planet's axis of rotation

alpha
rotation_symbol Symbol

Symbol denoting the planet's angle of rotation, starting when \(f_1\) is parallel to \(e_1\)

psi

Returns:

Type Description
Tuple[Mv]

Tuple of 3 basis vectors (as galgebra.mv.Mv objects) embedded in the planet with \(f_3\) parallel to the axis of rotation

Source code in src/analemma/algebra/frame.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@lru_cache(maxsize=_cache_max_size)
def planet(
    axis_tilt_symbol: sp.Symbol = alpha,
    rotation_symbol: sp.Symbol = psi,
) -> Tuple[mv.Mv]:
    r"""
    A vector frame embedded in a planet

    This frame accounts for the tilt of the planet's axis of rotation relative to the plane of its orbit around a star
    (via axis_tilt_symbol), and encodes the rotation itself via `rotation_symbol`. The frame is calculated by forming
    the two rotors

    $R_\alpha = \exp(-e_1 \wedge e_3\frac{1}{2}\alpha)$

    $R_\psi = \exp(-R_\alpha e_1 \tilde{R}_\alpha \wedge e_2\frac{1}{2}\psi)$

    and applying them together as $R_\psi R_\alpha$ to the fixed $\{e_i\}$ frame from [analemma.algebra.frame.base][].

    ![Earth's orientation and orbit](https://raw.githubusercontent.com/russellgoyder/analemma/main/docs/paper/figs/MainArena.png "Earth's orientation and orbit.").

    See also [Fixed Stars and Earth Frames](../nb/sundial_setup.md#fixed-stars-and-earth-frames)

    Parameters:
        axis_tilt_symbol: Symbol denoting the angle of tilt of the planet's axis of rotation
        rotation_symbol: Symbol denoting the planet's angle of rotation, starting when $f_1$ is parallel to $e_1$

    Returns:
        Tuple of 3 basis vectors (as galgebra.mv.Mv objects) embedded in the planet with $f_3$ parallel to the axis of rotation
    """
    (e1, e2, e3) = base(_fixed_stars_symbol)
    e1_prime = util.rotate(e1, axis_tilt_symbol, e1 ^ e3).trigsimp()

    f1 = util.rotate(e1_prime, rotation_symbol, e1_prime ^ e2).trigsimp().trigsimp()
    f2 = util.rotate(e2, rotation_symbol, e1_prime ^ e2).trigsimp().trigsimp()
    f3 = util.rotate(e3, axis_tilt_symbol, e1 ^ e3).trigsimp().trigsimp()

    return (f1, f2, f3)

scalar_element(value, base_symbol=_fixed_stars_symbol) cached

The unit scalar element of the Geometric Algebra (GA) scaled by value

Parameters:

Name Type Description Default
value float

The numerical value of the returned scalar

required
base_symbol str

Symbol to identify the basis vectors of the GA

_fixed_stars_symbol

Returns:

Type Description
Mv

The scalar element of the GA with value value

Source code in src/analemma/algebra/frame.py
75
76
77
78
79
80
81
82
83
84
85
86
87
@lru_cache(maxsize=_cache_max_size)
def scalar_element(value: float, base_symbol: str = _fixed_stars_symbol) -> mv.Mv:
    """
    The unit scalar element of the Geometric Algebra (GA) scaled by `value`

    Parameters:
        value: The numerical value of the returned scalar
        base_symbol: Symbol to identify the basis vectors of the GA

    Returns:
        The scalar element of the GA with value `value`
    """
    return space_algebra(base_symbol).mv(value, "scalar")

space_algebra(symbol) cached

The Geometric Algebra (GA) of 3-dimensional Euclidean space

Parameters:

Name Type Description Default
symbol str

Symbol to use for the basis vectors, eg 'e' gives \(e_1, e_2, e_3\) The vectors are one-based because zero is reserved for time, but relativitic effects are negligible in this analysis.

required

Returns:

Type Description
Ga

A galgebra.ga.Ga object representing the Geometric Algebra

Source code in src/analemma/algebra/frame.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@lru_cache(maxsize=_cache_max_size)
def space_algebra(symbol: str) -> Ga:
    """
    The Geometric Algebra (GA) of 3-dimensional Euclidean space

    Parameters:
        symbol: Symbol to use for the basis vectors, eg 'e' gives $e_1, e_2, e_3$
            The vectors are one-based because zero is reserved for time, but relativitic effects are negligible in this analysis.

    Returns:
        A [galgebra.ga.Ga](https://galgebra.readthedocs.io/en/latest/) object representing the Geometric Algebra
    """

    coords = sp.symbols("1 2 3", real=True)
    G3 = Ga(symbol, g=[1, 1, 1], coords=coords)

    return G3

sunray(orbit_symbol=sigma) cached

A vector parallel to rays of sun light traveling toward the center of the planet

This vector is formed by rotating \(e_1\) by the orbit angle using the rotor

\(R_\sigma = \exp(-e_1 \wedge e_2 \frac{1}{2}\sigma)\)

to give

\(s = R_\sigma e_1 \tilde{R}_\sigma = \cos\sigma \, e_1 + \sin\sigma e_2\)

See also Orbit Rotor and Meridian Plane

Parameters:

Name Type Description Default
orbit_symbol Symbol

The planet's orbit angle

sigma

Returns:

Type Description
Mv

The vector \(s\) parallel to sun rays traveling directly toward a planet from its star

Source code in src/analemma/algebra/frame.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
@lru_cache(maxsize=_cache_max_size)
def sunray(
    orbit_symbol: sp.Symbol = sigma,
) -> mv.Mv:
    r"""
    A vector parallel to rays of sun light traveling toward the center of the planet

    This vector is formed by rotating $e_1$ by the orbit angle using the rotor

    $R_\sigma = \exp(-e_1 \wedge e_2 \frac{1}{2}\sigma)$

    to give

    $s = R_\sigma e_1 \tilde{R}_\sigma = \cos\sigma \, e_1 + \sin\sigma e_2$

    See also [Orbit Rotor and Meridian Plane](../nb/sundial_setup.md#orbit-rotor-and-meridian-plane)

    Parameters:
        orbit_symbol: The planet's orbit angle

    Returns:
        The vector $s$ parallel to sun rays traveling directly toward a planet from its star
    """
    (e1, e2, _) = base(_fixed_stars_symbol)
    return util.rotate(e1, orbit_symbol, e1 ^ e2).trigsimp()

surface(latitude_symbol=theta) cached

A vector frame embedded in the surface of a planet

The frame is formed by rotating the frame given by analemma.algebra.frame.planet by an angle latitude_symbol in the \(f_1 \wedge f_3\) plane, using the rotor \(R_\theta = \exp(-f_3 \wedge f_1\frac{1}{2}\theta)\).

See also Surface Frame

.

Parameters:

Name Type Description Default
latitude_symbol Symbol

\(90^\circ\) minus the latitude at which the frame is embedded

theta

Returns:

Type Description
Tuple[Mv]

Tuple of 3 basis vectors (as galgebra.mv.Mv objects) at the surface of the planet with \(n_3\) pointing overhead

Note

latitude_symbol is only directly related to latitude. It is \(90^\circ\) minus the latitude at which the frame is embedded.

Source code in src/analemma/algebra/frame.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
@lru_cache(maxsize=_cache_max_size)
def surface(latitude_symbol: sp.Symbol = theta) -> Tuple[mv.Mv]:
    r"""
    A vector frame embedded in the surface of a planet

    The frame is formed by rotating the frame given by [analemma.algebra.frame.planet][] by an angle `latitude_symbol`
    in the $f_1 \wedge f_3$ plane, using the rotor $R_\theta = \exp(-f_3 \wedge f_1\frac{1}{2}\theta)$.

    See also [Surface Frame](../nb/sundial_setup.md#surface-frame)

    ![](https://raw.githubusercontent.com/russellgoyder/analemma/main/docs/paper/figs/SurfaceFrame.png "Frame embedded in Earth's surface.").

    Parameters:
        latitude_symbol: $90^\circ$ minus the latitude at which the frame is embedded

    Returns:
        Tuple of 3 basis vectors (as galgebra.mv.Mv objects) at the surface of the planet with $n_3$ pointing overhead

    Note:
        `latitude_symbol` is only directly related to latitude. It is $90^\circ$ minus the latitude at which the frame is embedded.
    """
    f1, f2, f3 = planet()

    n1 = util.rotate(f1, latitude_symbol, f3 ^ f1).trigsimp().trigsimp()
    n2 = f2

    # n3 needs a little love
    raw_n3 = util.rotate(f3, theta, f3 ^ f1).obj.trigsimp()
    sympy_n3 = sp.expand(
        sp.expand_trig(raw_n3)
    )  # galgebra's Mv doesn't have expand_trig as a method
    n3 = mv.Mv(
        sympy_n3, ga=space_algebra(_fixed_stars_symbol)
    )  # TODO replace with f1.Ga.mv()?

    return n1, n2, n3

surface_bivec(latitude_symbol=theta) cached

Frame of bivectors contructed from the surface vector frame

Given the vector frame \(n_1, n_2, n_3\), the bivector frame is formed as:

\(n_1\wedge n_2,\; n_1\wedge n_3,\; n_2\wedge n_3\)

See also Setup and Definitions

Parameters:

Name Type Description Default
latitude_symbol Symbol

\(90^\circ\) minus the latitude at which the frame is embedded

theta

Returns:

Type Description
Tuple[Mv]

Tuple of 3 basis bivectors (as galgebra.mv.Mv objects) at the surface of the planet with \(n_3\) pointing overhead

Note

latitude_symbol is only directly related to latitude. It is \(90^\circ\) minus the latitude at which the frame is embedded.

Source code in src/analemma/algebra/frame.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
@lru_cache(maxsize=_cache_max_size)
def surface_bivec(latitude_symbol: sp.Symbol = theta) -> Tuple[mv.Mv]:
    r"""
    Frame of bivectors contructed from the [surface][analemma.algebra.frame.surface] vector frame

    Given the vector frame $n_1, n_2, n_3$, the bivector frame is formed as:

    $n_1\wedge n_2,\; n_1\wedge n_3,\; n_2\wedge n_3$

    See also [Setup and Definitions](../nb/sundial_setup.md)

    Parameters:
        latitude_symbol: $90^\circ$ minus the latitude at which the frame is embedded

    Returns:
        Tuple of 3 basis bivectors (as galgebra.mv.Mv objects) at the surface of the planet with $n_3$ pointing overhead

    Note:
        `latitude_symbol` is only directly related to latitude. It is $90^\circ$ minus the latitude at which the frame is embedded.
    """
    n1, n2, n3 = surface(latitude_symbol)
    n12 = mv.Mv(sp.trigsimp(sp.expand_trig((n1 ^ n2).obj)), ga=n1.Ga)
    n13 = mv.Mv(sp.trigsimp(sp.expand_trig((n1 ^ n3).obj)), ga=n1.Ga)
    n23 = mv.Mv(sp.trigsimp(sp.expand_trig((n2 ^ n3).obj)), ga=n1.Ga)
    return n12, n13, n23