Skip to content

Commit c59b9a8

Browse files
Merge pull request #36 from nyx-space/gemini-docs
Gemini docs
2 parents 4d565ee + 741e7e7 commit c59b9a8

File tree

16 files changed

+714
-1
lines changed

16 files changed

+714
-1
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# The Almanac and the Context Pattern
2+
3+
In most legacy astrodynamics toolkits, loading "kernels" (data files) is a global operation. When you load a file, it enters a global memory pool accessible by any function in your program. This "Global State" pattern makes it extremely difficult to write concurrent code or to manage different scenarios in a single application.
4+
5+
## The Almanac as a Container
6+
7+
ANISE replaces the global pool with the `Almanac`. An `Almanac` is a self-contained object that stores:
8+
- Ephemeris data (SPICE SPK)
9+
- Orientation data (SPICE BPC, PCK)
10+
- Planetary data (PCA)
11+
- Spacecraft data (SCA)
12+
- Euler Parameter / Unit quaternion data (EPA)
13+
- Location data (LKA)
14+
- Instrument data (IKA)
15+
16+
```rust
17+
// In ANISE, you manage your own context
18+
let mut almanac = Almanac::default();
19+
almanac.load("de440.bsp")?;
20+
```
21+
22+
Because the `Almanac` is an object, you can have as many as you want. One thread can work with a high-fidelity Earth model stored in `almanac_A`, while another thread performs long-term trajectory analysis using a simplified model in `almanac_B`.
23+
24+
## Immutable Sharing and Cheap Clones
25+
26+
A common concern with moving away from global state is the overhead of passing around a large context object. ANISE solves this through its memory management:
27+
28+
1. **In-Memory storing**: When you load a kernel into an `Almanac`, the data is read once in memory (on the heap), reducing the overhead of maintaining a file handler;
29+
2. **Shared Data**: Internally, the `Almanac` uses reference-counting buffers;
30+
3. **Cheap Clones**: Cloning an `Almanac` does not copy the gigabytes of ephemeris data; it simply creates a new handle to the same underlying memory.
31+
32+
This allows you to pass the `Almanac` into parallel loops (e.g., using `rayon` in Rust) with near-zero overhead.
33+
34+
## The Search Engine
35+
36+
The `Almanac` acts as a search engine for your data. When you ask for the position of Mars relative to Earth, the `Almanac`:
37+
1. Looks up the IDs for Earth and Mars.
38+
2. Traverses the ephemeris tree to find a common ancestor.
39+
3. Chains together the necessary translations from the loaded SPK segments.
40+
4. Returns the result as a high-precision state vector.
41+
42+
By centralizing this logic in a thread-safe object, ANISE provides a clean API that hides the complexity of multi-segment, multi-file lookups.

