henry2004y / henry2004y/TestParticle.jl

Refactoring the Boris solvers

Open
#535 1 comment 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.