use scirs2_integrate::pde::implicit::{
BackwardEuler1D, CrankNicolson1D, ImplicitMethod, ImplicitOptions, ImplicitResult,
};
use scirs2_integrate::pde::{BoundaryCondition, BoundaryConditionType, BoundaryLocation, Domain};
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain = Domain::new(vec![0.0..10.0], vec![201])?;
let time_range = [0.0, 5.0];
let boundary_conditions = vec![
BoundaryCondition {
bc_type: BoundaryConditionType::Neumann,
location: BoundaryLocation::Lower,
dimension: 0,
value: 0.0, coefficients: None,
},
BoundaryCondition {
bc_type: BoundaryConditionType::Neumann,
location: BoundaryLocation::Upper,
dimension: 0,
value: 0.0, coefficients: None,
},
];
let diffusion = 0.5; let growth_rate = 1.0;
let diffusion_coeff = move |_x: f64, _t: f64, _u: f64| diffusion;
let reaction_term = move |_x: f64, _t: f64, u: f64| {
growth_rate * (1.0 - 2.0 * u)
};
let initial_condition = |x: f64| (-((x - 5.0) * (x - 5.0))).exp();
let options_cn = ImplicitOptions {
method: ImplicitMethod::CrankNicolson,
dt: Some(0.05),
save_every: Some(20),
verbose: true,
..Default::default()
};
let options_be = ImplicitOptions {
method: ImplicitMethod::BackwardEuler,
dt: Some(0.05),
save_every: Some(20),
verbose: true,
..Default::default()
};
println!("Solving Fisher-KPP equation with Crank-Nicolson method...");
let cn_solver = CrankNicolson1D::new(
domain.clone(),
time_range,
diffusion_coeff,
initial_condition,
boundary_conditions.clone(),
Some(options_cn),
)?
.with_reaction(reaction_term);
let cn_result = cn_solver.solve()?;
println!("\nSolving Fisher-KPP equation with Backward Euler method...");
let be_solver = BackwardEuler1D::new(
domain.clone(),
time_range,
diffusion_coeff,
initial_condition,
boundary_conditions.clone(),
Some(options_be),
)?
.with_reaction(reaction_term);
let be_result = be_solver.solve()?;
println!("\nFisher-KPP Simulation Results:");
println!("==============================");
println!("Domain: x ∈ [0, 10], t ∈ [0, 5.0]");
println!("Parameters: Diffusion D = {diffusion}, Growth rate r = {growth_rate}");
analyze_wave_propagation(&cn_result, &domain)?;
compare_solutions(&cn_result, &be_result, &domain)?;
println!("\nThis example demonstrates how implicit methods can effectively");
println!("simulate nonlinear reaction-diffusion systems like Fisher-KPP equation,");
println!("which develop traveling wave solutions.");
Ok(())
}
#[allow(dead_code)]
fn analyze_wave_propagation(
result: &ImplicitResult,
domain: &Domain,
) -> Result<(), Box<dyn std::error::Error>> {
let x_grid = domain.grid(0)?;
let nx = x_grid.len();
let times = &result.t;
let num_times = result.u.len();
let time_indices = [
0,
num_times / 4,
num_times / 2,
3 * num_times / 4,
num_times - 1,
];
println!("\nWave Propagation Analysis:");
println!("=========================");
println!("Position of wave front (u = 0.5) at selected times:");
println!("Time | Position | Theoretical Speed");
println!("-----|----------|------------------");
let mut prev_pos = 5.0; let mut prev_time = 0.0;
for &idx in &time_indices {
let time = times[idx];
let sol = &result.u[idx];
let mut front_pos = 5.0;
for i in 0..nx - 1 {
if sol[[i, 0]] > 0.5 && sol[[i + 1, 0]] <= 0.5 {
let x1 = x_grid[i];
let x2 = x_grid[i + 1];
let y1 = sol[[i, 0]];
let y2 = sol[[i + 1, 0]];
front_pos = x1 + (x2 - x1) * (0.5 - y1) / (y2 - y1);
break;
}
if sol[[i, 0]] <= 0.5 && sol[[i + 1, 0]] > 0.5 {
let x1 = x_grid[i];
let x2 = x_grid[i + 1];
let y1 = sol[[i, 0]];
let y2 = sol[[i + 1, 0]];
front_pos = x1 + (x2 - x1) * (0.5 - y1) / (y2 - y1);
break;
}
}
let speed = if time > prev_time {
(front_pos - prev_pos) / (time - prev_time)
} else {
0.0
};
let theoretical_speed = 2.0 * (0.5_f64 * 1.0).sqrt();
println!("{time:.1} | {front_pos:.4} | {speed:.4} (expected: {theoretical_speed:.4})");
prev_pos = front_pos;
prev_time = time;
}
let final_sol = result.u.last().expect("Operation failed");
println!("\nFinal solution values at specific locations:");
println!("Position | Value");
println!("---------|------");
for &x_pos in &[0.0, 2.5, 5.0, 7.5, 10.0] {
let i = ((x_pos - domain.ranges[0].start) / (domain.ranges[0].end - domain.ranges[0].start)
* (nx as f64 - 1.0))
.round() as usize;
println!("{:.1} | {:.4}", x_grid[i], final_sol[[i, 0]]);
}
Ok(())
}
#[allow(dead_code)]
fn compare_solutions(
cn_result: &ImplicitResult,
be_result: &ImplicitResult,
_domain: &Domain,
) -> Result<(), Box<dyn std::error::Error>> {
let cn_final = cn_result.u.last().expect("Operation failed");
let be_final = be_result.u.last().expect("Operation failed");
let mut max_diff: f64 = 0.0;
let mut avg_diff = 0.0;
let nx = cn_final.shape()[0];
for i in 0..nx {
let diff = (cn_final[[i, 0]] - be_final[[i, 0]]).abs();
max_diff = max_diff.max(diff);
avg_diff += diff;
}
avg_diff /= nx as f64;
println!("\nComparison between Crank-Nicolson and Backward Euler:");
println!("===============================================");
println!("Maximum absolute difference: {max_diff:.6e}");
println!("Average absolute difference: {avg_diff:.6e}");
println!(
"Relative difference: {:.2}%",
100.0 * avg_diff / cn_final.iter().sum::<f64>() * nx as f64
);
println!("\nPerformance comparison:");
println!(
"Crank-Nicolson: {:.4} seconds, {} linear solves",
cn_result.computation_time, cn_result.num_linear_solves
);
println!(
"Backward Euler: {:.4} seconds, {} linear solves",
be_result.computation_time, be_result.num_linear_solves
);
println!(
"Speed ratio (BE/CN): {:.2}",
cn_result.computation_time / be_result.computation_time
);
Ok(())
}