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
188 stars 20 forks source link

Add Ground Station cadence and availability constraints (more noise model design) #89

Closed ChristopherRabotin closed 1 year ago

ChristopherRabotin commented 1 year ago

High level description

One reason why the simulation may need to account for the availability of ground stations is to ensure that the measurements used in the orbit determination process are realistic and accurate. If a ground station is not available at a certain time, then the simulation cannot generate any measurements from that station at that time. This can lead to gaps in the measurements, which can affect the accuracy of the orbit determination process.

Requirements

Test plans

Overall, it is important to test a variety of different scenarios to ensure that the simulation handles the availability of ground stations correctly in all cases.

Design

API definition

Human: this does not adequately design a cadence and only includes a start time and a duration of availability. I think that the cadence can be generated using the TimeSeries iterator of hifitime (specify a start and end epoch and a step size).

Detailed design

/// Defines a ground station for use in the orbit determination process.
pub struct GroundStation<N>
where
    N: NoiseModel,
{
    /// The name of the ground station.
    pub name: String,
    /// The elevation mask of the ground station in degrees.
    pub elevation_mask: f64,
    /// The latitude of the ground station in degrees.
    pub latitude: f64,
    /// The longitude of the ground station in degrees.
    pub longitude: f64,
    /// The height of the ground station in km.
    pub height: f64,
    /// The frame in which this ground station is defined.
    pub frame: Frame,
    /// The noise model used to generate measurements from this ground station.
    pub noise_model: N,
    /// The time range and/or cadence for the availability of this ground station.
    pub availability: Option<(Epoch, Option<Duration>)>,
}

This structure is now generic over a NoiseModel type, which allows for different noise models to be used for different ground stations. The availability field is an Option type that allows for the specification of either a time range or a cadence for the availability of this ground station. If the availability field is None, then the ground station is always available.

Human: the noise model likely need to be tied to the kind of data being generated. In this proposal, it will fail

/// Defines a noise model for use in the orbit determination process.
pub trait NoiseModel {
    /// Defines the type of random number generator used by this noise model.
    type Rng: rand::Rng;

    /// Generates a noise vector with the given dimensions using the provided random number generator.
    fn generate_noise<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName;
}

/// Defines a normal noise model with the given standard deviation.
pub struct NormalNoiseModel {
    /// The standard deviation of the normal distribution.
    pub standard_deviation: f64,
}

impl NoiseModel for NormalNoiseModel {
    type Rng = rand_distr::StandardNormal;

    fn generate_noise<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName,
    {
        let mut noise = VectorN::zeros(dimensions);
        for element in noise.iter_mut() {
            *element = self.standard_deviation * rng.sample(rand_distr::StandardNormal);
        }
        noise
    }
}

When asked to write a NoiseModel trait for a Gauss Markov, ChatGPT proposes the following.

/// Defines a noise model for use in the orbit determination process.
pub trait NoiseModel {
    /// Defines the type of random number generator used by this noise model.
    type Rng: rand::Rng;

    /// Generates a noise vector with the given dimensions using the provided random number generator.
    fn generate_noise<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName;

    /// Generates a bias vector with the given dimensions using the provided random number generator.
    fn generate_bias<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName;
}

/// Defines a Gauss Markov noise model with the given standard deviation and correlation time.
pub struct GaussMarkovNoiseModel {
    /// The standard deviation of the Gauss Markov noise process.
    pub standard_deviation: f64,
    /// The correlation time of the Gauss Markov noise process.
    pub correlation_time: f64,
}

impl NoiseModel for GaussMarkovNoiseModel {
    type Rng = rand_distr::StandardNormal;

    fn generate_noise<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName,
    {
        let mut noise = VectorN::zeros(dimensions);
        for element in noise.iter_mut() {
            *element = self.standard_deviation * rng.sample(rand_distr::StandardNormal);
        }
        noise
    }

    fn generate_bias<D>(&self, rng: &mut Self::Rng, dimensions: D) -> VectorN<f64, D>
    where
        D: DimName,
    {
        let mut bias = VectorN::zeros(dimensions);
        for element in bias.iter_mut() {
            *element = self.standard_deviation * self.correlation_time * rng.sample(rand_distr::StandardNormal);
        }
        bias
    }
}

Human: this looks different to a Gauss Markov model from the NASA Navigation Filter Best Practices.

ChatGPT then proposes the following:

struct GaussMarkovNoiseModel {
    bias: f64,
    sigma: f64,
}

impl NoiseModel for GaussMarkovNoiseModel {
    fn noise_at(&self, delta_time: f64) -> f64 {
        self.bias + self.sigma * delta_time
    }
}

This is just a simple example, but it should illustrate how you can define a NoiseModel for a Gauss Markov random walk. You would then be able to use this NoiseModel in the GroundStation structure, for example by adding a NoiseModel parameter to the GroundStation structure. This would allow you to specify the noise model when creating a new GroundStation.

ChristopherRabotin commented 1 year ago

This is mostly implemented in #114 but lacks the thorough testing laid out here. When this issue is tackled, it will be to add the testing.

ChristopherRabotin commented 1 year ago

At the moment, there is a tracking device simulator trait defined as follows:

/// Tracking device simulator.
pub trait TrackingDeviceSim<MsrIn, Msr>: Configurable
where
    MsrIn: State,
    Msr: SimMeasurement,
    DefaultAllocator: Allocator<f64, <Msr::State as State>::Size>
        + Allocator<f64, <Msr::State as State>::Size, <Msr::State as State>::Size>
        + Allocator<f64, <Msr::State as State>::VecLength>
        + Allocator<f64, Msr::MeasurementSize>
        + Allocator<f64, Msr::MeasurementSize, <Msr::State as State>::Size>
        + Allocator<f64, MsrIn::Size>
        + Allocator<f64, MsrIn::Size, MsrIn::Size>
        + Allocator<f64, MsrIn::VecLength>,
{
    /// Returns the name of this tracking data simulator
    fn name(&self) -> String;

    /// Observes the input state and returns a measurement from itself to the input state, and returns None of the object is not visible.
    fn measure<R: Rng>(&mut self, input: &MsrIn, rng: &mut R, cosm: Arc<Cosm>) -> Option<Msr>;
}

To allow estimation of noise parameters from these tracking devices, there should be a new function (or a new trait?) that can simulate a measurement either without any noise (NoiseModel::ZERO -- probably const) or with a fixed noise value. If it's a new trait, it should probably just be called TrackingDevice.

Keep in mind that the ODP currently requires the devices to implement TrackingDeviceSim. This change will add a lot of code changes.