semiflow 0.15.0-beta

no_std Rust PDE solver: evolution equations via Chernoff approximation of operator semigroups (heat, diffusion, Schrodinger)
Documentation
<!-- Generated by `cargo xtask readme` from docs/readme/README.src.md. Do not edit by hand: edit the source and regenerate. -->
# semiflow

[![CI](https://github.com/VolkovIlia/semiflow/actions/workflows/ci.yml/badge.svg)](https://github.com/VolkovIlia/semiflow/actions/workflows/ci.yml)
[![Crates.io](https://img.shields.io/crates/v/semiflow)](https://crates.io/crates/semiflow)
[![Docs.rs](https://img.shields.io/docsrs/semiflow)](https://docs.rs/semiflow)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20837851.svg)](https://doi.org/10.5281/zenodo.20837851)

> **Status: beta (0.15.0-beta).** The API is stabilising toward 1.0; minor
> versions may still make breaking changes. Bug reports and feedback are welcome.

**SemiFlow solves evolution equations `∂ₜu = Lu` (heat, diffusion,
Schrödinger, graph and manifold PDEs, …) by Chernoff approximation of operator
semigroups: no matrix exponentials, no linear solves, a flat memory footprint.**
The core is a `no_std + alloc` Rust library with 3 runtime
dependencies (`num-traits`, `libm`, `num-complex`); the same engines are available from Python,
JavaScript/WebAssembly and C.

The method evaluates `e^{tL}f` by iterating an explicit step operator `S(τ)`:
`(S(t/n))ⁿ f → e^{tL} f` as `n → ∞`. Each step is an explicit stencil over a
`Vec`-backed grid with reused scratch buffers, so steady-state evolution does
not allocate.

> Mathematical foundation: **Theorem 6 of I. D. Remizov (2025)**, *Vladikavkaz
> Math. J.* 27(4), 124–135 ([doi:10.46698/a3908-1212-5385-q](https://doi.org/10.46698/a3908-1212-5385-q)).

> **Honest performance note.** The primary measured advantage is **memory
> frugality**: a flat ~3 MB working set across the 1D/2D/3D families versus
> 50–418 MB for heavy frameworks. Wall-clock speed is **not** a general
> strength — for matched-accuracy PDE solving SemiFlow is slower than adaptive
> ODE solvers and spectral methods, and parallelism pays off only on large 3D
> grids. Two measured niches: tail-latency-sensitive pricing
> (`Diffusion4thChernoff` ≈ 41 ns p99.9 per tick) and L1-resident low-rank
> carriers (`TtChernoff`, `ReverseChernoff`). Treat memory and latency as
> measured properties of the concrete grid types, not guarantees of the traits.
> The 2026-10 overhaul ([`docs/perf/overhaul-v0_15.md`](https://github.com/VolkovIlia/semiflow/blob/master/docs/perf/overhaul-v0_15.md))
> made the grid engines 2.6–39× faster bit-for-bit, the stiff Chebyshev graph
> action `O(√(λt))` instead of `O(λt)` (559× on issue #16), ETDRK4 2.1× and the
> general Taylor action 2.1× faster; Richardson extrapolation (`richardson=`)
> raises any engine's order by `levels − 1`. Head-to-head comparisons quoted in
> the user guide predate it.

## Install

```toml
[dependencies]
semiflow = "0.15.0-beta"
```

MSRV: **Rust 1.81**. The default build is `#![no_std]` + `alloc` with
AVX2/NEON kernels (feature `simd`, scalar lanes elsewhere); `std` is needed
only for `parallel`. See [below](#no_std).

## Quickstart

Solve `∂ₜu = ½·∂ₓₓu` from `u₀(x) = e^{-x²}` to `t = 1` and compare with the
closed-form solution `u(1,x) = 3^{-1/2}·e^{-x²/3}`:

```rust
use semiflow::{ChernoffSemigroup, DiffusionChernoff, Grid1D, GridFn1D, State};

let grid = Grid1D::new(-10.0, 10.0, 1000).expect("valid grid");
let u0 = GridFn1D::from_fn(grid, |x| (-x * x).exp());

// L = a(x)·∂²ₓ with a ≡ ½ (a' = a'' = 0), bound ‖a‖ = ½.
let diffusion = DiffusionChernoff::new(|_| 0.5_f64, |_| 0.0, |_| 0.0, 0.5, grid);
let semigroup = ChernoffSemigroup::new(diffusion, 100).expect("n >= 1");
let u1 = semigroup.evolve(1.0, &u0).expect("evolve");

let exact = GridFn1D::from_fn(grid, |x| (-x * x / 3.0).exp() / 3.0_f64.sqrt());
let mut err = u1.clone();
err.axpy(-1.0, &exact);
assert!(err.norm_sup() < 1e-5, "sup-norm error {}", err.norm_sup());
```

More: [Quickstart](https://github.com/VolkovIlia/semiflow/blob/master/docs/QUICKSTART.md), [User Guide](https://github.com/VolkovIlia/semiflow/blob/master/docs/USER_GUIDE.md) and
the runnable [examples](https://github.com/VolkovIlia/semiflow/blob/master/crates/semiflow/examples/README.md)
(`cargo run -p semiflow --example heat_2d_demo`).

<a id="no_std"></a>
## `no_std`

```toml
[dependencies]
semiflow = "0.15.0-beta"   # no_std + alloc, SIMD kernels
```

The crate is `#![no_std]` by default and needs only `alloc`; the `std`
feature is needed only for `parallel`. What that means in practice:

- Your binary provides a `#[global_allocator]` and a `#[panic_handler]`, as
  for any `no_std + alloc` program (a `std` program already has both).
- The target needs pointer-sized atomics (the engines share coefficient
  closures through `alloc::sync::Arc`): Cortex-M3/M4/M7/M33, RISC-V with the
  `a` extension, `x86_64-unknown-none` and similar work; `thumbv6m` does not.
- `SemiflowError` implements `Debug`, `Display` and `core::error::Error`
  (the same trait as `std::error::Error`) in every build.
- `f32`/`f64` transcendentals come from the pure-Rust [`libm`](https://crates.io/crates/libm)
  in every build, and the SIMD kernels use the same lane arithmetic with or
  without intrinsics, so `std`, `no_std`, `simd` and non-`simd` builds give
  bit-identical results on every CPU.
- `simd` works without `std` (the intrinsics come from `core::arch`);
  `parallel` requires `std`.

This is verified on every pull request, not just type-checked:

| CI job | What it proves |
|--------|----------------|
| `no-std` | the default and the featureless build for the host, `thumbv7em-none-eabihf`, `thumbv7m-none-eabi`, `riscv32imac-unknown-none-elf`, and at MSRV 1.81 |
| `no-std-test` | the full `semiflow` test suite passes in the default (`no_std`) configuration |
| `no-std-libm` | [`semiflow-nostd-check`](https://github.com/VolkovIlia/semiflow/blob/master/crates/semiflow-nostd-check) — closed-form oracle checks plus per-scenario output digests: the default `no_std` build (the job fails if anything enables `num-traits/std`), the `std` build and its AVX2 lanes must all produce the committed bits |
| `no-std-libm-neon` | the same digests from the `std` build on aarch64 (NEON lanes) |
| `no-std-qemu` | the same checks and digests as a bare-metal `#![no_main]` binary on Cortex-M3 (soft float) and Cortex-M4F (hard float) under QEMU — see [`nostd-qemu`](https://github.com/VolkovIlia/semiflow/blob/master/nostd-qemu/README.md) |

## Feature flags

| Feature | Default | Effect |
|---------|---------|--------|
| `simd` | yes | AVX2 (x86_64) / NEON (aarch64) kernels via `core::arch`, scalar lanes elsewhere. Works in `no_std`. Results are bit-identical to a non-`simd` build. |
| `std` | no | Links `std`. Needed only by `parallel`; math is `libm` either way, so results do not change. Without it the crate is `no_std + alloc` (the default). |
| `parallel` | no | Multi-threaded `apply` for 1D kernels and 2D/3D compositions via `std::thread::scope`; bit-identical across thread counts. Implies `std`; not for `wasm32`. |
| `linear-interp` | no | Enables `InterpKind::Linear` grid sampling (otherwise it returns `SemiflowError::Unsupported`). |
| `s3-poc` | no | Experimental S³ evolvers with a proven-boundary API (ADR-0169). |
| `slow-tests` | no | Internal: enables the long convergence gates. Not needed by users. |
| `diff-scipy` | no | Internal: SciPy differential test (needs Python). Not needed by users. |
| `tracking-alloc` | no | Internal: allocation counting in the `latency_tail` example. Not needed by users. |

## Engine catalogue

### 1D Chernoff functions

| Type | Order | Description |
|------|-------|-------------|
| `DiffusionChernoff` | 2 | 5-point formula for `a(x)∂²_x` |
| `Diffusion4thChernoff` | 4 | 4th-order spatial diffusion |
| `Diffusion6thChernoff` | 6 | 6th-order spatial diffusion |
| `Diffusion4thZeta4Chernoff` | 4 | ζ⁴ Richardson |
| `Diffusion6thZeta6Chernoff` | 6 | ζ⁶ nested-Richardson |
| `Diffusion8thZeta8Chernoff` | 8 | ζ⁸ Chebyshev M=64 |
| `DiffusionExpmvChernoff<F>` | — | Al-Mohy–Higham expmv action |
| `TruncatedExpDiffusionChernoff` | 2 | Truncated-exponential diffusion |
| `TruncatedExp4thDiffusionChernoff` | 4 | Truncated-exponential 4th-order |
| `DriftReactionChernoff` | exact | Characteristic-flow formula for `b(x)∂_x + c(x)` |
| `DriftReactionZeta4Chernoff` | 4 | ζ⁴ WITH drift via Richardson |
| `ShiftChernoff1D` | 2 | Formula (6) of Theorem 6, Remizov 2025 |

### Compositions

| Type | Description |
|------|-------------|
| `StrangSplit<D, R>` | Strang 2nd-order splitting: `D(τ/2) ∘ R(τ) ∘ D(τ/2)` |
| `AxisLift<C>` | Lift a 1D `ChernoffFunction` to a 2D grid row or column |

### 2D tensor-product

| Type | Description |
|------|-------------|
| `Strang2D<X, Y>` | Palindromic Strang: `Sx(τ/2) ∘ Sy(τ) ∘ Sx(τ/2)`, global order 2 |
| `NonSeparable2DChernoff` | Non-separable isotropic 2D operator |
| `NonSeparable2DAnisotropicChernoff` | Non-separable anisotropic 2D operator |

### 3D tensor-product

| Type | Description |
|------|-------------|
| `AxisLift3D<C>` | Lift a 1D `ChernoffFunction` to a 3D grid along `Axis::X/Y/Z` |
| `Strang3D<X, Y, Z>` | 3D palindromic Strang |

### Adaptive

| Type | Description |
|------|-------------|
| `AdaptivePI<C>` | PI step-size controller wrapping any `ChernoffFunction` |

### Boundary conditions

| Type | Description |
|------|-------------|
| `BoundaryPolicy::Dirichlet { value }` | Fixed-value stencil BC |
| `BoundaryPolicy::Neumann` | Clamp-to-boundary stencil BC |
| `BoundaryPolicy::Robin { alpha, beta }` | Robin stencil BC |
| `BoundaryPolicy::OddReflect` | Odd-image stencil BC (used by `DirichletHeat2ndChernoff`) |
| `KillingChernoff<C, R, F>` | Operator-level Dirichlet via Feynman-Kac killing |
| `Killing2ndChernoff<C, K, F>` | Order-2 soft-killing `e^{-τκ/2}·C(τ)·e^{-τκ/2}` |
| `ReflectedHeatChernoff<C, R, F>` | Neumann via Walsh 1986 image method |
| `DirichletHeat2ndChernoff<C, R, F>` | Order-2 absorbing wall via odd-image method (math §21.9) |
| `ObstacleChernoff<C, O, F>` | Projective-splitting obstacle / variational-inequality evolver |

### Resolvent and nonautonomous

| Type | Description |
|------|-------------|
| `LaplaceChernoffResolvent<C, F>` | `(λI − A)⁻¹ g` via Gauss-Laguerre 32-pt quadrature — unique to Remizov |
| `HowlandLift<C, F>` | Nonautonomous Howland lift on `L²([0,T], X)` |
| `HdrSnapshot<F>` | NIST nearest-rank percentile library (`no_std + alloc`) |

### Riemannian manifold

| Type | Description |
|------|-------------|
| `ManifoldChernoff<M, F>` | Curvature-corrected Gaussian on `T_xM`; order 1 (base) or 2 (with R/12) |
| `Torus<F, D>` | Flat torus backend |
| `Sphere2<F>` | Round 2-sphere backend |
| `Hyperbolic2<F>` | Poincaré disk backend (also the SABR volatility manifold) |
| `FubiniStudyCp1` | Kähler CP¹ / Fubini-Study backend |

### High-dimensional and sparse-grid

| Type | Description |
|------|-------------|
| `AnisotropicShiftChernoffND<F, D>` | d-D Gaussian shift via Gauss-Hermite tensor quadrature |
| `AnisotropicShiftZeta2ND` | Order-2 ζ²-correction variant |
| `AnisotropicShiftAdaptiveQ` | Adaptive per-point GH quadrature (41% fewer nodes/axis) |
| `SmolyakGridND<F, const D>` | Smolyak sparse-grid backend for D ≥ 5 |

### Matrix-valued operators

| Type | Description |
|------|-------------|
| `MatrixDiffusionChernoff<F, M>` | Coupled M-component 1D diffusion; Padé[13/13] expm |
| `MatrixDiffusionChernoff2D<F, M>` | Palindromic Strang 2D for M-component diffusion |
| `MatrixDiffusionChernoff3D<F, M>` | Same for 3D |
| `MatrixDiffusionChernoffComplex<F, M>` | Complex-valued Padé[13/13] coupled diffusion |

### Quantum graphs

| Type | Description |
|------|-------------|
| `QuantumGraphHeatChernoff<F>` | Heat on quantum graphs with Kirchhoff vertex condition |
| `QuantumSchrödingerChernoff<C>` | Complex Schrödinger on quantum graphs |

### Reverse-mode AD

| Type | Description |
|------|-------------|
| `ReverseChernoff<F>` | Reverse-mode AD over `(F_θ(τ))ⁿ u₀` via binomial checkpointing |
| `CheckpointSchedule` | `O(√n)` default schedule (Griewank-Walther) |
| `RegionMap` | DoF-aligned region partition enabling K>1 per-region parameter sensitivity |

**Scope:** constant-a `DiffusionChernoff<F>` only. Multi-parameter (K>1)
sensitivity via `ReverseChernoff::with_region_map`. Variable-coefficient
kernels are deferred.

### Tensor-train carrier

| Type | Description |
|------|-------------|
| `TtChernoff<F>` | TT-Chernoff evolver; storage `O(d·n·r²)` |
| `TtState<F>` | Tensor-train state `u(i₁,…,i_d) = G₁[i₁]·…·G_d[i_d]` |
| `VarCoefTt<F>` | Variable-coefficient TT carrier for separable diagonal `a_j(x_j)` |

**Scope:** linear diagonal-A Gaussian class (`TtChernoff`); separable diagonal
variable coefficients (`VarCoefTt`). Non-separable variable coefficients and
off-diagonal A are research-track. `VarCoefTt::new` returns
`SemiflowError::VarCoefOutOfClass` when inputs fall outside the separable
diagonal class.

### Gridless / particle-ensemble

| Type | Description |
|------|-------------|
| `GridlessChernoff<F, const D>` | Particle-ensemble Chernoff; implements `ChernoffFunction<F>` |
| `MeasureState<F, D>` | Particle-ensemble state; exposes `first_moment`, `variance`, `variance_per_axis` diagnostics (math §38.12) |
| `ParticleReduction` | Particle cap policy: `WeightedVoronoi { cap }` or `GaussianBackground` |

### Schrödinger, graph and quantum-graph operators

| Type | Description |
|------|-------------|
| `SchrödingerChernoffComplex` / `SchrodingerChernoff` | Schrödinger equation on a native complex (or real-pair) carrier |
| `GraphHeatChernoff` | Heat semigroup of a graph Laplacian |
| `GraphKrylovChernoff`, `graph_expmv_frechet` | Depth-independent `e^{−tL}·v` (Chebyshev / Lanczos) and its all-parameter Fréchet gradient (graded-mesh Duhamel quadrature: quadrature error independent of `λ_max·t`; f64 conditioning floor ≈ (r+n)·u·ρ̄t; `ImplicitEuler` path keeps its O(Δt) bias) |
| `QuantumGraphHeatChernoff` / `QuantumSchrödingerChernoff` | Metric graphs with Kirchhoff vertex conditions |

### Conservative, stiff and semilinear problems

| Type | Description |
|------|-------------|
| `ConservativeDiffusionChernoff`, `assemble_conservative_csr_1d`, `assemble_conservative_csr_nd` | Divergence-form `∂ₓ(k(x)∂ₓu)` with harmonic-mean faces (sharp material interfaces, optional contact resistance) |
| `MultilayerStack`, `multilayer_evolve`, `MassWeightedConservativeChernoff` | Stiff multilayer conduction in one depth-flat Krylov action |
| `SymmetricOperator`, `MassKOperator`, `EntrySensitivity`, `mass_lumped_evolve` | Externally assembled symmetric PSD sparse operators (FEM), the `(M, K)` problem without forming `M⁻¹K`, entry-wise gradients |
| `general_operator::GeneralOperator` | Possibly non-symmetric CSR operators via Taylor `expmv` or adaptive Arnoldi (`expmv_krylov`) |
| `SpdResolvent`, `SpdSolver` | `(λM + A)⁻¹b`: exact O(N) tridiagonal `LDLᵀ`, PCG with IC(0) or Jacobi; factor once, solve many (steady states, implicit steps) |
| `RationalKrylov`, `RationalKrylovChernoff`, `rational_krylov_expmv` | Shift-and-invert Krylov `e^{−tM⁻¹A}v` for very stiff SPD operators: solve count independent of `λ_max t` |
| `phi_action`, `phi_action_batched`, `phi_combination`, `Etdrk4`, `Nonlinearity` | Semilinear `∂ₜu = Lu + N(u)` by exponential time differencing (order 4) |
| `phi_combination_krylov`, `PhiSolver`, `KrylovPhiOptions` | Adaptive Arnoldi φ-actions for non-symmetric generators (`Etdrk4::with_phi_solver`): 33× fewer generator applications than Taylor at `τ‖G‖ = 4·10³` |
| `ReactionDiffusion`, `Species`, `ReactionSolver`, `RosenbrockOptions` | Multi-species `∂ₜu = D Δu + f(t, x, u)` by Strang splitting on any grid engine; stiff kinetics by adaptive L-stable Rosenbrock (ROS2) |
| `CrossDiffusion` | Constant `K×K` cross-diffusion matrix `D` by exact modal splitting (`D = PΛP⁻¹` verified; complex, negative or defective `D` rejected), with `ReactionDiffusion::with_cross_diffusion` |
| `SubordinatedChernoff` | Bochner–Phillips subordination of any engine |
| `HypoellipticChernoff` | Kolmogorov, Heisenberg and Engel (step-2/3 Carnot) operators |

### Executor

`ChernoffSemigroup<C, S>` — wraps a `ChernoffFunction` and a step count;
call `.evolve(t, &f)` to compute `(S(t/n))ⁿ f`.

## Examples

| Example | Description |
|---------|-------------|
| `heat_2d_demo.rs` | 2D tensor heat equation (Gaussian oracle, convergence table) |
| `strang_advdiff_demo.rs` | Strang splitting for 1D advection-diffusion |
| `cev_european_call.rs` | CEV European option pricing vs Schroder (1989) closed form |
| `boundary_demo.rs` | Boundary policy showcase (Dirichlet, Neumann, periodic) |
| `latency_tail.rs` | HFT-style p99.9 latency benchmark |
| `resolvent_perf.rs` | `LaplaceChernoffResolvent` L-gate bench harness |
| `heston_pricer.rs` | Heston ρ→0 pricer via palindromic Strang |
| `sabr_pricer.rs` | SABR-on-H² via `ManifoldChernoff<Hyperbolic2>` |
| `graph_par_speedup.rs` | Parallel graph-heat speedup (`--features parallel`) |
| `rough_heston_pricer.rs` | Oracle-validated risk-neutral rough-Heston pricer (`--rate`/`--price`); honest scope: solver of a 4-factor Markov approximation, not validated vs true rough-Heston |

```bash
cargo run -p semiflow --example heat_2d_demo
```
## Bindings

| Language | Package | Notes |
|----------|---------|-------|
| Rust | [`semiflow`](https://crates.io/crates/semiflow) ([docs.rs](https://docs.rs/semiflow)) | The full engine catalogue; `no_std + alloc` |
| Python | [`semiflow-pde`](https://pypi.org/project/semiflow-pde/) | 90 classes and the functions `version()`, `heisenberg_heat_kernel()`, `sample_gridfn2d()`, `edge_weight_grad()`, `edge_weight_grad_batched()`, `graph_expmv_frechet()`, `symmetric_op_expmv_frechet()`, `assemble_conservative_csr_1d()`, `mass_lumped_evolve()`, `phi_action()`, `phi_action_batched()`, `phi_combination()`, `shift1d_coeff_grad()`, `richardson_weights()`; NumPy in/out; complete `.pyi` stubs; see the [PyPI page](https://pypi.org/project/semiflow-pde/) |
| JavaScript / WASM | [`@semiflow/wasm`](https://www.npmjs.com/package/@semiflow/wasm) | Lite build on npm; the heavy-grid engines need a `--features full` build; see the [npm page](https://www.npmjs.com/package/@semiflow/wasm) |
| C / C++ | `semiflow-ffi` | `extern "C"` ABI with `catch_unwind` on every entry point; header `semiflow.h` |

The bindings mirror the user-facing engines, not the internal composition types
(`AxisLift`, `StrangSplit`, … are Rust-side building blocks). Variable
coefficients cross the language boundary as pre-sampled arrays (evaluated in
Rust, no call back into the host language during `evolve`); some classes also
accept a host-language callback (e.g. `Heat1D.with_a_function` in Python,
`Heat1D.withAFunction` in JS, `smf_state_new_with_closure` in C).
The [Bindings guide](https://github.com/VolkovIlia/semiflow/blob/master/docs/BINDINGS.md) has side-by-side examples.

## Documentation

| If you want to… | Read |
|-----------------|------|
| Get started | [Quickstart](https://github.com/VolkovIlia/semiflow/blob/master/docs/QUICKSTART.md) · [User Guide](https://github.com/VolkovIlia/semiflow/blob/master/docs/USER_GUIDE.md) · [Install](https://github.com/VolkovIlia/semiflow/blob/master/docs/INSTALL.md) |
| Use a binding | [Bindings guide](https://github.com/VolkovIlia/semiflow/blob/master/docs/BINDINGS.md) (C / Python / WASM) |
| Browse the Rust API | [docs.rs/semiflow](https://docs.rs/semiflow) |
| See worked examples | [`crates/semiflow/examples`](https://github.com/VolkovIlia/semiflow/blob/master/crates/semiflow/examples/README.md) |
| Understand accuracy and stability policy | [Precision policy](https://github.com/VolkovIlia/semiflow/blob/master/docs/precision-policy.md) · [API stability](https://github.com/VolkovIlia/semiflow/blob/master/docs/api-stability.md) |
| Read design decisions | [Architecture Decision Records](https://github.com/VolkovIlia/semiflow/blob/master/docs/adr/) |
| Follow changes | [CHANGELOG](https://github.com/VolkovIlia/semiflow/blob/master/CHANGELOG.md) · [GitHub Releases](https://github.com/VolkovIlia/semiflow/releases) |

## Accuracy and design

Every numerical claim is gated in CI against a closed-form or high-order
reference oracle (convergence-order and sup-norm tests). Design principles:
`no_std + alloc` core; 3 runtime dependencies; SIMD hot
paths isolated to `src/simd/` (AVX2 on x86_64, NEON on aarch64, scalar
fallback elsewhere); bit-reproducible parallelism across thread counts; no
`unsafe` in the math kernels. Builds with and without `simd` agree to rounding
but not always bit for bit, so golden files are tied to one configuration.

## How to cite

If you use SemiFlow in academic work, cite both the software and the theorem.

```bibtex
@software{volkov2026semiflow,
  author  = {Volkov, Ilia},
  title   = {{SemiFlow}: {Chernoff} Approximation of Operator Semigroups},
  year    = {2026},
  version = {0.15.0-beta},
  doi     = {10.5281/zenodo.20837851},
  url     = {https://doi.org/10.5281/zenodo.20837851}
}

@article{Remizov2025,
  author  = {I. D. Remizov},
  title   = {Chernoff Approximations of the Solution of Linear ODE with Variable Coefficients},
  journal = {Vladikavkaz Math. J.},
  volume  = {27},
  number  = {4},
  pages   = {124--135},
  year    = {2025},
  doi     = {10.46698/a3908-1212-5385-q}
}
```

The concept DOI [10.5281/zenodo.20837851](https://doi.org/10.5281/zenodo.20837851)
always resolves to the latest version; [`CITATION.cff`](https://github.com/VolkovIlia/semiflow/blob/master/CITATION.cff) has the
full entry (GitHub's "Cite this repository" reads it).

## License

Dual-licensed under [MIT](https://github.com/VolkovIlia/semiflow/blob/master/LICENSE-MIT) or [Apache-2.0](https://github.com/VolkovIlia/semiflow/blob/master/LICENSE-APACHE), at your
option.

## Author, contributing, security

SemiFlow is created and maintained by **Ilia Volkov**. See
[CONTRIBUTING.md](https://github.com/VolkovIlia/semiflow/blob/master/CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](https://github.com/VolkovIlia/semiflow/blob/master/CODE_OF_CONDUCT.md).
Report vulnerabilities privately as described in [SECURITY.md](https://github.com/VolkovIlia/semiflow/blob/master/SECURITY.md) —
not in public issues.