nyx-space / nyx

Nyx is a high fidelity, fast, reliable and validated astrodynamics toolkit library written in Rust and available in Python
https://nyxspace.com
GNU Affero General Public License v3.0
189 stars 20 forks source link

High fidelity transponder modeling (different noise models) #88

Closed ChristopherRabotin closed 1 year ago

ChristopherRabotin commented 1 year ago

High level description

The purpose of this issue is to implement high-fidelity transponder modeling in Nyx, which includes support for the Deep Space Network "Range Units" and Gauss-Markov modeling. This will enable users of Nyx to accurately simulate the performance of transponders in space missions, and to optimize the design and operation of transponder systems.

Requirements

Nyx shall support high-fidelity transponder modeling such that the following features are available to users:

Test plans

Design

The design of the high-fidelity transponder modeling feature in Nyx should follow the architecture and APIs of the existing orbit determination and navigation module, and should be implemented as a new module or a submodule of the existing module.

The transponder model should implement the Measurement trait, and should be represented by a new structure or class that provides functions for initializing, updating, and querying the model parameters. The transponder model should implement the Gauss-Markov model and the Deep Space Network "Range Units" algorithms, and should provide methods for generating simulated range and range-rate measurements.

The transponder model should be integrated with the existing orbit determination and navigation algorithms, such that the transponder model can be used to improve the accuracy and precision of the orbit estimates. The ODProcess structure or class should be updated to support the transponder model, and should provide methods for incorporating the transponder measurements into the orbit determination and navigation algorithms

API definition

Define how the Nyx APIs will be affect by this: what are new functions available, do any previous function change their definition, why call these functions by that name, etc.

High level architecture


struct RangeUnit {
    /// The time at which the measurement was made
    epoch: Time,
    /// The range measurement
    range: f64,
    /// The range rate measurement
    range_rate: f64,
    /// The sensitivity (H-tilde) matrix
    sensitivity: MatrixN<f64, U2>,
    /// Whether the transmitter and receiver were in line of sight
    visible: bool,
}

impl Measurement for RangeUnit {
    type State = StateVector;
    type MeasurementSize = U2;

    fn observation(&self) -> VectorN<f64, Self::MeasurementSize> {
        VectorN::new(self.range, self.range_rate)
    }

    fn sensitivity(&self) -> MatrixMN<f64, Self::MeasurementSize, <Self::State as State>::Size> {
        self.sensitivity
    }

    fn visible(&self) -> bool {
        self.visible
    }
}

The RangeUnit structure contains the time at which the measurement was made, the range and range-rate measurements, the sensitivity (H-tilde) matrix, and a flag indicating whether the transmitter and receiver were in line of sight.

The RangeUnit structure implements the Measurement trait by providing an implementation of the observation(), sensitivity(), and visible() methods. The observation() method returns the range and range-rate measurements as a 2-dimensional vector, the sensitivity() method returns the sensitivity matrix, and the visible() method returns the line-of-sight flag.

The RangeUnit structure is parameterized by the state vector type, which should be the same type used by the orbit determination and navigation algorithms. This allows the RangeUnit measurements to be seamlessly integrated with the existing algorithms.

Human: check that this is correct.

impl RangeUnit {
    /// Creates a new `RangeUnit` instance.
    ///
    /// This function takes the following parameters:
    /// - `epoch`: the time at which the measurement was made
    /// - `tx_frequency`: the transmitter frequency
    /// - `rx_frequency`: the receiver frequency
    /// - `turn_around_ratio`: the turn-around ratio
    /// - `sensitivity`: the sensitivity (H-tilde) matrix
    /// - `visible`: a flag indicating whether the transmitter and receiver were in line of sight
    ///
    /// The function returns a `RangeUnit` instance with the computed range and range-rate.
    fn new(
        epoch: Time,
        tx_frequency: f64,
        rx_frequency: f64,
        turn_around_ratio: f64,
        sensitivity: MatrixN<f64, U2>,
        visible: bool,
    ) -> Self {
        // Compute the range and range-rate
        let range = SPEED_OF_LIGHT / (tx_frequency - turn_around_ratio * rx_frequency);
        let range_rate = SPEED_OF_LIGHT * turn_around_ratio * rx_frequency / (tx_frequency - turn_around_ratio * rx_frequency).powi(2);

        // Create the `RangeUnit` instance
        RangeUnit {
            epoch,
            range,
            range_rate,
            sensitivity,
            visible,
        }
    }
}
ChristopherRabotin commented 1 year ago

The GMAT User Guide for 2020a details how this is computed in GMAT. Also look at the ODTK math spec.

ChristopherRabotin commented 1 year ago

Claude:

Absolutely, here is a summary of relating measurement noise to Pr/N0 (received signal power to noise density ratio) and range:

For range measurements, higher Pr/N0 corresponds to higher SNR (signal-to-noise ratio) which means lower noise. Pr/N0 depends on:

So to model range noise from Pr/N0 data:

  1. Get a data set of Pr/N0 values at different ranges for your system. This establishes the relationship between Pr/N0, range and noise.

  2. Determine a "threshold" Pr/N0 where noise starts becoming unacceptable for your accuracy needs. Call this Pr/N0_thresh. Ranges with Pr/N0 below this will have excess noise.

  3. Fit a curve to your data to get a function relating Pr/N0 to range: Pr/N0 = f(range). You now have a model for how Pr/N0 drops off with increasing range.

  4. For any range, use your fit function to calculate the expected Pr/N0. If Pr/N0 >= Pr/N0_thresh, use it to scale your range noise sigma. Higher Pr/N0 means lower sigma. If Pr/N0 < Pr/N0_thresh, use an excess sigma value instead, since your accuracy needs are not being met.

  5. Also record the range at which your fit function gives Pr/N0_thresh. This is the max usable range before excess noise - measurements past this will have higher uncertainty.

So in summary, to add a noise model based on Pr/N0 and range to Nyx:

  1. Have users enter a data set of Pr/N0 vs range values for their transmitter/receiver system.

  2. Have them specify their Pr/N0_thresh and excess noise sigma.

  3. Fit and store the function relating Pr/N0 to range for their data.

  4. At each filter measurement update, get the current range.

    • If within max usable range, calculate the expected Pr/N0 and scale sigma inversely.
    • If past max usable range, use excess sigma instead.
  5. Add noise from the appropriate distribution before performing the update.

This allows properly modeling how range noise increases with range as the received signal power and SNR drops based on a user's specific system characteristics. Please let me know if this provides a helpful summary for adding a Pr/N0 and range-based noise model to Nyx! I aimed to give a high-level overview of the steps involved while still relating how and why this method works. Feel free to ask for clarification on any part of the concept or process.