use scirs2_core::ndarray::{array, ArrayView1};
use scirs2_integrate::error::IntegrateResult;
use scirs2_integrate::ode::{
solve_ivp_with_events, terminal_event, EventAction, EventDirection, EventSpec, ODEMethod,
ODEOptions, ODEOptionsWithEvents,
};
#[allow(dead_code)]
fn main() -> IntegrateResult<()> {
println!("=== Event Detection Flowchart Example ===");
println!("This example will demonstrate the event detection system flow\n");
println!("Step 1: Define an ODE system");
println!(" Our system is a simple damped oscillator:");
println!(" y''(t) + 0.2*y'(t) + y(t) = 0");
println!(" Initial conditions: y(0) = 1, y'(0) = 0");
println!(" This produces a damped oscillation that gradually decays.\n");
let f = |_t: f64, y: ArrayView1<f64>| {
array![
y[1], -y[0] - 0.2 * y[1] ]
};
let y0 = array![1.0, 0.0];
println!("Step 2: Define event functions");
println!(" Event functions return a value that crosses zero when an event occurs.");
println!(" We'll define three event functions:");
println!(" 1. Zero crossing: when y = 0");
println!(" 2. Peak detection: when y' = 0");
println!(" 3. Threshold: when y < 0.1 (terminal event)\n");
let event_funcs = vec![
|_t: f64, y: ArrayView1<f64>| y[0],
|_t: f64, y: ArrayView1<f64>| y[1],
|_t: f64, y: ArrayView1<f64>| y[0].abs() - 0.1,
];
println!("Step 3: Configure event specifications");
println!(" Each event needs configuration to specify:");
println!(" - Direction: rising, falling, or both");
println!(" - Action: continue or stop");
println!(" - Other parameters like precision and threshold\n");
let event_specs = vec![
EventSpec {
id: "zero_crossing".to_string(),
direction: EventDirection::Both,
action: EventAction::Continue,
threshold: 1e-8,
max_count: None,
precise_time: true,
},
EventSpec {
id: "peak".to_string(),
direction: EventDirection::Both,
action: EventAction::Continue,
threshold: 1e-8,
max_count: None,
precise_time: true,
},
];
println!("Step 4: Set up solver options");
println!(" Solver options determine how the ODE is solved.");
println!(" For event detection, we need to enable dense output.\n");
let options = ODEOptionsWithEvents::new(
ODEOptions {
method: ODEMethod::RK45,
rtol: 1e-6,
atol: 1e-8,
dense_output: true, ..Default::default()
},
event_specs,
);
println!("Step 5: Solve the ODE with event detection");
println!(" The solver will integrate the ODE and detect events along the way.\n");
let result = solve_ivp_with_events(
f,
[0.0, 50.0], y0,
event_funcs,
options,
)?;
println!("Step 6: Analyze the results");
println!(" The solver detected events and recorded them:");
println!(
" - Zero crossings: {}",
result.events.get_count("zero_crossing")
);
println!(" - Peaks detected: {}", result.events.get_count("peak"));
println!(
" - Threshold triggered: {}",
result.events.get_count("threshold")
);
println!("\nEvent timeline:");
let mut all_events = Vec::new();
for event in &result.events.events {
all_events.push((event.time, &event.id, event.state[0], event.direction));
}
all_events.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
for (time, id, value, direction) in all_events {
let dir_str = match (id.as_str(), direction) {
("zero_crossing", 1) => "↑ (rising)",
("zero_crossing", -1) => "↓ (falling)",
("peak", 1) => "↑ (valley)",
("peak", -1) => "↓ (peak)",
("threshold", _) => "↓ (terminal)",
_ => "?",
};
println!(" t = {time:.4}: {id:15} | y = {value:.6} | direction: {dir_str}");
}
println!("\nFinal state:");
println!(
" t = {:.4}",
result.base_result.t.last().expect("Operation failed")
);
println!(
" y = {:.6}",
result.base_result.y.last().expect("Operation failed")[0]
);
println!(
" y' = {:.6}",
result.base_result.y.last().expect("Operation failed")[1]
);
println!(" Terminated by event: {}", result.event_termination);
println!("\nStep 7: Using dense output");
println!(" Dense output allows evaluating the solution at any time point.");
println!(" Example: Evaluate at intermediate points between events.");
if let Some(ref dense) = result.dense_output {
let zero_events = result.events.get_events("zero_crossing");
if !zero_events.is_empty() {
let first_zero = zero_events[0].time;
let t_start = 0.0;
let delta = (first_zero - t_start) / 6.0;
println!("\n Values approaching first zero crossing:");
for i in 1..=5 {
let t = t_start + i as f64 * delta;
let y = dense.evaluate(t)?;
println!(" t = {:.4}, y = {:.6}, y' = {:.6}", t, y[0], y[1]);
}
}
}
println!("\nEvent detection flowchart complete!");
println!("This demonstrates how event detection is integrated with the ODE solver.");
Ok(())
}