Added the roadmap
This commit is contained in:
244
roadmap/features/05-discrete-callbacks.md
Normal file
244
roadmap/features/05-discrete-callbacks.md
Normal 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
|
||||
Reference in New Issue
Block a user