| Crates.io | scirs2-integrate |
| lib.rs | scirs2-integrate |
| version | 0.1.0-beta.2 |
| created_at | 2025-04-12 12:47:52.369056+00 |
| updated_at | 2025-09-20 08:35:39.195289+00 |
| description | Numerical integration module for SciRS2 (scirs2-integrate) |
| homepage | |
| repository | https://github.com/cool-japan/scirs |
| max_upload_size | |
| id | 1630896 |
| size | 4,777,366 |
🚀 Production-Ready Release 0.1.0-beta.2 (First Beta)
A comprehensive, high-performance numerical integration library for Rust that provides SciPy-compatible functionality with enhanced performance, memory safety, and parallel processing capabilities.
This release represents feature-complete, production-ready code suitable for use in scientific computing applications, research projects, and production systems requiring robust numerical integration capabilities.
quad, solve_ivp, solve_bvp, LSODA, Radau, BDF, DOP853, and moreResult types throughoutAdd the following to your Cargo.toml:
[dependencies]
scirs2-integrate = "0.1.0-beta.2"
ndarray = "0.16.1"
Enable optional features for enhanced performance:
[dependencies]
scirs2-integrate = { version = "0.1.0-beta.2", features = ["simd", "parallel"] }
Available features:
simd: SIMD optimizations for numerical operationsparallel: Parallel computation capabilitiesautodiff: Automatic differentiation support (experimental)symplectic: Symplectic integrators for Hamiltonian systemsparallel_jacobian: Parallel Jacobian computationBasic usage examples:
use scirs2_integrate::{quad, ode, gaussian, romberg, monte_carlo};
use scirs2_core::error::CoreResult;
use ndarray::ArrayView1;
// Numerical integration using simpson's rule
fn integrate_example() -> CoreResult<f64> {
// Define a function to integrate
let f = |x| x.sin();
// Integrate sin(x) from 0 to pi
let result = quad::simpson(f, 0.0, std::f64::consts::PI, None)?;
// The exact result should be 2.0
println!("Integral of sin(x) from 0 to pi: {}", result);
Ok(result)
}
// Using Gaussian quadrature for high accuracy
fn gaussian_example() -> CoreResult<f64> {
// Integrate sin(x) from 0 to pi with Gauss-Legendre quadrature
let result = gaussian::gauss_legendre(|x| x.sin(), 0.0, std::f64::consts::PI, 5)?;
println!("Gauss-Legendre result: {}", result);
// The error should be very small with just 5 points
Ok(result)
}
// Using Romberg integration for high accuracy
fn romberg_example() -> CoreResult<f64> {
let result = romberg::romberg(|x| x.sin(), 0.0, std::f64::consts::PI, None)?;
println!("Romberg result: {}, Error: {}", result.value, result.abs_error);
// Romberg integration converges very rapidly
Ok(result.value)
}
// Monte Carlo integration for high-dimensional problems
fn monte_carlo_example() -> CoreResult<f64> {
// Define options for Monte Carlo integration
let options = monte_carlo::MonteCarloOptions {
n_samples: 100000,
seed: Some(42), // For reproducibility
..Default::default()
};
// Integrate a 3D function: f(x,y,z) = sin(x+y+z) over [0,1]³
let result = monte_carlo::monte_carlo(
|point: ArrayView1<f64>| (point[0] + point[1] + point[2]).sin(),
&[(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
Some(options)
)?;
println!("Monte Carlo result: {}, Std Error: {}", result.value, result.std_error);
Ok(result.value)
}
// Solving an ODE: dy/dx = -y, y(0) = 1
fn ode_example() -> CoreResult<()> {
// Define the ODE: dy/dx = -y
let f = |_x, y: &[f64]| vec![-y[0]];
// Initial condition
let y0 = vec![1.0];
// Time points at which we want the solution
let t_span = (0.0, 5.0);
let t_eval = Some(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]);
// Solve the ODE
let result = ode::solve_ivp(f, t_span, y0, None, t_eval, None)?;
// Print the solution
println!("Times: {:?}", result.t);
println!("Values: {:?}", result.y);
// The exact solution is y = e^(-x)
println!("Exact solution at x=5: {}", (-5.0f64).exp());
println!("Numerical solution at x=5: {}", result.y.last().unwrap()[0]);
Ok(())
}
Functions for numerical integration:
// Basic quadrature methods
use scirs2_integrate::quad::{
trapezoid, // Trapezoidal rule integration
simpson, // Simpson's rule integration
adaptive_quad, // Adaptive quadrature with error estimation
quad, // General-purpose integration
};
// Gaussian quadrature methods
use scirs2_integrate::gaussian::{
gauss_legendre, // Gauss-Legendre quadrature
multi_gauss_legendre, // Multi-dimensional Gauss-Legendre quadrature
GaussLegendreQuadrature, // Object-oriented interface for Gauss-Legendre
};
// Romberg integration methods
use scirs2_integrate::romberg::{
romberg, // Romberg integration with Richardson extrapolation
multi_romberg, // Multi-dimensional Romberg integration
RombergOptions, // Options for controlling Romberg integration
RombergResult, // Results including error estimates
};
// Monte Carlo integration methods
use scirs2_integrate::monte_carlo::{
monte_carlo, // Basic Monte Carlo integration
importance_sampling, // Monte Carlo with importance sampling
MonteCarloOptions, // Options for controlling Monte Carlo integration
MonteCarloResult, // Results including statistical error estimates
ErrorEstimationMethod, // Methods for estimating error in Monte Carlo
};
Solvers for ordinary differential equations:
use scirs2_integrate::ode::{
// ODE Methods
ODEMethod, // Enum of available ODE methods
ODEOptions, // Options for ODE solvers
ODEResult, // Result of ODE integration
// Solve Initial Value Problems
solve_ivp, // Solve initial value problem for a system of ODEs
};
// Available methods include:
// - ODEMethod::Euler // First-order Euler method
// - ODEMethod::RK4 // Fourth-order Runge-Kutta method (fixed step)
// - ODEMethod::RK45 // Dormand-Prince method (variable step)
// - ODEMethod::RK23 // Bogacki-Shampine method (variable step)
// - ODEMethod::DOP853 // Dormand-Prince 8(5,3) high-accuracy method
// - ODEMethod::BDF // Backward differentiation formula (for stiff problems)
// - ODEMethod::Radau // Implicit Runge-Kutta Radau IIA method (L-stable)
// - ODEMethod::LSODA // Livermore Solver with automatic method switching
// - ODEMethod::EnhancedBDF // Enhanced BDF with improved Jacobian handling
// - ODEMethod::EnhancedLSODA // Enhanced LSODA with better stiffness detection
Solvers for two-point boundary value problems:
use scirs2_integrate::bvp::{
// BVP solver functions
solve_bvp, // Solve a two-point boundary value problem
solve_bvp_auto, // Automatically set up and solve common BVP types
// BVP Types
BVPOptions, // Options for BVP solvers
BVPResult, // Result of BVP solution
};
Common numerical methods used across integration algorithms:
use scirs2_integrate::utils::{
// Numerical differentiation
numerical_jacobian, // Compute numerical Jacobian of a vector function
numerical_jacobian_with_param, // Compute Jacobian with scalar parameter
// Linear algebra
solve_linear_system, // Solve linear system using Gaussian elimination
// Nonlinear solvers
newton_method, // Newton's method for nonlinear systems
newton_method_with_param, // Newton's method with scalar parameter
};
The module includes comprehensive performance optimization features:
Accelerates convergence of fixed-point iterations and iterative solvers:
use scirs2_integrate::acceleration::{AndersonAccelerator, AcceleratorOptions};
use ndarray::Array1;
// Create accelerator with custom options
let options = AcceleratorOptions {
memory_depth: 5, // Number of previous iterates to store
regularization: 1e-8, // Regularization for numerical stability
damping: 0.8, // Damping factor
..Default::default()
};
let mut accelerator = AndersonAccelerator::new(2, options);
// In your iteration loop
let x_current = Array1::from_vec(vec![1.0, 2.0]);
let g_x = Array1::from_vec(vec![1.1, 1.9]); // G(x_current)
if let Some(x_accelerated) = accelerator.accelerate(x_current.view(), g_x.view()) {
// Use accelerated update for next iteration
}
Automatically detects hardware characteristics and optimizes parameters:
use scirs2_integrate::autotuning::{HardwareDetector, AutoTuner};
// Detect hardware automatically
let hardware = HardwareDetector::detect();
println!("Detected {} CPU cores", hardware.cpu_cores);
println!("L3 cache: {} MB", hardware.l3_cache_size / (1024 * 1024));
// Create auto-tuner and get optimized parameters
let tuner = AutoTuner::new(hardware);
let profile = tuner.tune_for_problem_size(100000);
println!("Recommended threads: {}", profile.num_threads);
println!("Optimal block size: {}", profile.block_size);
Cache-friendly algorithms and memory pooling for better performance:
use scirs2_integrate::memory::{MemoryPool, CacheFriendlyMatrix, BlockingStrategy};
// Use memory pool for frequent allocations
let mut pool = MemoryPool::new(1024 * 1024); // 1MB pool
let buffer = pool.allocate(1000);
// Cache-friendly matrix operations
let matrix = CacheFriendlyMatrix::new(1000, 1000, MatrixLayout::RowMajor);
let blocking = BlockingStrategy::auto_detect(); // Automatically choose block size
// Perform blocked operations for better cache utilization
let result = matrix.blocked_multiply(&other_matrix, &blocking);
Dynamic load balancing for adaptive algorithms:
use scirs2_integrate::scheduling::{WorkStealingPool, Task};
// Create work-stealing pool with automatic thread count
let pool = WorkStealingPool::new(0); // 0 = use all available cores
// Submit adaptive integration tasks
let tasks = vec![
Task::new(|| adaptive_integrate_region(0.0, 0.25)),
Task::new(|| adaptive_integrate_region(0.25, 0.5)),
Task::new(|| adaptive_integrate_region(0.5, 0.75)),
Task::new(|| adaptive_integrate_region(0.75, 1.0)),
];
let results = pool.execute_all(tasks);
Vectorized operations for better performance on modern CPUs:
// Enable SIMD features in Cargo.toml:
// scirs2-integrate = { version = "0.1.0-beta.2", features = ["simd"] }
use scirs2_integrate::ode::utils::simd_ops;
// SIMD-accelerated vector operations (when available)
let mut y = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
let dy = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
// Performs y = y + a * dy using SIMD when possible
simd_ops::simd_axpy(&mut y.view_mut(), 2.0, &dy.view());
For high-dimensional problems, Monte Carlo integration is often the most practical approach:
use scirs2_integrate::monte_carlo::{monte_carlo, MonteCarloOptions};
use std::marker::PhantomData;
use ndarray::ArrayView1;
// Integrate a function over a 5D hypercube
let f = |x: ArrayView1<f64>| {
// Sum of squared components: ∫∫∫∫∫(x² + y² + z² + w² + v²) dx dy dz dw dv
x.iter().map(|&xi| xi * xi).sum()
};
let options = MonteCarloOptions {
n_samples: 100000,
seed: Some(42), // For reproducibility
_phantom: PhantomData,
..Default::default()
};
// Integrate over [0,1]⁵
let result = monte_carlo(
f,
&[(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
Some(options)
).unwrap();
println!("Result: {}, Standard error: {}", result.value, result.std_error);
Romberg integration uses Richardson extrapolation to accelerate convergence:
use scirs2_integrate::romberg::{romberg, RombergOptions};
// Function to integrate
let f = |x: f64| x.sin();
// Options
let options = RombergOptions {
max_iters: 10,
abs_tol: 1e-12,
rel_tol: 1e-12,
};
// Integrate sin(x) from 0 to pi
let result = romberg(f, 0.0, std::f64::consts::PI, Some(options)).unwrap();
println!("Result: {}, Error: {}, Iterations: {}",
result.value, result.abs_error, result.n_iters);
// Romberg table gives the sequence of approximations
println!("Convergence history: {:?}", result.table);
The module includes adaptive integration methods that adjust step size based on error estimation:
// Example of adaptive quadrature
use scirs2_integrate::quad::adaptive_quad;
let f = |x| x.sin();
let a = 0.0;
let b = std::f64::consts::PI;
let atol = 1e-8; // Absolute tolerance
let rtol = 1e-8; // Relative tolerance
let result = adaptive_quad(&f, a, b, atol, rtol, None).unwrap();
println!("Integral: {}, Error estimate: {}", result.0, result.1);
Support for systems of ODEs:
// Lotka-Volterra predator-prey model
use ndarray::array;
use scirs2_integrate::ode::{solve_ivp, ODEOptions, ODEMethod};
// Define the system: dx/dt = alpha*x - beta*x*y, dy/dt = delta*x*y - gamma*y
let lotka_volterra = |_t, y| {
let (x, y) = (y[0], y[1]);
let alpha = 1.0;
let beta = 0.1;
let delta = 0.1;
let gamma = 1.0;
array![
alpha * x - beta * x * y, // dx/dt
delta * x * y - gamma * y // dy/dt
]
};
// Initial conditions
let initial_state = array![10.0, 5.0]; // Initial populations of prey and predator
// Options for adaptive solver
let options = ODEOptions {
method: ODEMethod::RK45, // Use adaptive Runge-Kutta
rtol: 1e-6, // Relative tolerance
atol: 1e-8, // Absolute tolerance
..Default::default()
};
// Solve the system
let result = solve_ivp(lotka_volterra, [0.0, 20.0], initial_state, Some(options)).unwrap();
// Plot or analyze the results
println!("Time points: {:?}", result.t);
println!("Prey population at t=20: {}", result.y.last().unwrap()[0]);
println!("Predator population at t=20: {}", result.y.last().unwrap()[1]);
Detecting events during ODE integration:
use ndarray::{array, ArrayView1};
use scirs2_integrate::ode::{
solve_ivp_with_events, ODEMethod, ODEOptions, EventSpec,
EventDirection, EventAction, ODEOptionsWithEvents
};
use std::f64::consts::PI;
// Simulate a bouncing ball with gravity and a coefficient of restitution
let g = 9.81; // Gravity
let coef_restitution = 0.8; // Energy loss on bounce
// Initial conditions: height = 10m, velocity = 0 m/s
let y0 = array![10.0, 0.0];
// ODE function: dy/dt = [v, -g]
let f = |_t: f64, y: ArrayView1<f64>| array![y[1], -g];
// Event function: detect when ball hits the ground (h = 0)
let event_funcs = vec![
|_t: f64, y: ArrayView1<f64>| y[0] // Ball hits ground when height = 0
];
// Event specification: detect impact and continue integration
let event_specs = vec![
EventSpec {
id: "ground_impact".to_string(),
direction: EventDirection::Falling, // Only detect when height becomes zero from above
action: EventAction::Continue, // Don't stop the simulation on impact
threshold: 1e-8,
max_count: None,
precise_time: true,
}
];
// Create options with event detection
let options = ODEOptionsWithEvents::new(
ODEOptions {
method: ODEMethod::RK45,
rtol: 1e-6,
atol: 1e-8,
dense_output: true, // Required for precise event detection
..Default::default()
},
event_specs,
);
// Solve with event detection
let result = solve_ivp_with_events(f, [0.0, 10.0], y0, event_funcs, options).unwrap();
// Access detected events
println!("Number of impacts: {}", result.events.get_count("ground_impact"));
// Get details of first impact
if let Some(first_impact) = result.events.get_events("ground_impact").first() {
println!("First impact at t = {}, velocity = {}",
first_impact.time, first_impact.state[1]);
}
Solving an ODE with a time-dependent mass matrix:
use ndarray::{array, Array1, Array2, ArrayView1};
use scirs2_integrate::ode::{solve_ivp, ODEMethod, ODEOptions, MassMatrix};
use std::f64::consts::PI;
// Create a time-dependent mass matrix for a variable-mass pendulum
let time_dependent_mass = |t: f64| {
let mut m = Array2::<f64>::eye(2);
m[[0, 0]] = 1.0 + 0.5 * t.sin(); // Mass oscillates with time
m
};
// Create the mass matrix specification
let mass = MassMatrix::time_dependent(time_dependent_mass);
// ODE function: f(t, y) = [y[1], -g*sin(y[0])]
// The mass matrix format means the ODE is:
// [m(t) 0] [θ'] = [ ω ]
// [ 0 1] [ω'] [-g·sin(θ)]
let g = 9.81;
let f = |_t: f64, y: ArrayView1<f64>| array![y[1], -g * y[0].sin()];
// Initial conditions: angle = 30°, angular velocity = 0
let y0 = array![PI/6.0, 0.0];
// Create options with mass matrix
let options = ODEOptions {
method: ODEMethod::Radau, // Implicit method with direct mass matrix support
rtol: 1e-6,
atol: 1e-8,
mass_matrix: Some(mass),
..Default::default()
};
// Solve the ODE
let result = solve_ivp(f, [0.0, 10.0], y0, Some(options)).unwrap();
// Analyze the solution
let final_angle = result.y.last().unwrap()[0] * 180.0 / PI; // Convert to degrees
println!("Final angle: {:.2}°", final_angle);
println!("Number of steps: {}", result.n_steps);
Using both event detection and mass matrices together:
use ndarray::{array, Array1, Array2, ArrayView1};
use scirs2_integrate::ode::{
solve_ivp_with_events, terminal_event, ODEMethod, ODEOptions, EventSpec,
EventDirection, EventAction, ODEOptionsWithEvents, MassMatrix
};
use std::f64::consts::PI;
// State-dependent mass matrix for a bead on a wire
let state_dependent_mass = |_t: f64, y: ArrayView1<f64>| {
let r = y[0];
let alpha = 0.1; // Wire shape parameter
// Derivative of height function: dh/dr = 2*alpha*r
let dhdr = 2.0 * alpha * r;
// Effective mass includes constraint contribution
let effective_mass = 1.0 * (1.0 + dhdr * dhdr);
// Create mass matrix
let mut mass_matrix = Array2::<f64>::eye(2);
mass_matrix[[0, 0]] = effective_mass;
mass_matrix
};
// Create the mass matrix specification
let mass = MassMatrix::state_dependent(state_dependent_mass);
// ODE function with centrifugal and gravity forces
let omega = 2.0; // Angular velocity of the wire
let g = 9.81; // Gravity
let alpha = 0.1; // Wire shape parameter
let f = |_t: f64, y: ArrayView1<f64>| {
let r = y[0];
let dhdr = 2.0 * alpha * r;
// Forces along the wire
let gravity_component = -g * dhdr / (1.0 + dhdr * dhdr).sqrt();
let centrifugal_force = omega * omega * r;
let net_force = gravity_component + centrifugal_force;
array![y[1], net_force]
};
// Event functions to detect turning points
let event_funcs = vec![
|_t: f64, y: ArrayView1<f64>| y[1], // Velocity = 0
|_t: f64, y: ArrayView1<f64>| 2.0 - y[0], // Terminal event at r = 2.0
];
// Event specifications
let event_specs = vec![
EventSpec {
id: "turning_point".to_string(),
direction: EventDirection::Both,
action: EventAction::Continue,
threshold: 1e-8,
max_count: None,
precise_time: true,
},
terminal_event::<f64>("max_radius", EventDirection::Falling),
];
// Create options with both mass matrix and event detection
let options = ODEOptionsWithEvents::new(
ODEOptions {
method: ODEMethod::Radau, // Needed for state-dependent mass
rtol: 1e-6,
atol: 1e-8,
dense_output: true,
mass_matrix: Some(mass),
..Default::default()
},
event_specs
);
// Initial conditions: r = 0.5, v = 0
let y0 = array![0.5, 0.0];
// Solve the system
let result = solve_ivp_with_events(f, [0.0, 20.0], y0, event_funcs, options).unwrap();
// Analyze the results
println!("Turning points detected: {}", result.events.get_count("turning_point"));
println!("Terminated by max radius event: {}", result.event_termination);
// Get terminal state
if result.event_termination {
let terminal_event = result.events.get_events("max_radius")[0];
println!("Final radius: {:.3}, velocity: {:.3}",
terminal_event.state[0], terminal_event.state[1]);
}
Solving a two-point boundary value problem:
use ndarray::{array, ArrayView1};
use scirs2_integrate::bvp::{solve_bvp, BVPOptions};
use std::f64::consts::PI;
// Solve the harmonic oscillator ODE: y'' + y = 0
// as a first-order system: y0' = y1, y1' = -y0
// with boundary conditions y0(0) = 0, y0(π) = 0
// Exact solution: y0(x) = sin(x), y1(x) = cos(x)
// Define the ODE system
let fun = |_x: f64, y: ArrayView1<f64>| array![y[1], -y[0]];
// Define the boundary conditions
let bc = |ya: ArrayView1<f64>, yb: ArrayView1<f64>| {
// Boundary conditions: y0(0) = 0, y0(π) = 0
array![ya[0], yb[0]]
};
// Initial mesh: 5 points from 0 to π
let x = vec![0.0, PI/4.0, PI/2.0, 3.0*PI/4.0, PI];
// Initial guess: zeros
let y_init = vec![
array![0.0, 0.0],
array![0.0, 0.0],
array![0.0, 0.0],
array![0.0, 0.0],
array![0.0, 0.0],
];
// Set options
let options = BVPOptions {
tol: 1e-6,
max_iter: 50,
..Default::default()
};
// Solve the BVP
let result = solve_bvp(fun, bc, Some(x), y_init, Some(options)).unwrap();
// The solution should be proportional to sin(x)
println!("BVP solution successfully computed with {} iterations", result.n_iter);
println!("Final residual norm: {:.2e}", result.residual_norm);
The boundary value problem (BVP) solver implements a collocation method that discretizes the differential equation on a mesh and uses a residual-based approach to find the solution. It supports:
The ODE solvers provide:
Performance characteristics:
The library supports solving partial differential equations (PDEs):
The module includes several numerical utilities that are useful for solving differential equations:
See the CONTRIBUTING.md file for contribution guidelines.
Result types with detailed error messagesThis library is ready for:
For production deployments, we recommend:
[dependencies]
scirs2-integrate = { version = "0.1.0-beta.2", features = ["parallel", "simd"] }
Enable all optimizations for maximum performance in production environments.
This project is dual-licensed under:
You can choose to use either license. See the LICENSE file for details.
scirs2-integrate v0.1.0-beta.2 - Production-ready numerical integration for Rust