#!/usr/bin/env python3 import sys import math import re sys.path.append('../source/python') import astronomy #----------------------------------------------------------------------------------------------------------- def Test_AstroTime(): expected_ut = 6910.270978506945 expected_tt = 6910.271779431480 time = astronomy.Time.Make(2018, 12, 2, 18, 30, 12.543) diff = time.ut - expected_ut if abs(diff) > 1.0e-12: print('Test_AstroTime: excessive UT error {}'.format(diff)) sys.exit(1) diff = time.tt - expected_tt if abs(diff) > 1.0e-12: print('Test_AstroTime: excessive TT error {}'.format(diff)) sys.exit(1) s = str(time.Utc()) if s != '2018-12-02 18:30:12.543000': print('Test_AstroTime: Utc() returned incorrect string "{}"'.format(s)) sys.exit(1) time = astronomy.Time.Make(2018, 12, 31, 23, 59, 59.9994) s = str(time) if s != '2018-12-31T23:59:59.999Z': print('Test_AstroTime: expected 2018-12-31T23:59:59.999Z but found {}'.format(s)) sys.exit(1) time = astronomy.Time.Make(2018, 12, 31, 23, 59, 59.9995) s = str(time) if s != '2019-01-01T00:00:00.000Z': print('Test_AstroTime: expected 2019-01-01T00:00:00.000Z but found {}'.format(s)) sys.exit(1) print('Current time =', astronomy.Time.Now()) #----------------------------------------------------------------------------------------------------------- def Test_GeoMoon(): time = astronomy.Time.Make(2019, 6, 24, 15, 45, 37) vec = astronomy.GeoMoon(time) print('Test_GeoMoon: vec = {:0.16f}, {:0.16f}, {:0.16f}'.format(vec.x, vec.y, vec.z)) # Correct values obtained from C version of GeoMoon calculation cx, cy, cz = 0.002674036155459549, -0.0001531716308218381, -0.0003150201604895409 dx, dy, dz = vec.x - cx, vec.y - cy, vec.z - cz diff = math.sqrt(dx*dx + dy*dy + dz*dz) print('Test_GeoMoon: diff = {}'.format(diff)) if diff > 4.34e-19: print('Test_GeoMoon: EXCESSIVE ERROR') sys.exit(1) #----------------------------------------------------------------------------------------------------------- def Test_Issue46(): # https://github.com/cosinekitty/astronomy/issues/46 observer = astronomy.Observer(29, -81, 10) time = astronomy.Time(-93692.7685882873047376) print('time.ut = {:0.16f}'.format(time.ut)) print('time.tt = {:0.16f}'.format(time.tt)) body = astronomy.Body.Sun j2000 = astronomy.Equator(body, time, observer, False, False) print('j2000 ra = {:0.16f}'.format(j2000.ra)) print('j2000 dec = {:0.16f}'.format(j2000.dec)) ofdate = astronomy.Equator(body, time, observer, True, True) print('ofdate ra = {:0.16f}'.format(ofdate.ra)) print('ofdate dec = {:0.16f}'.format(ofdate.dec)) hor = astronomy.Horizon(time, observer, ofdate.ra, ofdate.dec, astronomy.Refraction.Airless) print('azimuth = {:0.16f}'.format(hor.azimuth)) print('altitude = {:0.16f}'.format(hor.altitude)) return 0 #----------------------------------------------------------------------------------------------------------- def Test_AstroCheck(printflag): time = astronomy.Time.Make(1700, 1, 1, 0, 0, 0) stop = astronomy.Time.Make(2200, 1, 1, 0, 0, 0) observer = astronomy.Observer(29, -81, 10) if printflag: print('o {:0.6f} {:0.6f} {:0.6f}'.format(observer.latitude, observer.longitude, observer.height)) dt = 10 + math.pi/100 bodylist = [ astronomy.Body.Sun, astronomy.Body.Moon, astronomy.Body.Mercury, astronomy.Body.Venus, astronomy.Body.Earth, astronomy.Body.Mars, astronomy.Body.Jupiter, astronomy.Body.Saturn, astronomy.Body.Uranus, astronomy.Body.Neptune, astronomy.Body.Pluto ] while time.tt < stop.tt: for body in bodylist: name = body.name if body != astronomy.Body.Moon: pos = astronomy.HelioVector(body, time) if printflag: print('v {} {:0.16f} {:0.16f} {:0.16f} {:0.16f}'.format(name, pos.t.tt, pos.x, pos.y, pos.z)) if body != astronomy.Body.Earth: j2000 = astronomy.Equator(body, time, observer, False, False) ofdate = astronomy.Equator(body, time, observer, True, True) hor = astronomy.Horizon(time, observer, ofdate.ra, ofdate.dec, astronomy.Refraction.Airless) if printflag: print('s {} {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f}'.format(name, time.tt, time.ut, j2000.ra, j2000.dec, j2000.dist, hor.azimuth, hor.altitude)) pos = astronomy.GeoMoon(time) if printflag: print('v GM {:0.16f} {:0.16f} {:0.16f} {:0.16f}'.format(pos.t.tt, pos.x, pos.y, pos.z)) j2000 = astronomy.Equator(astronomy.Body.Moon, time, observer, False, False) ofdate = astronomy.Equator(astronomy.Body.Moon, time, observer, True, True) hor = astronomy.Horizon(time, observer, ofdate.ra, ofdate.dec, astronomy.Refraction.Airless) if printflag: print('s GM {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f} {:0.16f}'.format(time.tt, time.ut, j2000.ra, j2000.dec, j2000.dist, hor.azimuth, hor.altitude)) time = time.AddDays(dt) #----------------------------------------------------------------------------------------------------------- def Test_Seasons(filename): with open(filename, 'rt') as infile: lnum = 0 current_year = 0 mar_count = sep_count = jun_count = dec_count = 0 max_minutes = 0.0 for line in infile: lnum += 1 line = line.strip() m = re.match(r'^(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z\s+([A-Za-z]+)$', line) if not m: print('Test_Seasons: Invalid data on line {} of file {}'.format(lnum, filename)) return 1 year = int(m.group(1)) month = int(m.group(2)) day = int(m.group(3)) hour = int(m.group(4)) minute = int(m.group(5)) name = m.group(6) if year != current_year: current_year = current_year seasons = astronomy.Seasons(year) correct_time = astronomy.Time.Make(year, month, day, hour, minute, 0) if name == 'Equinox': if month == 3: calc_time = seasons.mar_equinox mar_count += 1 elif month == 9: calc_time = seasons.sep_equinox sep_count += 1 else: print('Test_Seasons: {} line {}: Invalid equinox date in test data'.format(filename, lnum)) return 1 elif name == 'Solstice': if month == 6: calc_time = seasons.jun_solstice jun_count += 1 elif month == 12: calc_time = seasons.dec_solstice dec_count += 1 else: print('Test_Seasons: {} line {}: Invalid solstice date in test data'.format(filename, lnum)) return 1 elif name == 'Aphelion': continue # not yet calculated elif name == 'Perihelion': continue # not yet calculated else: print('Test_Seasons: {} line {}: unknown event type {}'.format(filename, lnum, name)) return 1 # Verify that the calculated time matches the correct time for this event. diff_minutes = (24.0 * 60.0) * abs(calc_time.tt - correct_time.tt) if diff_minutes > max_minutes: max_minutes = diff_minutes if diff_minutes > 1.7: print('Test_Seasons: {} line {}: excessive error ({}): {} minutes.'.format(filename, lnum, name, diff_minutes)) return 1 print('Test_Seasons: verified {} lines from file {} : max error minutes = {:0.3f}'.format(lnum, filename, max_minutes)) print('Test_Seasons: Event counts: mar={}, jun={}, sep={}, dec={}'.format(mar_count, jun_count, sep_count, dec_count)) return 0 #----------------------------------------------------------------------------------------------------------- def Test_MoonPhase(filename): threshold_seconds = 120.0 # max tolerable prediction error in seconds max_arcmin = 0.0 maxdiff = 0.0 quarter_count = 0 with open(filename, 'rt') as infile: lnum = 0 prev_year = 0 for line in infile: lnum += 1 line = line.strip() m = re.match(r'^([0-3]) (\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+\.\d+)Z$', line) if not m: print('Test_MoonPhase: invalid data format in {} line {}'.format(filename, lnum)) return 1 quarter = int(m.group(1)) year = int(m.group(2)) month = int(m.group(3)) day = int(m.group(4)) hour = int(m.group(5)) minute = int(m.group(6)) second = float(m.group(7)) expected_elong = 90.0 * quarter expected_time = astronomy.Time.Make(year, month, day, hour, minute, second) angle = astronomy.MoonPhase(expected_time) degree_error = abs(angle - expected_elong) if degree_error > 180.0: degree_error = 360.0 - degree_error arcmin = 60.0 * degree_error if arcmin > 1.0: print('Test_MoonPhase({} line {}): EXCESSIVE ANGULAR ERROR: {} arcmin'.format(filename, lnum, arcmin)) return 1 max_arcmin = max(max_arcmin, arcmin) if year != prev_year: prev_year = year # The test data contains a single year's worth of data for every 10 years. # Every time we see the year value change, it breaks continuity of the phases. # Start the search over again. start_time = astronomy.Time.Make(year, 1, 1, 0, 0, 0.0) mq = astronomy.SearchMoonQuarter(start_time) else: # Yet another lunar quarter in the same year. expected_quarter = (1 + mq.quarter) % 4 mq = astronomy.NextMoonQuarter(mq) # Expect the next consecutive quarter. if expected_quarter != mq.quarter: print('Test_MoonPhase({} line {}): SearchMoonQuarter returned quarter {}, but expected {}.'.format(filename, lnum, mq.quarter, expected_quarter)) return 1 quarter_count += 1 # Make sure the time matches what we expect. diff_seconds = abs(mq.time.tt - expected_time.tt) * (24.0 * 3600.0) if diff_seconds > threshold_seconds: print('Test_MoonPhase({} line {}): excessive time error {:0.3f} seconds.'.format(filename, lnum, diff_seconds)) return 1 maxdiff = max(maxdiff, diff_seconds) print('Test_MoonPhase: passed {} lines for file {} : max_arcmin = {:0.6f}, maxdiff = {:0.3f} seconds, {} quarters.' .format(lnum, filename, max_arcmin, maxdiff, quarter_count)) return 0 #----------------------------------------------------------------------------------------------------------- def TestElongFile(filename, targetRelLon): with open(filename, 'rt') as infile: lnum = 0 for line in infile: lnum += 1 line = line.strip() m = re.match(r'^(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z ([A-Za-z]+)$', line) if not m: print('TestElongFile({} line {}): invalid data format'.format(filename, lnum)) return 1 year = int(m.group(1)) month = int(m.group(2)) day = int(m.group(3)) hour = int(m.group(4)) minute = int(m.group(5)) name = m.group(6) body = astronomy.BodyCode(name) if body < 0: print('TestElongFile({} line {}): invalid body name "{}"'.format(filename, lnum, name)) return 1 search_time = astronomy.Time.Make(year, 1, 1, 0, 0, 0) expected_time = astronomy.Time.Make(year, month, day, hour, minute, 0) found_time = astronomy.SearchRelativeLongitude(body, targetRelLon, search_time) if found_time is None: print('TestElongFile({} line {}): SearchRelativeLongitude failed.'.format(filename, lnum)) return 1 diff_minutes = (24.0 * 60.0) * (found_time.tt - expected_time.tt) print('TestElongFile: {:<7s} error = {:6.3} minutes'.format(name, diff_minutes)) if abs(diff_minutes) > 15.0: print('TestElongFile({} line {}): EXCESSIVE ERROR.'.format(filename, lnum)) return 1 print('TestElongFile: passed {} rows of data'.format(lnum)) return 0 def TestPlanetLongitudes(body, outFileName, zeroLonEventName): startYear = 1700 stopYear = 2200 rlon = 0.0 sum_diff = 0.0 count = 0 name = body.name with open(outFileName, 'wt') as outfile: time = astronomy.Time.Make(startYear, 1, 1, 0, 0, 0) stopTime = astronomy.Time.Make(stopYear, 1, 1, 0, 0, 0) while time.tt < stopTime.tt: count += 1 event = zeroLonEventName if rlon == 0.0 else 'sup' found_time = astronomy.SearchRelativeLongitude(body, rlon, time) if found_time is None: print('TestPlanetLongitudes({}): SearchRelativeLongitudes failed'.format(name)) return 1 if count >= 2: # Check for consistent intervals. # Mainly I don't want to skip over an event! day_diff = found_time.tt - time.tt sum_diff += day_diff if count == 2: min_diff = max_diff = day_diff else: min_diff = min(min_diff, day_diff) max_diff = max(max_diff, day_diff) geo = astronomy.GeoVector(body, found_time, True) dist = geo.Length() outfile.write('e {} {} {:0.16f} {:0.16f}\n'.format(name, event, found_time.tt, dist)) # Search for the opposite longitude vent next time. time = found_time rlon = 180.0 - rlon if body == astronomy.Body.Mercury: thresh = 1.65 elif body == astronomy.Body.Mars: thresh = 1.30 else: thresh = 1.07 ratio = max_diff / min_diff print('TestPlanetLongitudes({:<7s}): {:5d} events, ratio={:5.3f}, file: {}'.format(name, count, ratio, outFileName)) if ratio > thresh: print('TestPlanetLongitudes({}): EXCESSIVE EVENT INTERVAL RATIO'.format(name)) return 1 return 0 ElongTestData = [ # Max elongation data obtained from: # http://www.skycaramba.com/greatest_elongations.shtml ( astronomy.Body.Mercury, "2010-01-17T05:22Z", "2010-01-27T05:22Z", 24.80, 'morning' ), ( astronomy.Body.Mercury, "2010-05-16T02:15Z", "2010-05-26T02:15Z", 25.10, 'morning' ), ( astronomy.Body.Mercury, "2010-09-09T17:24Z", "2010-09-19T17:24Z", 17.90, 'morning' ), ( astronomy.Body.Mercury, "2010-12-30T14:33Z", "2011-01-09T14:33Z", 23.30, 'morning' ), ( astronomy.Body.Mercury, "2011-04-27T19:03Z", "2011-05-07T19:03Z", 26.60, 'morning' ), ( astronomy.Body.Mercury, "2011-08-24T05:52Z", "2011-09-03T05:52Z", 18.10, 'morning' ), ( astronomy.Body.Mercury, "2011-12-13T02:56Z", "2011-12-23T02:56Z", 21.80, 'morning' ), ( astronomy.Body.Mercury, "2012-04-08T17:22Z", "2012-04-18T17:22Z", 27.50, 'morning' ), ( astronomy.Body.Mercury, "2012-08-06T12:04Z", "2012-08-16T12:04Z", 18.70, 'morning' ), ( astronomy.Body.Mercury, "2012-11-24T22:55Z", "2012-12-04T22:55Z", 20.60, 'morning' ), ( astronomy.Body.Mercury, "2013-03-21T22:02Z", "2013-03-31T22:02Z", 27.80, 'morning' ), ( astronomy.Body.Mercury, "2013-07-20T08:51Z", "2013-07-30T08:51Z", 19.60, 'morning' ), ( astronomy.Body.Mercury, "2013-11-08T02:28Z", "2013-11-18T02:28Z", 19.50, 'morning' ), ( astronomy.Body.Mercury, "2014-03-04T06:38Z", "2014-03-14T06:38Z", 27.60, 'morning' ), ( astronomy.Body.Mercury, "2014-07-02T18:22Z", "2014-07-12T18:22Z", 20.90, 'morning' ), ( astronomy.Body.Mercury, "2014-10-22T12:36Z", "2014-11-01T12:36Z", 18.70, 'morning' ), ( astronomy.Body.Mercury, "2015-02-14T16:20Z", "2015-02-24T16:20Z", 26.70, 'morning' ), ( astronomy.Body.Mercury, "2015-06-14T17:10Z", "2015-06-24T17:10Z", 22.50, 'morning' ), ( astronomy.Body.Mercury, "2015-10-06T03:20Z", "2015-10-16T03:20Z", 18.10, 'morning' ), ( astronomy.Body.Mercury, "2016-01-28T01:22Z", "2016-02-07T01:22Z", 25.60, 'morning' ), ( astronomy.Body.Mercury, "2016-05-26T08:45Z", "2016-06-05T08:45Z", 24.20, 'morning' ), ( astronomy.Body.Mercury, "2016-09-18T19:27Z", "2016-09-28T19:27Z", 17.90, 'morning' ), ( astronomy.Body.Mercury, "2017-01-09T09:42Z", "2017-01-19T09:42Z", 24.10, 'morning' ), ( astronomy.Body.Mercury, "2017-05-07T23:19Z", "2017-05-17T23:19Z", 25.80, 'morning' ), ( astronomy.Body.Mercury, "2017-09-02T10:14Z", "2017-09-12T10:14Z", 17.90, 'morning' ), ( astronomy.Body.Mercury, "2017-12-22T19:48Z", "2018-01-01T19:48Z", 22.70, 'morning' ), ( astronomy.Body.Mercury, "2018-04-19T18:17Z", "2018-04-29T18:17Z", 27.00, 'morning' ), ( astronomy.Body.Mercury, "2018-08-16T20:35Z", "2018-08-26T20:35Z", 18.30, 'morning' ), ( astronomy.Body.Mercury, "2018-12-05T11:34Z", "2018-12-15T11:34Z", 21.30, 'morning' ), ( astronomy.Body.Mercury, "2019-04-01T19:40Z", "2019-04-11T19:40Z", 27.70, 'morning' ), ( astronomy.Body.Mercury, "2019-07-30T23:08Z", "2019-08-09T23:08Z", 19.00, 'morning' ), ( astronomy.Body.Mercury, "2019-11-18T10:31Z", "2019-11-28T10:31Z", 20.10, 'morning' ), ( astronomy.Body.Mercury, "2010-03-29T23:32Z", "2010-04-08T23:32Z", 19.40, 'evening' ), ( astronomy.Body.Mercury, "2010-07-28T01:03Z", "2010-08-07T01:03Z", 27.40, 'evening' ), ( astronomy.Body.Mercury, "2010-11-21T15:42Z", "2010-12-01T15:42Z", 21.50, 'evening' ), ( astronomy.Body.Mercury, "2011-03-13T01:07Z", "2011-03-23T01:07Z", 18.60, 'evening' ), ( astronomy.Body.Mercury, "2011-07-10T04:56Z", "2011-07-20T04:56Z", 26.80, 'evening' ), ( astronomy.Body.Mercury, "2011-11-04T08:40Z", "2011-11-14T08:40Z", 22.70, 'evening' ), ( astronomy.Body.Mercury, "2012-02-24T09:39Z", "2012-03-05T09:39Z", 18.20, 'evening' ), ( astronomy.Body.Mercury, "2012-06-21T02:00Z", "2012-07-01T02:00Z", 25.70, 'evening' ), ( astronomy.Body.Mercury, "2012-10-16T21:59Z", "2012-10-26T21:59Z", 24.10, 'evening' ), ( astronomy.Body.Mercury, "2013-02-06T21:24Z", "2013-02-16T21:24Z", 18.10, 'evening' ), ( astronomy.Body.Mercury, "2013-06-02T16:45Z", "2013-06-12T16:45Z", 24.30, 'evening' ), ( astronomy.Body.Mercury, "2013-09-29T09:59Z", "2013-10-09T09:59Z", 25.30, 'evening' ), ( astronomy.Body.Mercury, "2014-01-21T10:00Z", "2014-01-31T10:00Z", 18.40, 'evening' ), ( astronomy.Body.Mercury, "2014-05-15T07:06Z", "2014-05-25T07:06Z", 22.70, 'evening' ), ( astronomy.Body.Mercury, "2014-09-11T22:20Z", "2014-09-21T22:20Z", 26.40, 'evening' ), ( astronomy.Body.Mercury, "2015-01-04T20:26Z", "2015-01-14T20:26Z", 18.90, 'evening' ), ( astronomy.Body.Mercury, "2015-04-27T04:46Z", "2015-05-07T04:46Z", 21.20, 'evening' ), ( astronomy.Body.Mercury, "2015-08-25T10:20Z", "2015-09-04T10:20Z", 27.10, 'evening' ), ( astronomy.Body.Mercury, "2015-12-19T03:11Z", "2015-12-29T03:11Z", 19.70, 'evening' ), ( astronomy.Body.Mercury, "2016-04-08T14:00Z", "2016-04-18T14:00Z", 19.90, 'evening' ), ( astronomy.Body.Mercury, "2016-08-06T21:24Z", "2016-08-16T21:24Z", 27.40, 'evening' ), ( astronomy.Body.Mercury, "2016-12-01T04:36Z", "2016-12-11T04:36Z", 20.80, 'evening' ), ( astronomy.Body.Mercury, "2017-03-22T10:24Z", "2017-04-01T10:24Z", 19.00, 'evening' ), ( astronomy.Body.Mercury, "2017-07-20T04:34Z", "2017-07-30T04:34Z", 27.20, 'evening' ), ( astronomy.Body.Mercury, "2017-11-14T00:32Z", "2017-11-24T00:32Z", 22.00, 'evening' ), ( astronomy.Body.Mercury, "2018-03-05T15:07Z", "2018-03-15T15:07Z", 18.40, 'evening' ), ( astronomy.Body.Mercury, "2018-07-02T05:24Z", "2018-07-12T05:24Z", 26.40, 'evening' ), ( astronomy.Body.Mercury, "2018-10-27T15:25Z", "2018-11-06T15:25Z", 23.30, 'evening' ), ( astronomy.Body.Mercury, "2019-02-17T01:23Z", "2019-02-27T01:23Z", 18.10, 'evening' ), ( astronomy.Body.Mercury, "2019-06-13T23:14Z", "2019-06-23T23:14Z", 25.20, 'evening' ), ( astronomy.Body.Mercury, "2019-10-10T04:00Z", "2019-10-20T04:00Z", 24.60, 'evening' ), ( astronomy.Body.Venus, "2010-12-29T15:57Z", "2011-01-08T15:57Z", 47.00, 'morning' ), ( astronomy.Body.Venus, "2012-08-05T08:59Z", "2012-08-15T08:59Z", 45.80, 'morning' ), ( astronomy.Body.Venus, "2014-03-12T19:25Z", "2014-03-22T19:25Z", 46.60, 'morning' ), ( astronomy.Body.Venus, "2015-10-16T06:57Z", "2015-10-26T06:57Z", 46.40, 'morning' ), ( astronomy.Body.Venus, "2017-05-24T13:09Z", "2017-06-03T13:09Z", 45.90, 'morning' ), ( astronomy.Body.Venus, "2018-12-27T04:24Z", "2019-01-06T04:24Z", 47.00, 'morning' ), ( astronomy.Body.Venus, "2010-08-10T03:19Z", "2010-08-20T03:19Z", 46.00, 'evening' ), ( astronomy.Body.Venus, "2012-03-17T08:03Z", "2012-03-27T08:03Z", 46.00, 'evening' ), ( astronomy.Body.Venus, "2013-10-22T08:00Z", "2013-11-01T08:00Z", 47.10, 'evening' ), ( astronomy.Body.Venus, "2015-05-27T18:46Z", "2015-06-06T18:46Z", 45.40, 'evening' ), ( astronomy.Body.Venus, "2017-01-02T13:19Z", "2017-01-12T13:19Z", 47.10, 'evening' ), ( astronomy.Body.Venus, "2018-08-07T17:02Z", "2018-08-17T17:02Z", 45.90, 'evening' ) ] def ParseDate(text): m = re.match(r'^(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z$', text) if not m: print('ParseDate: invalid date text "{}"'.format(text)) raise Exception('Bad elongation test data') year = int(m.group(1)) month = int(m.group(2)) day = int(m.group(3)) hour = int(m.group(4)) minute = int(m.group(5)) return astronomy.Time.Make(year, month, day, hour, minute, 0) def TestMaxElong(body, searchText, eventText, angle, visibility): name = body.name searchTime = ParseDate(searchText) eventTime = ParseDate(eventText) evt = astronomy.SearchMaxElongation(body, searchTime) if evt is None: print('TestMaxElong({} {}): SearchMaxElongation failed.'.format(name, searchText)) return 1 if evt.visibility != visibility: print('TestMaxElong({} {}): SearchMaxElongation returned visibility {}, but expected {}'.format(name, searchText, evt.visibility.name, visibility.name)) return 1 hour_diff = 24.0 * abs(evt.time.tt - eventTime.tt) arcmin_diff = 60.0 * abs(evt.elongation - angle) print('TestMaxElong: {:<7s} {:<7s} elong={:5.2f} ({:4.2f} arcmin, {:5.3f} hours)'.format(name, visibility.name, evt.elongation, arcmin_diff, hour_diff)) if hour_diff > 0.6: print('TestMaxElong({} {}): EXCESSIVE HOUR ERROR.'.format(name, searchText)) return 1 if arcmin_diff > 3.4: print('TestMaxElong({} {}): EXCESSIVE ARCMIN ERROR.'.format(name, searchText)) return 1 return 0 def SearchElongTest(): for (body, searchText, eventText, angle, visibility) in ElongTestData: if 0 != TestMaxElong(body, searchText, eventText, angle, astronomy.Visibility[visibility.title()]): return 1 return 0 def Test_Elongation(): if 0 != TestElongFile('longitude/opposition_2018.txt', 0.0): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Mercury, "temp/py_longitude_Mercury.txt", "inf"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Venus, "temp/py_longitude_Venus.txt", "inf"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Mars, "temp/py_longitude_Mars.txt", "opp"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Jupiter, "temp/py_longitude_Jupiter.txt", "opp"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Saturn, "temp/py_longitude_Saturn.txt", "opp"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Uranus, "temp/py_longitude_Uranus.txt", "opp"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Neptune, "temp/py_longitude_Neptune.txt", "opp"): return 1 if 0 != TestPlanetLongitudes(astronomy.Body.Pluto, "temp/py_longitude_Pluto.txt", "opp"): return 1 if 0 != SearchElongTest(): return 1 return 0 #----------------------------------------------------------------------------------------------------------- def ParseJplHorizonsDateTime(line): m = re.match(r'^\s*(\d{4})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2})\s(\d{2}):(\d{2})\s+(.*)$', line) if not m: return None, None year = int(m.group(1)) month = 1 + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].index(m.group(2)) day = int(m.group(3)) hour = int(m.group(4)) minute = int(m.group(5)) rest = m.group(6) time = astronomy.Time.Make(year, month, day, hour, minute, 0) return time, rest def CheckMagnitudeData(body, filename): limit = 0.012 sum_squared_diff = 0.0 with open(filename, 'rt') as infile: count = lnum = 0 for line in infile: lnum += 1 line = line.strip() (time, rest) = ParseJplHorizonsDateTime(line) if (time is not None) and (rest is not None) and not ('n.a.' in rest): data = [float(t) for t in rest.split()] if len(data) != 7: print('CheckMagnitudeData({} line {}): invalid data format'.format(filename, lnum)) return 1 (mag, sbrt, dist, rdot, delta, deldot, phase_angle) = data illum = astronomy.Illumination(body, time) diff = illum.mag - mag if abs(diff) > limit: print('CheckMagnitudeData({} line {}): EXCESSIVE ERROR: correct mag={}, calc mag={}'.format(filename, lnum, mag, illum.mag)) return 1 sum_squared_diff += diff * diff if count == 0: diff_lo = diff_hi = diff else: diff_lo = min(diff_lo, diff) diff_hi = max(diff_hi, diff) count += 1 if count == 0: print('CheckMagnitudeData: Did not find any data in file: {}'.format(filename)) return 1 rms = math.sqrt(sum_squared_diff / count) print('CheckMagnitudeData: {:<21s} {:5d} rows diff_lo={:0.4f} diff_hi={:0.4f} rms={:0.4f}'.format(filename, count, diff_lo, diff_hi, rms)) return 0 def CheckSaturn(): # JPL Horizons does not include Saturn's rings in its magnitude models. # I still don't have authoritative test data for Saturn's magnitude. # For now, I just test for consistency with Paul Schlyter's formulas at: # http://www.stjarnhimlen.se/comp/ppcomp.html#15 data = [ ( "1972-01-01T00:00Z", -0.31904865, +24.50061220 ), ( "1980-01-01T00:00Z", +0.85213663, -1.85761461 ), ( "2009-09-04T00:00Z", +1.01626809, +0.08380716 ), ( "2017-06-15T00:00Z", -0.12318790, -26.60871409 ), ( "2019-05-01T00:00Z", +0.32954097, -23.53880802 ), ( "2025-09-25T00:00Z", +0.51286575, +1.52327932 ), ( "2032-05-15T00:00Z", -0.04652109, +26.95717765 ) ] for (dtext, mag, tilt) in data: time = ParseDate(dtext) illum = astronomy.Illumination(astronomy.Body.Saturn, time) print('Saturn: date={} calc mag={:12.8f} ring_tilt={:12.8f}'.format(dtext, illum.mag, illum.ring_tilt)) mag_diff = abs(illum.mag - mag) if mag_diff > 1.0e-8: print('CheckSaturn: Excessive magnitude error {}'.format(mag_diff)) return 1 tilt_diff = abs(illum.ring_tilt - tilt) if (tilt_diff > 1.0e-8): print('CheckSaturn: Excessive ring tilt error {}'.format(tilt_diff)) return 1 return 0 def TestMaxMag(body, filename): # Example of input data: # # 2001-02-21T08:00Z 2001-02-27T08:00Z 23.17 19.53 -4.84 # # JPL Horizons test data has limited floating point precision in the magnitude values. # There is a pair of dates for the beginning and end of the max magnitude period, # given the limited precision. We pick the point halfway between as the supposed max magnitude time. with open(filename, 'rt') as infile: lnum = 0 search_time = astronomy.Time.Make(2001, 1, 1, 0, 0, 0) for line in infile: lnum += 1 line = line.strip() tokenlist = line.split() if len(tokenlist) == 5: time1 = ParseDate(tokenlist[0]) time2 = ParseDate(tokenlist[1]) if time1 and time2: center_time = time1.AddDays(0.5*(time2.ut - time1.ut)) correct_mag = float(tokenlist[4]) illum = astronomy.SearchPeakMagnitude(body, search_time) mag_diff = abs(illum.mag - correct_mag) hours_diff = 24.0 * abs(illum.time.ut - center_time.ut) print('TestMaxMag: mag_diff={:0.3f}, hours_diff={:0.3f}'.format(mag_diff, hours_diff)) if hours_diff > 7.1: print('TestMaxMag({} line {}): EXCESSIVE TIME DIFFERENCE.'.format(filename, lnum)) return 1 if mag_diff > 0.005: print('TestMaxMag({} line {}): EXCESSIVE MAGNITUDE DIFFERENCE.'.format(filename, lnum)) return 1 search_time = time2 print('TestMaxMag: processed {} lines from file {}'.format(lnum, filename)) return 0 def Test_Magnitude(): nfailed = 0 nfailed += CheckMagnitudeData(astronomy.Body.Sun, 'magnitude/Sun.txt') nfailed += CheckMagnitudeData(astronomy.Body.Moon, 'magnitude/Moon.txt') nfailed += CheckMagnitudeData(astronomy.Body.Mercury, 'magnitude/Mercury.txt') nfailed += CheckMagnitudeData(astronomy.Body.Venus, 'magnitude/Venus.txt') nfailed += CheckMagnitudeData(astronomy.Body.Mars, 'magnitude/Mars.txt') nfailed += CheckMagnitudeData(astronomy.Body.Jupiter, 'magnitude/Jupiter.txt') nfailed += CheckSaturn() nfailed += CheckMagnitudeData(astronomy.Body.Uranus, 'magnitude/Uranus.txt') nfailed += CheckMagnitudeData(astronomy.Body.Neptune, 'magnitude/Neptune.txt') nfailed += CheckMagnitudeData(astronomy.Body.Pluto, 'magnitude/Pluto.txt') nfailed += TestMaxMag(astronomy.Body.Venus, 'magnitude/maxmag_Venus.txt') if nfailed > 0: print('Test_Magnitude: failed {} test(s).'.format(nfailed)) return 1 return 0 #----------------------------------------------------------------------------------------------------------- def Test_RiseSet(filename): sum_minutes = 0.0 max_minutes = 0.0 nudge_days = 0.01 observer = None current_body = None a_dir = 0 b_dir = 0 with open(filename, 'rt') as infile: lnum = 0 for line in infile: lnum += 1 line = line.strip() # Moon 103 -61 1944-01-02T17:08Z s # Moon 103 -61 1944-01-03T05:47Z r m = re.match(r'^([A-Za-z]+)\s+(-?[0-9\.]+)\s+(-?[0-9\.]+)\s+(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z\s+([sr])$', line) if not m: print('Test_RiseSet({} line {}): invalid data format'.format(filename, lnum)) return 1 name = m.group(1) longitude = float(m.group(2)) latitude = float(m.group(3)) year = int(m.group(4)) month = int(m.group(5)) day = int(m.group(6)) hour = int(m.group(7)) minute = int(m.group(8)) kind = m.group(9) correct_time = astronomy.Time.Make(year, month, day, hour, minute, 0) direction = astronomy.Direction.Rise if kind == 'r' else astronomy.Direction.Set body = astronomy.BodyCode(name) if body < 0: print('Test_RiseSet({} line {}): invalid body name "{}"'.format(filename, lnum, name)) return 1 # Every time we see a new geographic location, start a new iteration # of finding all rise/set times for that UTC calendar year. if (observer is None) or (observer.latitude != latitude) or (observer.longitude != longitude) or (current_body != body): current_body = body observer = astronomy.Observer(latitude, longitude, 0) r_search_date = s_search_date = astronomy.Time.Make(year, 1, 1, 0, 0, 0) b_evt = None print('Test_RiseSet: {:<7s} lat={:0.1f} lon={:0.1f}'.format(name, latitude, longitude)) if b_evt is not None: # Recycle the second event from the previous iteration as the first event. a_evt = b_evt a_dir = b_dir b_evt = None else: r_evt = astronomy.SearchRiseSet(body, observer, astronomy.Direction.Rise, r_search_date, 366.0) if r_evt is None: print('Test_RiseSet({} line {}): rise search failed'.format(filename, lnum)) return 1 s_evt = astronomy.SearchRiseSet(body, observer, astronomy.Direction.Set, s_search_date, 366.0) if s_evt is None: print('Test_RiseSet({} line {}): set search failed'.format(filename, lnum)) return 1 # Expect the current event to match the earlier of the found times. if r_evt.tt < s_evt.tt: a_evt = r_evt b_evt = s_evt a_dir = astronomy.Direction.Rise b_dir = astronomy.Direction.Set else: a_evt = s_evt b_evt = r_evt a_dir = astronomy.Direction.Set b_dir = astronomy.Direction.Rise # Nudge the event times forward a tiny amount. r_search_date = r_evt.AddDays(nudge_days) s_search_date = s_evt.AddDays(nudge_days) if a_dir != direction: print('Test_RiseSet({} line {}): expected dir={} but found {}'.format(filename, lnum, a_dir, direction)) return 1 error_minutes = (24.0 * 60.0) * abs(a_evt.tt - correct_time.tt) sum_minutes += error_minutes ** 2 max_minutes = max(max_minutes, error_minutes) if error_minutes > 0.56: print('Test_RiseSet({} line {}): excessive prediction time error = {} minutes.'.format(filename, lnum, error_minutes)) print(' correct = {}, calculated = {}'.format(correct_time, a_evt)) return 1 rms_minutes = math.sqrt(sum_minutes / lnum) print('Test_RiseSet: passed {} lines: time errors in minutes: rms={:0.4f}, max={:0.4f}'.format(lnum, rms_minutes, max_minutes)) return 0 #----------------------------------------------------------------------------------------------------------- def LunarApsis(filename): max_minutes = 0.0 max_km = 0.0 with open(filename, 'rt') as infile: start_time = astronomy.Time.Make(2001, 1, 1, 0, 0, 0) lnum = 0 for line in infile: lnum += 1 if lnum == 1: apsis = astronomy.SearchLunarApsis(start_time) else: apsis = astronomy.NextLunarApsis(apsis) tokenlist = line.split() if len(tokenlist) != 3: print('LunarApsis({} line {}): invalid data format'.format(filename, lnum)) return 1 correct_time = ParseDate(tokenlist[1]) if not correct_time: print('LunarApsis({} line {}): invalid time'.format(filename, lnum)) return 1 kind = astronomy.ApsisKind(int(tokenlist[0])) if apsis.kind != kind: print('LunarApsis({} line {}): Expected kind {} but found {}'.format(filename, lnum, kind, apsis.kind)) return 1 dist_km = float(tokenlist[2]) diff_minutes = (24.0 * 60.0) * abs(apsis.time.ut - correct_time.ut) diff_km = abs(apsis.dist_km - dist_km) if diff_minutes > 35.0: print('LunarApsis({} line {}): Excessive time error = {} minutes.'.format(filename, lnum, diff_minutes)) return 1 if diff_km > 25.0: print('LunarApsis({} line {}): Excessive distance error = {} km.'.format(filename, lnum, diff_km)) return 1 max_minutes = max(max_minutes, diff_minutes) max_km = max(max_km, diff_km) print('LunarApsis: found {} events, max time error = {:0.3f} minutes, max distance error = {:0.3f} km.'.format(lnum, max_minutes, max_km)) return 0 def Test_Apsis(): if 0 != LunarApsis('apsides/moon.txt'): return 1 return 0 #----------------------------------------------------------------------------------------------------------- def CompareMatrices(caller, a, b, tolerance): for i in range(3): for j in range(3): diff = abs(a.rot[i][j] - b.rot[i][j]) if diff > tolerance: print('ERROR({}): matrix[{}][{}] = {}, expected {}, diff {}'.format(caller, i, j, a.rot[i][j], b.rot[i][j], diff)) sys.exit(1) def Rotation_MatrixInverse(): a = astronomy.RotationMatrix([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) v = astronomy.RotationMatrix([ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]) b = astronomy.InverseRotation(a) CompareMatrices('Rotation_MatrixInverse', b, v, 0) def Rotation_MatrixMultiply(): a = astronomy.RotationMatrix([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) b = astronomy.RotationMatrix([ [10, 13, 16], [11, 14, 17], [12, 15, 18] ]) v = astronomy.RotationMatrix([ [84, 201, 318], [90, 216, 342], [96, 231, 366] ]) c = astronomy.CombineRotation(b, a) CompareMatrices('Rotation_MatrixMultiply', c, v, 0) def VectorDiff(a, b): dx = a.x - b.x dy = a.y - b.y dz = a.z - b.z return math.sqrt(dx*dx + dy*dy + dz*dz) def Test_EQJ_ECL(): r = astronomy.Rotation_EQJ_ECL() # Calculate heliocentric Earth position at a test time. time = astronomy.Time.Make(2019, 12, 8, 19, 39, 15) ev = astronomy.HelioVector(astronomy.Body.Earth, time) # Use the existing astronomy.Ecliptic() to calculate ecliptic vector and angles. ecl = astronomy.Ecliptic(ev) print('Test_EQJ_ECL ecl = ({}, {}, {})'.format(ecl.ex, ecl.ey, ecl.ez)) # Now compute the same vector via rotation matrix. ee = astronomy.RotateVector(r, ev) dx = ee.x - ecl.ex dy = ee.y - ecl.ey dz = ee.z - ecl.ez diff = math.sqrt(dx*dx + dy*dy + dz*dz) print('Test_EQJ_ECL ee = ({}, {}, {}); diff = {}'.format(ee.x, ee.y, ee.z, diff)) if diff > 1.0e-16: print('Test_EQJ_ECL: EXCESSIVE VECTOR ERROR') sys.exit(1) # Reverse the test: go from ecliptic back to equatorial. ir = astronomy.Rotation_ECL_EQJ() et = astronomy.RotateVector(ir, ee) idiff = VectorDiff(et, ev) print('Test_EQJ_ECL ev diff = {}'.format(idiff)) if idiff > 1.0e-16: print('Test_EQJ_ECL: EXCESSIVE REVERSE ROTATION ERROR') sys.exit(1) def Test_EQJ_EQD(body): # Verify conversion of equatorial J2000 to equatorial of-date, and back. # Use established functions to calculate spherical coordinates for the body, in both EQJ and EQD. time = astronomy.Time.Make(2019, 12, 8, 20, 50, 0) observer = astronomy.Observer(+35, -85, 0) eq2000 = astronomy.Equator(body, time, observer, False, True) eqdate = astronomy.Equator(body, time, observer, True, True) # Convert EQJ spherical coordinates to vector. v2000 = astronomy.VectorFromEquator(eq2000, time) # Find rotation matrix. r = astronomy.Rotation_EQJ_EQD(time) # Rotate EQJ vector to EQD vector. vdate = astronomy.RotateVector(r, v2000) # Convert vector back to angular equatorial coordinates. equcheck = astronomy.EquatorFromVector(vdate) # Compare the result with the eqdate. ra_diff = abs(equcheck.ra - eqdate.ra) dec_diff = abs(equcheck.dec - eqdate.dec) dist_diff = abs(equcheck.dist - eqdate.dist) print('Test_EQJ_EQD: {} ra={}, dec={}, dist={}, ra_diff={}, dec_diff={}, dist_diff={}'.format( body.name, eqdate.ra, eqdate.dec, eqdate.dist, ra_diff, dec_diff, dist_diff )) if ra_diff > 1.0e-14 or dec_diff > 1.0e-14 or dist_diff > 4.0e-15: print('Test_EQJ_EQD: EXCESSIVE ERROR') sys.exit(1) # Perform the inverse conversion back to equatorial J2000 coordinates. ir = astronomy.Rotation_EQD_EQJ(time) t2000 = astronomy.RotateVector(ir, vdate) diff = VectorDiff(t2000, v2000) print('Test_EQJ_EQD: {} inverse diff = {}'.format(body.name, diff)) if diff > 3.0e-15: print('Test_EQJ_EQD: EXCESSIVE INVERSE ERROR') sys.exit(1) def Test_EQD_HOR(body): # Use existing functions to calculate horizontal coordinates of the body for the time+observer. time = astronomy.Time.Make(1970, 12, 13, 5, 15, 0) observer = astronomy.Observer(-37, +45, 0) eqd = astronomy.Equator(body, time, observer, True, True) print('Test_EQD_HOR {}: OFDATE ra={}, dec={}'.format(body.name, eqd.ra, eqd.dec)) hor = astronomy.Horizon(time, observer, eqd.ra, eqd.dec, astronomy.Refraction.Normal) # Calculate the position of the body as an equatorial vector of date. vec_eqd = astronomy.VectorFromEquator(eqd, time) # Calculate rotation matrix to convert equatorial J2000 vector to horizontal vector. rot = astronomy.Rotation_EQD_HOR(time, observer) # Rotate the equator of date vector to a horizontal vector. vec_hor = astronomy.RotateVector(rot, vec_eqd) # Convert the horizontal vector to horizontal angular coordinates. xsphere = astronomy.HorizonFromVector(vec_hor, astronomy.Refraction.Normal) diff_alt = abs(xsphere.lat - hor.altitude) diff_az = abs(xsphere.lon - hor.azimuth) print('Test_EQD_HOR {}: trusted alt={}, az={}; test alt={}, az={}; diff_alt={}, diff_az={}'.format( body.name, hor.altitude, hor.azimuth, xsphere.lat, xsphere.lon, diff_alt, diff_az)) if diff_alt > 4.0e-14 or diff_az > 1.0e-13: print('Test_EQD_HOR: EXCESSIVE HORIZONTAL ERROR.') sys.exit(1) # Confirm that we can convert back to horizontal vector. check_hor = astronomy.VectorFromHorizon(xsphere, time, astronomy.Refraction.Normal) diff = VectorDiff(check_hor, vec_hor) print('Test_EQD_HOR {}: horizontal recovery: diff = {}'.format(body.name, diff)) if diff > 2.0e-15: print('Test_EQD_HOR: EXCESSIVE ERROR IN HORIZONTAL RECOVERY.') sys.exit(1) # Verify the inverse translation from horizontal vector to equatorial of-date vector. irot = astronomy.Rotation_HOR_EQD(time, observer) check_eqd = astronomy.RotateVector(irot, vec_hor) diff = VectorDiff(check_eqd, vec_eqd) print('Test_EQD_HOR {}: OFDATE inverse rotation diff = {}'.format(body.name, diff)) if diff > 2.0e-15: print('Test_EQD_HOR: EXCESSIVE OFDATE INVERSE HORIZONTAL ERROR.') sys.exit(1) # Exercise HOR to EQJ translation. eqj = astronomy.Equator(body, time, observer, False, True) vec_eqj = astronomy.VectorFromEquator(eqj, time) yrot = astronomy.Rotation_HOR_EQJ(time, observer) check_eqj = astronomy.RotateVector(yrot, vec_hor) diff = VectorDiff(check_eqj, vec_eqj) print('Test_EQD_HOR {}: J2000 inverse rotation diff = {}'.format(body.name, diff)) if diff > 4.0e-15: print('Test_EQD_HOR: EXCESSIVE J2000 INVERSE HORIZONTAL ERROR.') sys.exit(1) # Verify the inverse translation: EQJ to HOR. zrot = astronomy.Rotation_EQJ_HOR(time, observer) another_hor = astronomy.RotateVector(zrot, vec_eqj) diff = VectorDiff(another_hor, vec_hor) print('Test_EQD_HOR {}: EQJ inverse rotation diff = {}'.format(body.name, diff)) if diff > 3.0e-15: print('Test_EQD_HOR: EXCESSIVE EQJ INVERSE HORIZONTAL ERROR.') sys.exit(1) IdentityMatrix = astronomy.RotationMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) def CheckInverse(aname, bname, arot, brot): crot = astronomy.CombineRotation(arot, brot) caller = 'CheckInverse({},{})'.format(aname, bname) CompareMatrices(caller, crot, IdentityMatrix, 2.0e-15) def CheckCycle(cyclename, arot, brot, crot): xrot = astronomy.CombineRotation(arot, brot) irot = astronomy.InverseRotation(xrot) CompareMatrices(cyclename, crot, irot, 2.0e-15) def Test_RotRoundTrip(): # In each round trip, calculate a forward rotation and a backward rotation. # Verify the two are inverse matrices. time = astronomy.Time.Make(2067, 5, 30, 14, 45, 0) observer = astronomy.Observer(+28, -82, 0) # Round trip #1: EQJ <==> EQD. eqj_eqd = astronomy.Rotation_EQJ_EQD(time) eqd_eqj = astronomy.Rotation_EQD_EQJ(time) CheckInverse('eqj_eqd', 'eqd_eqj', eqj_eqd, eqd_eqj) # Round trip #2: EQJ <==> ECL. eqj_ecl = astronomy.Rotation_EQJ_ECL() ecl_eqj = astronomy.Rotation_ECL_EQJ() CheckInverse('eqj_ecl', 'ecl_eqj', eqj_ecl, ecl_eqj) # Round trip #3: EQJ <==> HOR. eqj_hor = astronomy.Rotation_EQJ_HOR(time, observer) hor_eqj = astronomy.Rotation_HOR_EQJ(time, observer) CheckInverse('eqj_hor', 'hor_eqj', eqj_hor, hor_eqj) # Round trip #4: EQD <==> HOR. eqd_hor = astronomy.Rotation_EQD_HOR(time, observer) hor_eqd = astronomy.Rotation_HOR_EQD(time, observer) CheckInverse('eqd_hor', 'hor_eqd', eqd_hor, hor_eqd) # Round trip #5: EQD <==> ECL. eqd_ecl = astronomy.Rotation_EQD_ECL(time) ecl_eqd = astronomy.Rotation_ECL_EQD(time) CheckInverse('eqd_ecl', 'ecl_eqd', eqd_ecl, ecl_eqd) # Round trip #6: HOR <==> ECL. hor_ecl = astronomy.Rotation_HOR_ECL(time, observer) ecl_hor = astronomy.Rotation_ECL_HOR(time, observer) CheckInverse('hor_ecl', 'ecl_hor', hor_ecl, ecl_hor) # Verify that combining different sequences of rotations result # in the expected combination. # For example, (EQJ ==> HOR ==> ECL) must be the same matrix as (EQJ ==> ECL). # Each of these is a "triangle" of relationships between 3 orientations. # There are 4 possible ways to pick 3 orientations from the 4 to form a triangle. # Because we have just proved that each transformation is reversible, # we only need to verify the triangle in one cyclic direction. CheckCycle('eqj_ecl, ecl_eqd, eqd_eqj', eqj_ecl, ecl_eqd, eqd_eqj) # excluded corner = HOR CheckCycle('eqj_hor, hor_ecl, ecl_eqj', eqj_hor, hor_ecl, ecl_eqj) # excluded corner = EQD CheckCycle('eqj_hor, hor_eqd, eqd_eqj', eqj_hor, hor_eqd, eqd_eqj) # excluded corner = ECL CheckCycle('ecl_eqd, eqd_hor, hor_ecl', ecl_eqd, eqd_hor, hor_ecl) # excluded corner = EQJ print('Test_RotRoundTrip: PASS') def Test_Rotation(): Rotation_MatrixInverse() Rotation_MatrixMultiply() Test_EQJ_ECL() Test_EQJ_EQD(astronomy.Body.Mercury) Test_EQJ_EQD(astronomy.Body.Venus) Test_EQJ_EQD(astronomy.Body.Mars) Test_EQJ_EQD(astronomy.Body.Jupiter) Test_EQJ_EQD(astronomy.Body.Saturn) Test_EQD_HOR(astronomy.Body.Mercury) Test_EQD_HOR(astronomy.Body.Venus) Test_EQD_HOR(astronomy.Body.Mars) Test_EQD_HOR(astronomy.Body.Jupiter) Test_EQD_HOR(astronomy.Body.Saturn) Test_RotRoundTrip() print('Python Test_Rotation: PASS') return 0 #----------------------------------------------------------------------------------------------------------- def Test_Refraction(): alt = -90.1 while alt <= +90.1: refr = astronomy.RefractionAngle(astronomy.Refraction.Normal, alt) corrected = alt + refr inv_refr = astronomy.InverseRefractionAngle(astronomy.Refraction.Normal, corrected) check_alt = corrected + inv_refr diff = abs(check_alt - alt) if diff > 2.0e-14: print('Test_Refraction: ERROR - excessive error: alt={}, refr={}, diff={}'.format(alt, refr, diff)) return 1 alt += 0.001 print('Python Test_Refraction: PASS') return 0 #----------------------------------------------------------------------------------------------------------- if __name__ == '__main__': if len(sys.argv) == 2: if sys.argv[1] == 'time': Test_AstroTime() sys.exit(0) if sys.argv[1] == 'moon': Test_GeoMoon() sys.exit(0) if sys.argv[1] == 'astro_check' or sys.argv[1] == 'astro_profile': Test_AstroCheck(sys.argv[1] == 'astro_check') sys.exit(0) if sys.argv[1] == 'elongation': sys.exit(Test_Elongation()) if sys.argv[1] == 'magnitude': sys.exit(Test_Magnitude()) if sys.argv[1] == 'apsis': sys.exit(Test_Apsis()) if sys.argv[1] == 'issue46': sys.exit(Test_Issue46()) if sys.argv[1] == 'rotation': sys.exit(Test_Rotation()) if sys.argv[1] == 'refraction': sys.exit(Test_Refraction()) if len(sys.argv) == 3: if sys.argv[1] == 'seasons': sys.exit(Test_Seasons(sys.argv[2])) if sys.argv[1] == 'moonphase': sys.exit(Test_MoonPhase(sys.argv[2])) if sys.argv[1] == 'riseset': sys.exit(Test_RiseSet(sys.argv[2])) print('test.py: Invalid command line arguments.') sys.exit(1)