144 lines
5.1 KiB
Python
144 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the Rust source for the IAU 2000A nutation series.
|
|
|
|
The series is the MHB2000 model. It has a luni-solar part and a planetary
|
|
part, both long tables of integer argument multipliers and real
|
|
coefficients, so they are transcribed mechanically from the reference
|
|
rather than by hand.
|
|
|
|
The reference is the ERFA library, a derivative of the IAU SOFA library
|
|
maintained by the International Astronomical Union. Its source is the
|
|
authoritative statement of the series.
|
|
|
|
Field order is the one the reference uses, which is not the order the
|
|
coefficients are listed in the paper:
|
|
|
|
luni-solar: multipliers l, l', F, D, Om; then sp, spt, cp, ce, cet, se
|
|
planetary: multipliers l, F, D, Om, Me, Ve, E, Ma, Ju, Sa, Ur, Ne, pa;
|
|
then sp, cp, se, ce
|
|
|
|
The two series use different argument sets and different coefficient
|
|
groupings for the obliquity, so the generated Rust types keep them separate.
|
|
|
|
Fetch the reference with:
|
|
|
|
curl -O https://raw.githubusercontent.com/liberfa/erfa/master/src/nut00a.c
|
|
|
|
Usage:
|
|
|
|
python validation/tools/generate_nutation_table.py path/to/nut00a.c
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def extract_array(source: str, name: str) -> str:
|
|
"""Returns the body of a C array initializer."""
|
|
start = source.index(name + "[] = {")
|
|
brace = source.index("{", start)
|
|
depth = 0
|
|
index = brace
|
|
while index < len(source):
|
|
if source[index] == "{":
|
|
depth += 1
|
|
elif source[index] == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
break
|
|
index += 1
|
|
return source[brace : index + 1]
|
|
|
|
|
|
def parse_terms(body: str) -> list[list[str]]:
|
|
"""Returns the numeric terms of an array of brace groups."""
|
|
body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
|
|
terms = []
|
|
for group in re.findall(r"\{([^{}]*)\}", body):
|
|
values = group.replace("\n", " ").strip()
|
|
if values:
|
|
terms.append([value.strip() for value in values.split(",")])
|
|
return terms
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
if len(argv) < 2:
|
|
print(__doc__, file=sys.stderr)
|
|
return 2
|
|
source = Path(argv[1]).read_text()
|
|
|
|
lunisolar = parse_terms(extract_array(source, "xls"))
|
|
planetary = parse_terms(extract_array(source, "xpl"))
|
|
|
|
if any(len(term) != 11 for term in lunisolar):
|
|
print("unexpected luni-solar row width", file=sys.stderr)
|
|
return 1
|
|
if any(len(term) != 17 for term in planetary):
|
|
print("unexpected planetary row width", file=sys.stderr)
|
|
return 1
|
|
|
|
print("//! The IAU 2000A nutation series.")
|
|
print("//!")
|
|
print("//! The table is generated from the reference implementation of the")
|
|
print("//! model by `validation/tools/generate_nutation_table.py` and is")
|
|
print("//! reproduced without change.")
|
|
print("//!")
|
|
print("//! The field order follows the reference, which is not the order the")
|
|
print("//! coefficients are listed in the paper.")
|
|
print("//!")
|
|
print("//! Source: the MHB2000 model of Mathews, Herring and Buffett, adopted by")
|
|
print("//! the International Astronomical Union in 2000 and distributed as part")
|
|
print("//! of the IAU SOFA library, as implemented by ERFA.")
|
|
print()
|
|
print("use super::{PlanetaryTerm, SeriesTerm};")
|
|
print()
|
|
|
|
print("/// The luni-solar terms of the series.")
|
|
print("///")
|
|
print("/// The multipliers apply to the arguments `l`, `l'`, `F`, `D`, and `Om`")
|
|
print("/// in that order. The coefficients are the longitude sine, its time")
|
|
print("/// derivative, the longitude cosine, the obliquity cosine, its time")
|
|
print("/// derivative, and the obliquity sine, in units of 0.1 microarcsecond.")
|
|
print(f"pub static LUNI_SOLAR: [SeriesTerm; {len(lunisolar)}] = [")
|
|
for term in lunisolar:
|
|
multipliers = ", ".join(term[:5])
|
|
coefficients = ", ".join(
|
|
value if "." in value or "e" in value.lower() else value + ".0"
|
|
for value in term[5:]
|
|
)
|
|
print(
|
|
f" SeriesTerm {{ argument: [{multipliers}], "
|
|
f"coefficients: [{coefficients}] }},"
|
|
)
|
|
print("];")
|
|
print()
|
|
|
|
print("/// The planetary terms of the series.")
|
|
print("///")
|
|
print("/// The multipliers apply to the arguments `l`, `F`, `D`, `Om`, the mean")
|
|
print("/// longitudes of Mercury through Uranus, the mean longitude of Neptune,")
|
|
print("/// and the general accumulated precession, in that order. The")
|
|
print("/// coefficients are the longitude sine, the longitude cosine, the")
|
|
print("/// obliquity sine, and the obliquity cosine, in units of 0.1")
|
|
print("/// microarcsecond.")
|
|
print(f"pub static PLANETARY: [PlanetaryTerm; {len(planetary)}] = [")
|
|
for term in planetary:
|
|
multipliers = ", ".join(term[:13])
|
|
coefficients = ", ".join(
|
|
value if "." in value or "e" in value.lower() else value + ".0"
|
|
for value in term[13:]
|
|
)
|
|
print(
|
|
f" PlanetaryTerm {{ argument: [{multipliers}], "
|
|
f"coefficients: [{coefficients}] }},"
|
|
)
|
|
print("];")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv))
|