Added the roadmap

This commit is contained in:
Connor Johnstone
2025-10-23 16:47:48 -04:00
parent 8d4aed4e84
commit e3788bf607
39 changed files with 3888 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
# Feature: Discrete Callbacks
## Overview
Discrete callbacks trigger at discrete events based on conditions that don't require zero-crossing detection. Unlike continuous callbacks which detect sign changes, discrete callbacks check conditions at specific points (e.g., after each step, at specific times, when certain criteria are met).
**Key Characteristics:**
- Condition-based (not zero-crossing)
- Evaluated at discrete points (typically end of each step)
- No interpolation or root-finding needed
- Can trigger multiple times or once
- Complementary to continuous callbacks
## Why This Feature Matters
- **Common use cases**: Time-based events, iteration limits, convergence criteria
- **Simpler than continuous**: No root-finding overhead
- **Essential for many simulations**: Parameter updates, logging, termination conditions
- **Foundation for advanced callbacks**: Basis for SavingCallback, TerminateSteadyState, etc.
## Dependencies
- Existing callback infrastructure (continuous callbacks already implemented)
## Implementation Approach
### Callback Structure
```rust
pub struct DiscreteCallback<'a, const D: usize, P> {
/// Condition function: returns true when callback should fire
pub condition: &'a dyn Fn(f64, SVector<f64, D>, &P) -> bool,
/// Effect function: modifies ODE state
pub effect: &'a dyn Fn(&mut ODE<D, P>),
/// Fire only once, or every time condition is true
pub single_trigger: bool,
/// Has this callback already fired? (for single_trigger)
pub has_fired: bool,
}
```
### Evaluation Points
Discrete callbacks are checked:
1. After each successful step
2. Before continuous callback interpolation
3. Can also check before step (for preset times)
### Interaction with Continuous Callbacks
Priority order:
1. Discrete callbacks (checked first)
2. Continuous callbacks (if any triggered, may interpolate backward)
### Key Differences from Continuous
| Aspect | Continuous | Discrete |
|--------|-----------|----------|
| Detection | Zero-crossing with root-finding | Boolean condition |
| Timing | Exact (via interpolation) | At step boundaries |
| Cost | Higher (root-finding) | Lower (simple check) |
| Use case | Physical events | Logic-based events |
## Implementation Tasks
### Core Structure
- [ ] Define `DiscreteCallback` struct
- [ ] Condition function field
- [ ] Effect function field
- [ ] `single_trigger` flag
- [ ] `has_fired` state (if single_trigger)
- [ ] Constructor
- [ ] Convenience constructors
- [ ] `new()` - full specification
- [ ] `repeating()` - always repeat
- [ ] `single()` - fire once only
### Integration with Problem
- [ ] Update `Problem` to handle both callback types
- [ ] Separate storage: `Vec<ContinuousCallback>` and `Vec<DiscreteCallback>`
- [ ] Or unified `Callback` enum:
```rust
pub enum Callback<'a, const D: usize, P> {
Continuous(ContinuousCallback<'a, D, P>),
Discrete(DiscreteCallback<'a, D, P>),
}
```
- [ ] Update solver loop in `Problem::solve()`
- [ ] After each successful step:
- [ ] Check all discrete callbacks
- [ ] If condition true and (!single_trigger || !has_fired):
- [ ] Apply effect
- [ ] Mark as fired if single_trigger
- [ ] Then check continuous callbacks
### Standard Discrete Callbacks
Pre-built common callbacks:
- [ ] **`stop_at_time(t_stop)`**
- [ ] Condition: `t >= t_stop`
- [ ] Effect: `stop`
- [ ] Single trigger: true
- [ ] **`max_iterations(n)`**
- [ ] Requires iteration counter in Problem
- [ ] Condition: `iteration >= n`
- [ ] Effect: `stop`
- [ ] **`periodic(interval, effect)`**
- [ ] Fires every `interval` time units
- [ ] Requires state to track last fire time
### Testing
- [ ] **Basic discrete callback test**
- [ ] Simple ODE
- [ ] Callback that stops at t=5.0
- [ ] Verify integration stops exactly at step containing t=5.0
- [ ] **Single trigger test**
- [ ] Callback with single_trigger=true
- [ ] Condition that becomes true, false, true again
- [ ] Verify fires only once
- [ ] **Multiple triggers test**
- [ ] Callback with single_trigger=false
- [ ] Condition that oscillates
- [ ] Verify fires each time condition is true
- [ ] **Combined callbacks test**
- [ ] Both discrete and continuous callbacks
- [ ] Verify both types work together
- [ ] Discrete should fire first
- [ ] **State modification test**
- [ ] Callback that modifies ODE parameters
- [ ] Verify effect persists
- [ ] Integration continues correctly
### Benchmarking
- [ ] Compare overhead vs no callbacks
- [ ] Should be minimal (just boolean check)
- [ ] Compare vs continuous callback for same logical event
- [ ] Discrete should be faster
### Documentation
- [ ] Docstring explaining discrete vs continuous
- [ ] When to use each type
- [ ] Examples:
- [ ] Stop at specific time
- [ ] Parameter update every N time units
- [ ] Terminate when condition met
- [ ] Integration with CallbackSet (future)
## Testing Requirements
### Stop at Time Test
```rust
fn test_stop_at_time() {
let params = ();
fn derivative(_t: f64, y: Vector1<f64>, _p: &()) -> Vector1<f64> {
Vector1::new(y[0])
}
let ode = ODE::new(&derivative, 0.0, 10.0, Vector1::new(1.0), ());
let dp45 = DormandPrince45::new();
let controller = PIController::default();
let stop_callback = DiscreteCallback::single(
&|t: f64, _y, _p| t >= 5.0,
&stop,
);
let mut problem = Problem::new(ode, dp45, controller)
.with_discrete_callback(stop_callback);
let solution = problem.solve();
// Should stop at first step after t=5.0
assert!(solution.times.last().unwrap() >= &5.0);
assert!(solution.times.last().unwrap() < &5.5); // Reasonable step size
}
```
### Parameter Modification Test
```rust
// Callback that changes parameter at t=5.0
// Verify slope of solution changes at that point
```
## References
1. **Julia Implementation**:
- `DiffEqCallbacks.jl/src/discrete_callbacks.jl`
- `OrdinaryDiffEq.jl` - check order of callback evaluation
2. **Design Patterns**:
- "Event Handling in DifferentialEquations.jl"
- DifferentialEquations.jl documentation on callback types
3. **Use Cases**:
- Sundials documentation on user-supplied functions
- MATLAB ODE event handling
## Complexity Estimate
**Effort**: Small (4-6 hours)
- Relatively simple addition
- Similar structure to existing continuous callbacks
- Main work is integration and testing
**Risk**: Low
- Straightforward concept
- Minimal changes to solver core
- Easy to test
## Success Criteria
- [ ] DiscreteCallback struct defined and documented
- [ ] Integrated into Problem solve loop
- [ ] Single-trigger functionality works correctly
- [ ] Can combine with continuous callbacks
- [ ] All tests pass
- [ ] Performance overhead < 5%
- [ ] Documentation with examples
## Future Enhancements
- [ ] CallbackSet for managing multiple callbacks
- [ ] Priority/ordering for callback execution
- [ ] PresetTimeCallback (fires at specific predetermined times)
- [ ] Integration with save points (saveat)
- [ ] Callback composition and chaining