docs/anise/explanation/analysis.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Analysis Engine
2+
3+
ANISE includes a powerful **Analysis Engine** designed to answer complex astrodynamics questions declaratively. Instead of writing imperative loops to check conditions at every time step, you define *what* you want to compute, and ANISE figures out *how* to compute it efficiently.
4+
5+
## Core Philosophy
6+
7+
The analysis engine is built on three pillars:
8+
9+
1. **Declarative Queries**: You describe the state of the system (e.g., "The distance between Earth and Mars") as an expression tree, rather than a sequence of math operations.
10+
2. **Continuous Resolution**: Unlike simple step-by-step propagation, the engine uses root-finding algorithms (specifically the **Brent solver**) to find exact times of events, such as when a satellite enters an eclipse or crosses a specific altitude.
11+
3. **Serialization**: All queries can be serialized to **S-expressions** (symbolic expressions). This allows you to define a query in Python, send it to a remote Rust/Python worker, and execute it safely without allowing arbitrary code execution.
12+
13+
## Expression Trees
14+
15+
Everything in the analysis engine is an **Expression**. These expressions can be nested to form complex queries.
16+
17+
- **`StateSpec`**: Defines the "Context" of an orbital state. Who is the target? Who is the observer? What frame are we in?
18+
- *Example*: "LRO (Target) as seen from the Moon (Observer) in the Moon Body-Fixed frame."
19+
- **`VectorExpr`**: Computes a 3D vector.
20+
- *Examples*: `Radius`, `Velocity`, `SunVector`, `OrbitalMomentum`.
21+
- **`ScalarExpr`**: Computes a single floating-point number.
22+
- *Examples*: `Norm` (of a vector), `DotProduct`, `AngleBetween`, `OrbitalElement` (eccentricity, SMA), `ShadowFunction`.
23+
- **`DcmExpr`**: Defines how to compute a direct cosine matrix, used to correctly align instruments with respect to the orbit frame
24+
- *Examples*: `Triad` for an align/clock vector setup, `R1` for a rotation about X
25+
26+
### Example
27+
To compute the **Beta Angle** (angle between the orbit plane and the vector to the Sun), you don't write a function. You build it:
28+
29+
```rust
30+
// In pseudo-code representation of the internal tree
31+
Shape: AngleBetween(
32+
VectorA: SunVector(Observer),
33+
VectorB: OrbitalMomentum(Target, Observer)
34+
)
35+
```
36+
37+
## Event Finding (The Superlinear Solver)
38+
39+
One of ANISE's most powerful features is its ability to find **Events**. An Event is defined by a `ScalarExpr` and a `Condition`.
40+
41+
- **Scalar**: Any continuous values (e.g., "Elevation Angle").
42+
- **Condition**: A threshold or extrema (e.g., `= 4.57 deg`, `> 5.0 deg`, `Maximum`, `Minimum`).
43+
44+
### The Problem with Steps
45+
If you simply loop through time with a 1-minute step, you might miss short events (like a 30-second eclipse) or get inaccurate start/stop times.
46+
47+
### The ANISE Solution: Brent's Method
48+
ANISE uses an adaptive checking method combined with **Brent's method** for root finding.
49+
50+
1. **Search**: It scans the time domain using an adapative step scanner inspired from adaptive step Runge Kutta methods
51+
2. **Bracket**: When it detects that the condition changed (e.g., elevation went from negative to positive), it knows a root (0 crossings) exists in that interval.
52+
3. **Solve**: It deploys the Brent solver to find the *exact* event when determining the value crossed the threshold.
53+
54+
This allows for **superlinear convergence**, meaning it finds high-precision times with very few function evaluations compared to brute-force searching.
55+
56+
## Reporting
57+
58+
The engine provides three main reporting modes:
59+
60+
1. **`report_scalars`**: Evaluates a list of expressions at fixed time steps. This is parallelized using `rayon` for maximum speed.
61+
2. **`report_events`**: Finds discrete instants where a condition happens (e.g., "Apoapsis", "Max Elevation").
62+
3. **`report_event_arcs`**: Finds durations where a condition holds true (e.g., "Eclipse Duration", "Comm Window").
63+
4. **`report_visibility_arcs`**: Compute the Azimuth, Elevation, Range, and Range-rate ("AER") for each communication pass from a given location on _any_ celestial object.
64+
65+
## Safety and S-Expressions
66+
67+
Because analysis queries are just data structures (enums), they can be serialized into a lisp-like S-expression format.
68+
69+
```lisp
70+
(Event
71+
(scalar (AngleBetween (Radius) (SunVector)))
72+
(condition (LessThan 90.0))
73+
)
74+
```
75+
76+
This is critical for web services or distributed systems. A client can craft a complex query and send it to a server. The server parses the S-expression and executes it. Because the server *only* executes valid ANISE expressions, there is **no risk of arbitrary code injection**, unlike pickling Python objects or sending raw scripts.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Frame Safety and Graph Lookups
2+
3+
In space mission design, one of the most common sources of error is the misuse of reference frames. Forgetting to rotate from a body-fixed frame to an inertial frame, or confusing two different inertial frames (like J2000 and EME2000), can lead to mission-critical failures.
4+
5+
ANISE introduces the concept of **Frame Safety** to eliminate these errors.
6+
7+
## Frames are Not Just IDs
8+
9+
In the SPICE toolkit, frames are often referred to by integer IDs. While ANISE maintains compatibility with NAIF IDs, it treats Frames as rich objects.
10+
11+
Every Frame in ANISE has an `ephemeris_id` and an `orientation_id`, which store the central object identifier and the reference frame orientation identifier. Each frame may also set properties related to the central object, notably:
12+
- **`mu_km3_s2`**: the gravitational parameter in km^3/s^2
13+
- **`shape`**: the tri-axial ellipsoid shape of the central object, defined by its semi major and semi minor axes and its polar axis.
14+
15+
These optional data may be required for specific computations. For example, the calculation of the semi-major axis of the orbit requires that orbit's frame to set the `mu_km3_s2`. You may fetch the available frame information loaded in the almanac using the `frame_info` function, e.g. `my_almanac.frame_info(EARTH_J2000)`.
16+
17+
ANISE also defines a `FrameUid` which is identical to the `Frame` but only stores the ephemeris and orientation IDs. A number of commonly used frames are defined in the ANISE constants, though none of these definitions include the gravitational data or the shape data: these _must_ be loaded at runtime.
18+
19+
## Validation Before Computation
20+
21+
When you request a transformation in ANISE, the toolkit doesn't just blindly multiply matrices or vectors. It performs a **Frame Check**.
22+
23+
Before any computation:
24+
1. ANISE checks that the target and observer frames are part of the same "graph" (they have a path between them).
25+
2. It ensures the transformation is physically valid (e.g., you aren't trying to rotate between two frames that don't have a defined orientation relationship).
26+
3. It identifies the "Common Ancestor"—the point in the frame tree where the paths from the two frames meet.
27+
28+
## Tree Traversal and Path Finding
29+
30+
ANISE represents the relationship between frames as a tree.
31+
32+
When transforming from Frame A to Frame B:
33+
- **Translation Path**: Finds the common ephemeris ancestor (e.g., the Solar System Barycenter) and sums the vectors along the branches.
34+
- **Rotation Path**: Finds the common orientation ancestor and composes the Direction Cosine Matrices (DCMs) or Quaternions.
35+
36+
If a path cannot be found (e.g., you haven't loaded the necessary SPK or PCK files), ANISE returns a clear error at the start of the query (when building the paths), rather than producing junk data or crashing with a segfault.
37+
38+
## The `Frame` Object
39+
40+
In the ANISE API, a `Frame` object carries its metadata with it. When you build an `Orbit`, it is attached to a specific `Frame`. When you transform that `Orbit` to a new frame, ANISE uses the metadata to ensure the transformation is accurate, including handling the necessary time conversions for body-fixed rotations.

docs/anise/explanation/index.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
> Explanation is a discusive treatment of a subject, that permits reflection. Explanation is **understanding-oriented**.
22
-- [_Diataxis_](https://www.diataxis.fr/tutorials/)
33

4-
This section delves into the design of ANISE, how it is validated, and how its results may very minutely differ from those of the SPICE toolkit but why you should actually trust ANISE instead.
4+
This section delves into the design of ANISE, its architecture, and the core concepts that make it a modern alternative to legacy toolkits.
5+
6+
- **[Why ANISE?](why-anise.md)**: The philosophy and architectural decisions behind the toolkit.
7+
- **[Almanac and Context](almanac-and-context.md)**: Understanding the data-driven design and thread safety.
8+
- **[Frame Safety](frame-safety.md)**: How ANISE prevents coordinate system errors.
9+
- **[Analysis Engine](analysis.md)**: Declarative queries and superlinear event finding.
10+
- **[Time](time.md)**: The importance of high-precision timekeeping.
11+
- **[Validation](validation.md)**: How we ensure ANISE matches (or exceeds) the accuracy of the SPICE toolkit.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Why ANISE?
2+
3+
ANISE (Attitude, Navigation, Instrument, Spacecraft, Ephemeris) was born out of a need for a modern, high-performance, and thread-safe toolkit for space mission design and operations. While the NAIF SPICE toolkit has been the industry standard for decades, its architecture was designed for an era of single-threaded, procedural programming.
4+
5+
## Designed for Modern Hardware
6+
7+
Modern computing environments, from high-performance workstations to cloud-based clusters, rely on multi-core processors. Legacy toolkits often struggle in these environments due to:
8+
9+
- **Global State**: SPICE uses a global kernel pool. This means you cannot easily load different sets of kernels for different threads or perform concurrent queries without complex locking mechanisms (mutexes) that severely bottleneck performance.
10+
- **Thread Safety**: Because of its global state and internal cache mechanisms, SPICE is inherently NOT thread-safe.
11+
12+
**ANISE is thread-safe by design.** It eliminates global state by using the `Almanac` as a first-class object. You can create multiple `Almanac` instances, each with its own set of loaded kernels, and share them across threads safely.
13+
14+
## Safety through Rust
15+
16+
By building on Rust, ANISE leverages a powerful type system and ownership model to provide guarantees that other toolkits cannot:
17+
18+
- **Memory Safety**: Rust prevents common bugs like null pointer dereferences, buffer overflows, and data races at compile time.
19+
- **Frame Safety**: ANISE doesn't just treat frames as integer IDs. It understands the relationship between frames. It validates that any transformation (rotation or translation) you request is physically possible before even attempting the math. No more mixing up J2000 and ITRF93 by accident.
20+
21+
## Precision and Performance
22+
23+
ANISE matches SPICE's mathematical precision—and in many cases, exceeds it.
24+
25+
- **Integer-based Time**: By using the `hifitime` library, ANISE avoids the rounding errors inherent in floating-point time representations (used by SPICE). This is particularly critical for high-fidelity planetary rotations where a few microseconds of error can accumulate over years.
26+
- **Limited file system calls**: ANISE uses copies the content of files on the heap on load, allowing it to query large kernel files (like DE440) with zero file system waits, minimal memory overhead and maximum speed.
27+
28+
## Multi-language from the Core
29+
30+
ANISE is an ecosystem. While the core engine is written in Rust for maximum performance, it is designed to be easily accessible from other languages.
31+
- **Rust**: Native performance and type safety.
32+
- **Python**: A pythonic API (`anise-py`) that offers the performance and safety of the Rust core.
33+
- **CLI/GUI**: Tools for quick inspection and visualization that work cross-platform.
34+
- **C++**: While planned, this interface is stalled due to limited resources.

docs/anise/reference/almanac.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Almanac Reference
2+
3+
The `Almanac` is the primary interface for ANISE. It serves as the context for all ephemeris, orientation, and physical constant lookups.
4+
5+
The following is a _small subset_ of all the functions available in the Almanac. Please refer to <https://docs.rs/anise/latest/anise/almanac/struct.Almanac.html> for the exhaustive list: Python functions are (almost) always named identically.
6+
7+
## Key Concepts
8+
9+
- **Thread Safety**: The `Almanac` is `Send + Sync`, meaning it can be safely shared across threads.
10+
- **Data Encapsulation**: It stores loaded kernel data internally; there is no global pool.
11+
- **Method Chaining**: Loading methods often use a builder pattern or return a new handle for easy initialization.
12+
13+
## Loading Data
14+
15+
The `Almanac` supports loading various kernel types. You can load files directly or allow ANISE to "guess" the file type.
16+
17+
### `load`
18+
Loads any supported ANISE or SPICE file.
19+
- **Rust**: `almanac.load("path/to/file")?`
20+
- **Python**: `almanac.load("path/to/file")`
21+
22+
### Specialized Loaders
23+
If you know the file type, you can use specialized loaders for better performance or specific configuration:
24+
- `with_spk(spk)`: Add SPK (ephemeris) data.
25+
- `with_bpc(bpc)`: Add BPC (high-precision orientation) data.
26+
- `with_planetary_data(pca)`: Add an ANISE planetary constant data kernel (gravity and tri-axial ellipsoid constants and low-fidelity orientation).
27+
- `with_location_data(lka)`: Add an ANISE location data kernel (landmarks defined by their latitude, longitude, and height above the ellipsoid on a given body fixed frame).
28+
- `with_instrument_data(ika)`: Add an ANISE instrument kernel (an instrument is defined by its rotation quaternion/EP, its offset from the body center, and its field of view).
29+
30+
## Coordinate Transformations
31+
32+
The most common use of the `Almanac` is to transform positions and velocities between frames.
33+
34+
### `transform_to`
35+
Transforms an `Orbit` (state vector) into a different frame at its own epoch.
36+
- **Parameters**: `orbit`, `target_frame`, `aberration`.
37+
- **Returns**: A new `Orbit` in the target frame.
38+
39+
### `translate`
40+
Calculates the relative position and velocity between two frames.
41+
- **Parameters**: `target`, `observer`, `epoch`, `aberration`.
42+
- **Returns**: A `StateVector` (Position + Velocity).
43+
44+
### `rotate`
45+
Calculates the rotation (DCM) from one orientation frame to another.
46+
- **Parameters**: `target`, `observer`, `epoch`.
47+
- **Returns**: A 3x3 Direction Cosine Matrix.
48+
49+
## Physical Constants
50+
51+
The `Almanac` also stores physical data for bodies defined in the kernels.
52+
53+
### `frame_info`
54+
Retrieves a `Frame` object containing metadata for a given NAIF ID.
55+
- **Includes**: Gravitational parameter ($\mu$), body radii, and frame type.
56+
57+
### `angular_velocity_deg_s`
58+
Returns the angular velocity vector in deg/s of the from_frame wrt to the to_frame.
59+
60+
## Built-in Constants
61+
62+
ANISE provides a set of common NAIF IDs and frames in the `constants` module for ease of use (e.g., `EARTH_J2000`, `SUN_J2000`, `MOON_J2000`).

0 commit comments

Comments
 (0)