The numerical propagator includes an event detection system that identifies specific orbital conditions during propagation. Events are defined by user-configurable detectors that monitor the spacecraft state and trigger when certain criteria are met. They can also be coupled with event callbacks to respond to detected events in real-time.
When an event is detected, the propagator uses a bisection algorithm to precisely locate the event time within a specified tolerance. The detected events are logged and can be accessed after propagation. Users can also configure how an event will affect the propagation, such as stopping propagation or continuing without interruption.
Events provide an extensible mechanism for implementing complex mission scenarios, such as maneuver execution, autonomous operations, and other condition-based actions.
The library also provides a set of premade event detectors for common scenarios, which can be used directly or serve as templates for custom detectors. You can find more details about premade events in the Premade Events documentation with a complete list of available types in the library API docuementation at Premade Event Detectors.
Time events trigger at specific epochs. They're useful for scheduled operations like data collection windows, communication passes, or timed maneuvers.
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and stateepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)oe=np.array([bh.R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)params=np.array([500.0,2.0,2.2,2.0,1.3])# Create propagatorprop=bh.NumericalOrbitPropagator(epoch,state,bh.NumericalPropagationConfig.default(),bh.ForceModelConfig.default(),params,)# Add time events at specific epochsevent_30min=bh.TimeEvent(epoch+1800.0,"30-minute mark")event_1hr=bh.TimeEvent(epoch+3600.0,"1-hour mark")# Add a terminal event that stops propagationevent_terminal=bh.TimeEvent(epoch+5400.0,"90-minute stop").set_terminal()prop.add_event_detector(event_30min)prop.add_event_detector(event_1hr)prop.add_event_detector(event_terminal)# Propagate for 2 hours (will stop at 90 minutes due to terminal event)prop.propagate_to(epoch+7200.0)# Check detected eventsevents=prop.event_log()print(f"Detected {len(events)} events:")foreventinevents:dt=event.window_open-epochprint(f" '{event.name}' at t+{dt:.1f}s")# Verify propagation stopped at terminal eventfinal_time=prop.current_epoch-epochprint(f"\nPropagation stopped at: t+{final_time:.1f}s (requested: t+7200s)")# Validateassertlen(events)==3# All three events detectedassertabs(final_time-5400.0)<1.0# Stopped at 90 minprint("\nExample validated successfully!")
usebraheasbh;usebh::events::DTimeEvent;usebh::traits::DStatePropagator;usenalgebraasna;fnmain(){// Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh::initialize_eop().unwrap();bh::initialize_sw().unwrap();// Create initial epoch and stateletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0,);letstate=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);letparams=na::DVector::from_vec(vec![500.0,2.0,2.2,2.0,1.3]);// Create propagatorletmutprop=bh::DNumericalOrbitPropagator::new(epoch,na::DVector::from_column_slice(state.as_slice()),bh::NumericalPropagationConfig::default(),bh::ForceModelConfig::default(),Some(params),None,None,None,).unwrap();// Add time events at specific epochsletevent_30min=DTimeEvent::new(epoch+1800.0,"30-minute mark".to_string());letevent_1hr=DTimeEvent::new(epoch+3600.0,"1-hour mark".to_string());// Add a terminal event that stops propagationletevent_terminal=DTimeEvent::new(epoch+5400.0,"90-minute stop".to_string()).set_terminal();prop.add_event_detector(Box::new(event_30min));prop.add_event_detector(Box::new(event_1hr));prop.add_event_detector(Box::new(event_terminal));// Propagate for 2 hours (will stop at 90 minutes due to terminal event)prop.propagate_to(epoch+7200.0);// Check detected eventsletevents=prop.event_log();println!("Detected {} events:",events.len());foreventinevents{letdt=event.window_open-epoch;println!(" '{}' at t+{:.1}s",event.name,dt);}// Verify propagation stopped at terminal eventletfinal_time=prop.current_epoch()-epoch;println!("\nPropagation stopped at: t+{:.1}s (requested: t+7200s)",final_time);// Validateassert_eq!(events.len(),3);// All three events detectedassert!((final_time-5400.0).abs()<1.0);// Stopped at 90 minprintln!("\nExample validated successfully!");}
Value events trigger when a user-defined function crosses a value value. This is the most flexible event type, enabling detection of arbitrary orbital conditions.
Value events are defined with a value function which accepts the current epoch and state vector, returning a scalar value.
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and stateepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)oe=np.array([bh.R_EARTH+500e3,0.01,45.0,15.0,30.0,45.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)params=np.array([500.0,2.0,2.2,2.0,1.3])# Create propagatorprop=bh.NumericalOrbitPropagator(epoch,state,bh.NumericalPropagationConfig.default(),bh.ForceModelConfig.default(),params,)# Define custom value function: detect when z-component crosses zero# This detects equator crossings (ascending and descending node)defz_position(epoch,state):"""Return z-component of position (meters)."""returnstate[2]# Create ValueEvent: detect when z crosses 0 (equator crossing)# Ascending node: z goes from negative to positive (INCREASING)ascending_node=bh.ValueEvent("Ascending Node",z_position,0.0,# target valuebh.EventDirection.INCREASING,)# Descending node: z goes from positive to negative (DECREASING)descending_node=bh.ValueEvent("Descending Node",z_position,0.0,bh.EventDirection.DECREASING,)prop.add_event_detector(ascending_node)prop.add_event_detector(descending_node)# Propagate for 3 orbitsorbital_period=bh.orbital_period(oe[0])prop.propagate_to(epoch+3*orbital_period)# Check detected eventsevents=prop.event_log()ascending=[eforeineventsif"Ascending"ine.name]descending=[eforeineventsif"Descending"ine.name]print("Equator crossings over 3 orbits:")print(f" Ascending nodes: {len(ascending)}")print(f" Descending nodes: {len(descending)}")foreventinevents[:6]:# Show first 6dt=event.window_open-epochz=event.entry_state[2]print(f" '{event.name}' at t+{dt:.1f}s (z={z:.1f} m)")# Validateassertlen(ascending)==3# One per orbitassertlen(descending)==3# One per orbitprint("\nExample validated successfully!")
usebraheasbh;usebh::events::{DValueEvent,EventDirection};usebh::traits::DStatePropagator;usenalgebraasna;usestd::f64::consts::PI;fnmain(){// Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh::initialize_eop().unwrap();bh::initialize_sw().unwrap();// Create initial epoch and stateletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.01,45.0,0.0,0.0,0.0,);letstate=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);letparams=na::DVector::from_vec(vec![500.0,2.0,2.2,2.0,1.3]);// Create propagatorletmutprop=bh::DNumericalOrbitPropagator::new(epoch,na::DVector::from_column_slice(state.as_slice()),bh::NumericalPropagationConfig::default(),bh::ForceModelConfig::default(),Some(params),None,None,None,).unwrap();// Create ValueEvent: detect when z crosses 0 (equator crossing)// Ascending node: z goes from negative to positive (INCREASING)letascending_node_fn=|_t:bh::Epoch,state:&na::DVector<f64>,_params:Option<&na::DVector<f64>>|state[2];letascending_node=DValueEvent::new("Ascending Node",ascending_node_fn,// z-component0.0,// target valueEventDirection::Increasing,);// Descending node: z goes from positive to negative (DECREASING)letdescending_node_fn=|_t:bh::Epoch,state:&na::DVector<f64>,_params:Option<&na::DVector<f64>>|state[2];letdescending_node=DValueEvent::new("Descending Node",descending_node_fn,0.0,EventDirection::Decreasing,);prop.add_event_detector(Box::new(ascending_node));prop.add_event_detector(Box::new(descending_node));// Propagate for 3 orbitsletorbital_period=2.0*PI*(oe[0].powi(3)/bh::GM_EARTH).sqrt();prop.propagate_to(epoch+3.0*orbital_period);// Check detected eventsletevents=prop.event_log();letascending:Vec<_>=events.iter().filter(|e|e.name.contains("Ascending")).collect();letdescending:Vec<_>=events.iter().filter(|e|e.name.contains("Descending")).collect();println!("Equator crossings over 3 orbits:");println!(" Ascending nodes: {}",ascending.len());println!(" Descending nodes: {}",descending.len());foreventinevents.iter().take(6){letdt=event.window_open-epoch;letz=event.entry_state[2];println!(" '{}' at t+{:.1}s (z={:.1} m)",event.name,dt,z);}// Validateassert_eq!(ascending.len(),3);// One per orbitassert_eq!(descending.len(),3);// One per orbitprintln!("\nExample validated successfully!");}