The NumericalPropagator provides a general-purpose numerical integrator for arbitrary ordinary differential equations (ODEs). Unlike NumericalOrbitPropagator which has built-in orbital force models, the generic propagator accepts user-defined dynamics functions for any dynamical system.
Propagating non-orbital systems (simple harmonic oscillators, population models, etc.)
Implementing custom force models not available in NumericalOrbitPropagator
Integrating coupled systems (orbit + attitude, multiple bodies, etc.)
Prototyping custom dynamics before committing to the orbital framework
For orbital mechanics with extended state (mass, battery, temperature tracking), prefer NumericalOrbitPropagator with additional_dynamics. See Extending Spacecraft State.
importnumpyasnpimportbraheasbh# Initialize EOP data (needed for epoch operations)bh.initialize_eop()# Create initial epochepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Simple Harmonic Oscillator (SHO)# State: [x, v] where x is position and v is velocity# Dynamics: dx/dt = v, dv/dt = -omega^2 * xomega=2.0*np.pi# 1 Hz oscillation frequency# Initial state: displaced from equilibriumx0=1.0# 1 meter displacementv0=0.0# Starting from restinitial_state=np.array([x0,v0])defsho_dynamics(t,state,params):"""Simple harmonic oscillator dynamics."""x,v=state[0],state[1]omega_sq=params[0]ifparamsisnotNoneelseomega**2returnnp.array([v,-omega_sq*x])# Parameters (omega^2)params=np.array([omega**2])# Create generic numerical propagatorprop=bh.NumericalPropagator(epoch,initial_state,sho_dynamics,bh.NumericalPropagationConfig.default(),params,)# Propagate for several periodsperiod=2*np.pi/omega# Period = 2*pi/omega = 1 secondprop.propagate_to(epoch+5*period)# Sample trajectoryprint("Simple Harmonic Oscillator Trajectory:")print(" omega = 2*pi rad/s (1 Hz)")print(" x0 = 1.0 m, v0 = 0.0 m/s")print("\nTime (s) Position (m) Velocity (m/s) Analytical x")print("-"*55)foriinrange(11):t=i*period/2# Sample at half-period intervalsstate=prop.state(epoch+t)# Analytical solution: x(t) = x0*cos(omega*t), v(t) = -x0*omega*sin(omega*t)x_analytical=x0*np.cos(omega*t)print(f" {t:.2f}{state[0]:+.6f}{state[1]:+.6f}{x_analytical:+.6f}")# Validate - after full period should return to initialfinal_state=prop.state(epoch+5*period)error_x=abs(final_state[0]-x0)error_v=abs(final_state[1]-v0)print("\nAfter 5 periods:")print(f" Position error: {error_x:.2e} m")print(f" Velocity error: {error_v:.2e} m/s")asserterror_x<0.01# Within 1 cmasserterror_v<0.1# Within 10 cm/sprint("\nExample validated successfully!")
usebraheasbh;usebh::traits::{DStatePropagator,DStateProvider};usenalgebraasna;usestd::f64::consts::PI;fnmain(){// Initialize EOP data (needed for epoch operations)bh::initialize_eop().unwrap();// Create initial epochletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);// Simple Harmonic Oscillator (SHO)// State: [x, v] where x is position and v is velocity// Dynamics: dx/dt = v, dv/dt = -omega^2 * xletomega=2.0*PI;// 1 Hz oscillation frequency// Initial state: displaced from equilibriumletx0=1.0;// 1 meter displacementletv0=0.0;// Starting from restletinitial_state=na::DVector::from_vec(vec![x0,v0]);// SHO dynamics functionletdynamics_fn:bh::DStateDynamics=Box::new(move|_t:f64,state:&na::DVector<f64>,params:Option<&na::DVector<f64>>|{letx=state[0];letv=state[1];letomega_sq=params.map(|p|p[0]).unwrap_or(omega*omega);na::DVector::from_vec(vec![v,-omega_sq*x])},);// Parameters (omega^2)letparams=na::DVector::from_vec(vec![omega*omega]);// Create generic numerical propagatorletmutprop=bh::DNumericalPropagator::new(epoch,initial_state,dynamics_fn,bh::NumericalPropagationConfig::default(),Some(params),None,// No control inputNone,// No initial covariance).unwrap();// Propagate for several periodsletperiod=2.0*PI/omega;// Period = 2*pi/omega = 1 secondprop.propagate_to(epoch+5.0*period);// Sample trajectoryprintln!("Simple Harmonic Oscillator Trajectory:");println!(" omega = 2*pi rad/s (1 Hz)");println!(" x0 = 1.0 m, v0 = 0.0 m/s");println!("\nTime (s) Position (m) Velocity (m/s) Analytical x");println!("{}","-".repeat(55));foriin0..11{lett=(iasf64)*period/2.0;// Sample at half-period intervalsletstate=prop.state(epoch+t).unwrap();// Analytical solution: x(t) = x0*cos(omega*t), v(t) = -x0*omega*sin(omega*t)letx_analytical=x0*(omega*t).cos();println!(" {:.2} {:+.6} {:+.6} {:+.6}",t,state[0],state[1],x_analytical);}// Validate - after full period should return to initialletfinal_state=prop.state(epoch+5.0*period).unwrap();leterror_x=(final_state[0]-x0).abs();leterror_v=(final_state[1]-v0).abs();println!("\nAfter 5 periods:");println!(" Position error: {:.2e} m",error_x);println!(" Velocity error: {:.2e} m/s",error_v);assert!(error_x<0.01);// Within 1 cmassert!(error_v<0.1);// Within 10 cm/sprintln!("\nExample validated successfully!");}
The dynamics function defines the system's equations of motion. It receives the current time (seconds from epoch), state vector, and optional parameters, returning the state derivative.
defdynamics(t:float,state:np.ndarray,params:np.ndarray|None)->np.ndarray:""" Compute state derivative for given time and state. Args: t: Time in seconds from reference epoch state: Current state vector (N-dimensional) params: Optional parameter vector Returns: State derivative vector (same dimension as state) """dstate=np.zeros(len(state))# Compute derivatives based on your equations of motion# ...returndstate
letdynamics_fn:bh::DStateDynamics=Box::new(|t:f64,state:&na::DVector<f64>,params:Option<&na::DVector<f64>>|->na::DVector<f64>{// t: Time in seconds from reference epoch// state: Current state vector (N-dimensional)// params: Optional parameter vectorletmutdstate=na::DVector::zeros(state.len());// Compute derivatives based on your equations of motion// ...dstate});
# Define parametersparams=np.array([omega**2,damping_coeff,mass])# Access in dynamics functiondefdynamics(t,state,params):omega_sq=params[0]damping=params[1]mass=params[2]# Use parameters in computation...# Create propagator with parametersprop=bh.NumericalPropagator(epoch,initial_state,dynamics,bh.NumericalPropagationConfig.default(),params# Pass parameters here)
// Define parametersletparams=na::DVector::from_vec(vec![omega*omega,damping_coeff,mass]);// Access in dynamics functionletdynamics_fn:bh::DStateDynamics=Box::new(|_t,state,params|{letp=params.unwrap();letomega_sq=p[0];letdamping=p[1];letmass=p[2];// Use parameters in computation...});// Create propagator with parametersletprop=bh::DNumericalPropagator::new(epoch,initial_state,dynamics_fn,bh::NumericalPropagationConfig::default(),Some(params),// Pass parameters hereNone,None).unwrap();
NumericalPropagator supports a separate control_input function that adds control contributions to the state derivative at each integration step. This separates the natural dynamics from control logic, making it easier to enable/disable control or swap control strategies.
The following example shows a damped harmonic oscillator where damping is implemented via control_input:
importnumpyasnpimportbraheasbh# Initialize EOP data (needed for epoch operations)bh.initialize_eop()# Create initial epochepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Simple Harmonic Oscillator with damping control# State: [x, v] where x is position and v is velocity# Natural dynamics: dx/dt = v, dv/dt = -omega^2 * x# Control adds damping: u = -c * vomega=2.0*np.pi# 1 Hz natural frequencydamping_ratio=0.1# Damping ratio (zeta)damping_coeff=2*damping_ratio*omega# c = 2*zeta*omega# Initial state: displaced from equilibriumx0=1.0# 1 meter displacementv0=0.0# Starting from restinitial_state=np.array([x0,v0])defsho_dynamics(t,state,params):"""Simple harmonic oscillator dynamics (undamped). This function defines only the natural dynamics. Control is added separately via control_input. """x,v=state[0],state[1]omega_sq=params[0]ifparamsisnotNoneelseomega**2returnnp.array([v,-omega_sq*x])defdamping_control(t,state,params):"""Damping control input: u = -c * v (opposes velocity). The control_input function returns a state derivative contribution that is added to the dynamics output at each integration step. """dx=np.zeros(len(state))v=state[1]# Control adds acceleration that opposes velocitydx[1]=-damping_coeff*vreturndx# Parameters (omega^2)params=np.array([omega**2])# Create propagator with dynamics AND control_inputprop_damped=bh.NumericalPropagator(epoch,initial_state,sho_dynamics,bh.NumericalPropagationConfig.default(),params,control_input=damping_control,# Separate control function)# Create undamped propagator for comparison (no control_input)prop_undamped=bh.NumericalPropagator(epoch,initial_state,sho_dynamics,bh.NumericalPropagationConfig.default(),params,)# Propagate for several periodsperiod=2*np.pi/omega# Period = 1 secondprop_damped.propagate_to(epoch+10*period)prop_undamped.propagate_to(epoch+10*period)# Sample trajectory and compareprint("Damped vs Undamped Harmonic Oscillator:")print(f" Natural frequency: {omega/(2*np.pi):.1f} Hz")print(f" Damping ratio: {damping_ratio}")print(f" Damping coefficient: {damping_coeff:.3f} /s")print("\nTime (s) Damped x Undamped x Amplitude ratio")print("-"*55)foriinrange(11):t=i*period# Sample at period intervalsstate_damped=prop_damped.state(epoch+t)state_undamped=prop_undamped.state(epoch+t)ratio=abs(state_damped[0])/max(abs(state_undamped[0]),1e-10)print(f" {t:.1f}{state_damped[0]:+.4f}{state_undamped[0]:+.4f}{ratio:.3f}")# Validate - damped oscillator should decayfinal_damped=prop_damped.state(epoch+10*period)final_undamped=prop_undamped.state(epoch+10*period)# Expected decay: amplitude ~ exp(-zeta*omega*t) = exp(-0.1 * 2*pi * 10) ~ 0.002expected_ratio=np.exp(-damping_ratio*omega*10*period)actual_ratio=abs(final_damped[0])/abs(x0)print("\nAfter 10 periods:")print(f" Damped amplitude: {abs(final_damped[0]):.4f} m")print(f" Undamped amplitude: {abs(final_undamped[0]):.4f} m")print(f" Expected decay ratio: {expected_ratio:.4f}")print(f" Actual decay ratio: {actual_ratio:.4f}")assertabs(final_damped[0])<abs(final_undamped[0])# Damped has smaller amplitudeassertactual_ratio<0.1# Should decay significantlyprint("\nExample validated successfully!")
usebraheasbh;usebh::traits::{DStatePropagator,DStateProvider};usenalgebraasna;usestd::f64::consts::PI;fnmain(){// Initialize EOP data (needed for epoch operations)bh::initialize_eop().unwrap();// Create initial epochletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);// Simple Harmonic Oscillator with damping control// State: [x, v] where x is position and v is velocity// Natural dynamics: dx/dt = v, dv/dt = -omega^2 * x// Control adds damping: u = -c * vletomega=2.0*PI;// 1 Hz natural frequencyletdamping_ratio=0.1;// Damping ratio (zeta)// Initial state: displaced from equilibriumletx0=1.0;// 1 meter displacementletv0=0.0;// Starting from restletinitial_state=na::DVector::from_vec(vec![x0,v0]);// SHO dynamics function (undamped - natural dynamics only)// Control is added separately via control_inputletsho_dynamics:bh::DStateDynamics=Box::new(move|_t:f64,state:&na::DVector<f64>,params:Option<&na::DVector<f64>>|{letx=state[0];letv=state[1];letomega_sq=params.map(|p|p[0]).unwrap_or(omega*omega);na::DVector::from_vec(vec![v,-omega_sq*x])},);// Another copy for the undamped propagatorletsho_dynamics_undamped:bh::DStateDynamics=Box::new(move|_t:f64,state:&na::DVector<f64>,params:Option<&na::DVector<f64>>|{letx=state[0];letv=state[1];letomega_sq=params.map(|p|p[0]).unwrap_or(omega*omega);na::DVector::from_vec(vec![v,-omega_sq*x])},);// Damping control input: u = -c * v (opposes velocity)// The control_input function returns a state derivative contribution// that is added to the dynamics output at each integration step.letdamping_coeff=2.0*damping_ratio*omega;letdamping_control:bh::DControlInput=Some(Box::new(move|_t:f64,state:&na::DVector<f64>,_params:Option<&na::DVector<f64>>|{letv=state[1];// Control adds acceleration that opposes velocityna::DVector::from_vec(vec![0.0,-damping_coeff*v])},));// Parameters (omega^2)letparams=na::DVector::from_vec(vec![omega*omega]);// Create propagator with dynamics AND control_inputletmutprop_damped=bh::DNumericalPropagator::new(epoch,initial_state.clone(),sho_dynamics,bh::NumericalPropagationConfig::default(),Some(params.clone()),damping_control,// Separate control functionNone,// No initial covariance).unwrap();// Create undamped propagator for comparison (no control_input)letmutprop_undamped=bh::DNumericalPropagator::new(epoch,initial_state,sho_dynamics_undamped,bh::NumericalPropagationConfig::default(),Some(params),None,// No control inputNone,).unwrap();// Propagate for several periodsletperiod=2.0*PI/omega;// Period = 1 secondprop_damped.propagate_to(epoch+10.0*period);prop_undamped.propagate_to(epoch+10.0*period);// Sample trajectory and compareprintln!("Damped vs Undamped Harmonic Oscillator:");println!(" Natural frequency: {:.1} Hz",omega/(2.0*PI));println!(" Damping ratio: {}",damping_ratio);println!(" Damping coefficient: {:.3} /s",damping_coeff);println!("\nTime (s) Damped x Undamped x Amplitude ratio");println!("{}","-".repeat(55));foriin0..11{lett=(iasf64)*period;// Sample at period intervalsletstate_damped=prop_damped.state(epoch+t).unwrap();letstate_undamped=prop_undamped.state(epoch+t).unwrap();letratio=state_damped[0].abs()/state_undamped[0].abs().max(1e-10);println!(" {:.1} {:+.4} {:+.4} {:.3}",t,state_damped[0],state_undamped[0],ratio);}// Validate - damped oscillator should decayletfinal_damped=prop_damped.state(epoch+10.0*period).unwrap();letfinal_undamped=prop_undamped.state(epoch+10.0*period).unwrap();// Expected decay: amplitude ~ exp(-zeta*omega*t) = exp(-0.1 * 2*pi * 10) ~ 0.002letexpected_ratio=(-damping_ratio*omega*10.0*period).exp();letactual_ratio=final_damped[0].abs()/x0;println!("\nAfter 10 periods:");println!(" Damped amplitude: {:.4} m",final_damped[0].abs());println!(" Undamped amplitude: {:.4} m",final_undamped[0].abs());println!(" Expected decay ratio: {:.4}",expected_ratio);println!(" Actual decay ratio: {:.4}",actual_ratio);assert!(final_damped[0].abs()<final_undamped[0].abs());// Damped has smaller amplitudeassert!(actual_ratio<0.1);// Should decay significantlyprintln!("\nExample validated successfully!");}
The generic propagator supports the same event detection system as NumericalOrbitPropagator. Events can detect when computed quantities cross threshold values:
importnumpyasnpimportbraheasbh# Initialize EOP data (needed for epoch operations)bh.initialize_eop()# Create initial epochepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Simple Harmonic Oscillator# State: [x, v] where x is position and v is velocityomega=2.0*np.pi# 1 Hz oscillation frequency# Initial state: displaced from equilibriumx0=1.0# 1 meter displacementv0=0.0# Starting from restinitial_state=np.array([x0,v0])defsho_dynamics(t,state,params):"""Simple harmonic oscillator dynamics."""x,v=state[0],state[1]omega_sq=params[0]ifparamsisnotNoneelseomega**2returnnp.array([v,-omega_sq*x])# Parameters (omega^2)params=np.array([omega**2])# Create propagatorprop=bh.NumericalPropagator(epoch,initial_state,sho_dynamics,bh.NumericalPropagationConfig.default(),params,)# Define value function for zero crossing detection# ValueEvent receives (epoch, state) and returns a scalardefposition_value(current_epoch,state):"""Return position component for event detection."""returnstate[0]# Create ValueEvent to detect position zero crossings# INCREASING: x goes from negative to positive (moving right through origin)positive_crossing=bh.ValueEvent("Positive Crossing",position_value,0.0,# Target valuebh.EventDirection.INCREASING,)# DECREASING: x goes from positive to negative (moving left through origin)negative_crossing=bh.ValueEvent("Negative Crossing",position_value,0.0,bh.EventDirection.DECREASING,)# Add event detectors to propagatorprop.add_event_detector(positive_crossing)prop.add_event_detector(negative_crossing)# Propagate for 5 periodsperiod=2*np.pi/omega# Period = 1 secondprop.propagate_to(epoch+5*period)# Get event logevents=prop.event_log()print("Simple Harmonic Oscillator Zero Crossings:")print(f" omega = {omega:.4f} rad/s (1 Hz)")print(f" Period = {period:.4f} s")print(" Expected crossings per period: 2 (one each direction)")print()positive_events=[eforeineventsif"Positive"ine.name]negative_events=[eforeineventsif"Negative"ine.name]print(f"Total events detected: {len(events)}")print(f" Positive crossings: {len(positive_events)}")print(f" Negative crossings: {len(negative_events)}")print()print("Event details:")print(" Time (s) Type Position Velocity")print("-"*60)foreventinevents[:10]:# Show first 10 eventst=event.window_open-epochx=event.entry_state[0]v=event.entry_state[1]print(f" {t:.4f}{event.name:<18}{x:+.6f}{v:+.6f}")# Validate# In 5 periods, we should have 5 positive crossings and 5 negative crossingsassertlen(positive_events)==5,(f"Expected 5 positive crossings, got {len(positive_events)}")assertlen(negative_events)==5,(f"Expected 5 negative crossings, got {len(negative_events)}")# Check timing: crossings should occur at quarter periods# Starting from x=1, v=0: oscillator moves left first (cosine motion)# Negative crossing (moving left) at T/4, 5T/4, 9T/4, ...# Positive crossing (moving right) at 3T/4, 7T/4, 11T/4, ...expected_negative_times=[(0.25+i)*periodforiinrange(5)]expected_positive_times=[(0.75+i)*periodforiinrange(5)]fori,eventinenumerate(negative_events):t=event.window_open-epochexpected=expected_negative_times[i]error=abs(t-expected)asserterror<0.02,(f"Negative crossing {i}: expected t={expected:.4f}, got t={t:.4f}")fori,eventinenumerate(positive_events):t=event.window_open-epochexpected=expected_positive_times[i]error=abs(t-expected)asserterror<0.02,(f"Positive crossing {i}: expected t={expected:.4f}, got t={t:.4f}")print("\nTiming verified: all crossings within 0.02s of expected times")print("\nExample validated successfully!")
usebraheasbh;usebh::events::{DValueEvent,EventDirection};usebh::traits::DStatePropagator;usenalgebraasna;usestd::f64::consts::PI;fnmain(){// Initialize EOP data (needed for epoch operations)bh::initialize_eop().unwrap();// Create initial epochletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);// Simple Harmonic Oscillator// State: [x, v] where x is position and v is velocityletomega=2.0*PI;// 1 Hz oscillation frequency// Initial state: displaced from equilibriumletx0=1.0;// 1 meter displacementletv0=0.0;// Starting from restletinitial_state=na::DVector::from_vec(vec![x0,v0]);// SHO dynamics functionletdynamics_fn:bh::DStateDynamics=Box::new(move|_t:f64,state:&na::DVector<f64>,params:Option<&na::DVector<f64>>|{letx=state[0];letv=state[1];letomega_sq=params.map(|p|p[0]).unwrap_or(omega*omega);na::DVector::from_vec(vec![v,-omega_sq*x])},);// Parameters (omega^2)letparams=na::DVector::from_vec(vec![omega*omega]);// Create propagatorletmutprop=bh::DNumericalPropagator::new(epoch,initial_state,dynamics_fn,bh::NumericalPropagationConfig::default(),Some(params),None,// No control inputNone,// No initial covariance).unwrap();// Create ValueEvent to detect position zero crossings// INCREASING: x goes from negative to positive (moving right through origin)letpositive_fn=|_t:bh::Epoch,state:&na::DVector<f64>,_params:Option<&na::DVector<f64>>|state[0];letpositive_crossing=DValueEvent::new("Positive Crossing",positive_fn,0.0,// Target valueEventDirection::Increasing,);// DECREASING: x goes from positive to negative (moving left through origin)letnegative_fn=|_t:bh::Epoch,state:&na::DVector<f64>,_params:Option<&na::DVector<f64>>|state[0];letnegative_crossing=DValueEvent::new("Negative Crossing",negative_fn,0.0,EventDirection::Decreasing,);// Add event detectors to propagatorprop.add_event_detector(Box::new(positive_crossing));prop.add_event_detector(Box::new(negative_crossing));// Propagate for 5 periodsletperiod=2.0*PI/omega;// Period = 1 secondprop.propagate_to(epoch+5.0*period);// Get event logletevents=prop.event_log();println!("Simple Harmonic Oscillator Zero Crossings:");println!(" omega = {:.4} rad/s (1 Hz)",omega);println!(" Period = {:.4} s",period);println!(" Expected crossings per period: 2 (one each direction)");println!();letpositive_events:Vec<_>=events.iter().filter(|e|e.name.contains("Positive")).collect();letnegative_events:Vec<_>=events.iter().filter(|e|e.name.contains("Negative")).collect();println!("Total events detected: {}",events.len());println!(" Positive crossings: {}",positive_events.len());println!(" Negative crossings: {}",negative_events.len());println!();println!("Event details:");println!(" Time (s) Type Position Velocity");println!("{}","-".repeat(60));foreventinevents.iter().take(10){lett=event.window_open-epoch;letx=event.entry_state[0];letv=event.entry_state[1];println!(" {:.4} {:<18} {:+.6} {:+.6}",t,event.name,x,v);}// Validate// In 5 periods, we should have 5 positive crossings and 5 negative crossingsassert_eq!(positive_events.len(),5,"Expected 5 positive crossings, got {}",positive_events.len());assert_eq!(negative_events.len(),5,"Expected 5 negative crossings, got {}",negative_events.len());// Check timing: crossings should occur at quarter periods// Starting from x=1, v=0: oscillator moves left first (cosine motion)// Negative crossing (moving left) at T/4, 5T/4, 9T/4, ...// Positive crossing (moving right) at 3T/4, 7T/4, 11T/4, ...letexpected_negative_times:Vec<f64>=(0..5).map(|i|(0.25+iasf64)*period).collect();letexpected_positive_times:Vec<f64>=(0..5).map(|i|(0.75+iasf64)*period).collect();for(i,event)innegative_events.iter().enumerate(){lett=event.window_open-epoch;letexpected=expected_negative_times[i];leterror=(t-expected).abs();assert!(error<0.02,"Negative crossing {}: expected t={:.4}, got t={:.4}",i,expected,t);}for(i,event)inpositive_events.iter().enumerate(){lett=event.window_open-epoch;letexpected=expected_positive_times[i];leterror=(t-expected).abs();assert!(error<0.02,"Positive crossing {}: expected t={:.4}, got t={:.4}",i,expected,t);}println!("\nTiming verified: all crossings within 0.02s of expected times");println!("\nExample validated successfully!");}
The generic propagator supports arbitrary state dimensions. The following pseudocode illustrates common extensions:
Illustrative Pseudocode
The examples below are simplified pseudocode to illustrate the concepts. For complete, runnable examples of extended state propagation, see Extending Spacecraft State.
defhill_clohessy_wiltshire(t,state,params):# State: [x, y, z, vx, vy, vz] in Hill frame (RTN)x,y,z,vx,vy,vz=staten=params[0]# Mean motion of reference orbit [rad/s]# HCW equations (linearized relative motion)ax=3*n**2*x+2*n*vyay=-2*n*vxaz=-n**2*zreturnnp.array([vx,vy,vz,ax,ay,az])
lethcw_dynamics:bh::DStateDynamics=Box::new(|_t,state,params|{// State: [x, y, z, vx, vy, vz] in Hill frame (RTN)let(x,y,z)=(state[0],state[1],state[2]);let(vx,vy,vz)=(state[3],state[4],state[5]);letn=params.unwrap()[0];// Mean motion [rad/s]// HCW equations (linearized relative motion)letax=3.0*n*n*x+2.0*n*vy;letay=-2.0*n*vx;letaz=-n*n*z;na::DVector::from_vec(vec![vx,vy,vz,ax,ay,az])});