# Ephemeris Calculation Core 0.5.3

This package contains only the numerical routines needed to calculate a
topocentric small-body ephemeris. It has no GUI, CLI, web application, object
download, station catalog, map, updater, cache, or Find_Orb runtime dependency.

The calling program supplies:

- osculating orbit elements and their TDB epoch;
- optional JPL/Marsden nongravitational parameters and covariance;
- exact WGS84 observer coordinates;
- UTC output times.

The package returns rows. It never contacts a network service.

## Numerical model

- universal initialization for elliptic, near-parabolic, parabolic, and
  hyperbolic orbits;
- adaptive DOP853 integration;
- perturbations by all eight planets and Pluto;
- Earth and Moon treated separately;
- optional perturbations by a locally generated JPL Horizons N16 asteroid set;
- solar relativistic correction;
- close-Earth J2/J3/J4 correction;
- optional JPL/Marsden A1/A2/A3 activity model;
- iterative light-time correction;
- JPL DE440s positions supplied in the package;
- ICRS topocentric RA/Dec and local apparent horizontal coordinates;
- optional linearized sky-plane covariance projection.

## Installation

Use Python 3.11 or newer:

```text
python -m venv .venv
.venv\Scripts\python -m pip install .
```

## Minimal use

```python
from ephemeris_core import (
    CalculationOptions,
    ObserverLocation,
    OrbitSolution,
    build_times,
    calculate,
    parse_utc,
)

orbit = OrbitSolution.from_perihelion_elements(
    full_name="caller supplied object",
    epoch_jd_tdb=2461000.5,
    eccentricity=0.191,
    perihelion_distance_au=0.746,
    perihelion_jd_tdb=2461007.42,
    arg_perihelion_deg=126.6,
    ascending_node_deg=204.0,
    inclination_deg=3.3,
    orbit_source="frozen test elements",
    orbit_id="example-1",
)
observer = ObserverLocation(longitude_deg=-116.863, latitude_deg=33.356, height_m=1712)
times = build_times(parse_utc("2026-07-21T23:30:00Z"), count=3, step_minutes=30)
result = calculate(orbit, observer, times, options=CalculationOptions())

for row in result.rows:
    print(row.values["utc"], row.values["ra_hms"], row.values["dec_dms"])
```

The complete runnable example is in `examples/python_example.py`.

`CalculationOptions()` selects the fast model: adaptive planet selection and
no asteroid perturbers. Critical objects are marked in the warning fields.
Use `CalculationOptions(calculation_mode="precise")` for every planet plus a
locally generated Horizons N16 asteroid set. The selected model is always
recorded in the result method and perturber fields. If N16 data have not yet
been generated, precise mode raises an explicit `FileNotFoundError`; it never
silently substitutes an older or less complete asteroid model.

## Optional N16 precision data

The public package deliberately contains no precomputed N16 state table.
Generate it locally after installation:

```text
python n16-tools\n16_generate.py
python n16-tools\n16_verify.py
```

The generator downloads 16 object-specific SPKs directly from JPL Horizons,
creates `n16-states-f64.bin`, records source details and SHA-256 values in
`manifest.json`, and verifies the result. Python, NumPy and SpiceyPy/CSPICE are its
only runtime dependencies.

By default, the core and generator use the platform-specific Ephemeris data
directory. Set `EPHEMERIS_N16_DIRECTORY` or pass `--output` to use another
directory.

Set `CalculationOptions(include_planetary_moids=True)` to add osculating MOIDs
for Mercury through Neptune and the Moon. The planet values are numerical
two-orbit minima at the supplied top-level element epoch. `moid_au` remains the
Earth MOID supplied with the orbit solution when available; a numerical Earth
value is used only as a fallback. The Moon value is a heliocentric osculating
snapshot at that epoch, not a long-term independent lunar-orbit invariant.

Run the frozen smoke test from the extracted directory with:

```text
python -m unittest discover -s tests
```

## Element contract

Angles are degrees. Distances are au. Epoch and perihelion time are TDB Julian
dates. Longitude is positive east. Output calendar times are UTC. The preferred
conic-independent element set is `e, q, tp, w, om, i`:

- `e`: eccentricity;
- `q`: perihelion distance in au;
- `tp`: perihelion passage as JD TDB;
- `w`: argument of perihelion;
- `om`: longitude of ascending node;
- `i`: inclination.

Do not mix an UTC Julian date with a TDB element epoch. Do not silently replace
topocentric coordinates by a geocenter. A station code by itself is not input;
the caller must resolve it to exact coordinates.

If `covariance` contains its own `epoch` and a complete `elements` list for
`e, q, tp, w, om, i`, precise and uncertainty calculations deliberately start
propagation from that fitted orbit epoch. The fast nominal method starts at the
current top-level osculating epoch to avoid unnecessary multi-decade
integration. The fitted epoch is important when a convenient published epoch
lies on the other side of a close planetary encounter: integrating the fitted
pre-encounter state forward is much better conditioned than integrating a
post-encounter state backward. The selected source is recorded in
`UniversalElements.source_file`.

## Accuracy statement

The calculation uses the JPL DE440s kernel and has been tested with frozen
elements over ordinary, cometary, hyperbolic, nongravitational, and close-
approach cases. Same-element comparisons are the relevant numerical test.

This is not JPL Horizons and is not JPL software. Agreement with a current
Horizons page also depends on using the same orbit solution, epoch, time scales,
observer coordinates, and physical model. During a very close encounter, using
a different element epoch or orbit solution can move the apparent position
substantially even if the propagator itself is correct. Preserve the top-level
elements, fitted covariance elements, orbit source, and solution ID.

See `VALIDATION.md` in the built package for the exact reports bundled with this
release. Empty covariance output means unavailable, never zero.

## Asteroid data provenance

The package contains the Apache-2.0-licensed generator but no precomputed N16
state table. The generator retrieves source SPKs and GMs from official Horizons
services and records body IDs, source URLs, coverage, units, generation time,
generator version and SHA-256 checksums in the local manifest.

Downloaded SPKs and locally generated data are not relicensed under Apache 2.0.
See `DATA-SOURCES-AND-TERMS.md` before redistributing them.

The old NAIF 300-asteroid SPK from 2010 is not packaged and is not a runtime
model. It is retained only in the parent laboratory as a historical diagnostic.

## Public surface

The supported imports are listed in `ephemeris_core.__all__`. Internal modules
`core`, `dynamics`, and `universal_orbit` are supplied as readable source for
auditing, but callers should use the public imports where possible.

`AI_USAGE.md` states the strict integration rules for any coding assistant.

## License

The project-owned calculation core, N16 generator, examples, schemas, tests and
documentation in this distribution are licensed under Apache License 2.0.
Third-party software and astronomical data retain their own terms.

See `LICENSE`, `NOTICE`, `THIRD-PARTY-NOTICES.md` and
`DATA-SOURCES-AND-TERMS.md`.
