Giter Site home page Giter Site logo

nyx-space / nyx Goto Github PK

View Code? Open in Web Editor NEW
149.0 8.0 17.0 172.96 MB

Nyx is a high fidelity, fast, reliable and validated astrodynamics toolkit library written in Rust and available in Python

Home Page: https://nyxspace.com

License: GNU Affero General Public License v3.0

Rust 93.45% Python 6.47% Dockerfile 0.06% Dhall 0.02%
space-mission astrodynamics orbital-mechanics gmat odtk orbit-determination python rust spice trajectory-optimization

nyx's People

Contributors

baoyachi avatar christopherrabotin avatar cmleinz avatar dependabot-support avatar dependabot[bot] avatar jake-asquith-spire avatar larsnaesbye avatar saichikine avatar striezel avatar thibfrgsgmz avatar thomasantony avatar tilblechschmidt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nyx's Issues

Backward time propagation

Currently, only forward time propagation is allowed. It should be possible to perform backward propagation. This is used as a validation in GMAT.

Propagate until different conditions

This is an abstract issue

It should be possible to propagate a spacecraft until a variety of conditions, e.g. prop.until_condition(cond, &mut dyn, error_ctrl). I'm not sure how to fully implement this, but I think the general premise would be to provide any stopping function which returns whether or not the condition has been met, and in what direction the propagator should look for next. The signature might be something like fn condition(time: f64, state: State<F>) -> Result<(), NextDirection> where NextDirection is an enum with Backward, Forward and Precisely(f64). The Precisely would be used to specify an exact amount of time by which the propagator should step backward. This would be useful for time propagation or possibly two-body dynamics propagation where there is an analytical solution to orbital element variations. (Note: I'm not sure the Precisely is really needed... if someone is only doing a time propagation, they should use the prop.until_time_elapsed function).

Further, there should be a feature to allow for trivial propagation cases, like "propagate to periapse/apoapse/AoL/etc.". This would be simple to use.

Add a TLE state initializer

TLEs are the format used by NORAD to report on spacecraft states, so it makes sense to be able to load a TLE into nyx.

Search through Navigation Trajectories

High level description

ANISE adds supports for SPICE files and introduces a new SPK type for storing covariance information. The purpose of this issue is to allow searching through navigation trajectories for navigation related events, e.g. "when does my covariance drop below X", current implementation only allows searching the estimated state not its covariance.

Requirements

  • Nyx shall be able to find specific orbit determination events in the results of an OD Process.

Test plans

How do we test that these requirements are fulfilled correctly? What are some edge cases we should be aware of when developing the test code.

Design

This is the design section. Each subsection has its own subsection in the quality assurance document.

Algorithm demonstration

If this issue requires a change in an algorithm, it should be described here. This algorithm should be described thoroughly enough to be used as documentation. This section may also simply refer to an algorithm in the literature or in another piece of software that has been validated. The quality of that reference will be determined case by case.

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

Document, discuss, and optionally upload design diagram into this section.

Detailed design

The detailed design *will be used in the documentation of how Nyx works.

Feel free to fill out additional QA sections here, but these will typically be determined during the development, including the release in which this issue will be tackled.

Automatically differentiate state transition matrix via the use of dual numbers

(And probably write a paper on this method)

Summary

I determined how to compute the state transition matrix of dynamics using dual numbers. By semi-clever organization of the vector returned by the equation of motion, it is possible to generate the gradient of the EOM for the provided state.

The following proof of concept uses the dual space, and as such requires N calls to the EOMs, where N corresponds to the size of the state. However, if using a hyperdual space of size N, the EOMs only need to be computed once.

Proof of concept

Output

Note that the expected STM is computed through the analytical version, as done here

     Running `target/debug/dualgradtest`

  ┌                                                                                                                                                                         ┐
  │                           0                           0                           0                           1                           0                           0 │
  │                           0                           0                           0                           0                           1                           0 │
  │                           0                           0                           0                           0                           0                           1 │
  │ -0.000000018628398676538285   -0.0000000408977477510809  -0.00000001544439654966729                           0                           0                           0 │
  │ -0.000000040897747751080905    0.0000000452532717518738  0.000000031658392121967554                           0                           0                           0 │
  │  -0.00000001544439654966729   0.00000003165839212196755 -0.000000026624873075335535                           0                           0                           0 │
  └                                                                                                                                                                         ┘


|delta| = 5.909567257049796e-23

main.rs

extern crate dual_num;
extern crate nalgebra as na;

use dual_num::{differentiate, DualNumber, Float, FloatConst};
use na::{DimName, Matrix6, U3, U6, Vector3, Vector6};

fn diff<F>(x: Vector6<f64>, func: F) -> Matrix6<f64>
where
    F: Fn(&Vector6<DualNumber<f64>>) -> Vector6<DualNumber<f64>>,
{
    let mut grad_as_slice = Vec::with_capacity(U6::dim() * U6::dim());
    for coord in 0..U6::dim() {
        let x11 = Vector6::<DualNumber<f64>>::new(
            DualNumber::new(x[(0, 0)], if coord == 0 { 1.0 } else { 0.0 }),
            DualNumber::new(x[(1, 0)], if coord == 1 { 1.0 } else { 0.0 }),
            DualNumber::new(x[(2, 0)], if coord == 2 { 1.0 } else { 0.0 }),
            DualNumber::new(x[(3, 0)], if coord == 3 { 1.0 } else { 0.0 }),
            DualNumber::new(x[(4, 0)], if coord == 4 { 1.0 } else { 0.0 }),
            DualNumber::new(x[(5, 0)], if coord == 5 { 1.0 } else { 0.0 }),
        );
        let df_di = func(&x11);
        for i in 0..6 {
            grad_as_slice.push(df_di[(i, 0)].dual());
        }
    }

    let grad = Matrix6::from_column_slice(&grad_as_slice);
    grad
}

fn eom(state: &Vector6<DualNumber<f64>>) -> Vector6<DualNumber<f64>> {
    let radius = state.fixed_rows::<U3>(0).into_owned();
    let velocity = state.fixed_rows::<U3>(3).into_owned();
    let norm = (radius[(0, 0)].powi(2) + radius[(1, 0)].powi(2) + radius[(2, 0)].powi(2)).sqrt();
    let body_acceleration_f = DualNumber::from_real(-398_600.4415) / norm.powi(3);
    let body_acceleration = Vector3::new(
        radius[(0, 0)] * body_acceleration_f,
        radius[(1, 0)] * body_acceleration_f,
        radius[(2, 0)] * body_acceleration_f,
    );
    Vector6::from_iterator(velocity.iter().chain(body_acceleration.iter()).cloned())
}

fn main() {
    let state = Vector6::new(
        -9042.862233600335,
        18536.333069123244,
        6999.9570694864115,
        -3.28878900377057,
        -2.226285193102822,
        1.6467383807226765,
    );
    let grad = diff(state, eom);
    println!("{}", grad);

    let mut expected = Matrix6::zeros();
    expected[(0, 3)] = 1.0;
    expected[(1, 4)] = 1.0;
    expected[(2, 5)] = 1.0;
    expected[(3, 0)] = -0.000000018628398676538285;
    expected[(4, 0)] = -0.00000004089774775108092;
    expected[(5, 0)] = -0.0000000154443965496673;
    expected[(3, 1)] = -0.00000004089774775108092;
    expected[(4, 1)] = 0.000000045253271751873843;
    expected[(5, 1)] = 0.00000003165839212196757;
    expected[(3, 2)] = -0.0000000154443965496673;
    expected[(4, 2)] = 0.00000003165839212196757;
    expected[(5, 2)] = -0.000000026624873075335538;

    println!("|delta| = {:e}", (grad - expected).norm());
}

Cargo.toml

[package]
name = "dualgradtest"
version = "0.1.0"
authors = ["Christopher Rabotin <[email protected]>"]

[dependencies]
dual_num = "0.1"
nalgebra = "0.15"

Finite burn targeting

Converted from https://gitlab.com/nyx-space/nyx/-/issues/128 by ChatGPT

High level description

The purpose of this new feature is to enable users of the astrodynamics toolkit to take a set of maneuvers generated from an instantaneous maneuver finder, such as the Lambert algorithm, and find the finite burn equivalent with a configured spacecraft.

Requirements

The purpose of this section is to fill out the Requirements of the QA process.

Requirements answer the question: what does the system need to do? It does not answer the question of how does the system do this?

Test plans

To test that this feature is working correctly, we can use the following test plans:

  1. Create a set of maneuvers using the Lambert algorithm and a known spacecraft configuration.
  2. Use the new to_finite_burn() method of the Maneuver structure to find the finite burn equivalent of the maneuvers.
  3. Compare the resulting finite burn maneuver with the expected result to ensure it is accurate.
  4. Test the feature with various spacecraft configurations and maneuver sets to ensure it works for different scenarios. This should include maneuvers that are purely in-plane, purely out-of-plane, and at least one that has both in-plane and out-of-plane changes in orbital elements.
  5. Test the feature with edge cases, such as maneuvers with a very small delta-V, to ensure it can handle these situations correctly.

Design

Algorithm demonstration

This should use the algorithm from AAS-12-236 by Jacob Williams et al. in the section "Impulsive to Finite Burn Conversion."

API definition

The new to_finite_burn() method will be added to the existing Maneuver structure. It will take the following argument:

+ `spacecraft`: A `Spacecraft` structure representing the configuration of the spacecraft for which the finite burn equivalent maneuver should be found.

High level architecture

The to_finite_burn() method will be implemented as part of the existing astrodynamics toolkit. It will use the Spacecraft structure and the Maneuver structure as inputs, and will return the finite burn equivalent of the maneuvers.

Detailed design

                                +---------------+
                                | Instantaneous |
                                |   Maneuver    |
                                |    Finder     |
                                +---------------+
                                        |
                                        v
                                +---------------+
                                |   Maneuver    |
                                |  Structure    |
                                +-------+-------+
                                        |
                                        | to_finite_burn() method
                                        |
                                        v
                                +---------------+
                                | Finite Burn   |
                                |   Maneuver    |
                                +---------------+

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

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

  • Nyx shall support the simulation of the availability of ground stations.
  • Nyx shall support the specification of a time range and/or a cadence for the availability of each ground station.
  • Nyx shall use the availability of ground stations to generate measurements for the orbit determination process in a realistic and accurate manner.
  • Nyx shall support two way measurements (instead of one-way currently)
  • Nyx shall support light time delay in two way measurements Will be done after #86 via nyx-space/anise#26
  • Nyx shall support Gauss Markov noise models
  • Nyx shall support loading GM models from YAML (via the Configurable trait)

Test plans

  • Test that the simulation correctly generates measurements from ground stations when they are available.
  • Test that the simulation does not generate measurements from ground stations when they are not available.
  • Test that the simulation can handle multiple ground stations with different availability time ranges and/or cadences.
  • Test that the simulation can handle situations where a ground station becomes unavailable during the simulation.
  • Test that the simulation can handle situations where a ground station becomes available during the simulation.
  • Test that the simulation can handle situations where a ground station is continuously available throughout the simulation.
  • Test that the simulation can handle situations where a ground station is never available throughout the simulation.
  • Test that the simulation can handle situations where all ground stations are available at different times but there are no gaps in the availability of at least one ground station.
  • Test that the simulation can handle situations where all ground stations are available at different times and there are gaps in the availability of all ground stations.
  • Test that the simulation can handle situations where the availability of a ground station overlaps with the availability of another ground station.
  • Test that the simulation can handle situations where the availability of a ground station does not overlap with the availability of another ground station.

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.

Attitude module

It should be possible to simulate the attitude of a spacecraft or an instrument on that spacecraft.

Currently, nalgebra supports quaternion (cf. this), but I would like to add the support for CRPs and MRPs. More importantly though, nalgebra allows one to define the position of something on a rigid body. Hence, it is possible to define the position of an instrument at a given time, propagate the overall rigid body (i.e. the spacecraft), and get the updated attitude of that instrument.

The attitude dynamics should also include gravity torques for higher fidelity.

This module should also provide the control algorithms as developed in Schaub & Junkins and in the AVS lab.

Backpropagation causes any further propagation (forwards or backwards) to loop indefinitely

Example code:

extern crate nalgebra as na;
extern crate nyx_space as nyx;

use hifitime::julian::ModifiedJulian;
use nyx::celestia::{State, EARTH, ECI};
use nyx::dynamics::celestial::TwoBodyWithDualStm;
use nyx::propagators::error_ctrl::RSSStepPV;
use nyx::propagators::*;

fn main() {
    let init = State::from_cartesian_eci(
        -9000.0,
        18500.0,
        7000.0,
        -3.0,
        -2.0,
        1.0,
        ModifiedJulian { days: 100.0 },
    );

    let mut dynamics = TwoBodyWithDualStm::from_state::<EARTH, ECI>(init);

    let mut prop = Propagator::new::<RK89>(
        &mut dynamics,
        &PropOpts::with_fixed_step(10.0, RSSStepPV {}),
    );

    prop.until_time_elapsed(20.0);
    println!("Forward prop 1");

    prop.until_time_elapsed(-10.0);
    println!("Back prop 1");

    // The code above completes, but both of the calls below will diverge.

    prop.until_time_elapsed(20.0);
    println!("Forward prop 2");

    prop.until_time_elapsed(-10.0);
    println!("Finished");
}

The above code will loop indefinitely. after doing a back propagation it does not matter whether the next probagation is backwards or forwards, it will lock up either way. The step size and propagation time does not seem to matter either.

You are able to propagate forward several times without this issue, it only arises after the first back propagation.

Publish 0.0.1

  • Find another package name: nyx is already taken on cargo =/
  • Add list of features

Integrators with custom stop condition(s)

Motivation

