diff --git a/docs/anise/explanation/almanac-and-context.md b/docs/anise/explanation/almanac-and-context.md new file mode 100644 index 0000000..14a52f7 --- /dev/null +++ b/docs/anise/explanation/almanac-and-context.md @@ -0,0 +1,42 @@ +# The Almanac and the Context Pattern + +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. + +## The Almanac as a Container + +ANISE replaces the global pool with the `Almanac`. An `Almanac` is a self-contained object that stores: +- Ephemeris data (SPICE SPK) +- Orientation data (SPICE BPC, PCK) +- Planetary data (PCA) +- Spacecraft data (SCA) +- Euler Parameter / Unit quaternion data (EPA) +- Location data (LKA) +- Instrument data (IKA) + +```rust +// In ANISE, you manage your own context +let mut almanac = Almanac::default(); +almanac.load("de440.bsp")?; +``` + +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`. + +## Immutable Sharing and Cheap Clones + +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: + +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; +2. **Shared Data**: Internally, the `Almanac` uses reference-counting buffers; +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. + +This allows you to pass the `Almanac` into parallel loops (e.g., using `rayon` in Rust) with near-zero overhead. + +## The Search Engine + +The `Almanac` acts as a search engine for your data. When you ask for the position of Mars relative to Earth, the `Almanac`: +1. Looks up the IDs for Earth and Mars. +2. Traverses the ephemeris tree to find a common ancestor. +3. Chains together the necessary translations from the loaded SPK segments. +4. Returns the result as a high-precision state vector. + +By centralizing this logic in a thread-safe object, ANISE provides a clean API that hides the complexity of multi-segment, multi-file lookups. diff --git a/docs/anise/explanation/analysis.md b/docs/anise/explanation/analysis.md new file mode 100644 index 0000000..c9afaaf --- /dev/null +++ b/docs/anise/explanation/analysis.md @@ -0,0 +1,76 @@ +# Analysis Engine + +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. + +## Core Philosophy + +The analysis engine is built on three pillars: + +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. +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. +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. + +## Expression Trees + +Everything in the analysis engine is an **Expression**. These expressions can be nested to form complex queries. + +- **`StateSpec`**: Defines the "Context" of an orbital state. Who is the target? Who is the observer? What frame are we in? + - *Example*: "LRO (Target) as seen from the Moon (Observer) in the Moon Body-Fixed frame." +- **`VectorExpr`**: Computes a 3D vector. + - *Examples*: `Radius`, `Velocity`, `SunVector`, `OrbitalMomentum`. +- **`ScalarExpr`**: Computes a single floating-point number. + - *Examples*: `Norm` (of a vector), `DotProduct`, `AngleBetween`, `OrbitalElement` (eccentricity, SMA), `ShadowFunction`. +- **`DcmExpr`**: Defines how to compute a direct cosine matrix, used to correctly align instruments with respect to the orbit frame + - *Examples*: `Triad` for an align/clock vector setup, `R1` for a rotation about X + +### Example +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: + +```rust +// In pseudo-code representation of the internal tree +Shape: AngleBetween( + VectorA: SunVector(Observer), + VectorB: OrbitalMomentum(Target, Observer) +) +``` + +## Event Finding (The Superlinear Solver) + +One of ANISE's most powerful features is its ability to find **Events**. An Event is defined by a `ScalarExpr` and a `Condition`. + +- **Scalar**: Any continuous values (e.g., "Elevation Angle"). +- **Condition**: A threshold or extrema (e.g., `= 4.57 deg`, `> 5.0 deg`, `Maximum`, `Minimum`). + +### The Problem with Steps +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. + +### The ANISE Solution: Brent's Method +ANISE uses an adaptive checking method combined with **Brent's method** for root finding. + +1. **Search**: It scans the time domain using an adapative step scanner inspired from adaptive step Runge Kutta methods +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. +3. **Solve**: It deploys the Brent solver to find the *exact* event when determining the value crossed the threshold. + +This allows for **superlinear convergence**, meaning it finds high-precision times with very few function evaluations compared to brute-force searching. + +## Reporting + +The engine provides three main reporting modes: + +1. **`report_scalars`**: Evaluates a list of expressions at fixed time steps. This is parallelized using `rayon` for maximum speed. +2. **`report_events`**: Finds discrete instants where a condition happens (e.g., "Apoapsis", "Max Elevation"). +3. **`report_event_arcs`**: Finds durations where a condition holds true (e.g., "Eclipse Duration", "Comm Window"). +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. + +## Safety and S-Expressions + +Because analysis queries are just data structures (enums), they can be serialized into a lisp-like S-expression format. + +```lisp +(Event + (scalar (AngleBetween (Radius) (SunVector))) + (condition (LessThan 90.0)) +) +``` + +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. diff --git a/docs/anise/explanation/frame-safety.md b/docs/anise/explanation/frame-safety.md new file mode 100644 index 0000000..84deb6c --- /dev/null +++ b/docs/anise/explanation/frame-safety.md @@ -0,0 +1,40 @@ +# Frame Safety and Graph Lookups + +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. + +ANISE introduces the concept of **Frame Safety** to eliminate these errors. + +## Frames are Not Just IDs + +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. + +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: +- **`mu_km3_s2`**: the gravitational parameter in km^3/s^2 +- **`shape`**: the tri-axial ellipsoid shape of the central object, defined by its semi major and semi minor axes and its polar axis. + +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)`. + +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. + +## Validation Before Computation + +When you request a transformation in ANISE, the toolkit doesn't just blindly multiply matrices or vectors. It performs a **Frame Check**. + +Before any computation: +1. ANISE checks that the target and observer frames are part of the same "graph" (they have a path between them). +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). +3. It identifies the "Common Ancestor"—the point in the frame tree where the paths from the two frames meet. + +## Tree Traversal and Path Finding + +ANISE represents the relationship between frames as a tree. + +When transforming from Frame A to Frame B: +- **Translation Path**: Finds the common ephemeris ancestor (e.g., the Solar System Barycenter) and sums the vectors along the branches. +- **Rotation Path**: Finds the common orientation ancestor and composes the Direction Cosine Matrices (DCMs) or Quaternions. + +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. + +## The `Frame` Object + +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. diff --git a/docs/anise/explanation/index.md b/docs/anise/explanation/index.md index cf34bf9..db93618 100644 --- a/docs/anise/explanation/index.md +++ b/docs/anise/explanation/index.md @@ -1,4 +1,11 @@ > Explanation is a discusive treatment of a subject, that permits reflection. Explanation is **understanding-oriented**. -- [_Diataxis_](https://www.diataxis.fr/tutorials/) -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. \ No newline at end of file +This section delves into the design of ANISE, its architecture, and the core concepts that make it a modern alternative to legacy toolkits. + +- **[Why ANISE?](why-anise.md)**: The philosophy and architectural decisions behind the toolkit. +- **[Almanac and Context](almanac-and-context.md)**: Understanding the data-driven design and thread safety. +- **[Frame Safety](frame-safety.md)**: How ANISE prevents coordinate system errors. +- **[Analysis Engine](analysis.md)**: Declarative queries and superlinear event finding. +- **[Time](time.md)**: The importance of high-precision timekeeping. +- **[Validation](validation.md)**: How we ensure ANISE matches (or exceeds) the accuracy of the SPICE toolkit. diff --git a/docs/anise/explanation/why-anise.md b/docs/anise/explanation/why-anise.md new file mode 100644 index 0000000..cf08de5 --- /dev/null +++ b/docs/anise/explanation/why-anise.md @@ -0,0 +1,34 @@ +# Why ANISE? + +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. + +## Designed for Modern Hardware + +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: + +- **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. +- **Thread Safety**: Because of its global state and internal cache mechanisms, SPICE is inherently NOT thread-safe. + +**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. + +## Safety through Rust + +By building on Rust, ANISE leverages a powerful type system and ownership model to provide guarantees that other toolkits cannot: + +- **Memory Safety**: Rust prevents common bugs like null pointer dereferences, buffer overflows, and data races at compile time. +- **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. + +## Precision and Performance + +ANISE matches SPICE's mathematical precision—and in many cases, exceeds it. + +- **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. +- **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. + +## Multi-language from the Core + +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. +- **Rust**: Native performance and type safety. +- **Python**: A pythonic API (`anise-py`) that offers the performance and safety of the Rust core. +- **CLI/GUI**: Tools for quick inspection and visualization that work cross-platform. +- **C++**: While planned, this interface is stalled due to limited resources. diff --git a/docs/anise/reference/almanac.md b/docs/anise/reference/almanac.md new file mode 100644 index 0000000..1af968f --- /dev/null +++ b/docs/anise/reference/almanac.md @@ -0,0 +1,62 @@ +# Almanac Reference + +The `Almanac` is the primary interface for ANISE. It serves as the context for all ephemeris, orientation, and physical constant lookups. + +The following is a _small subset_ of all the functions available in the Almanac. Please refer to for the exhaustive list: Python functions are (almost) always named identically. + +## Key Concepts + +- **Thread Safety**: The `Almanac` is `Send + Sync`, meaning it can be safely shared across threads. +- **Data Encapsulation**: It stores loaded kernel data internally; there is no global pool. +- **Method Chaining**: Loading methods often use a builder pattern or return a new handle for easy initialization. + +## Loading Data + +The `Almanac` supports loading various kernel types. You can load files directly or allow ANISE to "guess" the file type. + +### `load` +Loads any supported ANISE or SPICE file. +- **Rust**: `almanac.load("path/to/file")?` +- **Python**: `almanac.load("path/to/file")` + +### Specialized Loaders +If you know the file type, you can use specialized loaders for better performance or specific configuration: +- `with_spk(spk)`: Add SPK (ephemeris) data. +- `with_bpc(bpc)`: Add BPC (high-precision orientation) data. +- `with_planetary_data(pca)`: Add an ANISE planetary constant data kernel (gravity and tri-axial ellipsoid constants and low-fidelity orientation). +- `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). +- `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). + +## Coordinate Transformations + +The most common use of the `Almanac` is to transform positions and velocities between frames. + +### `transform_to` +Transforms an `Orbit` (state vector) into a different frame at its own epoch. +- **Parameters**: `orbit`, `target_frame`, `aberration`. +- **Returns**: A new `Orbit` in the target frame. + +### `translate` +Calculates the relative position and velocity between two frames. +- **Parameters**: `target`, `observer`, `epoch`, `aberration`. +- **Returns**: A `StateVector` (Position + Velocity). + +### `rotate` +Calculates the rotation (DCM) from one orientation frame to another. +- **Parameters**: `target`, `observer`, `epoch`. +- **Returns**: A 3x3 Direction Cosine Matrix. + +## Physical Constants + +The `Almanac` also stores physical data for bodies defined in the kernels. + +### `frame_info` +Retrieves a `Frame` object containing metadata for a given NAIF ID. +- **Includes**: Gravitational parameter ($\mu$), body radii, and frame type. + +### `angular_velocity_deg_s` +Returns the angular velocity vector in deg/s of the from_frame wrt to the to_frame. + +## Built-in Constants + +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`). diff --git a/docs/anise/reference/analysis-api.md b/docs/anise/reference/analysis-api.md new file mode 100644 index 0000000..bd25ca8 --- /dev/null +++ b/docs/anise/reference/analysis-api.md @@ -0,0 +1,129 @@ +# Analysis API + +The Analysis API allows you to query the almanac for data products, events, and contact windows. + +## Reporting Scalars + +Use `report_scalars` to generate time-series data for analysis or plotting. + +### `almanac.report_scalars(report, time_series)` + +Expects a `ReportScalars` object containing a list of expressions and a `StateSpec`. + +**Parameters:** +- `report`: A `ReportScalars` instance defined with: + - `state_spec`: The context (Target, Observer, Frame) to evaluate against. + - `scalars`: A list of `ScalarExpr` items. +- `time_series`: A `hifitime::TimeSeries` defining the start, end, and step. + +**Returns:** +A dictionary (or map) where keys are Epochs and values are maps of `{ "Alias": Value }`. + +```python + target_frame = analysis.FrameSpec.Loaded(Frames.EME2000) + observer_frame = analysis.FrameSpec.Loaded(Frames.MOON_J2000) + + state = analysis.StateSpec( + target_frame=target_frame, + observer_frame=observer_frame, + ab_corr=None, + ) + + # Define all scalars to be calculated + scalars = [ + analysis.ScalarExpr.Element(analysis.OrbitalElement.SemiMajorAxis), + analysis.ScalarExpr.Element(analysis.OrbitalElement.Eccentricity), + analysis.ScalarExpr.Element(analysis.OrbitalElement.Rmag), + analysis.ScalarExpr.BetaAngle(), + analysis.ScalarExpr.SolarEclipsePercentage(eclipsing_frame=Frames.VENUS_J2000), + ] + + # Add optional aliases (Tuple of (Scalar, Alias)) + scalars_with_aliases = [(s, None) for s in scalars] + + # Build the final report object + report = analysis.ReportScalars(scalars_with_aliases, state) + + # Generate the report data + series = TimeSeries( + Epoch("2025-01-01 00:00:00 UTC"), + Epoch("2025-01-02 12:00:00 UTC"), + Unit.Day * 0.5, + inclusive=True, + ) + data = almanac.report_scalars(report, series) +``` + +## Finding Events + +Use `report_events` to find specific instants (points in time). + +### `almanac.report_events(state, event, start, end)` + +Finds discrete events such as: +- **Orbital Events**: Apoapsis, Periapsis. +- **Min/Max**: Time of maximum elevation, minimum range. +- **Crossings**: Time when altitude crosses 100km. + +**Returns:** +A list of `EventDetails` objects, each containing: +- `epoch`: The precise time of the event. +- `value`: The value of the scalar at that time. +- `orbit`: The full spacecraft state at that time. + +```python + # Define an event: Sun Angle < 90.0 degrees (Sunset) + sun_has_set = analysis.Event( + analysis.ScalarExpr.SunAngle(observer_id=-85), # LRO ID + Condition.LessThan(90.0), + Unit.Second * 0.5, + ab_corr=None, + ) + + # Predefined events are also available + apolune = Event.apoapsis() + perilune = Event.periapsis() + + # Find distinct events (points in time) + apo_events = almanac.report_events(lro_state_spec, apolune, start_epoch, end_epoch) +``` + +## Finding Intervals (Arcs) + +Use `report_event_arcs` to find durations. + +### `almanac.report_event_arcs(state, event, start, end)` + +Finds time intervals where a condition is continuously true. +- **Visibility**: "When can I see the station?" +- **Battery**: "When am I in sunlight?" +- **Geometry**: "When is the Earth-Sun-Probe angle < 90?" + +**Returns:** +A list of `EventArc` objects, each containing: +- `start`: The `EventDetails` of the start (Rising edge). +- `end`: The `EventDetails` of the end (Falling edge). + +```python + eclipse = Event.total_eclipse(Frames.MOON_J2000) + + # Find durations (arcs) + sunset_arcs = almanac.report_event_arcs( + lro_state_spec, sun_has_set, start_epoch, end_epoch + ) + + eclipse_arcs = almanac.report_event_arcs( + lro_state_spec, eclipse, start_epoch, start_epoch + period * 3 + ) +``` + +**Returns:** +A list of `EventArc` objects, each containing: +- `start`: The `EventDetails` of the start (Rising edge). +- `end`: The `EventDetails` of the end (Falling edge). + +The [`EventArc`](https://docs.rs/anise/latest/anise/analysis/event/struct.EventArc.html#impl-EventArc) provides the following helpers: +- `duration`: the duration of the event, as a hifitime Duration +- `start_epoch`, `end_epoch`: the epochs of the start and end of the event +- `midpoint_epoch`: the half-way point in the event, useful if you need to check for some calculation that happens undoubtedly during the event itself. + diff --git a/docs/anise/reference/api/index.md b/docs/anise/reference/api/index.md new file mode 100644 index 0000000..41acaed --- /dev/null +++ b/docs/anise/reference/api/index.md @@ -0,0 +1,18 @@ +# API Documentation + +ANISE is available as a Rust crate and a Python package. Both share the same high-performance core but provide language-specific interfaces. + +## [Rust API](https://docs.rs/anise/latest/anise/) + +The Rust API provides the most complete and performant access to ANISE. It includes full type safety for frames and epochs. + +- [Almanac](https://docs.rs/anise/latest/anise/struct.Almanac.html) +- [Orbit](https://docs.rs/anise/latest/anise/struct.Orbit.html) +- [Epoch](https://docs.rs/anise/latest/anise/time/index.html) + +## Python API + +The Python API (`anise-py`) is designed to be familiar to users of `SpiceyPy` and `MONTE`, while offering the performance of Rust. + +- [Python Module Overview](python/index.md) (Internal documentation) +- [Examples and Jupyter Notebooks](https://github.com/nyx-space/anise/tree/main/anise-py/examples) diff --git a/docs/anise/reference/azimuth-elevation-range.md b/docs/anise/reference/azimuth-elevation-range.md new file mode 100644 index 0000000..c90f413 --- /dev/null +++ b/docs/anise/reference/azimuth-elevation-range.md @@ -0,0 +1,69 @@ +# Azimuth, Elevation, and Range (AER) + +AER provides the relative orientation and distance between an observer (transmitter) and a target (receiver). In ANISE, this is typically computed from a ground station (`Location`) toward a spacecraft or another celestial body. + +## The `AzElRange` Struct + +This structure stores the result of an AER calculation. + +- **`epoch`**: The time at which the calculation was performed. +- **`azimuth_deg`**: The horizontal angle from North (clockwise), between 0 and 360 degrees. +- **`elevation_deg`**: The angle above the horizon, between -180 and 180 degrees (typically -90 to 90). +- **`range_km`**: The Euclidean distance between the observer and the target. +- **`range_rate_km_s`**: The instantaneous speed along the line of sight (positive if moving away). +- **`light_time`**: The one-way light time (calculated as `range / c`). +- **`mask_deg`**: The elevation of the terrain mask at this azimuth. +- **`obstructed_by`**: If the view is blocked by a celestial body, this field identifies the body. + +### Helper Methods +- `is_valid()`: Returns true if the range is $> 1$ mm and angles are finite. +- `is_obstructed()`: Returns true if there is a celestial obstruction or if the target is below the terrain mask. +- `elevation_above_mask_deg()`: Returns the difference between the target's elevation and the local terrain mask elevation. + +## Almanac Functions + +The `Almanac` provides several functions to compute AER: + +| Function | Description | +| :--- | :--- | +| `azimuth_elevation_range_sez` | Computes AER between two `Orbit` objects. | +| `azimuth_elevation_range_sez_from_location` | Computes AER using a `Location` object as the transmitter. | +| `azimuth_elevation_range_sez_from_location_id` | Uses a location ID from a loaded Location Kernel. | +| `azimuth_elevation_range_sez_from_location_name` | Uses a location name (alias) from a loaded Location Kernel. | + +## Mathematical Implementation + +ANISE follows the algorithm described in **Vallado, Section 4.4.3** for SEZ (South-East-Zenith) coordinate transformations. + +### 1. Line-of-Sight Calculation +If an `obstructing_body` is provided, ANISE first performs an ellipsoid intersection test. A line segment is drawn from the transmitter to the receiver. If this segment intersects the body's tri-axial ellipsoid, the `obstructed_by` field is populated, and the calculation reflects the obstruction. + +### 2. SEZ Frame Transformation +The transmitter's state is used to define a local **SEZ frame**: +- **Zenith (Z)**: Normal to the reference ellipsoid at the transmitter's location. +- **South (S)**: Points toward the South pole of the central body. +- **East (E)**: Completes the right-handed system ($E = Z \times S$). + +### 3. Coordinate Rotation +Both the transmitter ($\mathbf{r}_{tx}$) and receiver ($\mathbf{r}_{rx}$) position vectors are rotated into this SEZ frame. The relative vector is computed as: +$$\boldsymbol{\rho}_{sez} = \mathbf{r}_{rx\_sez} - \mathbf{r}_{tx\_sez}$$ + +### 4. Angle Computation +- **Range**: $\rho = \|\boldsymbol{\rho}_{sez}\|$ +- **Elevation**: $\phi = \arcsin\left(\frac{\rho_z}{\rho}\right)$ +- **Azimuth**: $A = \operatorname{atan2}(\rho_y, -\rho_x)$ + +The range-rate $\dot{\rho}$ is computed by projecting the relative velocity vector onto the unit line-of-sight vector: +$$\dot{\rho} = \frac{\boldsymbol{\rho} \cdot \mathbf{v}_{rel}}{\rho}$$ + +## Testing and Validation + +### Rust Tests +Validated in `anise/src/almanac/aer.rs`: +- `verif_edge_case`: Ensures stability when transmitter and receiver are at the same location. +- `gmat_verif`: Validates range and range-rate results against GMAT (General Mission Analysis Tool) for DSN station Madrid. + +### Python Tests +Validated in `anise-py/tests/test_analysis.py`: +- `test_location_accesses`: Demonstrates building a location kernel, loading it, and calculating visibility/AER for the Lunar Reconnaissance Orbiter (LRO). +- The `report_visibility_arcs` function uses these computations to determine contact windows. diff --git a/docs/anise/reference/frame.md b/docs/anise/reference/frame.md new file mode 100644 index 0000000..6360292 --- /dev/null +++ b/docs/anise/reference/frame.md @@ -0,0 +1,40 @@ +# Frame Reference + +Frames define the coordinate systems used for all calculations in ANISE. Every position, velocity, and rotation is defined with respect to a specific Frame. + +## Frame Identification + +ANISE is compatible with the standard NAIF ID system. You can refer to frames using: +1. **Integer IDs**: e.g., `399` for Earth. +2. **Predefined Constants**: e.g., `anise::constants::frames::EARTH_J2000`. +3. **Names**: In some interfaces (like Python), strings can be used if they have been registered in the `Almanac`. + +## Frame Safety + +The `Frame` object in ANISE is more than just an ID; it's a validated descriptor. When you create an `Orbit`, it is "tagged" with a `Frame`. + +```rust +let frame = almanac.frame_info(EARTH_J2000)?; +let orbit = Orbit::cartesian(x, y, z, vx, vy, vz, epoch, frame); +``` + +If you attempt to perform a math operation between two orbits in different frames, ANISE will either: +- **Automatic Transform**: Some APIs will automatically transform the state to a common frame if the `Almanac` is provided. +- **Error**: If no `Almanac` is provided to resolve the relationship, ANISE will refuse to perform the operation to prevent physical errors. + +## Metadata +A `Frame` object retrieved via `almanac.frame_info()` contains data retrieved from the loaded datasets (SPK, PCK, BPC): + +- **`mu_km3_s2`**: The gravitational parameter ($GM$) of the center body in $km^3/s^2$. +- **`shape`**: A tri-axial `Ellipsoid` defining the body's semi-major, semi-minor, and polar radii. It provides methods for surface intersection testing and calculating lighting angles (emission, solar incidence). +- **`ephemeris_id`**: The NAIF ID used for translation and trajectory lookups. +- **`orientation_id`**: The NAIF ID used for rotation lookups. + +### Orientation and Phase Angles +For planetocentric (body-fixed) frames, ANISE uses the loaded PCK or BPC data to determine the body's orientation. This is defined by three key phase angles: +1. **Right Ascension (RA)** of the body's north pole. +2. **Declination (Dec)** of the body's north pole. +3. **Prime Meridian (W)** location at the given epoch. + +These angles are often modeled as polynomials in time (e.g., $RA = RA_0 + RA_1 \Delta t + RA_2 \Delta t^2 \dots$). ANISE evaluates these to compute the exact rotation matrix between inertial and body-fixed frames. + diff --git a/docs/anise/reference/index.md b/docs/anise/reference/index.md index 116bf98..b7b9424 100644 --- a/docs/anise/reference/index.md +++ b/docs/anise/reference/index.md @@ -3,4 +3,14 @@ This reference includes the API documentation for all of the ANISE libraries. It also contains the mathematical specifications of most calculations used in ANISE. +- **[Almanac](almanac.md)**: The central context object for loading data and performing transformations. +- **[Frame](frame.md)**: Definitions of coordinate frames, their types, and metadata. +- **[Orbit and States](orbit.md)**: How orbital states are represented, initialized, and manipulated. +- **[Analysis API](analysis-api.md)**: Tools for high-level querying, event finding, and reporting. +- **[Azimuth, Elevation, Range](azimuth-elevation-range.md)**: Computing relative orientations and visibility. +- **[Location Kernels](location.md)**: Defining ground stations and observatories. +- **[Instrument Kernels](instrument.md)**: Modeling spacecraft sensors and fields of view. +- **[API Documentation](api/index.md)**: Detailed API references for Rust and Python. +- **[Math Specs](mathspec/orbital_elements.md)**: Detailed mathematical derivations and implementations. + As for the rest of the Nyx Space tools, if you think this reference is missing something, feel free to request it as part of a discussion on Github or via the contact form. \ No newline at end of file diff --git a/docs/anise/reference/instrument.md b/docs/anise/reference/instrument.md new file mode 100644 index 0000000..d6323f3 --- /dev/null +++ b/docs/anise/reference/instrument.md @@ -0,0 +1,36 @@ +# Instrument Kernels + +Instrument Kernels allow you to define sensors, cameras, and antennas attached to a spacecraft. + +## The `Instrument` Object + +An `Instrument` defines the sensor's geometry relative to the spacecraft bus. + +### Mounting +The mounting is defined by a rigid transformation from the **Spacecraft Body Frame** to the **Instrument Frame**. +- **`q_to_i`**: A quaternion (rotation) describing the orientation. +- **`offset_i`**: A translation vector (lever arm) from the spacecraft center of mass to the sensor origin. + +### Field of View (FOV) +The FOV defines the sensitive volume of the instrument. +- **Conical**: Circular FOV (e.g., dish antennas, LIDARs). Defined by a `half_angle_deg`. +- **Rectangular**: Box FOV (e.g., camera sensors). Defined by `x_half_angle_deg` (Width) and `y_half_angle_deg` (Height). Assumes the boresight is +Z. + +## Functionality + +### FOV Margin +ANISE can compute the **FOV Margin**, which is the angle between the target and the nearest FOV boundary. +- **Positive**: Target is inside. +- **Negative**: Target is outside. +- **Zero**: Target is exactly on the edge. + +This property is continuous, making it suitable for the **Analysis Engine's** event finder. You can solve for `margin = 0` to find exact entry/exit times. + +### Footprints +The `footprint` method projects the FOV boundary onto a target planetary body (Ellipsoid). +- Raycasts the FOV edges. +- Intersects with the target shape. +- Returns a list of points (lat/lon/alt) forming the polygon of the footprint on the surface. + +This accounts for the full chain: +`Inertial -> Spacecraft Body -> Instrument -> Ray -> Target Inertial -> Target Fixed`. diff --git a/docs/anise/reference/location.md b/docs/anise/reference/location.md new file mode 100644 index 0000000..c9a21de --- /dev/null +++ b/docs/anise/reference/location.md @@ -0,0 +1,66 @@ +# Location Kernels + +Location Kernels (`.lka` files) allow you to define ground stations, observatories, or other points of interest on the surface of a celestial body. + +## The `Location` Object + +A location is defined by its geodetic coordinates: + +- `latitude_deg`: Geodetic latitude. +- `longitude_deg`: Geodetic longitude (positive East). +- `height_km`: Altitude above the reference ellipsoid. +- `frame`: The central body frame (e.g., `ITRF93` for Earth, `IAU_MOON` for Moon). + +## Terrain Masks + +Real-world ground stations do not have a perfect 0-degree horizon. Mountains or buildings may obstruct the view. +ANISE supports **Terrain Masks** to account for this. + +A `TerrainMask` is a list of Azimuth/Elevation pairs. +- **Azimuth**: The start of the mask segment. +- **Elevation**: The minimum elevation required for visibility in that segment. + +If a mask is provided, `ElevationFromLocation` computations will account for it, and visibility searches will use it as the "horizon" limit. + +## Working with `.lka` Files + +ANISE uses a custom Dhall-based or binary format for loading locations. + +### Loading (Rust/Python) + +```python +# Python +almanac = Almanac("de440.bsp").load("dsn_stations.lka") + +# Now you can reference locations by ID or Name +azel = almanac.azimuth_elevation_range_sez_from_location_name( + orbit_state, + "DSS-65" +) +``` + +### Creating (Python) + +You can create location files programmatically: + +```python +from anise.astro import Location, TerrainMask, FrameUid + +# Define a mask +mask = [TerrainMask(0.0, 5.0), TerrainMask(180.0, 10.0)] + +# Create location +dss65 = Location( + latitude_deg=40.427, + longitude_deg=4.250, + height_km=0.834, + frame=FrameUid(399, 399), # Earth + terrain_mask=mask, + terrain_mask_ignored=False +) + +# Export +entry = LocationDhallSetEntry(dss65, id=65, alias="DSS-65") +dhallset = LocationDhallSet([entry]) +dhallset.to_dataset().save_as("my_locations.lka", True) +``` diff --git a/docs/anise/reference/orbit.md b/docs/anise/reference/orbit.md new file mode 100644 index 0000000..72bec0c --- /dev/null +++ b/docs/anise/reference/orbit.md @@ -0,0 +1,46 @@ +# Orbit and State Reference + +In ANISE, an `Orbit` represents the full state of an object at a specific time within a specific coordinate frame. + +## The `Orbit` Struct + +An `Orbit` consists of: +- **State Vector**: Position and Velocity ($\mathbf{r}, \mathbf{v}$). +- **Epoch**: The time at which the state is defined (using `hifitime`). +- **Frame**: The reference frame in which the vectors are expressed. + +## Initialization + +You can initialize an `Orbit` from various representations, including with the following initializers. Refer to for the exhaustive list, which includes Keplerian altitudes, Keplerian apses radii, Keplerian mean anomaly, etc. + +### Cartesian +`Orbit::cartesian(x, y, z, vx, vy, vz, epoch, frame)` +Directly sets the position and velocity in km and km/s. + +### Keplerian +`Orbit::keplerian(sma, ecc, inc, raan, aop, true_anomaly, epoch, frame)` +Calculates the state from classical orbital elements. +- **Note**: Requires the frame to have a defined gravitational parameter ($\mu$). + +### Geodetic / LatLongAlt +`Orbit::try_latlongalt(lat, lon, alt, epoch, frame)` +Defines a state relative to a body's surface. Useful for ground stations. + +## Common Operations + +### `transform_to` +`almanac.transform_to(orbit, target_frame, aberration)` +Returns a new `Orbit` expressed in the `target_frame`. This accounts for both translation and rotation of the frames. + +### Orbital Elements +You can extract orbital elements from an `Orbit` object: +- `sma_km()`, `ecc()`, `inc_deg()`, `raan_deg()`, `aop_deg()`, `ta_deg()` +- **Other Anomalies**: `ma_deg()` (Mean Anomaly), `ea_deg()` (Eccentric Anomaly). +- **Other Parameters**: `periapsis_km()`, `apoapsis_km()`, `hmag()` (Specific Angular Momentum). +- **Python**: Accessible as methods (e.g., `orbit.sma_km()`). + + +### Propagation (Two-Body) +ANISE provides basic two-body propagation for quick estimates: +- `at_epoch(new_epoch)`: Propagates the orbit to a new time using Keplerian motion. +- **Warning**: For high-fidelity propagation including J2, solar radiation pressure, etc., use a dedicated propagator like those found in the `Nyx` library. diff --git a/docs/llms.txt b/docs/llms.txt new file mode 100644 index 0000000..a75036c --- /dev/null +++ b/docs/llms.txt @@ -0,0 +1,26 @@ +# Nyx Space and ANISE + +Nyx is a high fidelity astrodynamics tool licensed in AGPL 3.0. + +ANISE is validated and was used to land Blue Ghost on the Moon. It is available as MPL 2.0. + +## Documentation Structure + +The documentation is organized into the following top-level sections: + +- **Home**: The landing page of the documentation (index.md). +- **Nyx**: Contains the showcase, user guide, and the MathSpec which details the mathematical models used. + - **MathSpec**: Detailed mathematical specifications. +- **Hifitime**: Documentation for the Hifitime library, including design, Python and Rust user guides, and changelog. +- **ANISE**: Documentation for the ANISE library. + - **Tutorials**: Jupyter notebook tutorials. + - **Explanation**: Explains the concepts and architecture. + - **Reference**: Detailed API documentation and reference material. +- **Pricing**: Information about pricing (pricing.md). +- **Blog**: Updates and articles. + +For detailed documentation, please refer to the rest of the website: https://nyxspace.com + +## Tutorials + +Agents are recommended to execute the Jupyter notebook tutorials to learn how to use the tools. diff --git a/mkdocs.yml b/mkdocs.yml index 0bb0092..3e9119f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,10 +99,22 @@ nav: - Sun Probe Earth angle (SPE): anise/tutorials/Tutorial 06 - Sun probe Earth angle.ipynb - Explanation: - Explanation index: anise/explanation/index.md + - Why ANISE?: anise/explanation/why-anise.md + - Almanac: anise/explanation/almanac-and-context.md + - Frame Safety: anise/explanation/frame-safety.md - Time management: anise/explanation/time.md + - Analysis Engine: anise/explanation/analysis.md - Validation: anise/explanation/validation.md - Reference: - Reference index: anise/reference/index.md + - Almanac: anise/reference/almanac.md + - Frame: anise/reference/frame.md + - Orbit and States: anise/reference/orbit.md + - Analysis API: anise/reference/analysis-api.md + - Azimuth, Elevation, Range: anise/reference/azimuth-elevation-range.md + - Location Kernels: anise/reference/location.md + - Instrument Kernels: anise/reference/instrument.md + - API Documentation: anise/reference/api/index.md - Math Spec: - Orbital elements: anise/reference/mathspec/orbital_elements.md - Interpolation methods: