Skip to content

Commit

Permalink
Enforce strict type checking in all Python demo programs.
Browse files Browse the repository at this point in the history
Use mypy to check all Python demo programs.
Updated the demos to pass type checking.
There were a couple of small mistakes found, so this was worth the effort.
  • Loading branch information
cosinekitty committed Oct 3, 2023
1 parent bbaf5bf commit 871c26a
Show file tree
Hide file tree
Showing 11 changed files with 67 additions and 51 deletions.
2 changes: 1 addition & 1 deletion demo/python/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import astronomy
from astro_demo_common import ParseArgs

def Camera(observer, time):
def Camera(observer: astronomy.Observer, time: astronomy.Time) -> int:
tolerance = 1.0e-15

# Calculate the topocentric equatorial coordinates of date for the Moon.
Expand Down
9 changes: 5 additions & 4 deletions demo/python/constellation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
#

import sys
from astronomy import Time, Body, Constellation, GeoVector, EquatorFromVector
from astronomy import Time, Body, Constellation, ConstellationInfo, GeoVector, EquatorFromVector
from typing import Tuple

#------------------------------------------------------------------------------

def BodyConstellation(body, time):
def BodyConstellation(body: Body, time: Time) -> ConstellationInfo:
vec = GeoVector(body, time, False)
equ = EquatorFromVector(vec)
return Constellation(equ.ra, equ.dec)

#------------------------------------------------------------------------------

def FindConstellationChange(body, c1, t1, t2):
def FindConstellationChange(body: Body, c1: ConstellationInfo, t1: Time, t2: Time) -> Tuple[Time, ConstellationInfo]:
# Do a binary search to find what time between t1 and t2
# is the boundary between being in constellation c1 and some other
# constellation. Return the tuple (tx, cx), where tx is the
Expand All @@ -40,7 +41,7 @@ def FindConstellationChange(body, c1, t1, t2):

#------------------------------------------------------------------------------

def FindConstellationChanges(body, startTime, stopTime, dayIncrement):
def FindConstellationChanges(body: Body, startTime: Time, stopTime: Time, dayIncrement: float) -> int:
t1 = startTime
c1 = BodyConstellation(body, t1)
while t1 < stopTime:
Expand Down
1 change: 1 addition & 0 deletions demo/python/demotest
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ TestDemo()
local name=$1
shift
echo "Testing Python demo: $name"
mypy --strict $name.py || Fail "Error in mypy verification of $name.py."
./$name.py $* > test/$name${SUFFIX}.txt || Fail "Error testing $name.py."
diff {correct,test}/$name${SUFFIX}.txt || Fail "Incorrect output from $name.py."
}
Expand Down
3 changes: 2 additions & 1 deletion demo/python/galactic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import sys
from astronomy import *
from typing import Tuple

UsageText = r'''
USAGE: galactic olat olon glat glon [yyyy-mm-ddThh:mm:ssZ]
Expand All @@ -31,7 +32,7 @@
'''


def GalacticToHorizontal(time, observer, glat, glon):
def GalacticToHorizontal(time: Time, observer: Observer, glat: float, glon: float) -> Tuple[float, float]:
# Calculate a matrix that converts galactic coordinates
# to J2000 equatorial coordinates.
rot = Rotation_GAL_EQJ()
Expand Down
25 changes: 13 additions & 12 deletions demo/python/horizon.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,34 @@
# python3 horizon.py latitude longitude [yyyy-mm-ddThh:mm:ssZ]
#
import sys
import astronomy
from astronomy import *
from astro_demo_common import ParseArgs
from typing import Tuple

NUM_SAMPLES = 4

def ECLIPLON(i):
def ECLIPLON(i: int) -> float:
return (360.0 * i) / NUM_SAMPLES


