henry2004y / henry2004y/TestParticle.jl
Refactoring the Boris solvers
- Dominant language
- Julia
- Stars
- 22
- Forks
- 5
- Avg merge
- 21h 17m
- Merged PRs (30d)
- 2
Description
Maintaining the Boris solvers like we do currently here is a burden. In the long term, we should create a new specific solver package OrdinaryDiffEqBoris.jl to be fully compatible with the SciML workflow.
Example: https://github.com/SciML/OrdinaryDiffEq.jl/tree/master/lib/OrdinaryDiffEqSymplecticRK
This should be listed in the goal of v1.0 release
---
## Package Structure & Dependencies
The package should have a standard Julia package structure. In `Project.toml`, you must depend on `OrdinaryDiffEqCore` (which houses the base types and interfaces for all OrdinaryDiffEq algorithms). Depending on your algorithm, you may also need `SciMLBase` (for problem types), `RecursiveArrayTools`, and `MuladdMacro` (for performance).
File Structure:
```
OrdinaryDiffEqMySolver/
├── Project.toml
├── src/
│ ├── OrdinaryDiffEqMySolver.jl # Main module, exports, and imports
│ ├── algorithms.jl # Struct definitions for your algorithms
│ ├── alg_utils.jl # Algorithm traits (order, fsal, etc.)
│ ├── myalg_caches.jl # Memory caches (in-place and out-of-place)
│ ├── myalg_perform_step.jl # Core mathematical stepping logic
│ └── myalg_tableaus.jl # (Optional) Algorithm coefficients
└── test/
└── runtests.jl # Convergence and regression tests
```
### Main Module Setup
Your main module needs to import the necessary interface functions from OrdinaryDiffEqCore so you can extend them.
```julia
module OrdinaryDiffEqMySolver
import OrdinaryDiffEqCore: alg_order, initialize!, perform_step!,
OrdinaryDiffEqAlgorithm, OrdinaryDiffEqAdaptiveAlgorithm,
OrdinaryDiffEqMutableCache, OrdinaryDiffEqConstantCache,
alg_cache, isfsal
# Include your source files
include("algorithms.jl")
include("alg_utils.jl")
include("myalg_caches.jl")
include("myalg_perform_step.jl")
# Export your new algorithm(s)
export MyNewAlg
end
```
### Defining the Algorithm (`algorithms.jl`)
Define your algorithm type. It must inherit from the appropriate supertype (e.g., OrdinaryDiffEqAlgorithm or OrdinaryDiffEqAdaptiveAlgorithm).
```julia
struct MyNewAlg{F} <: OrdinaryDiffEqAlgorithm
# You can store algorithm-specific parameters here (like tolerances or tableau choices)
end
# Standard constructor
MyNewAlg() = MyNewAlg{true}() # e.g., true for standard defaults
```
### Algorithm Traits (`alg_utils.jl`)
You must define traits so the SciML ecosystem knows how to handle your algorithm.
```julia
# Order of the method (used for dt calculation and convergence testing)
OrdinaryDiffEqCore.alg_order(alg::MyNewAlg) = 4
# Does it use First Same As Last (FSAL) property?
OrdinaryDiffEqCore.isfsal(alg::MyNewAlg) = false
```
### Caches (`myalg_caches.jl`)
The cache is where you pre-allocate all the memory required for your algorithm. You must define two types of caches:
- Constant Cache (for out-of-place functions, e.g., static arrays or scalars where mutation isn't used).
- Mutable Cache (for in-place functions, where you modify arrays directly to avoid allocations).
```julia
# Out-of-place cache
struct MyNewAlgConstantCache <: OrdinaryDiffEqConstantCache
# Store constants or small non-allocating variables
end
# In-place cache (preallocated arrays)
struct MyNewAlgCache{uType, rateType} <: OrdinaryDiffEqMutableCache
u_temp::uType
k1::rateType
k2::rateType
# ...
end
# Define how to build the cache from the algorithm
function OrdinaryDiffEqCore.alg_cache(alg::MyNewAlg, u, rate_prototype, ::Type{uEltypeNoUnits}, ::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t, dt, reltol, p, calck, ::Val{true}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
# In-place (Val{true}): allocate memory based on `rate_prototype`
MyNewAlgCache(similar(u), similar(rate_prototype), similar(rate_prototype))
end
function OrdinaryDiffEqCore.alg_cache(alg::MyNewAlg, u, rate_prototype, ::Type{uEltypeNoUnits}, ::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t, dt, reltol, p, calck, ::Val{false}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
# Out-of-place (Val{false}): return constant cache
MyNewAlgConstantCache()
end
```
## Stepping Logic (`myalg_perform_step.jl`)
This is the core of your solver. You must implement initialize! and perform_step!.
```julia
function OrdinaryDiffEqCore.initialize!(integrator, cache::MyNewAlgConstantCache)
# Set up any initial variables, like integrator.kshortsize if you are using specific interpolations
integrator.kshortsize = 2
integrator.k = typeof(integrator.k)(undef, integrator.kshortsize)
integrator.fsalfirst = integrator.f(integrator.uprev, integrator.p, integrator.t)
end
function OrdinaryDiffEqCore.perform_step!(integrator, cache::MyNewAlgConstantCache, repeat_step=false)
@unpack t, dt, uprev, u, f, p = integrator
# 1. Evaluate derivatives
k1 = f(uprev, p, t)
k2 = f(uprev + dt * k1, p, t + dt)
# 2. Update state
u = uprev + (dt / 2) * (k1 + k2)
# 3. Store result back into integrator
integrator.u = u
end
```
Note: You would write a similar perform_step! for your mutable cache, utilizing broadcasting (e.g., `@. u = uprev + (dt/2)*(k1 + k2)`) and in-place function calls (`f(k1, uprev, p, t)`).
## Summary Checklist for a Complete Implementation
- Integrator Variables: Familiarize yourself with the integrator object. Key fields include integrator.u (current state), integrator.uprev (previous state), integrator.dt (step size), integrator.t (current time), and integrator.f (the ODE function).
- In-place vs Out-of-place: You must support both forms (Val{true} and Val{false} dispatches in alg_cache).
- Interpolation (Optional but recommended): If you want your solver to support dense output (e.g., continuous saving), you will need to tie it into the interpolation system within OrdinaryDiffEqCore.
- Testing: Add tests in test/runtests.jl using DiffEqDevTools.jl. You can run convergence tests (analyticless_convergence_tests or ode_convergence_tests) to ensure your algorithm achieves the alg_order you claimed.
Contributor guide
Research direction
Start by locating the existing Boris solver implementation, then compare its interfaces with the OrdinaryDiffEqSymplecticRK example and the proposed OrdinaryDiffEqBoris.jl structure. Done means a standalone package with the listed OrdinaryDiffEqCore integration, in-place and out-of-place support, and convergence or regression tests in test/runtests.jl.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- backend, performance, testing
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100