The two main ODE package in Rust, including (ode)[https://crates.io/crates/ode] and (oem)[https://crates.io/crates/eom] integrate over a set of time values. In nyx, there will be a greater need to propagate until a given more general condition (e.g. "propagate until next periapse" or "propagate until semi-major axis error is with 1e-3 km"). These would be difficult to implement using the above packages.

Moreover, these packages support at best an RK4 in explicit form, which isn't enough in terms of accuracy for the goal of this library.

New features

  • Butcher table implementation for fixed and adaptive RK methods: at minimum RK78 must be implemented.
  • High performance (bench mark shall include an Earth point mass orbit propagation with J2 for 100 days in less than a tenth of a second).
  • Ease of implementation both as a Rust "mission script" and for interoperability with Python.
  • Coded for speed via genericity (i.e. using VectorN and alga::Real)

Limitations

  • Single thread implementation: after reading up on multicore RK implementations, these seem more significantly more complex to implement and add value after 128 cores.

Constraints

  • Only stable Rust features to ensure forward compatibility.

Use SPICE files via ANISE for import/export and computations

High level description

The ANISE Rust library should soon be usable, cf. anise.rs. Once that's done, this issue will start using ANISE in Nyx. This means removing all dependencies on Protobufs and the XB file format, and switch to reading SPICE data as inputs (for BPC ad SPK). This will also allow exporting trajectory data in SPK format.

Requirements

  • Nyx shall natively read SPICE files
  • Nyx shall use ANISE for frame transformations
  • Nyx shall use ANISE for light time corrections in orbit determination modeling (requires nyx-space/anise#26)
  • Nyx shall deserialize YAML definitions using the specified frames in the loaded kernels
  • Nyx shall provide a default DE440s with low fidelity body fixed frames (PCK)
  • #312
  • Nyx shall switch to IAU Earth Frame for drag computations

Test plans

  • All of the validation will need to be re-run and checked (the errors are expected to be smaller than they currently are).
  • Export the validation results in markdown to embed them in the mathspec

Design

This is a significant refactoring since the Cosm structure will be replaced with an ANISE Context. Moreover, any function that currently requires a trajectory reference will likely now require both a reference to the context and a frame instead of just a trajectory. This work must also account for the fact that an ANISE Context has significantly less features than a Nyx Trajectory, but Nyx must maintain the superset of features.

Dynamics module

There should be a module which has most of the dynamics included, such as two_body_dynamics. Therefore, when doing two body dynamics with spherical harmonics, a developer should be able to call two_body_dynamics and harmonics on two parallel threads (using a clone of the state) for these to be applied strictly at the same position. This also allows the dynamics to be identical regardless of the calling order.

I'm not sure how to organize this: a subsequent issue will include attitude computations (and control algorithms) which may be included in the dynamics module. But I'm concerned that might be confusing. So, I gotta figure that out.

Must include:

  • Two body dynamics
  • Spherical harmonics
  • Third body dynamics (which can be called several times with different bodies, e.g. Jupiter on one thread, the Moon on the other)
  • Drag models from GMAT (check literature to only support the best representations)
  • Solar Radiation Pressure models (at least Cannonball, but more is better)

Initial orbit determination

High level description

Orbit determination is the process of determining the position and velocity of a spacecraft from observations, such as range, range rate, and angles data. This information is essential for spacecraft mission planning and trajectory optimization.

Currently, Nyx does not support initial orbit determination methods, such as Gibbs' method, that can be used without any initial state information. This issue aims to add support for initial orbit determination methods to Nyx, allowing users to determine the initial position and velocity of a spacecraft using observations.

Requirements

To support initial orbit determination in Nyx, the following requirements shall be met:

  • Nyx shall support initial orbit determination using range and range-rate data: "However, [this method] is limited to using range and range rate observations, and cannot be used with angles-only data." (ChatGPT)
  • Examples shall be provided of how to use the ODProcess structure to determine the initial position and velocity of a spacecraft using initial orbit determination methods.

Test plans

To test the ability to determine initial orbits using the ODProcess structure in Nyx, the following steps shall be taken:

  • Use the ODProcess structure in Nyx to determine the initial position and velocity of the spacecraft in the test cases using initial orbit determination methods.
  • Compare the determined initial orbits to known solutions for the test cases to verify their accuracy. These test cases can be generated from Nyx itself.
  • Test the ability to use initial orbit determination methods in spacecraft mission planning and trajectory optimization using the ODProcess structure in Nyx.
    est cases with a large number of observations to verify the accuracy and performance of the initial orbit determination methods with large datasets.
  • Test cases with a small number of observations to verify the ability of the initial orbit determination methods to handle cases with limited data.
  • Test cases with noisy or corrupted observations to verify the robustness of the initial orbit determination methods to errors in the data.
  • Test cases with extreme orbital conditions, such as high eccentricity or inclination, to verify the accuracy of the initial orbit determination methods in challenging situations.
  • Test cases with multiple spacecraft and/or targets to verify the ability of the initial orbit determination methods to handle multiple objects.

Design

This is the design section. Each subsection has its own subsection in the quality assurance document.

Algorithm demonstration

Potential sources:

ChatGPT:

Gibbs' method for initial orbit determination can provide an initial covariance for the position and velocity of the spacecraft. The covariance can be computed from the observations and used to estimate the uncertainty in the determined initial orbit.

The initial covariance is typically computed as a 3x3 or 6x6 matrix, depending on whether the position and velocity are treated as separate or combined variables. The diagonal elements of the covariance matrix represent the variance of the individual components of the initial state vector, while the off-diagonal elements represent the covariance between the different components. The initial covariance can be used, for example, to assess the reliability of the determined initial orbit and to propagate uncertainty through subsequent calculations, such as trajectory optimization.

API definition

fn initial_orbit_determination(&mut self, observations: &[Observation]) -> Result<Estimate, NyxError> {}

// (...)
pub trait Estimate { // Human: replace this with the current `Estimate` structure
    fn state(&self) -> &Vector;
    fn covariance(&self) -> &Matrix;
}

High level architecture

  1. Define a set of observations containing range and range rate data for the spacecraft.
  2. Initialize the position and velocity of the spacecraft to a reasonable guess.
  3. Compute the range and range rate at the current estimate of the position and velocity.
  4. Update the position and velocity using the computed range and range rate and the measured values.
  5. Compute the covariance of the updated position and velocity.
  6. Repeat steps 3-5 until the position and velocity converge to a stable solution.
  7. Return the final estimate of the position and velocity, along with its covariance.

The observations are used to iteratively refine the initial estimate of the position and velocity of the spacecraft. The position and velocity are updated using the computed range and range rate and the measured values, and the covariance is computed at each step to provide an estimate of the uncertainty in the initial orbit. The process is repeated until the position and velocity converge to a stable solution, at which point the final estimate of the position and velocity, along with its covariance, is returned.

Detailed design

The detailed design *will be used in the documentation of how Nyx works.

Feel free to fill out additional QA sections here, but these will typically be determined during the development, including the release in which this issue will be tackled.

nyx fails build due to not implemented trait

Simply adding the nyx dependency to a cargo.toml and running a build produces errors.
Is this a rust version issue??!
What is going wrong here?!?

[dependencies]
nyx-space = "0.0.20"
cargo run

error[E0277]: the trait bound <Self as AutoDiff>::HyperStateSize: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\mod.rs:112:5
|
112 | / fn dual_eom(
113 | | &self,
114 | | epoch: Epoch,
115 | | integr_frame: Frame,
... |
125 | | + Allocator<Hyperdual<f64, Self::HyperStateSize>, Self::STMSize>,
126 | | Owned<f64, Self::HyperStateSize>: Copy;
| |_______________________________________________^ the trait dynamics::hyperdual::DimName is not implemented for <Self as AutoDiff>::HyperStateSize
|
= note: required because of the requirements on the impl of dynamics::hyperdual::Allocator<f64, <Self as AutoDiff>::HyperStateSize> for dynamics::hyperdual::DefaultAllocator
help: consider further restricting the associated type
|
126 | Owned<f64, Self::HyperStateSize>: Copy, ::HyperStateSize: dynamics::hyperdual::DimName;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound na::DefaultAllocator: na::allocator::Allocator<Hyperdual<f64, na::U7>, na::Dynamic, na::Dynamic> is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\sph_harmonics.rs:196:11
|
196 | a_nm: DMatrix<Hyperdual<f64, U7>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait na::allocator::Allocator<Hyperdual<f64, na::U7>, na::Dynamic, na::Dynamic> is not implemented for na::DefaultAllocator
|
= help: the following implementations were found:
<na::DefaultAllocator as na::allocator::Allocator<N, R, C>>
<na::DefaultAllocator as na::allocator::Allocator<N, R, na::Dynamic>>
<na::DefaultAllocator as na::allocator::Allocator<N, na::Dynamic, C>>

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\sph_harmonics.rs:196:11
|
196 | a_nm: DMatrix<Hyperdual<f64, U7>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound <Self as AutoDiff>::HyperStateSize: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\mod.rs:112:8
|
112 | fn dual_eom(
| ^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for <Self as AutoDiff>::HyperStateSize
|
= note: required because of the requirements on the impl of dynamics::hyperdual::Allocator<f64, <Self as AutoDiff>::HyperStateSize> for dynamics::hyperdual::DefaultAllocator
help: consider further restricting the associated type
|
126 | Owned<f64, Self::HyperStateSize>: Copy, ::HyperStateSize: dynamics::hyperdual::DimName;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound <Self as AutoDiff>::HyperStateSize: dynamics::hyperdual::Dim is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\mod.rs:116:16
|
116 | state: &VectorN<Hyperdual<f64, Self::HyperStateSize>, Self::STMSize>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::Dim is not implemented for <Self as AutoDiff>::HyperStateSize
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:43
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| --- required by this bound in Hyperdual
|
help: consider further restricting the associated type
|
126 | Owned<f64, Self::HyperStateSize>: Copy, ::HyperStateSize: dynamics::hyperdual::Dim;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\orbital.rs:224:16
|
224 | state: &VectorN<Hyperdual<f64, U7>, U6>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\orbital.rs:456:16
|
456 | state: &VectorN<Hyperdual<f64, U7>, U3>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\dynamics\sph_harmonics.rs:298:17
|
298 | radius: &Vector3<Hyperdual<f64, U7>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\od\ranging.rs:184:16
|
184 | state: &VectorN<Hyperdual<f64, U7>, U6>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\od\ranging.rs:325:16
|
325 | state: &VectorN<Hyperdual<f64, U7>, U6>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error[E0277]: the trait bound na::U7: dynamics::hyperdual::DimName is not satisfied
--> C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\nyx-space-0.0.20\src\od\ranging.rs:414:16
|
414 | state: &VectorN<Hyperdual<f64, U7>, U6>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait dynamics::hyperdual::DimName is not implemented for na::U7
|
::: C:\Users\jsima.cargo\registry\src\github.com-1ecc6299db9ec823\hyperdual-0.3.7\src\lib.rs:71:49
|
71 | pub struct Hyperdual<T: Copy + Scalar, N: Dim + DimName>(VectorN<T, N>)
| ------- required by this bound in Hyperdual

error: aborting due to 11 previous errors

For more information about this error, try rustc --explain E0277.
error: could not compile nyx-space

Propagator to be typed by Dynamics and ErrorCtrl

This will allow the Dynamics to not be mutable (they shouldn't because dynamics do not change), allowing the channels to be set in the Propagator instead of in the Dynamics (which makes more sense anyway), and which means the Dynamics can be copied (which is required for dual_num::Hyperdual).

  • ErrorCtrl trait
  • Type propagator (or PropOptions/PropOpts) with ErrorCtrl
  • Type propagator to the Dynamics

Fix propagator tolerance computation

Currently, I'm computing the error by taking the norm of the difference between the nominal and the local approximation higher order states. However, this isn't correct: the difference for the positions will be orders of magnitude different compared to the difference in velocity. As explained in the GMAT source code, there are different ways to compute that tolerance, but it should be relative nevertheless. They propose doing what I am doing but on the sized three vector instead of the whole vector: I would prefer avoid this approach here so that these integrators can be used for other applications. GMAT also uses a relative and absolute errors computed on each component, cf. this. That seems like a better approach.

This issue must also rename the adaptive step size names such that the first digit corresponds to the true order and the second to the local order (e.g. it's RK45 not RK54).

Thorough documentation with MkDocs

MkDocs is used by nalgebra, and it's frankly stunning.

Pages:

  • Getting started: must include how to install rust, a note on using Atom + racer for auto completion, and running a demo script
  • Tutorials: must include two body dynamics and higher fidelity models, attitude propagation and control, Kalman filtering, Monte Carlo simulations (effectively include one demo script of the most powerful features of this toolkit)
  • API reference: this is important for people developing specific tools which rely on nyx

Earth spherical harmonics validation

In order to validate the spherical harmonics, I need to be able to convert to a BodyFixed frame. The body fixed frame for the Earth in GMAT is quite complicated: http://gmat.sourceforge.net/doc/R2013a/html/CoordinateSystem.html . For the other bodies, I'll have to check how SPICE does it, which adds a dependency on #17.

I'm not sure how to code this up for genericity and speed. I think that I should code up the whole rotation matrix from ICRF to the final coordinate system required that way there are no rounding errors due to the matrix multiplications.

Part of the issue, I'll also update the spherical harmonics code to avoid recomputing the vr01, vr11, re and im matrices at each call (they are static). I will also fix the implementation to the JPL algorithm which is said to better represent higher order dynamics.

Some initial thoughts for this are in #9 .

Urgent: Support J2 in celestial dynamics

I was initially hoping to code up the full gravity field models instead of coding J2 separately, but I need this feature although I hadn't planned that.

  • Load EGM and SHADR files
  • Load .cof GMAT files for validation of the computation
  • CANCELED: Implement the spherical harmonics computation using vectors instead of matrices (should speed up computation, but also Vectors allow for runtime allocation, which isn't easily the case of matrices in rust (compile prefers knowing how memory needs to be allocated at compile time).
  • Validate: J2 validation will be handled once the body fixed frame can be computed
  • Think about how to parallelize this calculation: this can be quite heavy with high fidelity models. Keep in mind however that, unless using tokio, Rust processes are heavy (they aren't co-routines like in Go).

Template scenarios

As a library, nyx can seem quite complex to use at first sight (cf. the overhead needed to start a TwoBody and J2 propagation). Hence, I should propose two different ways of handling this. First, the dynamics module should have some templates of dynamics (such as Spherical Harmonics, Third Bodies, etc.). Those should also include thread management to make use of the advantages that Rust brings to such a tool. Secondly, there should be a high level templates module which also includes the refined final step of a propagation. Forgetting that leads to incorrect results, but the code behind it isn't necessarily trivial at first sight either.

Monte Carlo manager

It should be a very simple thing to use which can take any sized vector and add some noise to it based on a PDF (normal at least). It might also need a pointer to a random number generator: this is how it would be done in C++, although it's a bit ugly. But that's probably how it needs to be handled because error can also be added in a number of different parameters of a simulation.

Report files

Allow for the creation of report file from a given state.

I think the best would be to provide a StateExporter class which is initialized with a closure. That closure take a celestia::State and returns an owned list of f64. The StateExporter will then handle appending that to the file by itself, in a concurrent thread (so it can be plotted in "real time" in a relevant plotter like kst).

The initialization should also specify the format via an enum (e.g. export::Format::CSV, export::Format::TSV).

Finally, there need to be a way to define the headers somehow (maybe through another closure in the initializer?).

is it a propagation bug or a wrong way to use nyx?

Hello, I attempt to use nyx and find it excellent. But when I tried to propagate with different Durations, unexpected results occured where the propagated states went wrong, as the following

"2002-03-25T04:00:20 UTC",  orbit.to_cartesian_vec = [-6788.5845188791145, 2331.4710621121685, 71.11034446461193]
"2002-03-25T04:00:21 UTC",  orbit.to_cartesian_vec = [-6790.674818735843, 2325.2643650234904, 74.66524610277631]
"2002-03-25T04:00:22 UTC",  orbit.to_cartesian_vec = [-6792.7578002136515, 2319.055161973893, 78.2200672734499]
"2002-03-25T04:00:23 UTC",  orbit.to_cartesian_vec = [-6794.83346106769, 2312.8434596550974, 81.77480414556604]
"2002-03-25T04:00:24 UTC",  orbit.to_cartesian_vec = [-6796.901799060997, 2306.6292647615173, 85.32945288814886]
"2002-03-25T04:00:25 UTC",  orbit.to_cartesian_vec = [-6798.962811964503, 2300.4125839902536, 88.88400967031745]
"2002-03-25T04:00:26 UTC",  orbit.to_cartesian_vec = [137392.65367966949, 606372.4778695095, -334892.35353465774]
"2002-03-25T04:00:27 UTC",  orbit.to_cartesian_vec = [123954.60336859804, 549849.2153571148, -303558.0573523163]
"2002-03-25T04:00:28 UTC",  orbit.to_cartesian_vec = [111593.69633218335, 497875.7385530273, -274745.142208523]
"2002-03-25T04:00:29 UTC",  orbit.to_cartesian_vec = [100237.7763244794, 450145.96261613665, -248284.04051213822]
"2002-03-25T04:00:30 UTC",  orbit.to_cartesian_vec = [89818.5617098545, 406370.3095472298, -224014.32617687635]
"2002-03-25T04:00:31 UTC",  orbit.to_cartesian_vec = [80271.48917932897, 366275.03949872433, -201784.34442559507]

From "2002-03-25T04:00:25 UTC" to "2002-03-25T04:00:26 UTC", the state surges suddenly. What confuses me most is that when I assign the Duration to 300_u64 * TimeUnit::Second, everything would be fine, but when testing with 180_u64 * TimeUnit::Second, state surges.

I couldn't figure out whether it was a propagation bug or not. Was there anything wrong in the way I use nyx?


Here is the code that can regenerate above output

extern crate nyx_space as nyx;
use hifitime::Epoch;
use nyx::md::ui::*;
pub fn test2() {
    let cosm = Cosm::de438();
    // Grab the Earth Mean Equator J2000 frame
    let eme2k = cosm.frame("EME2000");
    // Set the initial start time of the scenario
    let epoch = Epoch::from_gregorian_utc(2002, 3, 25, 4, 0, 0, 0);
    // use the default propagator setup: a variable step Runge-Kutta 8-9
    // let setup = Propagator::default(OrbitalDynamics::two_body());
    let setup = Propagator::default(OrbitalDynamics::two_body());
    let (x, y, z, vx, vy, vz) = (
        -6745.245189,
        2455.068471,
        0.001314,
        -2.239819,
        -6.153855,
        3.555707,
    );
    let orbit_s = Orbit::cartesian(x, y, z, vx, vy, vz, epoch, eme2k);
    // Use the setup to seed a propagator with the initial state we defined above.
    let mut prop_s = setup.with(orbit_s);
    let res_s = prop_s.for_duration_with_traj(180_u64 * TimeUnit::Second);
    match res_s {
        Ok(result) => {
            for state_s in result.1.every(1_u64 * TimeUnit::Second) {
                let epoch_instant = state_s.epoch();
                let rv_s = state_s.to_cartesian_vec();
                let r_sat: [f64; 3] = [rv_s.x, rv_s.y, rv_s.z];
                println!(
                    "{:?},  orbit.to_cartesian_vec = {:?}",
                    epoch_instant.as_gregorian_utc_str(),
                    r_sat,
                );
            }
        }
        Err(e) => {
            println!("error with :{}", e);
        }
    };
}

High fidelity transponder modeling (different noise models)

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:

  • Support for the Deep Space Network "Range Units" for modeling the range and range-rate measurements from ground stations to spacecraft.
  • Gauss-Markov modeling for simulating the bias and noise processes in transponder systems.
  • Functions for initializing, updating, and querying the transponder model, including the bias estimate, covariance matrix, and other parameters.
  • Integration with the Nyx orbit determination and navigation module, such that the transponder model can be used to improve the accuracy and precision of the orbit estimates.

Test plans

  • Test the implementation of the Deep Space Network "Range Units" for simulating the range and range-rate measurements from ground stations to spacecraft. This should include tests with different types of range units, such as one-way and two-way, and with different measurement configurations, such as single- and dual-frequency measurements.
  • Test the functions for initializing, updating, and querying the transponder model, including the bias estimate, covariance matrix, and other parameters. This should include tests with different initial conditions, measurement data, and measurement covariances, and should verify the correctness and consistency of the model results.
  • Test the integration of the transponder model with the Nyx orbit determination and navigation module, such that the transponder model can be used to improve the accuracy and precision of the orbit estimates. This should include tests with synthetic data and real data from space missions, and should compare the results with and without transponder modeling.

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,
        }
    }
}

Frame management backbone

Requirements

  • Provide some default frames which are time varying
  • Frames may be centered to a celestial body or may be centered around another frame's center (e.g. spacecraft barycenter)
  • It must be trivial to re-define some or all of these frames as needed by a user (this is important for spacecraft frames and for custom body frames)

General idea

  1. Provide a Frame Manager which helps in the conversion between frames
  2. A Frame has a name, optionally a parent. If it has a parent, it must provide a way to convert itself to that parent
  3. The Frame Manager should not set any requirement on std lib.

A spacecraft definition

It should be possible to define a spacecraft with all the parameters one could want. This includes the following:

  • Name
  • Fuel tanks (with pressure info?)
  • Thrusters with their thrust values (from Newton and pound-force) and how much they pull from each defined fuel tank (really not sure how to do that). Once the attitude module is completed, these thruster can also be placed on the spacecraft, which will also allow the use of them for attitude control. Moreover, these should support beginning of life and end of life performance cuves
  • Antennas, their positions, their gain, their attitude, whether they are stowed, their slew rates (more ?)
  • Electrical power source with batteries (simplified discharge and charging models -- these might go into another module) and a cut-off of different equipment based on a minimum bus voltage threshold (i.e. EP could be prevented from running if the voltages are too low)

The biggest problem with this definition is how to script it: there may be a ton of possibilities, but most won't be used most of the time. Maybe provide a method such as attach_equipment and only allow specific dynamics to be used if a given piece of equipment is enabled?

Orbit management module

This module should handle all orbit manipulation. The organization of this module isn't necessarily trivial, and I really need to think of a good way of handling this. The constructors in smd were not all that useful, especially for the control laws.

Another thing to consider is the size of state vector. The propagators handle VectorN, so it must be possible to provide a VectorN which represents the full state which needs to be propagated, may that be simply a position and velocity vector, or P, V and STM, or equinoctal propagation if that feature is desired. I also need to support frames, as defined in GMAT here.

GMAT vs nyx difference in output states

I'm a novice in Rust, so please bear with me. I ran into a difference between a propagated state using only Earth point mass model. The nyx propagator that I used was CashKarp45 and the RK89 from GMAT. This could be due to the difference in the propagators rather than a nyx error; however, I put this here for documentation purposes just in case. The options that are specified for the propagator initialization is the following:

Propagator::new::<CashKarp45>(&Options::with_adaptive_step(0.1, 30.0, 1e-12));

Input
PropInfo { init: Some(State { datetime: 58112.04877314817, x: 6678.0, y: 0.0, z: 0.0, x_dot: -1.034425773665938, y_dot: 8.470618983406897, z_dot: 0.0 }), gm: 398600.4415, prop_time: 300.0 }
Output
Sending: State { datetime: 58112.05224537039, x: 5959.370605933568, y: 2488.2135004453403, z: 0.0, x_dot: -3.7494156073202514, y_dot: 7.926583218577195, z_dot: 0.0 }

The script is attached. You will have to rename it as a Test.script file. I put it as a .txt file to upload it here.
Test.txt

The output state for the 300 second GMAT propagation was:
X: 5959.745504208833, Y: 2487.420821985822, Z:0 VX:-3.748533621228721, VY: 7.926951403625875, VZ:0 Elapsed Time: 299.9999997206032

The Y coordinate differs by almost 1 km for a 300 second propagation. The good news is that when I switched my propagator to a RK87 using nyx. The results differed at the sub-meter level.

I am using nyx-space 0.0.3, so this issue might be resolved in the latest builds.

Dynamics module: Third body dynamics

Spawned from #5 ; Requires #4

  • Get equations from Schaub & Junkins
  • Validate with scripts using additional point masses of {Sun, Moon}, {Sun, Moon, Jupiter, Saturn}, and all defined planets but Mercury
  • Provide timing numbers for reference
  • Add validation results to the validation mark down page

Add tropospheric modeling for orbit determination

High level description

Refer to this IERS Tech Note on the calculations for the tropospheric effects based on wavelength.

This implementation should allow for great level of configuration as it will be the basis for the mission-ready orbit determination.

Other references:

Requirements

  • Nyx shall support tropospheric modeling in its orbit determination module. This modeling shall be frequency-dependent and shall account for the extra delay in the measurement of GNSS signals caused by the troposphere.
  • The model shall also account for electromagnetic beam bending.
  • The implementation of the tropospheric model shall be based on a well-established technique, such as the Saastamoinen or Niell mapping function.
  • The inclusion of tropospheric modeling in the orbit determination module shall improve the accuracy of position estimates.

Test plans

The test plan for the inclusion of tropospheric modeling in the orbit determination module should include the following:

  • Verify that the implementation of the tropospheric model is based on a well-established technique, such as the Saastamoinen or Niell mapping function.
  • Test the tropospheric model with a variety of input parameters, including different temperatures, pressures, and humidities, as well as different transmitter and receiver antenna locations.
  • Compare the position estimates obtained with and without the inclusion of tropospheric modeling. The position estimates with the tropospheric model should be more accurate than those without it.
  • Test the tropospheric model with a variety of GNSS frequencies to verify that it is frequency-dependent.
  • Verify that the tropospheric model correctly accounts for electromagnetic beam bending.
  • Test the tropospheric model in edge cases, such as extreme weather conditions or locations near the poles, where the effect of the troposphere is more pronounced.

In addition to these specific test cases, it is important to ensure that the inclusion of the tropospheric model does not negatively affect the performance of the orbit determination module in other aspects, such as computation time or numerical stability. As such, the test plan should also include a thorough regression testing of the orbit determination module to ensure that no existing functionality is broken.

Design

This is the design section. Each subsection has its own subsection in the quality assurance document.

Algorithm demonstration

If this issue requires a change in an algorithm, it should be described here. This algorithm should be described thoroughly enough to be used as documentation. This section may also simply refer to an algorithm in the literature or in another piece of software that has been validated. The quality of that reference will be determined case by case.

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

Document, discuss, and optionally upload design diagram into this section.

Detailed design

The detailed design *will be used in the documentation of how Nyx works.

Feel free to fill out additional QA sections here, but these will typically be determined during the development, including the release in which this issue will be tackled.

Event based targeting

Converted from https://gitlab.com/nyx-space/nyx/-/issues/226 with ChatGPT

High level description

Nyx currently supports basic spacecraft targeting, but does not have the ability to target specific events, such as periapses or eclipses. This issue aims to add support for event-based targeting to Nyx, allowing users to specify a target event and search for opportunities to achieve that target within a specified time frame.

Requirements

  1. Allow targeting such that specific events are met, e.g. "achieve a periapse radius of X km."
  2. Allow targeting using both events and objectives that are not event based

Test plans

  1. Test the ability to design missions backwards using event-based targeting by defining a mission objective and verifying that Nyx can find the necessary events to achieve that objective.
  2. Test the ability to optimize the timing of maneuvers using event-based targeting by defining a set of events that require a maneuver and verifying that Nyx can find the optimal time to execute the maneuver.

Edge cases to consider when developing test code for event-based targeting in Nyx include:

  • Events that occur close to the edge of the specified time frame.
  • Events with complex or nested criteria.
  • Events that require multiple maneuvers to achieve.

Design

The design for event-based spacecraft targeting in Nyx will involve updating the Objective structure to accept an array of event-based objectives. The Optimizer structure will then use this array to design missions and optimize maneuvers using event-based targeting.

API definition

To support event-based spacecraft targeting in Nyx, the following changes will be made to the Nyx APIs:

  • The Objective structure will be updated to accept an Either<Event, StateParameter> value in the target field. This field will define the event-based objective that the Optimizer will use to design missions and optimize maneuvers. If no event is specified, the Objective will target the specified state parameter at all times within the specified duration.
  • The Objective structure will also be updated to accept a Duration value in the duration field. This field will define the duration within which the Optimizer will search for events that meet the specified criteria.
  • The Optimizer structure will be updated to use the target and duration fields in the Objective to design missions and optimize maneuvers using event-based targeting.
use std::either::Either; // Human: ChatGPT choked here, `Either` is _not_ in std.

/// Defines a state parameter event finder
#[derive(Clone, Debug)]
pub struct Objective {
    /// The event or state parameter to target
    pub target: Either<Event, StateParameter>,
    /// The desired self.desired_value, must be in the same units as the state parameter
    pub desired_value: f64,
    /// The precision on the desired value
    pub tolerance: f64,
    /// A multiplicative factor this parameter's error in the targeting (defaults to 1.0)
    pub multiplicative_factor: f64,
    /// An additive factor to this parameters's error in the targeting (defaults to 0.0)
    pub additive_factor: f64,
    /// The duration within which the event must be achieved
    pub duration: Duration,
}

High level architecture

The high level architecture for event-based spacecraft targeting in Nyx will involve using the updated Objective and Optimizer structures to design missions and optimize maneuvers using event-based targeting.

In the high level architecture, the Optimizer uses the updated Objective structure to design missions and optimize maneuvers using event-based targeting. The Objective structure contains an Either<Event, StateParameter> value in the target field, which defines the event-based objective that the Optimizer will use. The Objective structure also contains a Duration value in the duration field, which defines the duration within which the Optimizer will search for events that meet the specified criteria. The Optimizer uses the Event structure to define the criteria for the event-based objective.

Detailed design

Here are some edge cases to consider when implementing event-based spacecraft targeting in Nyx:

  • The Optimizer may not be able to find any events that meet the specified criteria within the specified duration. In this case, the Optimizer should return an error or a warning to indicate that no events were found.
  • The Optimizer may find multiple events that meet the specified criteria within the specified duration. In this case, the Optimizer should allow the user to select which event to target, or it should automatically select the event with the highest probability of success or the least fuel consumption.
  • The Optimizer may not be able to design a mission or optimize a maneuver to achieve the selected event within the specified duration. In this case, the Optimizer should return an error or a warning to indicate that the mission or maneuver cannot be achieved within the specified duration.
  • The user may specify an event with criteria that are impossible to achieve, such as a periapse at a specific altitude in a circular orbit. In this case, the Optimizer should return an error or a warning to indicate that the specified event is impossible to achieve.
  • The user may specify a state parameter that is not relevant to the selected event, such as a state parameter for a different spacecraft. In this case, the Optimizer should return an error or a warning to indicate that the specified state parameter is not relevant to the selected event.

Control law module

In nyx::control_laws there should be at least the following laws:

  • Ruggiero (with efficiency computations)
  • Naasz
  • Fourier series (using N terms, possibly handled by a VectorN). Concerning the Fourier series, it is vital that it allows for an update of the coefficients to use from a main mission (we might need to change these at each orbit of a mission)

Provide additional error control functions for adaptive step size propagators

It is useful to provide different tolerances on different parts of the state vector. For example, the STM could have its own error tolerance independent from that of the state (e.g. it may be higher than the state itself). Similarly, when mass control is implemented for thrusting, then the mass ought to have its own tolerance.

Support CCSDS Maneuver Message format

Conversion of https://gitlab.com/nyx-space/nyx/-/issues/227 with ChatGPT.

High level description

The CCSDS (Consultative Committee for Space Data Systems) Maneuver Message is a standard format for transmitting commands to spacecraft to perform maneuvers. The CCSDS Maneuver Message specifies the parameters that define a spacecraft maneuver, such as the spacecraft's state vector, the maneuver delta-V vector, and the maneuver time.

This issue aims to add support for the CCSDS Maneuver Message format to Nyx, allowing users to output spacecraft maneuvers in this standard format for use in spacecraft mission design and orbit determination.

Requirements

To support the CCSDS Maneuver Message format in Nyx, the following requirements must be met:

  • Implement a new ManeuverMessage structure that represents a spacecraft maneuver using the CCSDS Maneuver Message format.
  • Update the Optimizer structure to allow users to output spacecraft maneuvers as ManeuverMessage values.
  • Provide examples of how Nyx can be used to design missions and optimize maneuvers using the CCSDS Maneuver Message format.

Test plans

To test the CCSDS Maneuver Message format in Nyx, the following steps can be taken:

  1. Define a set of known spacecraft maneuvers with known parameters, such as state vectors and delta-V vectors.
  2. Use the Optimizer in Nyx to design missions and optimize maneuvers using the known spacecraft maneuvers.
  3. Compare the output ManeuverMessage values to the known spacecraft maneuvers to verify their accuracy.
  4. Test the ability to output ManeuverMessage values in different scenarios, such as different spacecraft states and different maneuver objectives.

Design

API definition

The ManeuverMessage structure defines a spacecraft maneuver in the CCSDS Maneuver Message format. It contains fields for the spacecraft's state vector, the maneuver delta-V vector, and the time at which the maneuver should be performed. It also provides accessor methods for the spacecraft's position, velocity, attitude, mass, and delta-V magnitude at the time of the maneuver.

#[derive(Debug)]
pub struct ManeuverMessage {
    /// The spacecraft state vector at the time of the maneuver
    pub state: State,
    /// The maneuver delta-V vector
    pub delta_v: Vector3,
    /// The time at which the maneuver should be performed
    pub time: Instant, // Human: change to hifitime Epoch
}

impl ManeuverMessage {
    /// Returns the spacecraft's position at the time of the maneuver
    pub fn position(&self) -> Vector3 {
        self.state.position
    }

    /// Returns the spacecraft's velocity at the time of the maneuver
    pub fn velocity(&self) -> Vector3 {
        self.state.velocity
    }

    /// Returns the spacecraft's attitude at the time of the maneuver
    pub fn attitude(&self) -> UnitQuaternion {
        self.state.attitude
    }

    /// Returns the spacecraft's mass at the time of the maneuver
    pub fn mass(&self) -> f64 {
        self.state.mass
    }

    /// Returns the spacecraft's delta-V magnitude at the time of the maneuver
    pub fn delta_v_magnitude(&self) -> f64 {
        self.delta_v.magnitude()
   }
}

High level architecture

Document, discuss, and optionally upload design diagram into this section.

Detailed design

To support the CCSDS Maneuver Message format in Nyx, the following changes will be made to the Nyx APIs:

  • A new ManeuverMessage structure will be implemented to represent a spacecraft maneuver using the CCSDS Maneuver Message format. The ManeuverMessage structure will contain fields for the spacecraft's state vector, the maneuver delta-V vector, and the maneuver time, as specified by the CCSDS Maneuver Message format.
  • The Optimizer structure will be updated to allow users to output spacecraft maneuvers as ManeuverMessage values. The Optimizer will provide a new function, to_maneuver_message(), that will return a ManeuverMessage value for the optimized spacecraft maneuver.
  • Example code will be provided to demonstrate how Nyx can be used to design missions and optimize maneuvers using the CCSDS Maneuver Message format.

Example code

The following example code demonstrates how Nyx can be used to design a mission and optimize a maneuver using the CCSDS Maneuver Message format.

use nyx::{Optimizer, Objective, Event, StateParameter, Duration};
use nyx::maneuver_message::ManeuverMessage;

// Define the initial spacecraft state
let initial_state = State {
    // ...
};

// Define the target event and duration
let target = Event {
    parameter: StateParameter::Altitude,
    desired_value: 500.0,
    epoch_precision: Unit::Seconds(10.0),
    value_precision: 0.1,
};
let duration = Duration::Hours(1.0);

// Define the targeting objective
let objective = Objective {
    target: Either::Left(target),
    duration,
};

// Create the optimizer
let mut optimizer = Optimizer::new(initial_state);

// Optimize the spacecraft maneuver to achieve the target event
let result = optimizer.optimize(&[objective]);

// Check if a solution was found
if let Some(solution) = result {
    // Output the optimized spacecraft maneuver as a CCSDS Maneuver Message
    let maneuver_message = solution.get_maneuver_message();
    println!("CCSDS Maneuver Message: {:?}", maneuver_message);
} else {
    println!("Unable to find a solution");
}

Add least squares batch filter

High level description

Least squares batch filters and classical Kalman filters are both methods for estimating the state of a dynamic system from noisy measurements. They both use a mathematical model of the system to predict the state at future times and then use the measurements to correct the predictions and update the estimate of the state.

One advantage of a least squares batch filter is that it can provide a more accurate estimate of the state than a classical Kalman filter, especially when the measurements are correlated or the system is nonlinear. This is because the least squares method uses a least squares estimate to update the state, which is optimal in a least squares sense and takes into account the correlations between the measurements.

A disadvantage of a least squares batch filter is that it requires all of the measurements to be available at once in order to compute the optimal estimate of the state. This is known as a batch processing approach, and it is not suitable for systems that require a real-time estimate of the state. In contrast, a classical Kalman filter can update the estimate of the state incrementally as new measurements become available, using a recursive algorithm that is more computationally efficient than the batch approach.

Another disadvantage of a least squares batch filter is that it can be more sensitive to model errors and outliers in the measurements than a classical Kalman filter. This is because the least squares method assumes that the model and measurements are correct, and any errors or outliers in the data can bias the estimate of the state. In contrast, a classical Kalman filter can use a process noise covariance matrix to account for model errors and a measurement noise covariance matrix to downweight the influence of outliers on the estimate of the state.


The Levenberg-Marquardt optimization algorithm is a method for solving nonlinear least squares problems, which are optimization problems where the objective function is the sum of squares of nonlinear functions. This type of optimization problem is commonly used in state estimation, where the objective function is the sum of squares of the residuals between the measurements and the model predictions.

A Levenberg-Marquardt optimization algorithm can be used to solve a least squares batch filter problem, where the objective function is the sum of squares of the residuals between the measurements and the model predictions at the current estimate of the state. The algorithm can iteratively update the estimate of the state by computing the Jacobian matrix of the residuals with respect to the state variables and using this Jacobian matrix to compute a search direction for the update. The algorithm can then compute the optimal step size along this search direction using the Levenberg-Marquardt damping factor, which balances the tradeoff between the accuracy of the estimate and the convergence of the algorithm.

In summary, a Levenberg-Marquardt optimization algorithm can be used to solve a least squares batch filter problem, but it is not the only method that can be used. Other methods, such as the Gauss-Newton method or the trust-region method, can also be used to solve this type of optimization problem.

Requirements

  • Nyx shall support least squares batch filtering for state estimation.
  • Nyx shall provide an interface for specifying the mathematical model of the system and the measurement noise covariance matrix.
  • Nyx shall provide functions for initializing the filter and adding measurements to the filter.
  • Nyx shall provide functions for computing the optimal estimate of the state and the covariance matrix of the estimate.
  • Nyx shall provide functions for updating the filter with new measurements and for resetting the filter to a new initial state.
  • Nyx shall provide an example implementation of a least squares batch filter for a linear system with uncorrelated measurement noise.

Test plans

  • Test the interface for specifying the mathematical model of the system and the measurement noise covariance matrix. This should include testing the input validation for the model and the covariance matrix, as well as testing the correctness of the model and covariance matrix when used in the filter.
  • Test the functions for initializing the filter and adding measurements to the filter. This should include testing the input validation for the initial state and the measurements, as well as testing the correct initialization and updating of the filter state.
  • Test the functions for computing the optimal estimate of the state and the covariance matrix of the estimate. This should include testing the correctness of the estimates and the covariance matrix, as well as testing the sensitivity of the estimates to the initial state and the measurements.
  • Test the functions for updating the filter with new measurements and for resetting the filter to a new initial state. This should include testing the correct updating and resetting of the filter state, as well as testing the stability and convergence of the filter when applied to a variety of test cases.

Edge cases

  • Test the behavior of the filter when the model or the covariance matrix are incorrect or singular. This should include testing the sensitivity of the filter to errors in the model or the covariance matrix, as well as testing the robustness of the filter to outliers or missing measurements.
  • Test the behavior of the filter when the initial state or the measurements are noisy or biased. This should include testing the sensitivity of the filter to errors in the initial state or the measurements, as well as testing the ability of the filter to correct these errors over time.
  • Test the performance of the filter when applied to large or complex systems, with many state variables and measurements. This should include testing the computational efficiency and numerical stability of the filter, as well as testing the accuracy of the estimates and the convergence of the algorithm.

Design

This is the design section. Each subsection has its own subsection in the quality assurance document.

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

Human note: this does not seem correct. ChatGPT is recommending using a mix of Levenberg Marquart and a LSBF.

The filter receives measurements from the system and uses the mathematical model of the system to predict the state at future times. The filter then updates the estimate of the state using the measurements and the covariance matrix, and computes the objective function as the sum of squares of the residuals between the measurements and the model predictions. The filter can then iterate on this process, using the Jacobian matrix of the residuals to compute a search direction for the update and the Levenberg-Marquardt damping factor to compute the optimal step size along this search direction. The filter can continue to update the estimate of the state until the objective function reaches a minimum or the algorithm converges.

Detailed design

The detailed design *will be used in the documentation of how Nyx works.

Feel free to fill out additional QA sections here, but these will typically be determined during the development, including the release in which this issue will be tackled.

Add time to the `State`

This will add a dependence on hifitime. It'll also slightly complicate the state definition. However, this will likely be needed anyway when converting between frames (since we need to know what time it is when computing the planetary positions).

Celestial body/object trait and module

I think this should be its separate submodule so that each object can be in its own file: nyx::bodies::Earth; . This will be quite verbose for sure, but it'll allow for clarity.

Just like in propagators, the root mod.rs should have the trait for the Body. That should include the spherical harmonics of each body, with a possibility of setting those to zero somehow for objects were we don't have that information. It should also be possible to load these harmonics from a file, as is the case in GMAT.

Must include:

  • All eight planets
  • An SPK file reader with state interpolation method(s?) -- not sure how to fit that in, maybe by having one of the trait methods be the NAIF ID?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.