Astronomy_SphereFromVector was not checking its vector
argument for having a bad status. Now it does.
Added comments that clarify exactly what nutation and precession
functions do.
Added a performance test to measure how fast
a call to EclipticGeoMoon is. Results:
Over 3 trials, mean time = 24.064 seconds.
Number of calls = 14,609,700.
Time per call = 1.647 microseconds.
That is faster than I thought!
Also replaced conditional compilation with a runtime flag
to indicate whether a test should be skipped by the "all"
command. This allows me to run performance tests without
hacking a #define in the code, plus it ensures that even
though I don't run the performance tests every time I change
Astronomy Engine, they at least compile without errors.
This was another test where I was trying to figure out
an apparent anomaly that was actually based on my
flawed assumption about why obliquity changes over time.
I don't need the MoonLatitudes test any more.
Its purpose was to investigate what I thought was an
ecliptic latitude discrepancy. Now I understand that
changes in obliquity are caused by changes in the ecliptic
plane, not the equatorial plane.
I'm still trying to understand a discrepancy
between ecliptic latitudes calculated in two
different equinoxes: mean equinox of date and
mean J2000 equinox. I keep thinking the two
latitude values should be the same, because they
are measured against an essentially unchanging
ecliptic plane. I understand that ecliptic longitudes
should change as the Earth's equator precesses
by 47 arcseconds per century.
Added new test MoonEcliptic(), that compares
JPL Horizons calculations of the Moon's ecliptic
coordinates relative to equator of date versus
Astronomy_EclipticGeoMoon():
C MoonEcliptic: PASS: count=73050, max lat=1.825 arcsec, max lon=23.462 arcsec.
It is so weird that the maximum latitude discrepancy is less than 2 arcseconds.
Updated MoonVector test to measure maximum errors in addition to RMS errors.
Display distance errors in kilometers instead of dimensionless units.
Defined constant JD_2000 so all my tests that
convert Julian Dates from JPL Horizons can share it.
There is a discrepancy between ecliptic latitudes measured
using two different mean equinoxes: 1900 and 2000.
The discrepancy is very close to the change of obliquity
expected for a 100-year period: 47 arcseconds.
But ecliptic latitudes should remain constant as
the Earth's axis precesses!
MoonLatitudes now generates a CSV file for me to study the
ecliptic latitude discrepancy as a function of time.
I can see that it increases away from the year 2000,
and that it is periodic with the Moon's orbit.
This indicates an error in correcting for precession.
Generated new JPL Horizons test data for MoonVector that
covers the years 1900..2100 instead of 1980..2020.
Confirmed that its maximum error is still 1.16 arcsec.
That is error comparing EQJ vectors and ECL vectors.
The C unit test program ctest.c now includes a MoonLatitudes
function invoked by the name `moon_lat`.
This function demonstrates that there is a 47 arcsecond
discrepancy between ecliptic latitudes using the mean
equator of date and ecliptic latitudes using the mean
equator of J2000. This should not happen! I'm investigating.
Also, when running ctest without command-line arguments,
print a list of all available test names, so I don't have
to go open up the file ctest.c and read the code.
The documentation for SearchRiseSet and SearchAltitude needed
clarification about refraction and the part of the body solved
for (center versus limb). The JavaScript version was especially
lacking compared to documentation for the other languages.
Also documented SearchAltitude's limitations; it does not
work at or near maximum/minimum altitude.
Mention that user-defined stars are allowed for
SearchRiseSet, SearchAltitude, and SearchHourAngle.
Fixed a couple places where the Kotlin documentation had
broken links to other functions.
I had a copy-n-paste typo in the `dec` parameters
for all of the DefineStar functions. Fixed it.
The TypeScript version of HelioState did not handle
user-defined stars. Added support there.
Because we instantly know the heliocentric
distance of a user-defined star, there is no
need to convert it into a vector and then take
the length of the vector.
All of the HelioDistance functions now return
the distance directly, as an optimization.
Also, I decided it didn't make sense to have a
default definition for user-defined stars.
If the caller doesn't define a star, it should
be treated as an invalid body.
Added Python support for user-defined stars.
Defined new StateVector methods: Position and Velocity.
Defined division operator: Vector / float.
Bumped version number to 2.1.12.
SearchRiseSet and SearchHourAngle now work with user-defined stars.
Fixed a bug in InternalSearchAltitude where I failed to return an error
code when its call to MaxAltitudeSlope failed.
DefineStar now requires passing in the heliocentric
distance of the star expressed in light-years.
That way, I can directly support returning vectors
to a star from HelioVector, GeoVector, etc.
SearchRiseSet and SearchHourAngle now work with user-defined stars.
I'm starting to implement the ability to define
up to 8 distinct points in the sky as "stars"
that will be allowed as a `body` parameter to
some Astronomy Engine functions, to be determined.
Made sure all the altitude search functions
verify that the geographic latitude and target altitude
are valid numbers in the range [-90, +90].
Reworked the C version of the code to be clearer:
eliminated goofy ALTDIFF macro, split out max
altitude derivative into its own function MaxAltitudeSlope,
just like the other language implementations do.
Minor rewording of comments in MaxAltitudeSlope functions.
Python InvalidBodyError now includes the invalid body
in the diagnostic message.
The C# version of the altitude searches now work correctly
near the Earth's poles. This is a port of the C version.
Cleaned up a little code in the C version.
Do not recurse deeper when any solution would
have a rise+set or a set+rise consecutively
within one second. This would risk confusing the
search, which has a 0.1 second tolerance for convergence.
For practical reasons, such events are dubious anyway,
being swamped by unpredictable atmospheric refraction.
Plus it's just faster to not have to recurse so deeply.
Add a safety valve that fails the search if recursion
gets deeper than the above tolerance would ever allow
to happen in the first place.
This change improves performance significantly:
C RiseSet: passed 5909 lines: time errors in minutes: rms=0.2904, max=1.1541, recur=15, altcount=156881
Added helper function AscentError for cleaner code in error cases.
Track the maximum recursion depth in FindAscent.
Count the number of times the altitude_diff function is called.
These are important performance statistics that I want to
measure, so I can try a few optimization ideas.
Current results:
don@spearmint:~/github/astronomy/generate $ ./generate source && ./ctbuild && ./ctest -v riseset
./ctbuild: C compiler = gcc
./ctbuild: Built 'ctest' program.
C RiseSet: Moon lat=-61.0 lon=103.0
C RiseSet: Moon lat=-45.0 lon=150.0
C RiseSet: Moon lat=29.0 lon=-81.0
C RiseSet: Moon lat=80.0 lon=130.0
C RiseSet: Moon lat=83.0 lon=105.0
C RiseSet: Moon lat=84.0 lon=125.0
C RiseSet: Moon lat=85.0 lon=135.0
C RiseSet: Moon lat=85.0 lon=140.0
C RiseSet: Sun lat=-90.0 lon=0.0
C RiseSet: Sun lat=-88.0 lon=45.0
C RiseSet: Sun lat=-60.0 lon=-150.0
C RiseSet: Sun lat=15.0 lon=75.0
C RiseSet: Sun lat=29.0 lon=-81.0
C RiseSet: Sun lat=60.0 lon=0.0
C RiseSet: Sun lat=89.0 lon=30.0
C RiseSet: Sun lat=90.0 lon=0.0
C RiseSet: passed 5909 lines: time errors in minutes: rms=0.2904, max=1.1541, recur=19, altcount=171438
This is a whole new algorithm that efficiently finds
all rise/set events, even near the poles.
It uses a recursive bisection search that limits
recursion depth by knowing the maximum possible
|da/dt| = change in altitude with respect to time.
Finding apparent angular radius by dividing body radius
by distance is not quite as accurate as taking the
arcsine of the same ratio. Changed this for a slightly
more accurate answer.
Refactored so there is a single altitude function/context pair
instead of separate ones for SearchAltitude and SearchRiseSet.
This allows InternalSearchAltitude to fully understand the
problem to be solved, and also makes the code smaller.
InternalSearchAltitude will need to perform min/max altitude
calculations, which means it needs to know all the parameters
of the altitude search directly. They are no longer hidden
inside an opaque function pointer and context.
So now SearchAltitude and SearchRiseSet no longer create
contexts, nor pass in a function pointer. They just
pass the correct numeric parameters to the generic solver
InternalSearchAltitude, which packs the parameters into
the context for Search().
Unified the two context types into a single context type,
and the two callback functions into a single callback function.
Updated the version number so I can create a new
npm package to test the pull request from @matheo
that should allow TypeScript types to be exported correctly.
Updated CodeQL config to ignore source templates,
because they are not syntactically valid source code.
Ignore other stuff that is irrelevant to published
code quality.
Made various fixes based on helpful CodeQL analysis.
The rise/set search based on hour angles is complicated,
and doesn't handle oddities that happen close to the poles.
I'm starting to rework rise/set as a more brute force solution
that iterates through finite time steps.
I also added a series of Moon data for the arctic circle,
which includes some of the more painful special cases.
For example:
Moon 130 80 2034-05-16T13:21Z s
Moon 130 80 2034-05-16T13:51Z r
Here the Moon sets, then rises 30 minutes later.
So now I'm trying to figure out how to discover
arbitrarily brief intervals like this.
I want the time increments to scale intelligently
so that we don't waste time during long periods
of inactivity (body above or below the horizon continuously),
but without missing examples like the one above.
Added sunrise/sunset test data for locations
at or near the poles. This causes rise/set tests to fail.
I need to fix these cases!
Observations:
1. Sunrise/sunset at the poles has nothing to do with
the Earth's rotation. It is based only on the Sun's
declination. Therefore, it makes no sense to look
at hour angles there.
2. At the poles, there is no such thing as a meridian.
For example, at the north pole, no matter which way
you turn, you are facing south!
So the very notion of hour angles is meaningless.
3. At the poles, there is a single sunrise and a single sunset
per calendar year. The sun rises near the spring solstice
for that hemisphere, and sets near the autumn solstice.
4. Near the poles, sunrise/sunset behavior gets more complicated.
It is possible for there to be more than one sunset or sunrise
per 24-hour period, because of changes of Sun declination.
When parsing the rise/set data, the Linux
`sort` utility had weird behavior. Fixed it
by making parse_riseset.py do its own sorting
in an order that makes sense, before writing
to riseset.txt.
The Windows version of the GitHub Actions tests require
downloading Doxygen binaries. Every now and then this
step would break when Doxygen updates their version numbers.
Added a Python script to scrape the Doxygen website to
determine the correct URL to download.
commit_hook.bat calls the Python script to determine
the URL, so I don't have to keep manually updating it.