Brahe provides built-in event detectors for common orbital conditions. These premade events handle the underlying value function implementation, making it easy to detect frequently-needed conditions without writing custom detection logic.
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and state - orbit with inclination near valueepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# SSO-like orbitoe=np.array([bh.R_EARTH+600e3,0.001,97.8,0.0,0.0,0.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 orbital element events# Detect when inclination crosses 97.79 degrees (monitoring for stability)inc_event=bh.InclinationEvent(97.79,# value in degrees"Inc value",bh.EventDirection.ANY,bh.AngleFormat.DEGREES,)# Detect semi-major axis value (orbit decay monitoring)sma_event=bh.SemiMajorAxisEvent(bh.R_EARTH+599.5e3,# value in meters"SMA value",bh.EventDirection.DECREASING,)prop.add_event_detector(inc_event)prop.add_event_detector(sma_event)# Propagate for 3 orbitsorbital_period=bh.orbital_period(oe[0])prop.propagate_to(epoch+3*orbital_period)# Check detected eventsevents=prop.event_log()print(f"Detected {len(events)} orbital element events:")foreventinevents:dt=event.window_open-epoch# Get current orbital elementsr=event.entry_state[:3]v=event.entry_state[3:]alt=np.linalg.norm(r)-bh.R_EARTHprint(f" '{event.name}' at t+{dt:.1f}s (altitude: {alt/1e3:.1f} km)")# Count events by typeinc_events=[eforeineventsif"Inc"ine.name]sma_events=[eforeineventsif"SMA"ine.name]print(f"\nInclination value crossings: {len(inc_events)}")print(f"SMA value crossings: {len(sma_events)}")# The J2 perturbation causes slow variations - we may or may not cross values# depending on the exact parameters, so we just validate the events workprint("\nExample completed successfully!")
usebraheasbh;usebh::events::{DInclinationEvent,DSemiMajorAxisEvent,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 state - SSO-like orbitletepoch=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+600e3,0.001,97.8,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();// Add orbital element events// Detect when inclination crosses 97.79 degrees (monitoring for stability)letinc_event=DInclinationEvent::new(97.79,// value in degrees"Inc value".to_string(),EventDirection::Any,bh::AngleFormat::Degrees,);// Detect semi-major axis value (orbit decay monitoring)letsma_event=DSemiMajorAxisEvent::new(bh::R_EARTH+599.5e3,// value in meters"SMA value".to_string(),EventDirection::Decreasing,);prop.add_event_detector(Box::new(inc_event));prop.add_event_detector(Box::new(sma_event));// 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();println!("Detected {} orbital element events:",events.len());foreventinevents{letdt=event.window_open-epoch;letalt=event.entry_state.fixed_rows::<3>(0).norm()-bh::R_EARTH;println!(" '{}' at t+{:.1}s (altitude: {:.1} km)",event.name,dt,alt/1e3);}// Count events by typeletinc_events:Vec<_>=events.iter().filter(|e|e.name.contains("Inc")).collect();letsma_events:Vec<_>=events.iter().filter(|e|e.name.contains("SMA")).collect();println!("\nInclination value crossings: {}",inc_events.len());println!("SMA value crossings: {}",sma_events.len());// The J2 perturbation causes slow variations - we may or may not cross values// depending on the exact parameters, so we just validate the events workprintln!("\nExample completed successfully!");}
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and state - elliptical orbitepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Elliptical orbit: 300 km perigee, 800 km apogeeoe=np.array([bh.R_EARTH+550e3,0.036,45.0,0.0,0.0,0.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 altitude events# Detect when crossing 500 km altitude (both directions)event_500km=bh.AltitudeEvent(500e3,# value altitude in meters"500km crossing",bh.EventDirection.ANY,# Detect both increasing and decreasing)# Detect only when ascending through 600 kmevent_600km_up=bh.AltitudeEvent(600e3,"600km ascending",bh.EventDirection.INCREASING,)prop.add_event_detector(event_500km)prop.add_event_detector(event_600km_up)# Propagate for 2 orbitsorbital_period=bh.orbital_period(oe[0])prop.propagate_to(epoch+2*orbital_period)# Check detected eventsevents=prop.event_log()print(f"Detected {len(events)} altitude events:")foreventinevents:dt=event.window_open-epochalt=np.linalg.norm(event.entry_state[:3])-bh.R_EARTHprint(f" '{event.name}' at t+{dt:.1f}s (altitude: {alt/1e3:.1f} km)")# Count events by typecrossings_500=[eforeineventsif"500km"ine.name]crossings_600=[eforeineventsif"600km"ine.name]print(f"\n500 km crossings (any direction): {len(crossings_500)}")print(f"600 km ascending crossings: {len(crossings_600)}")# Validateassertlen(crossings_500)>=4# At least 2 per orbit, 2 orbitsassertlen(crossings_600)>=2# At least 1 per orbit (ascending only)print("\nExample validated successfully!")
usebraheasbh;usebh::events::{DAltitudeEvent,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 state - elliptical orbitletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);// Elliptical orbit: 300 km perigee, 800 km apogeeletoe=na::SVector::<f64,6>::new(bh::R_EARTH+550e3,0.036,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();// Add altitude events// Detect when crossing 500 km altitude (both directions)letevent_500km=DAltitudeEvent::new(500e3,// value altitude in meters"500km crossing".to_string(),EventDirection::Any,// Detect both increasing and decreasing);// Detect only when ascending through 600 kmletevent_600km_up=DAltitudeEvent::new(600e3,"600km ascending".to_string(),EventDirection::Increasing,);prop.add_event_detector(Box::new(event_500km));prop.add_event_detector(Box::new(event_600km_up));// Propagate for 2 orbitsletorbital_period=2.0*PI*(oe[0].powi(3)/bh::GM_EARTH).sqrt();prop.propagate_to(epoch+2.0*orbital_period);// Check detected eventsletevents=prop.event_log();println!("Detected {} altitude events:",events.len());foreventinevents{letdt=event.window_open-epoch;letalt=event.entry_state.fixed_rows::<3>(0).norm()-bh::R_EARTH;println!(" '{}' at t+{:.1}s (altitude: {:.1} km)",event.name,dt,alt/1e3);}// Count events by typeletcrossings_500:Vec<_>=events.iter().filter(|e|e.name.contains("500km")).collect();letcrossings_600:Vec<_>=events.iter().filter(|e|e.name.contains("600km")).collect();println!("\n500 km crossings (any direction): {}",crossings_500.len());println!("600 km ascending crossings: {}",crossings_600.len());// Validateassert!(crossings_500.len()>=4);// At least 2 per orbit, 2 orbitsassert!(crossings_600.len()>=2);// At least 1 per orbit (ascending only)println!("\nExample validated successfully!");}
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and state - LEO orbitepoch=bh.Epoch.from_datetime(2024,6,21,12,0,0.0,0.0,bh.TimeSystem.UTC)# LEO orbit with some inclinationoe=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,)# Add eclipse events with different edge types# Detect entry into eclipse (any shadow - umbra or penumbra)eclipse_entry=bh.EclipseEvent("Eclipse Entry",bh.EdgeType.RISING_EDGE,None)# Detect exit from eclipseeclipse_exit=bh.EclipseEvent("Eclipse Exit",bh.EdgeType.FALLING_EDGE,None)prop.add_event_detector(eclipse_entry)prop.add_event_detector(eclipse_exit)# Propagate for 5 orbitsorbital_period=bh.orbital_period(oe[0])prop.propagate_to(epoch+5*orbital_period)# Check detected eventsevents=prop.event_log()print(f"Detected {len(events)} eclipse events:")foreventinevents:dt=event.window_open-epochprint(f" '{event.name}' at t+{dt:.1f}s")# Count events by typeentries=[eforeineventsif"Entry"ine.name]exits=[eforeineventsif"Exit"ine.name]print(f"\nEclipse entries: {len(entries)}")print(f"Eclipse exits: {len(exits)}")# Calculate eclipse durationsiflen(entries)>0andlen(exits)>0:# Find pairs of entry/exit eventsdurations=[]fori,entryinenumerate(entries):# Find next exit after this entryforexit_eventinexits:ifexit_event.window_open>entry.window_open:duration=exit_event.window_open-entry.window_opendurations.append(duration)breakifdurations:avg_duration=sum(durations)/len(durations)print(f"\nAverage eclipse duration: {avg_duration:.1f}s ({avg_duration/60:.1f} min)")# Validate - should have roughly equal entries and exitsassertabs(len(entries)-len(exits))<=1,"Entry/exit count mismatch"assertlen(entries)>=4,"Expected at least 4 eclipse entries in 5 orbits"print("\nExample validated successfully!")
usebraheasbh;usebh::events::{DEclipseEvent,EdgeType};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 state - LEO orbitletepoch=bh::Epoch::from_datetime(2024,6,21,12,0,0.0,0.0,bh::TimeSystem::UTC);// LEO orbit with some inclinationletoe=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();// Add eclipse events with different edge types// Detect entry into eclipse (any shadow - umbra or penumbra)leteclipse_entry=DEclipseEvent::new("Eclipse Entry",EdgeType::RisingEdge,None);// Detect exit from eclipseleteclipse_exit=DEclipseEvent::new("Eclipse Exit",EdgeType::FallingEdge,None);prop.add_event_detector(Box::new(eclipse_entry));prop.add_event_detector(Box::new(eclipse_exit));// Propagate for 5 orbitsletorbital_period=2.0*PI*(oe[0].powi(3)/bh::GM_EARTH).sqrt();let_=prop.propagate_to(epoch+5.0*orbital_period);// Check detected eventsletevents=prop.event_log();println!("Detected {} eclipse events:",events.len());foreventinevents{letdt=event.window_open-epoch;println!(" '{}' at t+{:.1}s",event.name,dt);}// Count events by typeletentries:Vec<_>=events.iter().filter(|e|e.name.contains("Entry")).collect();letexits:Vec<_>=events.iter().filter(|e|e.name.contains("Exit")).collect();println!("\nEclipse entries: {}",entries.len());println!("Eclipse exits: {}",exits.len());// Calculate eclipse durationsif!entries.is_empty()&&!exits.is_empty(){letmutdurations:Vec<f64>=Vec::new();forentryin&entries{// Find next exit after this entryforexit_eventin&exits{ifexit_event.window_open>entry.window_open{letduration=exit_event.window_open-entry.window_open;durations.push(duration);break;}}}if!durations.is_empty(){letavg_duration:f64=durations.iter().sum::<f64>()/durations.len()asf64;println!("\nAverage eclipse duration: {:.1}s ({:.1} min)",avg_duration,avg_duration/60.0);}}// Validate - should have roughly equal entries and exitsassert!((entries.len()asi32-exits.len()asi32).abs()<=1,"Entry/exit count mismatch");assert!(entries.len()>=4,"Expected at least 4 eclipse entries in 5 orbits");println!("\nExample validated successfully!");}// Expected output:// Detected 10 eclipse events:// 'Eclipse Entry' at t+1923.4s// 'Eclipse Exit' at t+4078.2s// 'Eclipse Entry' at t+7508.5s// 'Eclipse Exit' at t+9663.3s// 'Eclipse Entry' at t+13093.6s// 'Eclipse Exit' at t+15248.4s// 'Eclipse Entry' at t+18678.7s// 'Eclipse Exit' at t+20833.5s// 'Eclipse Entry' at t+24263.8s// 'Eclipse Exit' at t+26418.6s//// Eclipse entries: 5// Eclipse exits: 5//// Average eclipse duration: 2154.9s (35.9 min)//// Example validated successfully!
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epoch and state - inclined orbitepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Inclined orbit for clear node crossingsoe=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,)# Add node crossing events# Ascending node: spacecraft crosses equator heading north (argument of latitude = 0)asc_event=bh.AscendingNodeEvent("Ascending Node")# Descending node: spacecraft crosses equator heading south (argument of latitude = 180 deg)desc_event=bh.DescendingNodeEvent("Descending Node")prop.add_event_detector(asc_event)prop.add_event_detector(desc_event)# Propagate for 3 orbitsorbital_period=bh.orbital_period(oe[0])prop.propagate_to(epoch+3*orbital_period)# Check detected eventsevents=prop.event_log()print(f"Detected {len(events)} node crossing events:")foreventinevents:dt=event.window_open-epoch# Compute geodetic latitude at eventr_eci=event.entry_state[:3]r_ecef=bh.position_eci_to_ecef(event.window_open,r_eci)geodetic=bh.position_ecef_to_geodetic(r_ecef,bh.AngleFormat.DEGREES)lat=geodetic[1]print(f" '{event.name}' at t+{dt:.1f}s (latitude: {lat:.2f} deg)")# Count events by typeascending=[eforeineventsif"Ascending"ine.name]descending=[eforeineventsif"Descending"ine.name]print(f"\nAscending node crossings: {len(ascending)}")print(f"Descending node crossings: {len(descending)}")# Validateassertlen(ascending)>=3# At least 3 ascending in 3 orbitsassertlen(descending)>=3# At least 3 descending in 3 orbits