def HorizontalCoords(ecliptic_longitude, time, rot_ecl_hor):
eclip = astronomy.Spherical(
def HorizontalCoords(ecliptic_longitude: float, time: Time, rot_ecl_hor: RotationMatrix) -> Spherical:
eclip = Spherical(
0.0, # being "on the ecliptic plane" means ecliptic latitude is zero.
ecliptic_longitude,
1.0 # any positive distance value will work fine.
)

# Convert ecliptic angular coordinates to ecliptic vector.
ecl_vec = astronomy.VectorFromSphere(eclip, time)
ecl_vec = VectorFromSphere(eclip, time)

# Use the rotation matrix to convert ecliptic vector to horizontal vector.
hor_vec = astronomy.RotateVector(rot_ecl_hor, ecl_vec)
hor_vec = RotateVector(rot_ecl_hor, ecl_vec)

# Find horizontal angular coordinates, correcting for atmospheric refraction.
return astronomy.HorizonFromVector(hor_vec, astronomy.Refraction.Normal)
return HorizonFromVector(hor_vec, Refraction.Normal)


def Search(time, rot_ecl_hor, e1, e2):
def SearchCrossing(time: Time, rot_ecl_hor: RotationMatrix, e1: float, e2: float) -> Tuple[float, Spherical]:
tolerance = 1.0e-6 # one-millionth of a degree is close enough!
# Binary search: find the ecliptic longitude such that the horizontal altitude
# ascends through a zero value. The caller must pass e1, e2 such that the altitudes
Expand All @@ -56,15 +57,15 @@ def Search(time, rot_ecl_hor, e1, e2):
e2 = e3


def FindEclipticCrossings(observer, time):
def FindEclipticCrossings(observer: Observer, time: Time) -> int:
# The ecliptic is a celestial circle that describes the mean plane of
# the Earth's orbit around the Sun. We use J2000 ecliptic coordinates,
# meaning the x-axis is defined to where the plane of the Earth's
# equator on January 1, 2000 at noon UTC intersects the ecliptic plane.
# The positive x-axis points toward the March equinox.
# Calculate a rotation matrix that converts J2000 ecliptic vectors
# to horizontal vectors for this observer and time.
rot = astronomy.Rotation_ECL_HOR(time, observer)
rot = Rotation_ECL_HOR(time, observer)

# Sample several points around the ecliptic.
# Remember the horizontal coordinates for each sample.
Expand All @@ -77,9 +78,9 @@ def FindEclipticCrossings(observer, time):
e2 = ECLIPLON(i+1)
if a1 * a2 <= 0.0:
if a2 > a1:
(ex, h) = Search(time, rot, e1, e2)
(ex, h) = SearchCrossing(time, rot, e1, e2)
else:
(ex, h) = Search(time, rot, e2, e1)
(ex, h) = SearchCrossing(time, rot, e2, e1)

if h.lon > 0.0 and h.lon < 180.0:
direction = 'ascends'
Expand Down
12 changes: 6 additions & 6 deletions demo/python/jupiter_moons.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# the Earth from the Jupiter system.
#
import sys
from astronomy import Time, JupiterMoons, GeoVector, EquatorFromVector, Body, C_AUDAY
from astronomy import Time, JupiterMoons, GeoVector, EquatorFromVector, Body, C_AUDAY, Vector

def PrintBody(name, geovec):
def PrintBody(name: str, geovec: Vector) -> None:
# Convert the geocentric vector into equatorial coordinates.
equ = EquatorFromVector(geovec)
print('{:<8s} RA {:10.6f} DEC {:10.6f} {:10.6f} AU'.format(name, equ.ra, equ.dec, equ.dist))
Expand Down Expand Up @@ -60,10 +60,10 @@ def PrintBody(name, geovec):
# "duck typing" for Pythonistas.

PrintBody('Jupiter', jv)
PrintBody('Io', jv + jm.io)
PrintBody('Europa', jv + jm.europa)
PrintBody('Ganymede', jv + jm.ganymede)
PrintBody('Callisto', jv + jm.callisto)
PrintBody('Io', jv + jm.io.Position())
PrintBody('Europa', jv + jm.europa.Position())
PrintBody('Ganymede', jv + jm.ganymede.Position())
PrintBody('Callisto', jv + jm.callisto.Position())
print()

sys.exit(0)
25 changes: 13 additions & 12 deletions demo/python/lunar_angles.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,26 @@
#
import sys
from astronomy import Time, Body, PairLongitude, Search
from typing import List

#------------------------------------------------------------------------------

class Event:
def __init__(self, body, time, angle):
def __init__(self, body: Body, time: Time, angle: float) -> None:
self.body = body
self.time = time
self.angle = angle

def __lt__(self, other):
def __lt__(self, other: 'Event') -> bool:
# This makes lists of Event objects sortable chronologically.
return self.time < other.time

def __str__(self):
def __str__(self) -> str:
return '{} {:<8s} {:3.0f}'.format(self.time, self.body.name, self.angle)

#------------------------------------------------------------------------------

def AdjustAngle(angle):
def AdjustAngle(angle: float) -> float:
# Force the angle into the half-open range (-180.0, +180.0]
while angle <= -180.0:
angle += 360.0
Expand All @@ -60,28 +61,28 @@ def AdjustAngle(angle):
#------------------------------------------------------------------------------

class PairSearchContext:
def __init__(self, body1, body2, targetRelLon):
def __init__(self, body1: Body, body2: Body, targetRelLon: float) -> None:
self.body1 = body1
self.body2 = body2
self.targetRelLon = targetRelLon


def LongitudeFunc(context, time):
def LongitudeFunc(context: PairSearchContext, time: Time) -> float:
lon = PairLongitude(context.body1, context.body2, time)
return AdjustAngle(lon - context.targetRelLon)


def LongitudeSearch(body1, body2, angle, t1, t2):
def LongitudeSearch(body1: Body, body2: Body, angle: float, t1: Time, t2: Time) -> Time:
context = PairSearchContext(body1, body2, angle)
tolerance_seconds = 0.1
t = Search(LongitudeFunc, context, t1, t2, tolerance_seconds)
if not t:
raise Exception('Search failure for body={}, t1={}, t2={}'.format(body, t1, t2))
raise Exception('Search failure for body1={}, body2={}, t1={}, t2={}'.format(body1.name, body2.name, t1, t2))
return t

#------------------------------------------------------------------------------

def Straddle(lon1, lon2, angle):
def Straddle(lon1: float, lon2: float, angle: float) -> bool:
# A pair of longitudes "straddles" an angle if
# the angle lies between the two longitudes modulo 360 degrees.
a1 = AdjustAngle(lon1 - angle)
Expand All @@ -90,7 +91,7 @@ def Straddle(lon1, lon2, angle):

#------------------------------------------------------------------------------

def AppendEvents(event_list, body, startTime, stopTime, dayIncrement):
def AppendEvents(event_list: List[Event], body: Body, startTime: Time, stopTime: Time, dayIncrement: float) -> None:
angle_list = [30.0*i for i in range(12)]
t1 = startTime
while t1 < stopTime:
Expand All @@ -108,12 +109,12 @@ def AppendEvents(event_list, body, startTime, stopTime, dayIncrement):

#------------------------------------------------------------------------------

def PrintMoonChart(startTime, stopTime, dayIncrement):
def PrintMoonChart(startTime: Time, stopTime: Time, dayIncrement: float) -> int:
# Make a list of all other bodies with which to compare the Moon.
otherBodyList = [Body.Sun, Body.Mercury, Body.Venus, Body.Mars, Body.Jupiter, Body.Saturn]

# Make a list of events for each body in that list.
event_list = []
event_list: List[Event] = []
for body in otherBodyList:
AppendEvents(event_list, body, startTime, stopTime, dayIncrement)

Expand Down
7 changes: 4 additions & 3 deletions demo/python/lunar_eclipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
# python3 lunar_eclipse.py [date]
#
import sys
from astronomy import Time, SearchLunarEclipse, NextLunarEclipse, EclipseKind
from astronomy import Time, SearchLunarEclipse, NextLunarEclipse, EclipseKind, LunarEclipseInfo
from typing import List


def PrintEclipse(e):
def PrintEclipse(e: LunarEclipseInfo) -> None:
# Calculate beginning/ending of different phases
# of an eclipse by subtracting/adding the peak time
# with the number of minutes indicated by the "semi-duration"
Expand All @@ -35,7 +36,7 @@ def PrintEclipse(e):
print()


def main(args):
def main(args: List[str]) -> int:
if len(args) == 1:
time = Time.Now()
elif len(args) == 2:
Expand Down
5 changes: 3 additions & 2 deletions demo/python/moonphase.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@
#
import sys
import astronomy
from typing import List

def QuarterName(quarter):
def QuarterName(quarter: int) -> str:
return [
'New Moon',
'First Quarter',
'Full Moon',
'Third Quarter'
][quarter]

def main(args):
def main(args: List[str]) -> int:
if len(args) == 1:
time = astronomy.Time.Now()
elif len(args) == 2:
Expand Down
7 changes: 4 additions & 3 deletions demo/python/riseset.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
# python3 riseset.py latitude longitude [yyyy-mm-ddThh:mm:ssZ]
#
import sys
from astronomy import Body, Direction, SearchRiseSet
from astronomy import Body, Time, Direction, SearchRiseSet
from astro_demo_common import ParseArgs
from typing import List, Optional

def PrintEvent(name, time):
def PrintEvent(name: str, time: Optional[Time]) -> None:
if time is None:
raise Exception('Failure to calculate ' + name)
print('{:<8s} : {}'.format(name, time))

def main(args):
def main(args: List[str]) -> int:
observer, time = ParseArgs(args)
sunrise = SearchRiseSet(Body.Sun, observer, Direction.Rise, time, 300)
sunset = SearchRiseSet(Body.Sun, observer, Direction.Set, time, 300)
Expand Down
22 changes: 15 additions & 7 deletions demo/python/triangulate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env python3
import sys
from math import sqrt
from astronomy import *
from typing import Tuple, Union


UsageText = r'''
USAGE: triangulate.py lat1 lon1 elv1 az1 alt1 lat2 lon2 elv2 az2 alt2
Expand All @@ -22,14 +25,17 @@

Verbose = False

def Debug(text):

def Debug(text: str) -> None:
if Verbose:
print(text)

def DotProduct(a, b):

def DotProduct(a: Vector, b: Vector) -> float:
return a.x*b.x + a.y*b.y + a.z*b.z

def Intersect(pos1, dir1, pos2, dir2):

def Intersect(pos1: Vector, dir1: Vector, pos2: Vector, dir2: Vector) -> Union[Tuple[Observer, float], Tuple[None, None]]:
F = DotProduct(dir1, dir2)
amb = Vector(pos1.x-pos2.x, pos1.y-pos2.y, pos1.z-pos2.z, pos1.t)
E = DotProduct(dir1, amb)
Expand All @@ -53,12 +59,13 @@ def Intersect(pos1, dir1, pos2, dir2):
c = Vector((a.x + b.x)/2, (a.y + b.y)/2, (a.z + b.z)/2, a.t)
Debug('c = {}'.format(c))
# Calculate the error radius in meters between the two skew lines.
dist = (KM_PER_AU * 1000 / 2) * math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2 + (a.z - b.z)**2)
dist = (KM_PER_AU * 1000 / 2) * sqrt((a.x - b.x)**2 + (a.y - b.y)**2 + (a.z - b.z)**2)
# Convert vector back to geographic coordinates
obs = VectorObserver(c, True)
return obs, dist

def DirectionVector(time, observer, altitude, azimuth):

def DirectionVector(time: Time, observer: Observer, altitude: float, azimuth: float) -> Vector:
# Convert horizontal angles to a horizontal unit vector.
hor = Spherical(altitude, azimuth, 1.0)
hvec = VectorFromHorizon(hor, time, Refraction.Airless)
Expand All @@ -68,6 +75,7 @@ def DirectionVector(time, observer, altitude, azimuth):
evec = RotateVector(rot, hvec)
return evec


if __name__ == '__main__':
# Validate and parse command line arguments.

Expand Down Expand Up @@ -108,15 +116,15 @@ def DirectionVector(time, observer, altitude, azimuth):

# Solve for the target point.
obs, error = Intersect(pos1, dir1, pos2, dir2)
if obs is None:
if obs is None or error is None:
print('ERROR: Could not find intersection.')
sys.exit(1)

print('Solution #1 = {}, err = {:0.3f} meters'.format(obs, error))

# Solve again with the inputs reversed, as a sanity check.
check_obs, check_error = Intersect(pos2, dir2, pos1, dir1)
if check_obs is None:
if check_obs is None or check_error is None:
print('INTERNAL ERROR: inconsistent solution.')
sys.exit(1)

Expand Down

0 comments on commit 871c26a

Please sign in to comment.