From 66eeb3e0a068b34f28550fa005253aba3d4c097a Mon Sep 17 00:00:00 2001 From: Don Cross Date: Thu, 5 May 2022 12:39:39 -0400 Subject: [PATCH] Python: Jupiter's moons returned by name It makes more sense to report Jupiter's moons with individually named structure fields rather than an array. It reduces the overall code and documentation size, and outside of unit testing, there are few cases where iterating over an array of moons is more lucid than using the names of the moons. This is a breaking change, but hopefully very few developers are using this function yet. Fixing the breakage is very simple. Also added operator overloads for adding and subtracting StateVector, just like we already had for Vector. --- demo/python/astronomy.py | 45 ++++++++++++++++++++++++---- demo/python/jupiter_moons.py | 15 +++++++--- generate/template/astronomy.py | 45 ++++++++++++++++++++++++---- generate/test.py | 18 ++++++----- source/python/README.md | 5 +++- source/python/astronomy/astronomy.py | 45 ++++++++++++++++++++++++---- 6 files changed, 146 insertions(+), 27 deletions(-) diff --git a/demo/python/astronomy.py b/demo/python/astronomy.py index e42b4cea..995a0368 100644 --- a/demo/python/astronomy.py +++ b/demo/python/astronomy.py @@ -265,6 +265,28 @@ class StateVector: self.vx, self.vy, self.vz, repr(self.t)) + def __add__(self, other): + return StateVector( + self.x + other.x, + self.y + other.y, + self.z + other.z, + self.vx + other.vx, + self.vy + other.vy, + self.vz + other.vz, + self.t + ) + + def __sub__(self, other): + return StateVector( + self.x - other.x, + self.y - other.y, + self.z - other.z, + self.vx - other.vx, + self.vy - other.vy, + self.vz - other.vz, + self.t + ) + @enum.unique class Body(enum.Enum): """The celestial bodies supported by Astronomy Engine calculations. @@ -3978,15 +4000,28 @@ class JupiterMoonsInfo: Attributes ---------- - moon : StateVector[4] - An array of state vectors, one for each of the four major moons - of Jupiter, in the following order: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto. + io : StateVector + The position and velocity of Jupiter's moon Io. + europa : StateVector + The position and velocity of Jupiter's moon Europa. + ganymede : StateVector + The position and velocity of Jupiter's moon Ganymede. + callisto : StateVector + The position and velocity of Jupiter's moon Callisto. """ def __init__(self, moon): - self.moon = moon + self.io = moon[0] + self.europa = moon[1] + self.ganymede = moon[2] + self.callisto = moon[3] def __repr__(self): - return 'JupiterMoonsInfo({})'.format(repr(self.moon)) + return 'JupiterMoonsInfo(io={}, europa={}, ganymede={}, callisto={})'.format( + repr(self.io), + repr(self.europa), + repr(self.ganymede), + repr(self.callisto) + ) def _JupiterMoon_elem2pv(time, mu, A, AL, K, H, Q, P): diff --git a/demo/python/jupiter_moons.py b/demo/python/jupiter_moons.py index b390593b..4cb9da33 100755 --- a/demo/python/jupiter_moons.py +++ b/demo/python/jupiter_moons.py @@ -52,11 +52,18 @@ if __name__ == '__main__': jm = JupiterMoons(backdate) + # Tricky: I'm "cheating" a little bit below by adding Vector `jv` + # to StateVector `jm.`, to result in a Vector position for each moon. + # This works because StateVector has all the fields that Vector has, + # plus the velocity components (vx, vy, vz). + # This is alarming to type purists, but just another normal day of + # "duck typing" for Pythonistas. + PrintBody('Jupiter', jv) - PrintBody('Io', jv + jm.moon[0]) - PrintBody('Europa', jv + jm.moon[1]) - PrintBody('Ganymede', jv + jm.moon[2]) - PrintBody('Callisto', jv + jm.moon[3]) + PrintBody('Io', jv + jm.io) + PrintBody('Europa', jv + jm.europa) + PrintBody('Ganymede', jv + jm.ganymede) + PrintBody('Callisto', jv + jm.callisto) print() sys.exit(0) diff --git a/generate/template/astronomy.py b/generate/template/astronomy.py index 82794623..437bc919 100644 --- a/generate/template/astronomy.py +++ b/generate/template/astronomy.py @@ -265,6 +265,28 @@ class StateVector: self.vx, self.vy, self.vz, repr(self.t)) + def __add__(self, other): + return StateVector( + self.x + other.x, + self.y + other.y, + self.z + other.z, + self.vx + other.vx, + self.vy + other.vy, + self.vz + other.vz, + self.t + ) + + def __sub__(self, other): + return StateVector( + self.x - other.x, + self.y - other.y, + self.z - other.z, + self.vx - other.vx, + self.vy - other.vy, + self.vz - other.vz, + self.t + ) + @enum.unique class Body(enum.Enum): """The celestial bodies supported by Astronomy Engine calculations. @@ -1936,15 +1958,28 @@ class JupiterMoonsInfo: Attributes ---------- - moon : StateVector[4] - An array of state vectors, one for each of the four major moons - of Jupiter, in the following order: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto. + io : StateVector + The position and velocity of Jupiter's moon Io. + europa : StateVector + The position and velocity of Jupiter's moon Europa. + ganymede : StateVector + The position and velocity of Jupiter's moon Ganymede. + callisto : StateVector + The position and velocity of Jupiter's moon Callisto. """ def __init__(self, moon): - self.moon = moon + self.io = moon[0] + self.europa = moon[1] + self.ganymede = moon[2] + self.callisto = moon[3] def __repr__(self): - return 'JupiterMoonsInfo({})'.format(repr(self.moon)) + return 'JupiterMoonsInfo(io={}, europa={}, ganymede={}, callisto={})'.format( + repr(self.io), + repr(self.europa), + repr(self.ganymede), + repr(self.callisto) + ) def _JupiterMoon_elem2pv(time, mu, A, AL, K, H, Q, P): diff --git a/generate/test.py b/generate/test.py index 331b83b3..9ec33213 100755 --- a/generate/test.py +++ b/generate/test.py @@ -109,6 +109,9 @@ def GeoMoon(): #----------------------------------------------------------------------------------------------------------- +def SelectJupiterMoon(jm, mindex): + return [jm.io, jm.europa, jm.ganymede, jm.callisto][mindex] + def AstroCheck(printflag): time = astronomy.Time.Make(1700, 1, 1, 0, 0, 0) stop = astronomy.Time.Make(2200, 1, 1, 0, 0, 0) @@ -147,7 +150,7 @@ def AstroCheck(printflag): jm = astronomy.JupiterMoons(time) if printflag: for mindex in range(4): - moon = jm.moon[mindex] + moon = SelectJupiterMoon(jm, mindex) print('j {:d} {:0.18e} {:0.18e} {:0.18e} {:0.18e} {:0.18e} {:0.18e} {:0.18e} {:0.18e}'.format(mindex, time.tt, time.ut, moon.x, moon.y, moon.z, moon.vx, moon.vy, moon.vz)) time = time.AddDays(dt) return 0 @@ -1813,19 +1816,20 @@ def JupiterMoons_CheckJpl(mindex, tt, pos, vel): vel_tolerance = 9.0e-4 time = astronomy.Time.FromTerrestrialTime(tt) jm = astronomy.JupiterMoons(time) + moon = SelectJupiterMoon(jm, mindex) - dx = v(pos[0] - jm.moon[mindex].x) - dy = v(pos[1] - jm.moon[mindex].y) - dz = v(pos[2] - jm.moon[mindex].z) + dx = v(pos[0] - moon.x) + dy = v(pos[1] - moon.y) + dz = v(pos[2] - moon.z) mag = sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]) pos_diff = sqrt(dx*dx + dy*dy + dz*dz) / mag if pos_diff > pos_tolerance: print('PY JupiterMoons_CheckJpl(mindex={}, tt={}): excessive position error {}'.format(mindex, tt, pos_diff)) return 1 - dx = v(vel[0] - jm.moon[mindex].vx) - dy = v(vel[1] - jm.moon[mindex].vy) - dz = v(vel[2] - jm.moon[mindex].vz) + dx = v(vel[0] - moon.vx) + dy = v(vel[1] - moon.vy) + dz = v(vel[2] - moon.vz) mag = sqrt(vel[0]*vel[0] + vel[1]*vel[1] + vel[2]*vel[2]) vel_diff = sqrt(dx*dx + dy*dy + dz*dz) / mag if vel_diff > vel_tolerance: diff --git a/source/python/README.md b/source/python/README.md index 5f589eba..27dd7173 100644 --- a/source/python/README.md +++ b/source/python/README.md @@ -507,7 +507,10 @@ and the velocities in AU/day. | Type | Attribute | Description | | --- | --- | --- | -| [`StateVector[4]`](#StateVector[4]) | `moon` | An array of state vectors, one for each of the four major moons of Jupiter, in the following order: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto. | +| [`StateVector`](#StateVector) | `io` | The position and velocity of Jupiter's moon Io. | +| [`StateVector`](#StateVector) | `europa` | The position and velocity of Jupiter's moon Europa. | +| [`StateVector`](#StateVector) | `ganymede` | The position and velocity of Jupiter's moon Ganymede. | +| [`StateVector`](#StateVector) | `callisto` | The position and velocity of Jupiter's moon Callisto. | --- diff --git a/source/python/astronomy/astronomy.py b/source/python/astronomy/astronomy.py index e42b4cea..995a0368 100644 --- a/source/python/astronomy/astronomy.py +++ b/source/python/astronomy/astronomy.py @@ -265,6 +265,28 @@ class StateVector: self.vx, self.vy, self.vz, repr(self.t)) + def __add__(self, other): + return StateVector( + self.x + other.x, + self.y + other.y, + self.z + other.z, + self.vx + other.vx, + self.vy + other.vy, + self.vz + other.vz, + self.t + ) + + def __sub__(self, other): + return StateVector( + self.x - other.x, + self.y - other.y, + self.z - other.z, + self.vx - other.vx, + self.vy - other.vy, + self.vz - other.vz, + self.t + ) + @enum.unique class Body(enum.Enum): """The celestial bodies supported by Astronomy Engine calculations. @@ -3978,15 +4000,28 @@ class JupiterMoonsInfo: Attributes ---------- - moon : StateVector[4] - An array of state vectors, one for each of the four major moons - of Jupiter, in the following order: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto. + io : StateVector + The position and velocity of Jupiter's moon Io. + europa : StateVector + The position and velocity of Jupiter's moon Europa. + ganymede : StateVector + The position and velocity of Jupiter's moon Ganymede. + callisto : StateVector + The position and velocity of Jupiter's moon Callisto. """ def __init__(self, moon): - self.moon = moon + self.io = moon[0] + self.europa = moon[1] + self.ganymede = moon[2] + self.callisto = moon[3] def __repr__(self): - return 'JupiterMoonsInfo({})'.format(repr(self.moon)) + return 'JupiterMoonsInfo(io={}, europa={}, ganymede={}, callisto={})'.format( + repr(self.io), + repr(self.europa), + repr(self.ganymede), + repr(self.callisto) + ) def _JupiterMoon_elem2pv(time, mu, A, AL, K, H, Q, P):