Changelog
Source: CHANGELOG.md
## Changelog
---
## [0.37.12] – 2026-03-02
### Added
- Shebang support to toml model files. First line of toml model files can optionally have the info about intended
package and its version. For example: `#! dynlib` or `#! dynlib:0.37`.
---
## [0.37.11] – 2026-02-13
### Fixed
- Made `python>=3.11` the minimum python requirement because tomllib is essential and it is not available in prior
versions.
- `bifurcation_diagram` plot tool was displaying extraction mode in the title by default. Now no title will be
displayed if the title is not provided.
---
## [0.37.10] – 2026-01-30
### Fixed
- Stepper sources were not being attached to sources with `export_model_sources()` when `disk_cache=False` is chosen.
This was causing some tests of `test_export_sources.py` to fail. Fixed this process in `build.py` so that stepper
sources are always attached regardless of `disk_cache`.
- Fixed failing test `test_runner_diskcache.py` by removing a hard-coded python path.
---
## [0.37.9] – 2026-01-30
### Fixed
- Mkdocs mkdocs-gen-files plugin for generating doc files was not playing well with multi-language support.
Removed this plugin. For displaying built-in model references, changelog, todo, issues in docs, the script
`tools/gen_model_docs.py` should be run manually. `.github/workflows/docs.yml` will run this script with every
commit, so Github pages will always display the up-to-date version.
- Fixed broken `mkdocs.yml` path prefixes due to the deployment.
### Changed
- Updated documentation links.
- Updated `tools/gen_model_docs.py` and `mkdocs_helpers.py` for manual doc generation.
---
## [0.37.8] – 2026-01-30
### Fixed
- Performed package install in docs.yml for Github pages deployment.
---
## [0.37.7] – 2026-01-30
### Fixed
- Fixed missing dependency for Github pages deployment.
---
## [0.37.6] – 2026-01-30
### Changed
- Introduced docs.yml for Github pages deployment.
---
## [0.37.5] – 2026-01-30
### Changed
- Updated github links in documentation.
---
## [0.37.4] – 2026-01-30
### Changed
- pyproject.toml was still incompatible with PYPI. Removed the incorrect line.
---
## [0.37.3] – 2026-01-30
### Changed
- Fixed license statement in pyproject.toml.
---
## [0.37.2] – 2026-01-30
### Added
- BSD 3-Clause License.
- Turkish README file.
### Changed
- Minor documentation changes.
---
## [0.37.1] – 2026-01-30
### Added
- Added Turkish documentation and improved existing English documentation.
---
## [0.37.0] – 2026-01-29
### Added
- `mkdocs` compatible documentation in docs folder with multiple language support. Only English docs are available
in this version.
- Added auto-generation scripts `gen_model_docs.py` and `mkdocs_helpers.py` for mkdocs. `tools/gen_model_docs.py`
is a single wrapper; so it should be run to generate docs locally.
### Changed
- Made `jit=False` and `disk_cache=False` defaults in `setup()` and `build()`.
- Removed unnecessary [meta] tables from built-in models.
---
## [0.36.13] – 2026-01-27
### Changed
- Renamed `label` key in [model] DSL table with `name` key. Replaced all occurrences throughout the package.
- Removed temporary development docs.
---
## [0.36.12] – 2026-01-27
### Added
- Added `model.print_equations()` for printing DSL equations (no generated code) and a demo example:
`print_equations_demo.py`. It accepts `tables` argument so that users can print various equations like
`equations.inverse` and `equations.jacobian`. This method is extendible via `register_equation_table()`
method inside `build.py` file. This method is only available for quickly checking equations. To view other
tables like aux values and events use `export_sources()` method.
---
## [0.36.11] – 2026-01-27
### Added
- Added `homoclinic_finder` analysis tool for searching and finding a homoclinic orbit for a given equilibrium.
A single parameter of an ODE system is changed between an interval during the search.
- Added `homoclinic_tracer` analysis tool for finding a homoclinic orbit of an equilibrium for fixed parameter values.
Its result can be plotted using `plot.manifold` plotter utility.
- Added example `homoclinic_finder_tracer.py` for finding a parameter value of an ODE model that might have a
homoclinic orbit and the tracer is used to visualize the homoclinic orbit for the found parameter.
---
## [0.36.10] – 2026-01-26
### Added
- Added `heteroclinic_finder` analysis tool for searching and finding a heteroclinic orbit between two equilibria.
A single parameter of an ODE system is changed between an interval during the search.
- Added `heteroclinic_tracer` analysis tool for finding a heteroclinic orbit between two equilibria for fixed
parameter values. Its result can be plotted using `plot.manifold` plotter utility.
- Added example `heteroclinic_finder_tracer.py` for finding a parameter value of an ODE model that might have a
heteroclinic orbit between its two equilibria. The tracer is used to visualize the heteroclinic orbit for the found
parameter.
---
## [0.36.9] – 2026-01-25
### Added
- Added analysis tool `trace_manifold_1d_ode()` for tracing stable/unstable 1D manifolds of ODE models. The results
can be plotted using the same `plot.manifold()` utility introduced previously.
- Added an example `manifold_ode_saddle.py` for demonstrating ODE manifold tracing.
---
## [0.36.8] – 2026-01-25
### Added
- Added analysis tool `trace_manifold_1d_map()` for tracing stable/unstable 1D manifolds of nD maps.
- Added `plot.manifold()` plot utility for plotting manifold analysis results.
- Added an example `manifold_henon.py` for demonstrating manifold tracing.
- Added another built-in Henon map model in `henon2.toml` which is the version used in Kathleen Alligood's book.
---
## [0.36.7] – 2026-01-18
### Added
- Introduced a fixed points / equilibria calculator in `analysis/fixed_points.py` file. It uses Newton solver with
model type awareness (ODE vs map). Seeds are required as initial guesses. `fixed_points()` method is attached to
the `FullModel` class for user convenience (fixed points of a model can be calculated easily for that model with
its current parameters).
### Tests
- Introduced `test_fixed_points.py` for testing the fixed point calculation feature.
---
## [0.36.6] – 2026-01-17
### Fixed
- `basin_auto` plot results were rotated. `basin_auto.py` now uses `np.meshgrid(..., indexing="ij")` with explicit
order="C" raveling for consistent grid flattening.
---
## [0.36.5] – 2026-01-17
### Changed
- Moved all of the runtime analysis utilities from `./analysis/runtime` folder to `./runtime/observers` folder.
- Renamed all runtime analyses as `observers`. `Sim.run()` now accepts `observers` argument instead of `analysis`.
Added _observer suffix to the names of these utilities to prevent import name clashes. These changes should provide
a clear separation between runtime and offline standalone analysis utilities.
- Added _sweep suffix to the names of sweep utilities to prevent import name clashes.
- Updated all analysis related examples and tests.
---
## [0.36.4] – 2026-01-12
### Added
- Inverse RHS equations support for map DSL definitions. [equations.inverse] (with `expr` keyword) or
[equations.inverse.rhs] can be used to define inverse map equations. Numba compatibility and caching is provided.
### Tests
- Added `test_inverse_equations.py` to test new inverse map behavior and updated old DSL related tests.
---
## [0.36.3] – 2026-01-08
### Added
- Added basin of attraction calculation analysis tool `basin_known`. It can calculate basin of attraction of known
attractors numerically.
- Added various basin of attraction calculation examples.
- Added `basin_plot` utility for plottin basin of attraction results.
- Added various utilities for investigating basin analysis results in `basin_stats.py`.
### Changed
- For sweep and basin analyses introduced chunk-based process parallelism with efficient worker initialization:
a chunk of the analysis is assigned to a worker; each worker process initializes its `Sim` object once and reuses
it across all chunks assigned to that worker, avoiding redundant JIT compilation and allocation overheads.
- Renamed `analysis/basin.py` -> `analysis/basin_auto.py`.
### Tests
- Fixed tests that contain `exprs` keyword.
---
## [0.36.2] – 2026-01-06
### Added
- Added basin of attraction calculation analysis tool `basin_auto` in `analysis/basin.py`. It can search for
attractors and determine their basins using recurrence-based basin estimation method (Datseris & Wagemakers 2022),
with an additional persistence-based early assignment and coarsened-grid fingerprint merging. Internally it is
called PCR-BM (Persistent Cell-Recurrence Basin Mapping).
- Added Henon map basin of attraction calculation demo example: `basin_henon_demo.py`.
- Added ODE-based limit cycle basin of attraction calculation example: `basin_limit_cycle.py`.
- Added basin of attraction plotting utility `plot_basin` in `plot/basin.py`.
- Added ETO (Energy Template Oscillator) with Circular L Curve built-in model `eto-circular.toml`.
- Added Duffing oscillator built-in model: `duffing.toml`.
### Changed
- Replaced underscore (_) from the built-in model names with hypen (-). For example: `exp_if.toml` -> `exp-if.toml`.
- Analyses can now trigger early exit from hooks by setting `runtime_ws.stop_flag`, even when the DSL stop table is
absent, as long as the analysis declares a `stop_phase_mask` (e.g., post‑step).
### Fixed
- Some DSL tables were using `expr` keyword for equation definitions and some were using `exprs`. Unified all
equation expression keywords as `expr`. Typos were not causing any errors but steppers would not advance. Now
any typo raises an error with a hint.
### Tests
- Added tests for analysis triggered early exit in `test_stop_early_exit.py`.
---
## [0.36.1] – 2026-01-04
### Added
- Added early exit feature: simulations can now stop early when a specified condition is met using the `stop`
field in the [sim] section.
- Added support for stop conditions with built-in DSL macros like `cross_up()`, `in_interval()`, `increasing()`,
`decreasing()`, and others.
- Added `exited_early` property to results to check if simulation stopped due to a stop condition.
- Added example `early_exit_demo.py`.
- Extended runtime workspace with `stop_flag` and `stop_phase_mask` arrays for stop condition evaluation. Since
only phase=`post` is supported `stop_phase_mask` is not necessary but it is there for future phase=`pre` and
phase=`post` extension.
### Changed
- Updated all runners (base, analysis, fastpath variants for both continuous and discrete systems) to evaluate
stop conditions after each step.
- Modified `update_aux()` function to also handle stop flag updates when stop conditions are enabled.
- Updated results API to consider `EARLY_EXIT` as a successful completion status alongside `DONE`.
- Enhanced model specification to include `StopSpec` for parsing and validating stop conditions.
- Updated AST checker to validate stop condition expressions and collect lag requests for stop conditions.
- Modified mods system to support setting `sim.stop` via TOML modifications.
- Updated emitter to compile stop condition expressions into the `update_aux` function.
- Attached exiting `EARLY_EXIT` status code (7) to the new early termination mechanism.
### Tests
- Added test file `test_stop_early_exit.py`.
---
## [0.36.0] – 2026-01-02
### Added
- Added `RunnerVariant` enum to consolidate runner architecture: `BASE`, `ANALYSIS`, `FASTPATH`, and
`FASTPATH_ANALYSIS`.
- Added FASTPATH runner templates for both continuous (ODE) and discrete (map) systems. Fastpath runners skip
event processing and buffer growth checks for fixed-step execution.
- Added `get_runner()` unified API in `runner_variants.py` to replace separate `get_runner_variant()` and
`get_runner_variant_discrete()` paths. Now supports explicit variant selection with hooks baked in as globals.
- Added `EARLY_EXIT` status code (7) in `runner_api.py` for future use with basin of attraction analysis.
- Renamed `runtime/fastpath/runner.py` to `runtime/fastpath/executor.py` for clarity.
- Added `runner_cache.py` disk cache helper module for runner variants with variant-aware and template-version
aware cache keys.
### Changed
- Consolidated runner templates into single source of truth in `runner_variants.py`, reducing code duplication.
- Analysis hooks are now always injected as global symbols (`ANALYSIS_PRE`, `ANALYSIS_POST`) rather than function
arguments. This enables simpler, more JIT-friendly code generation.
- Unified wrapper and fastpath execution to use `get_runner()` with explicit `RunnerVariant` selection based on
analysis presence and execution mode (continuous vs discrete).
- Updated `run_with_wrapper()` to always require `model_hash` and `stepper_name` for runner selection.
- Removed legacy dual-ABI runner path (analysis_kind + dispatch arguments). All variants now use hook globals.
- Disk cache keys now include variant type and template version for invalidation support.
- Fastpath now uses `RunnerVariant.FASTPATH_ANALYSIS` instead of legacy `ANALYSIS` path, enabling
future capability specialization.
### Removed
- Removed legacy `compiler/codegen/runner.py` (ODE runner template).
- Removed legacy `compiler/codegen/runner_discrete.py` (discrete runner template).
- Removed `analysis_dispatch_pre` and `analysis_dispatch_post` from runner ABI. Hooks are now baked in.
### Tests
- Updated `test_analysis_runtime.py` to use new unified runner API and explicit `model_hash`/`stepper_name`.
- Updated `test_fastpath_runner.py` to import from `executor` module.
- Updated `test_wrapper_reentry.py` to provide required `model_hash` and `stepper_name` arguments.
---
## [0.35.9] – 2025-12-29
### Fixed
- The `ab2` and `ab3` state history usage was wrong. Shifted tangent‑only Lyapunov propagation into `pre_step`
so `ab2`/`ab3` see (y_n, v_n) when forming g_n = J(y_n)v_n. `post_step` now only normalizes/accumulates (flow)
or computes Jv for map mode.
---
## [0.35.8] – 2025-12-29
### Changed
- Changed version numbers to v0 and updated all git tags accordingly. This will be an indicator that the package
is still alpha not a stable release.
---
## [0.35.7] – 2025-12-29
### Added
- Variational stepping support to the steppers: `rk2`, `ab2`, and `ab3`.
### Changed
- Multi-step variational stepping is now supported for Lyapunov analyses. However, combined calculations (advance
the state and the tangents together in a single stepper call) is not possible for them. All calculations are
tangent-only (advance only the tangents using a variational step routine while the state is advanced by the
normal runner path). Also Lyapunov Spectrum calculations always prefer tangent-only calculations.
---
## [0.35.6] – 2025-12-28
### Added
- `sweep.lyapunov_spectrum()` utility for plotting Lyapunov spectrum change of a system for a range of parameters.
Just like `sweep.lyapunov_mle()` it supports fast-path runner and parallelization.
- Added `sweep.lyapunov_spectrum()` example `lyapunov_sweep_spectrum_demo.py`.
### Changed
- Removed `SweepAnalysis.peaks()` method and introduced `SweepAnalysis.extrema()`. `peaks()` was only finding
maxima. New extrema() method can find maxima, minima and both (default) via its `kind` argument.
- Renamed `lyapunov_sweep_demo.py` -> `lyapunov_sweep_mle_demo.py`.
### Fixed
- Fast-path runner was treating transient duration as part of the `T` value. This issue was fixed for `N` before
but forgotten for `T` values. Now `T` values also ignore transient duration and recording starts at `t0` after
the `transient`.
### Tests
- Fixed `test_fastpath_runner.py`. It was assuming wrong fast-path transient behavior.
---
## [0.35.5] – 2025-12-27
### Added
- Added `JITUnavailableError` for clear failures when `jit=True` but numba is missing.
- Added variational workspace helpers for Euler and RK4 steppers so Lyapunov analyses can reuse stepper buffers.
- Added labeled horizontal-line support to plots.
### Changed
- Lyapunov MLE/spectrum now use stepper-provided variational workspaces and can prefer combined state+tangent steps.
Previously state+tangent steps were hardcoded in `lyapunov.py`.
- JIT-related code paths now raise instead of warning when numba is unavailable (guards, runners, steppers).
- Plot line label placement is more configurable (position, pad, rotation, color) and theme defaults are updated.
- Examples are updated to use the new vline/hline/vband helpers.
### Tests
- Renamed variational stepping tests to `tests/unit/test_variational_stepping.py`.
---
## [0.35.4] – 2025-12-27
### Added
- Added `builtin://ode/lorenz` model definition.
- Added support for horizontal lines (`hlines`) and horizontal bands (`hbands`) in plot utilities.
- Added variational stepping support to `Euler` stepper. Both Euler and RK4 now advertise
`variational_stepping=True` capability.
- Added `lyapunov_lorenz_demo.py` demonstrating Max Lyapunov and Lyapunov spectrum analyses of the Lorenz system.
### Changed
- Renamed `bands` argument to `vbands` in plot functions to distinguish from the new `hbands`.
- Lyapunov analyses (`lyapunov_mle`, `lyapunov_spectrum`) now strictly require the stepper to support variational
stepping. If the stepper does not support it, a `ValueError` is raised (no longer falls back to Euler).
- `CombinedAnalysis` now enforces stricter rules:
- Rejects combination if more than one analysis requires runner-level variational stepping.
- Rejects combination if any analysis mutates state.
- `AnalysisResult` now provides `trace_steps`, `trace_time`, and `record_interval` attributes.
- Transient warm-up phase now explicitly disables analysis hooks.
### Tests
- Updated `test_rk4_variational.py` to reflect strict stepper requirements and new `CombinedAnalysis` rules.
Added `test_transient_warmup_skips_analysis_hooks`.
---
## [0.35.3] – 2025-12-24
### Added
- Variational stepping support (simultaneous numerical integration of both the original dynamical system and
its variational / tangent equations using the same numerical method) for RK4 stepper via
`emit_step_with_variational()` and `emit_tangent_step()` methods.
### Changed
- Lyapunov exponent analysis (`lyapunov_mle` and `lyapunov_spectrum`) now automatically uses variational
stepping when the chosen stepper supports it (currently RK4), otherwise falls back to Euler integration.
- When computing Lyapunov exponents, the analysis tracks how small perturbations grow over time by integrating
"tangent vectors" (directional derivatives). For accurate results, both the main system and tangent vectors
should use the same numerical method. This update enables RK4 to integrate tangent vectors with full
4th-order accuracy instead of 1st-order Euler, significantly improving reliability for continuous systems.
### Fixed
- Lyapunov exponent calculations now use the same numerical method (e.g., RK4) for both the system state and
the tangent vectors, ensuring mathematical consistency and improved accuracy. Previously, tangent vectors
were always integrated using Euler method regardless of the chosen stepper.
### Tests
- Added comprehensive test suite (`test_rk4_variational.py`) covering RK4 variational mode selection, runtime
verification, Euler fallback behavior for steppers without variational support, and numerical accuracy
validation with known linear systems.
### Known Issues
- Only RK4 stepper is supported for variational stepping. Other fixed step steppers should be modified similarly.
---
## [0.35.2] – 2025-12-24
### Fixed
- Combined runtime analyses now share trace capacity correctly. Each module writes into its own slice with a
shared step-level cursor, preventing double-increment of `trace_count` and eliminating spurious
`TRACE_OVERFLOW` when multiple analyses with different trace widths run together. Both Python and generated
JIT hooks use the synchronized counter and import `numpy` explicitly in the generated namespace.
---
## [0.35.1] – 2025-12-24
### Changed
- Refactored analysis hook dispatch to eliminate `NumbaExperimentalFeatureWarning`. Analysis hooks (`pre_step`,
`post_step`) are now injected as global symbols (`ANALYSIS_PRE`, `ANALYSIS_POST`) into generated runner source
code via `exec()` with a custom namespace, making call targets statically resolvable by Numba instead of using
first-class function arguments.
- Runner variants are cached (64 entries max) keyed by (model_hash, stepper_name, analysis_signature_hash,
runner_type, jit), ensuring the same runner/analysis combination compiles only once per parameter sweep. Cache
mechanism uses LRU (Least Recently Used) eviction.
- Replaced `literal_unroll` over callable containers with explicit sequential call codegen for combined hooks,
avoiding callable containers entirely.
- When no analysis is active, base runner templates are used without any `analysis_kind` branching overhead.
### Added
- `signature(dtype) -> tuple` method to `AnalysisModule` protocol for stable cache key generation.
- New module `dynlib.compiler.codegen.runner_variants` with `get_runner_variant()`,
`get_runner_variant_discrete()`, `analysis_signature_hash()`, and `clear_variant_cache()` API.
### Fixed
- Resolved `NumbaExperimentalFeatureWarning` caused by first-class function types in analysis hook dispatch.
---
## [0.35.0] – 2025-12-24
### Added
- `lyapunov_spectrum()` analysis utility for performing Benettin QR / Shimada–Nagashima (reorthonormalization)
style Lyapunov spectrum analysis.
- Introduced `mode` option to `lyapunov_mle` and `lyapunov_spectrum` analyses which can be `auto`, `flow`, or `map`.
`auto` (default) mode can detect type of Lyapunov exponent calculation from the DSL model type.
### Known Issues
- Analyses hooks cause `NumbaExperimentalFeatureWarning` because they are passed as first-class functions to the
jitted runner.
- Analyses occasionally cause `TRACE_OVERFLOW` which should not be possible.
- `flow` mod analyses use Euler integration regardless of the `Sim` stepper.
---
## [0.34.10] – 2025-12-24
### Changed
- Removed the legacy `dynlib.runtime.model.Model` dataclass. `Sim` now expects the `FullModel` returned by `build()`,
eliminating the duplicate runtime model type. Maintaining both `Model` and `FullModel` class was hard. For example
`export_sources()` shortcut was attached to the old `Model` class instead of the newer `FullModel` class.
- Updated `export_sources.md` doc file and `export_sources_demo.py` example.
### Tests
- Replaced `Model` class usage with `FullModel` class.
---
## [0.34.9] – 2025-12-24
### Tests
- Added comprehensive test coverage for external Jacobian mode in implicit steppers (`bdf2`, `bdf2a`, `tr-bdf2a`,
`sdirk2`) via new test file `test_jacobian_external_mode.py`. Tests verify accuracy against analytic solutions,
consistency between internal and external modes, and correct behavior on both simple (1D exponential decay) and
complex (2D Van der Pol oscillator) systems.
---
## [0.34.8] – 2025-12-23
### Changed
- Modified steppers relying on Jacobian matrices (`bdf2`, `bdf2a`, `tr-bdf2a`, `sdirk`) so that they can utilize
DSL-generated Jacobian functions in their calculations alongside their previous finite difference numerical
approximations. These two modes can be selected using the stepper config value `jacobian_mode` which can be
`external` (use DSL Jacobian) or `internal` (use numerical approximation). Default is `internal` because I am
worried about users providing a faulty Jacobian matrix.
- Set JacobianPolicy of `bdf2`, `bdf2a`, `tr-bdf2a`, `sdirk` steppers to `optional` meaning that they can work
both with external and internal Jacobians.
- For steppers to utilize DSL Jacobian functions, added `jacobian_fn` and `jvp_fn` callable args to the `emit()`
function of all steppers. They are optional so steppers not using Jacobians are not changed.
- Ensured that `aux` values are calculated exactly once in Jacobian matrix entries to prevent repeated
recalculations and slowing down the steppers unnecessarily. This feature is called as aux hoisting.
- Removed `bdf2_scipy` and `bdf2a_scipy` because they were performing very poorly and there was no reason to keep
them around. However, stepper features like `jit_capable` are kept for future use.
- Updated `export_sources.md` file encouraging users to utilize `Model.export_sources()` instead of the standalone
function.
### Added
- Example for comparing run times of steppers with `external` and `internal` Jacobian modes. `external` mode is
slightly faster for all steppers.
### Tests
- Added `test_aux_hoist_jacobian.py` to test correct dependency resolution during `aux` variable hoisting in DSL
Jacobian functions.
- Removed `bdf2_scipy` and `bdf2a_scipy` related tests.
---
## [0.34.7] – 2025-12-23
### Changed
- Each parameter result had its own result data class. Unified sweep results with a single template: `SweepResult`
+ `TrajectoryPayload`. Removed individual data classes like `ParamSweepTrajResult` and the `ParamSweepMLEResult`
introduced in the previous version. Future sweep analyses should use this template.
---
## [0.34.6] – 2025-12-22
### Added
- Lyapunov MLE parameter sweep functionality via `sweep.lyapunov_mle()` for computing maximum Lyapunov exponents
across parameter ranges.
- Returns `ParamSweepMLEResult` with converged MLE values, log growth, step counts, and optional convergence
traces.
- Supports parallel execution via `parallel_mode` parameter ("auto", "threads", "none").
- Includes `stack_traces()` method for uniform-length trace analysis.
- Exported in `dynlib.analysis` namespace alongside `scalar`, `traj` sweep functions.
- Example script `examples/analysis/lyapunov_sweep_demo.py` demonstrating bifurcation diagram and MLE sweep
visualization for the logistic map.
### Changed
- `_LyapunovModule.resolve_hooks()` now caches compiled JIT hooks per dtype to avoid redundant JIT compilation
on every run, improving performance for repeated analysis calls.
---
## [0.34.5] – 2025-12-22
### Added
- `ResultsView.analysis` now returns `AnalysisResult` wrappers that dynamically expose analysis-specific outputs
and traces via named access.
- Generic design: field names are auto-discovered from each analysis module's `output_names` and `trace_names`
metadata - no hardcoding per analysis type.
- Attribute access for values: e.g., for Lyapunov MLE, `lyap.log_growth`, `lyap.steps` (from `output_names`),
and `lyap.mle` (from `trace_names`). Different analyses will have different field names.
- Trace attributes return final scalar values: `lyap.mle` returns the converged value (last element).
- Bracket access for full arrays: `lyap["mle"]` returns complete trace array for plotting/analysis.
- Mapping interface is provided as low-level generic API: `lyap["out"]`, `lyap["trace"]`, `lyap["stride"]`.
- Discovery support: `lyap.output_names`, `lyap.trace_names`, `list(lyap)`, `dir(lyap)` for introspection and
tab-completion.
- Scales from 1D (single MLE value) to nD (Lyapunov spectrum, multiple metrics) automatically.
### Changed
- Updated `lyapunov_logistic_map_demo.py` to demonstrate new API: `lyap.mle` instead of manual
`lyap["out"][0] / lyap["out"][1]` computation.
### Tests
- Extended `test_analysis_runtime.py` for the new `AnalysisResult` feature.
---
## [0.34.4] – 2025-12-22
### Changed
- Moved `src/dynlib/analysis/post/sweep.py` -> `src/dynlib/analysis/post/sweep.py` to gather all sweeps into one
place. Changed sweep imports accordingly.
### Fixed
- `lyapunov_mle()` now validates inputs consistently: a model or explicit `jvp` (with `n_state`) is required, and
`record_interval` is optional (defaults to stride 1). This prevents silent misuse when the factory is called
without a model context while keeping `Sim`-injected factory usage unchanged.
---
## [0.34.3] – 2025-12-22
### Changed
- Simplified runtime analysis API with consistent factory pattern:
- `lyapunov_mle()` now uses simple conditional logic: if `model` parameter is provided, returns `AnalysisModule`
directly; otherwise returns a factory function.
- Factory accepts `model` parameter that `Sim.run()` injects automatically via signature introspection.
- All parameters have sensible defaults: `jvp` and `n_state` extracted from model when not provided.
- Usage patterns:
- `analysis=lyapunov_mle()` — factory mode, Sim injects model (most common).
- `analysis=lyapunov_mle(record_interval=2)` — factory with custom params, Sim injects model.
- `analysis=lyapunov_mle(model=sim.model)` — direct mode, returns AnalysisModule immediately.
- This simple if/else pattern generalizes cleanly to future analysis modules with different signatures.
- Updated logistic map Lyapunov example to demonstrate simplified factory API.
---
## [0.34.2] – 2025-12-22
### Fixed
- Runtime `CombinedAnalysis` now composes child hooks with precomputed offsets and numba-friendly closures,
making combined analyses eligible for fast-path/JIT execution while preserving the Python path when `jit=False`.
- Analysis JIT compilation remains opt-in; pure-Python runners continue to dispatch uncompiled hooks when JIT is
not requested or numba is unavailable.
- With this and previous changes, all of the issues blocking numba compatibility (`jit=True`) of runtime analysis
modules are resolved.
---
## [0.34.1] – 2025-12-21
### Added
- DSL models can now declare Jacobians directly in TOML via `[equations.jacobian].exprs` with deterministic
state order semantics (state decleration order is used for determining the order of the matrix).
- Jacobian declarations generate JVP (Jacobian Vector Product) operators (and optional dense fill) that are
JIT/disk-cache aware and wired into models.
- Lyapunov runtime analysis now consumes JVPs, making Jacobian-dependent analyses work without Python
callbacks.
### Changed
- State declaration order is now part of the spec hash to avoid cache ambiguity and is preserved through mods
/build.
- Capability checks now distinguish JVP and dense-Jacobian requirements; `Sim` validates analysis requirements
early.
- Logistic map Lyapunov example updated to use DSL Jacobian instead of a Python function.
### Known Issues
- Models without a declared Jacobian still fall back to Python-only analyses; declarative TOML Jacobians are
now compiled to JVPs but dense numeric Jacobians are not generated automatically.
- Steppers using numerical Jacobian approximations can benefit from builtin Jacobian functions.
---
## [0.34.0] – 2025-12-21
### Added
- Runtime analysis system for computing diagnostics during simulation execution:
- New `dynlib.analysis.runtime` module with `AnalysisModule`, `AnalysisHooks`, and `TraceSpec` for building
analysis pipelines.
- `lyapunov_mle()` function computes maximum Lyapunov exponents for detecting chaos in discrete and continuous
systems.
- Analysis modules can run alongside integration via `Sim.run(analysis=...)` parameter.
- Support for both Python and JIT-compiled analysis hooks for performance.
- Trace buffers allow sampling analysis results at configurable intervals during runs.
- Fast-path integration now supports analysis modules when JIT hooks are provided.
- New Lyapunov example (`lyapunov_logistic_map_demo.py`) demonstrating chaos detection in the logistic map.
### Changed
- Reorganized analysis tools into two submodules for clarity:
- `dynlib.analysis.post`: Post-run analysis (sweeps, bifurcations, trajectory statistics) - moved from
`dynlib.analysis`.
- `dynlib.analysis.runtime`: During-run analysis (Lyapunov, online diagnostics).
- Enhanced `Results` and `ResultsView` with `.analysis` property for accessing runtime analysis outputs.
- Analysis module imports updated: use `from dynlib.analysis.post import sweep, traj` instead of
`from dynlib.analysis import .sweep, traj`
- Runner ABI version incremented to 3 to accommodate analysis buffer arguments
- All runners (continuous/discrete) now accept analysis workspace, output, trace buffers, and dispatch hooks.
- Fast-path capability checking now validates analysis module requirements (fixed-step, Jacobian, event
compatibility).
### Tests
- Added comprehensive tests for runtime analysis infrastructure (`test_analysis_runtime.py`).
### Known Issues
- Numba option will not work for runtime analysis modules. There are lots of problems for numba compatibility.
- Jacobian equation definitions are very crude and creates numba compatibility issues. TOML DSL definition would
be better.
---
## [0.33.1] – 2025-12-18
### Changed
- Fixed some minor plot related bugs.
- `export.savefig()` now can infer save format from the file extension of path. Otherwise `fmts` arg should
be used. If `fmts` and a path with file extension are used together, then an error is raised.
### Tests
- Added unit tests for `savefig()` behavior.
---
## [0.33.0] – 2025-12-18
### Added
- Introduced comprehensive bifurcation analysis tools for exploring parameter-dependent dynamics:
- `BifurcationExtractor` class provides post-processing of trajectory sweeps into bifurcation scatter data
- Multiple extraction modes: `.all()` (all points), `.tail(n)` (attractor cloud), `.final()` (convergent
states), `.peaks()` (local maxima)
- `BifurcationResult` dataclass holds bifurcation data with metadata for plotting
- `bifurcation_diagram()` plotting function with scatter-optimized defaults
- Convenient API: `sweep_result.bifurcation("x").tail(50)` extracts bifurcation data from parameter sweeps
- New module `dynlib.analysis.bifurcation` for bifurcation post-processing utilities
- New module `dynlib.plot.bifurcation` for bifurcation diagram plotting
- Added three comprehensive bifurcation examples:
- `bifurcation_logistic_map.py`: Basic bifurcation diagram demonstration
- `bifurcation_logistic_map_annotated.py`: Advanced analysis with annotations and zoomed cascade views
- `bifurcation_logistic_map_comparison.py`: Comparison of different extraction modes (final/tail/peaks)
- `sweep.traj()` now accepts `parallel_mode` and `max_workers` parameters for controlling fast-path batch
execution parallelism.
### Changed
- Plot module has been reorganized for better API clarity and consistency:
- Moved `cobweb()` from `dynlib.plot.analysis` to `dynlib.plot.cobweb()`
- Moved `hist()` from `dynlib.plot.analysis` to `dynlib.plot.utils`
- Added top-level convenience function `return_map()` as alias for `phase.return_map()`
- Removed `dynlib.plot.analysis` module (replaced by domain-specific modules)
- Updated examples to use new plot API imports
### Fixed
- Fixed critical bug where `N` parameter was incorrectly reduced by transient steps, causing fewer points to
be recorded than expected. Now `N` correctly specifies the number of recorded steps *after* transient warm
up completes.
- Vector field `scale` parameter now properly applies only when `normalize=True` is set, fixing unintended
scaling behavior.
### Tests
- Added comprehensive test coverage for bifurcation diagram functionality
- Added regression test `test_fastpath_transient_with_N()` to prevent transient/N interaction bugs
---
## [0.32.1] – 2025-12-16
### Changed
- Applied minor bug fixes to the new fast-path runner feature.
- Centralized fast-path capability gating in `src/dynlib/analysis/sweep.py` by adding `_assess_fastpath_support`,
reusing the same support for both batch and per-run fallbacks so we don’t re-evaluate or fabricate support
objects.
- `_run_batch_fast` now returns `(result, support)` without emitting its own warnings; batch callers warn once via
`_warn_fastpath_fallback`, including the capability reason when available.
- Fallback runs reuse the same `FastpathSupport` and suppress per-run warnings, ensuring only a single, informative
warning per sweep when fast-path is unavailable.
---
## [0.32.0] – 2025-12-16
### Added
- Introduced a fast-path analysis runner (`dynlib.runtime.fastpath`) optimized for parameter sweeps and
batch simulations:
- **Recording plans**: `FixedStridePlan` and `TailWindowPlan` pre-allocate buffers to avoid dynamic growth
- **Capability gating**: Automatic validation ensures fast-path constraints are met before execution
- **Stateless execution**: Runs don't mutate `Sim` session state, enabling safe parallel execution
- **Three-level API hierarchy**:
- Low-level: `run_single_fastpath()`, `run_batch_fastpath()` for raw model arrays
- High-level standalone: `fastpath_for_sim()`, `fastpath_batch_for_sim()` for `Sim` objects
- User-facing method: `Sim.fastpath()` for ergonomic one-off executions
- **Parallel execution modes**:
- `parallel_mode="auto"`: Automatically selects optimal strategy (default)
- `parallel_mode="threads"`: ThreadPoolExecutor with GIL-free execution for JIT builds (~Nx speedup)
- `parallel_mode="none"`: Sequential execution for debugging
- `max_workers` parameter controls thread pool size
- **Transient warm-up**: Optional `transient` parameter discards initial samples before recording
- **Selective recording**: Compatible with `record_vars` for memory-efficient variable selection
- Added `RecordingPlan` base class with `FixedStridePlan(stride)` for regular sampling and
`TailWindowPlan(stride, window)` for keeping only last N samples.
- Added `FastpathSupport` and `assess_capability()` for runtime validation of fast-path eligibility.
### Changed
- Parameter sweeps (`analysis.sweep.scalar()`, `analysis.sweep.traj()`) now attempt fast-path backend when
eligible before falling back to `Sim.run()`, providing automatic optimization without user intervention.
- Fast-path batch execution achieves near-linear speedup with JIT compilation (e.g., 8x on 8 cores) by
leveraging Numba's GIL-free execution in threaded environments.
- Sweep operations now emit a one-time warning when falling back to `Sim.run()`, helping users identify
performance optimization opportunities without flooding output. Pure Python + threads: ~1.1-1.3x speedup
(limited by GIL).
### Known Issues
- Fast-path requires fixed-step steppers (Euler, RK4, Map, etc.); adaptive steppers fall back to `Sim.run()`.
- Event logging is unsupported (apply-only events work fine).
- Lagged systems (`lag_x(k)`) currently disabled pending ring-buffer management improvements.
- Recording interval must be positive and known in advance.
---
## [0.31.5] – 2025-12-15
### Added
- Vector field plots can now create animations across parameter sweeps via `vectorfield_animate()` with
configurable frame rates, repeat settings, and custom update functions for parameters and fixed states.
- Vector field plots support multi-panel parameter sweeps via `vectorfield_sweep()` with shared or independent
colormaps, custom titles, and flexible grid layouts.
- Added examples `vectorfield_sweep_demo.py`, `vectorfield_animate_demo.py`, and `vectorfield_animation.py` to
demonstrate the new features.
### Changed
- When `normalize=True` in vector field plots, quiver arrows now use data-unit scaling to show unit vectors at
their true size instead of being auto-rescaled.
### Tests
- Added unit tests for vector field animations covering parameter sweeps, custom functions, frame control, and
animation properties.
- Added unit tests for vector field sweep functionality including shared speed normalization, custom sweep
definitions, and facet titles.
---
## [0.31.4] – 2025-12-15
### Added
- Vector field plots can color arrows and streamlines by speed magnitude via `speed_color=True`, with optional
`speed_cmap`/`speed_norm` forwarded to Matplotlib.
---
## [0.31.3] – 2025-12-15
### Added
- Vector field plotting now supports a streamlines mode via `mode="stream"` with kwargs forwarded to
`matplotlib.streamplot()`.
---
## [0.31.2] – 2025-12-15
### Added
- Vectorfield plot demo `vectorfield_highdim_demo.py` for demonstrating vector fields of 2D slices of higher
dimensional systems.
### Changed
- Renamed `series.multi()` `series` argument to `y` for API consistency.
---
## [0.31.1] – 2025-12-15
### Added
- Interactive vector field plots respond to clicks by simulating trajectories from that point using the compiled
model’s stepper, while nullclines can be toggled without recomputation (key `N`) and trajectories can be cleared
via keyboard shortcuts (key `C`).
- Vector field plotting now accepts a `stepper` override when compiling from URIs/ModelSpecs and exposes `T`/`dt`
arguments of the stepper.
### Tests
- Added coverage for stepper overrides in vector field plotting and interactive trajectory handling.
---
## [0.31.0] – 2025-12-15
### Added
- Added vector field plotting functions for ODE models into `plot/vectorfield.py`. It creates quiver plots and
can calculate nullclines numerically.
- Added demo example for vector field plotting.
### Tests
- Added unit tests for vector field evaluation and plotting in `tests/unit/test_vectorfield_eval.py`.
---
## [0.30.11] – 2025-12-10
### Added
- Added `TrajectoryAnalyzer`/`MultiVarAnalyzer` utilities under `dynlib.analysis` and exported them for easy
imports.
- Added `ResultsView.analyze()` helper that returns trajectory analyzers for recorded states/aux variables
with stats, extrema, crossing detection, and time-above/below helpers. `summary()` call returns all trajectory
analysis results.
- Added `examples/analysis/demo_trajectory_analysis.py` showing analyzer usage on a damped oscillator run.
### Tests
- Added unit tests covering analyzer selection (states vs aux), percentile validation, crossing/time-above
calculations, and analyzer caching for multi-var cases.
---
## [0.30.10] – 2025-12-03
### Added
- Added iterable `.runs` property to `ParamSweepTrajResult` for intuitive access to individual sweep runs:
- `SweepRun` dataclass provides `.param_value`, `.t`, and `["var"]` access for each run
- `SweepRunsView` provides list-like interface supporting indexing, iteration, and length
- Consistent API: `run["x"]` works like `res["x"]` but for individual runs
- Example: `for run in res.runs: plot(run.t, run["x"])`
### Changed
- Removed redundant `.t_all` shortcut properties from `ParamSweepTrajResult` in favor of unified `.runs` interface.
Now `res.t` is equivalent to `res.runs[0].t` as a shortcut.
---
## [0.30.9] – 2025-12-02
### Added
- Added `dt_max` parameter to adaptive ODE steppers (RK45, BDF2, BDF2A_scipy, TR-BDF2A) to limit maximum step size
during step size calculation.
- Added `inf` and `nan` imports to generated stepper modules for proper numba caching.
### Changed
- Enhanced initial step size selection to respect `dt_max` configuration from stepper configs.
- Listed `prod()` DSL expression as builtin (it was implemented but not defined).
---
## [0.30.8] – 2025-12-02
### Added
- Added `phase.multi()` function to plot multiple 2D phase trajectories on the same axes, useful for showing how
trajectories change with different parameters in phase space.
- Added `parameter_sweep.py` example showing how to use the sweep functions with a simple exponential decay model.
### Changed
- Enhanced `sweep.scalar()` and `sweep.traj()` functions with better documentation, support for initial time
offsets, and improved result classes with named variable access.
---
## [0.30.7] – 2025-11-25
### Added
- Enhanced the `analysis.sweep()` utility with data stacking for consistent run lengths, named access to
variables, and time axis convenience properties:
- `res.t` will yield time values for the first sim.
- `res.t_runs` and `res.t_all` will allow access to whole time value ndarray.
- `res[x]` will yield parameter sweep results for the `x` variable as (K,N) ndarray.
- Added support for numpy ndarrays in `plot.multi` function, allowing direct plotting of 1D and 2D arrays with
automatic naming.
---
## [0.30.6] – 2025-11-25
### Added
- Added a new `analysis` module with parameter sweep tools for running simulations with different parameter
values and collecting results. It is an early sketch.
---
## [0.30.5] – 2025-11-24
### Added
- Added support for `range()` function in DSL expressions. Arguments are automatically cast to integers for
proper Numba compatibility.
- Added validation to check that all identifiers used in expressions are properly declared as states, parameters,
auxiliary variables, functions, or constants, or are supported builtins and macros. This prevents typos and
undefined references.
### Tests
- Updated test models to remove manual `int()` casts around `range()` calls, as the function now handles type
conversion internally.
---
## [0.30.4] – 2025-11-24
### Added
- Added user-defined `[constants]` table support to inline model-specific numeric literals across DSL
expressions, with collision guards against states/params/aux and reserved identifiers.
---
## [0.30.3] – 2025-11-24
### Added
- DSL builtin constants `pi` and `e` are now inlined as numeric literals across equations, aux/functions,
events, and initial value expressions. These identifiers are reserved and cast to the model dtype at
codegen time to avoid runtime lookups or dtype mismatches.
- Added cross-section identifier guard in `src/dynlib/dsl/astcheck.py` and wired it into build_spec so models
now error when a name is reused across states/params/aux/functions (e.g., param V vs aux V).
### Tests
- Added semantic validation coverage in `tests/unit/test_semantic_validation.py` to ensure these conflicts
raise a clear `ModelLoadError`.
---
## [0.30.2] – 2025-11-23
### Changed
- Runners now refresh auxiliary variable values before recording initial conditions to ensure aux data is
available at the start for recording.
- Results API now properly handles variable names from recorded data instead of listing all available states.
---
## [0.30.1] – 2025-11-23
### Changed
- Warm-up now matches the new runner ABI and quadruplet callables so JIT compilation is triggered during
build again.
- `_warmup_jit_runner` now allocates AUX/selective-recording buffers, computes event log width, and passes
`state_rec_indices`, `aux_rec_indices`, and counts to the runner along with the AUX buffer.
- `_all_compiled` checks `update_aux` too, preventing skips when only the aux updater lacks signatures.
---
## [0.30.0] – 2025-11-23
### Added
- Added `update_aux` callable to compute auxiliary variables from current state values. This function is
called after each committed step to ensure aux variables are available for recording and event conditions.
Inside RHS the aux values are still replaced by expressions to perform fast stepper multi-stage calculations.
- Added selective variable recording via new `record_vars` parameter in `Sim.run()`. Users can now specify
exactly which variables to record:
- `record_vars=None` (default): Record all states (backward compatible)
- `record_vars=["x", "y"]`: Record specific states
- `record_vars=["aux.energy"]`: Record specific aux variables with explicit prefix
- `record_vars=["energy"]`: Record aux variables with auto-detection (no prefix needed)
- `record_vars=["x", "energy", "aux.power"]`: Mix states and aux variables
- `record_vars=[]`: Record nothing (only time, step, flags; equivalent to `record=False`)
- Added `aux_values` array to `RuntimeWorkspace` for storing computed auxiliary variable values during
simulation.
- Enhanced `Results` class with `AUX` array for recorded auxiliary variables, plus `state_names` and `aux_names`
metadata.
- Added `get_var()` and `__getitem__()` methods to `Results` for accessing recorded variables by name with auto-
detection.
- Added `to_pandas()` support for auxiliary variables in selective recording.
### Changed
- Updated runner functions to call `update_aux` after each committed step to maintain aux variable values.
- Modified JIT compilation to handle quadruplet (rhs, events_pre, events_post, update_aux) instead of triplet.
- Enhanced `ResultsView` to support accessing auxiliary variables alongside states.
- Updated `Sim` class to handle selective recording with proper buffer allocation and metadata tracking.
- Modified `run_with_wrapper` to pass selective recording parameters to runners.
### Tests
- Added 4 new tests in `tests/unit/test_selective_recording.py` to verify auto-detection behavior:
- `test_aux_auto_detection_without_prefix`: Aux variables work without prefix
- `test_mixed_auto_detect_and_explicit_prefix`: Mixing both syntaxes
- `test_state_priority_over_aux_same_name`: States take priority in detection
- `test_unknown_variable_helpful_error`: Error messages list available variables
- Updated existing tests to match improved error messages.
- Added comprehensive test coverage for selective recording functionality including buffer growth, resume
behavior, and error handling.
### Known Issues
- JIT warm-up is broken again. Possibly related to the new quadruplet approach.
---
## [0.29.6] – 2025-11-23
### Added
- Added support for adding and removing parameters via mods. Now `add.params` can be used to add new
parameters and `remove.params` to remove existing ones.
- Added validation to prevent unsupported targets in mod operations. Now clear error messages are shown
when trying to use invalid targets like `add.states` or `remove.states`.
- Added comprehensive documentation for mods in `docs/mods.md`, explaining all verb operations, supported
targets, error handling, and best practices.
### Tests
- Added tests for parameter modification via mods, including adding/removing parameters and validation of
unsupported targets.
---
## [0.29.5] – 2025-11-23
### Added
- Added support for `sum()` and `prod()` generator comprehensions in DSL expressions. These allow summing or
multiplying over ranges with optional conditions, like `sum(i*i for i in range(10))` or
`prod((i+1) for i in range(1, 5) if i % 2 == 0)`. They are compiled into efficient for-loops and work in
equations, aux variables, events, and functions.
- Updated documentation in `docs/dsl_macros_and_functions.md` with a new "Generator Comprehensions" section
explaining the syntax and examples.
### Tests
- Added comprehensive tests in `tests/unit/test_sum_generator_lowering.py` to verify the functionality works
correctly with both JIT and non-JIT compilation.
---
## [0.29.4] – 2025-11-21
### Added
- Added validation to prevent auxiliary variables from using reserved names like `t` to avoid conflicts
with runtime symbols. `_AUX_RESERVED_NAMES` list can be expanded to restrict aux names in the future.
### Changed
- Renamed auxiliary variable in Ikeda map model from `t` to `theta` for clarity and to avoid reserved
name conflict.
### Tests
- Added a test for reserved auxiliary name validation.
---
## [0.29.3] – 2025-11-20
### Added
- Added documentation for DSL model file template in `docs/dsl_model_template.md`.
- Added several builtin models for ODE and MAP types:
- ODE models: `exp_if` (Exponential Integrate-and-Fire), `fitzhugh_nagumo`, `hodgkin_huxley`, `leaky_if`
(Leaky Integrate-and-Fire), `quadratic_if`, `resonate_if`.
- MAP models: `henon`, `ikeda`, `logistic`, `lozi`, `sine`, `standard`.
---
## [0.29.2] – 2025-11-20
### Added
- Added `choose_default_stepper()` function to automatically select appropriate steppers based on model type
if DSL model spec and user does not provide one. The hard-coded defaults are `map` -> `map` and `ode` -> `rk4`.
- Added theme usage examples in collatz.py, detect_transition.py, and logistic_map.py to demonstrate theme
presets.
### Changed
- Improved plotting primitives with enhanced docstrings and better parameter handling.
- Standardized arguments of plotting functions.
---
## [0.29.1] – 2025-11-20
### Added
- Added plotting theme system using `ThemeSpec` dataclass with inheritance support for better theme management
and customization.
- Added `themes_demo.py` example to demonstrate all available theme presets with sample plots.
### Changed
- Enhanced savefig function to properly handle constrained layout without clipping.
- Improved style resolution with clear priority hierarchy separating visual patterns from rendering properties.
---
## [0.29.0] – 2025-11-20
### Added
- Added `dynlib` CLI entry point with `model validate`, `steppers list`, and `cache` management
subcommands for model validation, registry inspection, and JIT cache cleanup. The CLI entry point is
placed into `src/dynlib/cli.py`.
### Tests
- Added unit tests covering the new CLI flows (model validation success/failure, stepper filters, cache
listing/clearing).
---
## [0.28.9] – 2025-11-20
### Added
- Added validation script for adaptive ODE steppers tolerance sweep.
- Added RK2 (explicit midpoint) stepper for fixed-step ODE simulations.
- Added SDIRK2 (Alexander) stepper, a JIT-compatible implicit method for stiff ODEs.
### Tests
- Added basic accuracy and contract tests for RK2 and SDIRK2 steppers.
---
## [0.28.8] – 2025-11-20
### Added
- Added automatic initial step size selection for adaptive ODE steppers using Hairer/Shampine-style WRMS
norm heuristics. Now dt arg for adaptive ode steppers is just a suggestion and max step bound. Stepper
tol, atol, meta.order values are accessed automatically during heuristics. Placed the algorithm into
`initial_step.py` module. Heuristics are disabled if `resume=True`.
### Changed
- Updated simulation wrapper to choose initial dt based on stepper type and configuration.
- Modified Sim class to support WRMS config for adaptive steppers.
### Tests
- Added tests for new initial step size selection heuristic feature.
---
## [0.28.7] – 2025-11-20
### Changed
- Improved `bdf2a` stepper startup by using Richardson extrapolation for better accuracy on the first step.
- Optimized `tr-bdf2a` stepper by switching to modified Newton method with frozen Jacobian for faster
convergence. Full explicit Backwards Euler stage can be further optimized by estimating the error instead
of full implicit solve operation. However, for the sake of robustness, I skipped this optimization.
### Fixed
- Fixed wrong error estimation coefficients for `bdf2a` and `tr-bdf2a` steppers. Using error**0.5 was causing
parity between numba and python results. Used math.sqrt(error) for exactly matching results.
### Tests
- Added accuracy and contract tests for `bdf2a` and `tr-bdf2a` steppers.
---
## [0.28.6] – 2025-11-19
### Added
- Added `bdf2a` stepper, an adaptive BDF2 method with variable step size for stiff ODEs.
- Added `tr-bdf2a` stepper, an adaptive TR-BDF2 method combining trapezoidal rule and BDF2 for better
stability. It is not optimized, I might optimize it in the next version if it is feasible.
### Changed
- Improved RK45 stepper performance by moving k1 computation outside the adaptive retry loop.
- Renamed `StepperMeta` `stiff_ok` key to `stiff`.
---
## [0.28.5] – 2025-11-19
### Added
- Added `select_steppers()` function to filter steppers by metadata fields like kind, scheme, jit_capable,
etc.
- Added `list_steppers()` function to get a list of stepper names matching filter criteria.
- Added `validation/` folder with `ode_steppers_dt_sweep.py` script for benchmarking ODE stepper accuracy
across different time steps.
### Tests
- Fixed stepper name in `test_stepper_config.py` test to use a stepper with model config key.
---
## [0.28.4] – 2025-11-19
### Added
- Added `state()` and `param()` methods to `Sim` class for accessing individual state and parameter
values by name.
- Added `stepper` property to `Sim` class to access the stepper specification.
- Added a simple exponential decay model (`expdecay.toml`) as a builtin example.
### Changed
- Moved guards configuration earlier in `build()` to ensure guards are ready before JIT compilation.
- Updated `get_guards()` to install guard consumers automatically.
### Fixed
- `jit=True` and `disk_cache=False` combination was raising nopython errors for NaN/Inf guards (like
allfinite1d). Added `register_guards_consumer` function in `guards.py` to allow guards to update
existing stepper namespaces. In the future a proper numba inlineable functions registry would be
better but this fix works right now.
- Added endpoint clipping in the runner to handle cases where dt would overshoot `t_end`, ensuring
accurate final time steps.
- Registered guards consumers in RK45 and BDF2 steppers for proper NaN/Inf detection updates.
- Reordered history rotation in BDF2 stepper to prevent aliasing issues between current and proposed
states.
---
## [0.28.3] – 2025-11-19
### Added
- Added `bdf2a_scipy` stepper which is adaptive BDF2 solver based on scipy root solvers.
### Tests
- Added accuracy and contract tests for `bdf2a_scipy`.
### Fixed
- `bdf2` was underperforming according to the `accuracy_demo.py`. Added scale-aware residual bookkeeping
and a correction-based convergence check to the Newton loop so the solver can no longer declare success
while the update is still O(dt) in size; both BDF1 and BDF2 branches now track the largest state
magnitude and require the max residual and the scaled correction to fall below newton_tol before exiting,
which restores the intended second-order accuracy. This fixed its very low accuracy.
---
## [0.28.2] – 2025-11-18
### Added
- Added Van der Pol oscillator model and its example with jit-enabled `bdf2` stepper.
- Added a warning when `max_steps` is hit by the runners.
- Added `accuracy_demo.py` to examples for comparing errors of ode steppers against known models.
### Changed
- Renamed `bdf2_jit` -> `bdf2` and `bdf2` -> `bdf2_scipy` because jittable custom bdf implementation is
way faster than minpack-based scipy solver and its accuracy is similar. I will treat jittable BDF2
implementation as the main BDF stepper.
### Tests
- Added accuracy and contract tests for `bdf2_scipy`.
---
## [0.28.1] – 2025-11-18
### Added
- Added support for extra stepper configuration defaults in the [sim] section of model files. Previously
only hardcoded rtol/atol values were handled as [sim] stepper config values. Now unknown keys in [sim]
are stored as stepper defaults and used when building stepper configurations. The precedence is still
as in v2.28.0. [sim] stepper config values are not used if user overrides the stepper. Not all configs
should be listed. The stepper defaults are applied for the ones not provided.
### Changed
- Updated `SimDefaults` class to handle extra stepper configuration keys dynamically.
### Tests
- Added tests for stepper config handling, including runtime overrides, model defaults, and extra sim keys.
---
## [0.28.0] – 2025-11-18
### Added
- Introduced `ConfigMixin` base class in `config_base.py` for automatic stepper configuration handling.
It makes stepper declarations more concise.
- Added `config_utils.py` file that provides common tools for stepper config values handling.
- Added new `bdf2` stepper that uses `scipy.optimize.root` for solving implicit equations.
- Added `requires_scipy` flag to stepper capabilities to indicate scipy dependency.
- Added support for string enum values in stepper configurations via `config_enum_maps()`.
### Changed
- Refactored all stepper implementations to use `ConfigMixin` for config management.
- Updated `_build_stepper_config` to automatically convert string config values to integer enum values
using `convert_config_enums` (provided in `config_utils.py`).
- Updated stepper config default value precedence to: Stepper Config Defaults < Model [sim] table values
< User Inputs. This is handled by the `default_config` method of the `ConfigMixin` class. So this base
class must be applied to the steppers for this precedence to apply.
---
## [0.27.2] – 2025-11-18
### Added
- Added `jit_capable` flag to `StepperCaps` to specify if a stepper supports JIT compilation.
- Introduced `softdeps.py` module for centralized detection of optional dependencies like numba and
scipy.
- Added `stepper_checks.py` module to validate stepper capabilities and dependencies before building
models.
- New `StepperJitCapabilityError` exception raised when requesting JIT for incompatible steppers.
### Changed
- Updated `build()` function to perform stepper capability checks, ensuring compatibility with
requested options.
- Refactored dependency detection in `guards.py`, `jit/compile.py`, `runner.py`, and
`runner_discrete.py` to use the centralized `softdeps` system.
### Tests
- Added `test_stepper_jit_capability.py` to test JIT capability validation and error handling.
---
## [0.27.1] – 2025-11-17
### Changed
- Improved BDF2_JIT stepper by adding checks for NaN/Inf values during calculations to exit early in
case of invalid data. Also improved Jacobian calculations.
### Tests
- Added BDF2_JIT stepper contract and accuracy tests.
---
## [0.27.0] – 2025-11-17
### Added
- Jit compatible BDF2 (Backward Differentiation Formula 2nd Order) stepper `bdf2_jit` is added. It
utilizes a simple Newton method plus custom numeric Jacobian. Therefore, it is not as reliable as
other solvers utilizing minpack or similar dedicated root finders. However, it is numba compatible,
so it can be used for fast simulations or analysis of stiff models to some extend.
## Changed
- Changed `JacobianPolicy` values as `none`, `internal`, `optional`, `required`.
- `none` : No Jacobian during calculations.
- `internal` : Uses a numerical Jacobian approximation. Users can't pass externally.
- `optional` : Users can provide external Jacobian. Fallback is `internal`.
- `required` : USers should pass an external Jacobian.
### Fixed
- Simulations were continuing after transient or subsequent runs with `resume=True` even though a
`STEPFAIL` was raised by the steppers previously. Ensured the transient warm-up respects runner
failures by validating each warm-result before touching session state or shifting time, so `Sim.run`
now aborts immediately on a `STEPFAIL`/`NAN_DETECTED` instead of rolling forward with stale values.
Wrapped the recorded run in the same guard, so no rebasing, session-state updates, or segment
stitching happens unless the wrapper reports `DONE`, keeping resume histories consistent.
- Added `_ensure_runner_done` to give a clear RuntimeError that includes the failing phase and status
name, centralizing the exit check.
### Tests
- Added regression tests `tests/unit/test_sim_failure.py` that monkeypatch `_execute_run` to simulate
`STEPFAIL` returns, covering both transient and main segments, and verifying that state/records remain
untouched and `run()` raises.
- Updated `tests/unit/test_nan_inf_guards.py` to import pytest, expect `Sim.run()` to raise a RuntimeError
mentioning `NAN_DETECTED`, and assert the session state remains untouched after the aborted run. This
aligns the test with the stricter failure handling added to `Sim.run`.
---
## [0.26.5] – 2025-11-16
### Added
- Added new `StepperCaps` dataclass to hold stepper-specific features that can be added or removed
without changing the rest of the stepper `StepperMeta` declarations.
### Changed
- Moved `dense_output` flag from `StepperMeta` to `StepperCaps` for better organization.
- Updated all stepper implementations (Euler, RK4, RK45, AB2, AB3, Map) to use the new caps structure.
---
## [0.26.4]
### Tests
- Updated all tests according to the new workspaces design.
- All tests pass and examples work at this point.
### Fixed
- Running the entire unit suite started failing with `ModuleNotFoundError: dynlib_stepper_<digest>`
because numba’s cache metadata points to generated module names that were no longer importable
once earlier tests cleaned up their in-memory modules. Added a cache importer meta-path finder
plus sys.modules registration (`cache_importer.py`) so cached steppers/runners/triplets can
always be re-imported from the dynlib cache root, which restores `pytest tests/unit` stability
while keeping individual test
runs unchanged.
---
## [0.26.3] – 2025-11-16
### Changed
- Removed NaN/Inf checks from `AB2` and `AB3` steppers, since they are fixed-step solvers.
- Removed workbanks related docstrings from steppers.
---
## [0.26.2] – 2025-11-16
### Changed
- Updated `snapshot_demo.py` and `uri_demo.py` examples. All examples work at this point.
### Tests
- Updated `test_snapshot_persistence.py` test.
---
## [0.26.1] – 2025-11-16
### Changed
- Updated the docs throughout the package. Removed remnants of the old workbanks docs.
- Removed stepper_banks.md file and introduced stepper_workspace.md file.
- Updated ISSUES.md and TODO.md files.
### Fixed
- Cobweb plotter was still using the old workbanks API. Now it also uses runtime workspace.
---
## [0.26.0] – 2025-11-15
### Added
- Introduced separate stepper and runtime workspaces to cleanly separate responsibilities:
- **Stepper workspace**: Private to each stepper, implemented as a NamedTuple-of-NumPy-views
containing stepper-specific scratch arrays (e.g., stages, histories, Jacobians).
- **Runtime workspace**: Private to the runner and DSL machinery, implemented as a NamedTuple
containing lag buffers (lag_ring, lag_head, lag_info) for historical state access.
- Added `StepperSpec.workspace_type()` and `StepperSpec.make_workspace()` methods for steppers
to declare and allocate their workspace.
- Added `RuntimeWorkspace` NamedTuple in `src/dynlib/runtime/workspaces.py` for lag buffer
management.
- Added `make_runtime_workspace()` helper in `src/dynlib/runtime/workspaces.py` to allocate
runtime workspace from lag metadata.
### Changed
- Removed `WorkBanks` and `StructSpec` from public API: Eliminated the shared banking scheme
(sp, ss, sw*, iw0, bw0) that mixed stepper scratch with runtime state. Workspaces are now owned
by their respective components.
- Updated stepper ABI: Simplified stepper signature to `stepper(t, dt, y_curr, rhs, params,
runtime_ws, stepper_ws, stepper_config, y_prop, t_prop, dt_next, err_est) -> int32`, removing bank
arguments and passing workspaces directly.
- Updated runner ABI: Runner now accepts `runtime_ws` and `stepper_ws` instead of bank arrays.
Runner handles lag updates using runtime workspace instead of global `_LAG_STATE_INFO` and ss/iw0
banks. Removed `_LAG_STATE_INFO`.
- Refactored lagging system: Lag buffers moved to dedicated runtime workspace with circular
buffer access. Removed partitioning of ss/iw0 banks for lags.
- Migrated all steppers: Updated Euler, RK4, RK45, AB2, AB3, and Map steppers to use new
workspace pattern. Each stepper defines its own NamedTuple workspace type and factory.
- Updated code generation: RHS/event lowering now accesses lags via runtime workspace helpers
instead of ss/iw0. Generated code remains Numba-friendly.
- Enhanced workspace serialization: Added `snapshot_workspace()` and `restore_workspace()`
helpers for workspace persistence during resume.
### Fixed
- Eliminated cross-talk between lagging and stepper workspaces, preventing lag corruption in complex
models.
- Improved workspace reallocation safety: workspaces never grow during runs, only RecordingPools /
EventPools do.
### Tests
- Added comprehensive tests for new workspace allocation and migration.
- Updated all stepper tests to use new ABI.
- Added tests for lag system with runtime workspace.
- Verified resume/snapshot functionality with separated workspaces.
### Known Issues
- Many parts need refactoring for the new workspace approach. Most tests and examples will fail at
this point.
---
## [0.25.1] – 2025-11-15
### Added
- Added AB3 (Adams-Bashforth 3rd order) stepper for ODE simulations.
- Added basic and contract tests for AB3.
---
## [0.25.0] – 2025-11-15
### Added
- Added AB2 (Adams-Bashforth 2nd order) stepper for ODE simulations.
- Added basic tests for AB2 stepper accuracy.
- Added contract tests for ODE steppers to ensure JIT on/off parity and proper registration.
- Added separate test files for RK4 and RK45 steppers using the new API.
### Changed
- Updated RK45 stepper default tolerances (atol=1e-6, rtol=1e-3, max_factor=5.0) for better balance.
### Tests
- Reorganized ODE stepper tests. Now each stepper will have two test files:
1) `test_<stepper_name>_basic.py`
- Accuracy vs analytic decay
- Order test (dt vs dt/2)
2) `test_ode_stepper_contract.py` (single file for all steppers)
- JIT on/off parity
- Buffer growth invariance (your cap_rec tests)
- Transient warm-up behavior
- Registry / alias correctness
- Maybe a transient or growth test
- Refactored test files to use the `setup()` helper instead of manual model building.
- Deleted duplicate test model files (`decay_rk4.toml`, `decay_rk45.toml`).
- Removed old combined RK4/RK45 integration test file.
---
## [0.24.1] – 2025-11-14
### Added
- Added scalar DSL macros usable in aux, equations, and event actions: `sign(x)`, `heaviside(x)`,
`step(x)`, `relu(x)`, `clip(x, a, b)`, and `approx(x, y, tol)`. They lower to comparisons and
builtins only, keeping generated code Numba-friendly.
### Tests
- Added regression coverage for the new macros in `tests/unit/test_scalar_macros.py`.
---
## [0.24.0] – 2025-11-14
### Added
- Added DSL event macros for common transition detection:
- `cross_up(state, threshold)`,
- `cross_down(state, threshold)`,
- `cross_either(state, threshold)`,
- `changed(state)`,
- `in_interval(value, lower, upper)`,
- `enters_interval(state, lower, upper)`,
- `leaves_interval(state, lower, upper)`,
- `increasing(state)`, `decreasing(state)`.
### Changed
- Updated `detect_transition.py` example to use the new `cross_up` macro instead of manual lag
condition.
### Tests
- Added comprehensive tests for event macros in `test_event_macros.py`.
- Updated lag detection to recognize macro usage in expressions.
---
## [0.23.6] – 2025-11-14
### Fixed
- Event log buffer reallocation was causing data loss for post events. Reordered event handling in
runners (both `runner.py` and `runner_discrete.py`) to check post-events on proposed state before
committing. This way reallocation during post-events now occur before commit, and this prevents
uncaught event logs.
### Changed
- DSL event tables now default to `phase = "post"` when the key is omitted, simplifying the common
case where only post-step events are needed.
### Tests
- Added unit coverage ensuring the parser backfills the default event phase.
---
## [0.23.5] – 2025-11-14
### Added
- Added support for numeric expressions in states and parameters. You can now use strings like
"8/3" or "1/2" that get evaluated to numbers.
- Added a new example `detect_transition.py` showing how to use the lag system to detect when
a state variable crosses from negative to positive.
### Changed
- Improved TOML parsing error messages with better context, line numbers, and helpful hints for
common mistakes like division in values.
- Updated plotting functions to accept single numbers in `vlines` parameter, not just tuples.
### Tests
- Added tests for numeric expressions in model states and parameters.
- Added comprehensive tests for improved TOML error messages.
### Known Issues
- Event log buffer reallocation causes data loss.
---
## [0.23.4] – 2025-11-14
### Added
- Added `uses_lag` and `equations_use_lag` flags to model classes to track lag feature usage.
- Added `detect_equation_lag_usage` function to check if model equations depend on lag functions.
It tries to uncover all dependencies because aux and functions used in equations may also rely
on lagged values.
### Changed
- Updated cobweb plotting to prevent usage with models that have lag in equations, as it cannot
evaluate them properly. Future analysis / plot tools that use model equations should not forget
that lag mechanism will not work without a proper Sim object. They should perform a similar check.
---
## [0.23.3] – 2025-11-14
### Fixed
- `_LAG_STATE_INFO` value was shared globally between different python (non-jitted) runners. This
was causing corrupted lag info between different runners. Fixed lag state info to be per-runner
instance instead of global, preventing interference between models with different lag configurations.
### Tests
- Added new tests covering lag info corruption issue to `test_lag_system.py`.
---
## [0.23.2] – 2025-11-14
### Changed
- Removed support for the `prev_<name>` DSL shorthand. Now `lag_<name>()` is used as a shorthand for
one-step lag. `lag_<name>(k)` usage stays the same.
---
## [0.23.1] – 2025-11-14
### Added
- Added `ss_lag_reserved` field to `StructSpec` for lag buffer allocation in stepper state. If
a stepper needs to use the ss bank, it should use starting from this index. iw0 bank already
has `iw0_lag_reserved` from the previous version.
### Changed
- Updated stepper banks documentation with lag system partitioning rules for `ss` banks.
- Updated build process to include lag reservations in struct specification.
---
## [0.23.0] – 2025-11-14
### Added
- Added lag system to access historical state values in models using `lag_<name>(k)` for k steps
back or `prev_<name>` for one step back. This enables delay differential equations and lagged
feedback in both ODE and map models.
- Added automatic detection and validation of lag usage in model expressions, with sanity limits
on lag depths.
- Added circular buffer storage for lagged states using existing `ss` and `iw0` stepper banks with
partitioning to avoid ABI changes.
- Added lag buffer initialization with initial conditions and updates after committed steps only.
- Added comprehensive documentation for the lag system in `docs/lag_system.md`.
- Added `lag_state_info` metadata to `FullModel` and `Model` classes for runtime lag buffer
management.
### Changed
- Updated all stepper implementations (`euler`, `rk4`, `rk45`, `map`) to pass `ss` and `iw0`
parameters to RHS and event functions for lag support.
- Updated runner functions (`runner.py`, `runner_discrete.py`) to maintain lag buffers after step
commits and embed lag metadata as compile-time constants.
- Updated code generation (`emitter.py`, `rewrite.py`) to handle lag notation in expressions and
generate circular buffer access code.
- Updated model building (`build.py`) to augment stepper struct specs with lag requirements and
convert lag maps to runtime indices.
- Updated DSL parsing and validation (`astcheck.py`, `spec.py`) to collect lag requests and build
lag metadata.
- Updated simulation wrapper (`wrapper.py`) to initialize lag buffers on first run.
- Updated stepper banks documentation (`stepper_banks.md`) with `iw0` partitioning rules for lag
heads.
- Updated plotting primitives (`_primitives.py`) to handle lag buffers in cobweb plots.
### Tests
- Added comprehensive unit tests for lag system functionality in `tests/unit/test_lag_system.py`,
including buffer tracking, resume behavior, and correctness validation.
- Updated existing tests to accommodate new function signatures with `ss` and `iw0` parameters.
### Known Issues
- Some lag feature related issues will be resolved in the following updates.
---
## [0.22.0] – 2025-11-13
### Added
- Added support for builtin models using the "builtin://" URI scheme. This lets users access bundled
models without setting up paths manually.
- Added Izhikevich neuron model as a builtin model with presets for different spiking patterns like
regular spiking, bursting, and fast spiking.
- Updated path resolution to automatically include the builtin models directory in the search paths.
### Changed
- Updated izhikevich.py example to use the builtin Izhikevich model and show how to apply presets
during simulation runs.
### Tests
- Updated path resolution tests to check that the builtin tag is registered and models can be found.
---
## [0.21.6] – 2025-11-13
### Added
- Added `add_preset()` method to `Sim` class. It lets you create new presets from the current
session state or by providing specific values for states and parameters.
- Added support for partial presets. You can now define presets with only some states or
parameters instead of requiring all of them. Parameters are no longer mandatory.
### Changed
- Updated preset validation to allow empty states or params sections, as long as at least one is
defined.
- Modified `apply_preset()` to update only the provided values, leaving other states and parameters
unchanged.
- Enhanced `load_preset()` and `save_preset()` to handle partial presets and provide better error
messages.
- Updated presets demo in examples to show how to use the new preset features.
### Tests
- Added new tests for partial presets to the `test_presets.py` and updated existing ones.
---
## [0.21.5] – 2025-11-13
### Changed
- Updated cobweb plotting function so that it works with new v2 sim or model objects.
- Updated logistic_map.py example to use themes, grid layouts, and cobweb plots.
---
## [0.21.4] – 2025-11-13
### Added
- Added `state_vector()`, `param_vector()`, `state_dict()`, and `param_dict()` methods to `Sim`
class. They let getting state and parameter values as arrays or dictionaries from the current
session, model defaults, or saved snapshots.
### Changed
- Updated `izhikevich.py` to show how to access state/parameter values from snapshots.
---
## [0.21.3] – 2025-11-13
### Added
- Added `Sim.config()` method to set default simulation settings like dt, max_steps, record options,
and capacities. Stepper specific parameters can also be set with this method. They are forwarded
to `stepper_config()`.
- Added `facet.py` example showing how to create faceted plots with multiple subplots.
- Added support for colored bands in plotting functions, allowing bands to have custom colors.
### Changed
- Renamed plotting parameters from `events` to `vlines` for better clarity in time series plots.
- Updated `izhikevich.py` example to use `sim.config(dt=0.01)` and added enhanced plotting features
like ylim, colored bands, and vertical lines.
- Moved plot related examples into the `examples/plot` folder.
---
## [0.21.2] – 2025-11-13
### Added
- Introduced `Sim.assign()` method in `src/dynlib/runtime/sim.py` for assigning state and parameter
values dynamically during a simulation session.
### Changed
- Enhanced `_select_seed()` in `Sim` to use current session state values as defaults for
`resume=False` runs, allowing explicit overrides with `ic` and `params` arguments.
- Updated `examples/izhikevich.py` to demonstrate dynamic parameter assignment using `Sim.assign()`.
### Tests
- Added `tests/unit/test_sim_assign.py` to validate the functionality of `Sim.assign()`.
---
## [0.21.1] – 2025-11-13
### Changed
- Removed `tomli` package fallbacks and updated Python requirement as >= 3.11 instead of 3.10.
---
## [0.21.0] – 2025-11-12
### Added
- Reintroduced `guards.py` in `src/dynlib/compiler/` to provide universal finiteness checks for
steppers. This guard is applied universally to all steppers inside the runners and adaptive
steppers also use these checks internally inside their step size calculation loops.
- Added `allfinite1d` and `allfinite_scalar` functions for finiteness checks.
- Integrated `guards` with `runner`, `runner_discrete`, and `rk45` stepper for NaN/Inf detection.
- Added `test_nan_inf_guards.py` to validate the functionality of `guards`.
### Changed
- Updated `RK45Spec` to use `guards` for internal loops.
- Enhanced `build_callables` in `src/dynlib/compiler/build.py` to configure finiteness guards
based on JIT settings.
---
## [0.20.2] – 2025-11-12
### Changed
- Removed `guards.py` because it was poorly designed and implemented; was causing a lot of numba
compatibility and caching issues.
### Known Issues
- All tests pass now but there is no NaN/Inf checks anywhere at this point.
---
## [0.20.1] – 2025-11-12
### Fixed
- `build()` warm-up function `_warmup_jit_runner` was generating the wrong runner when dtype is not
float64. Updated `_warmup_jit_runner` in `src/dynlib/compiler/build.py` to ensure stepper control
values are always float64, regardless of model dtype. This fixed wrong runner caching issue during
warm-up.
- Enhanced comments in `src/dynlib/compiler/build.py` to clarify the use of Python floats for stepper
configurations.
- Fixed exception handling in `_jit_compile_with_disk_cache` in `src/dynlib/compiler/jit/compile.py`
to catch `DiskCacheUnavailable` correctly.
### Known Issues
- `rk45.py` tests are failing.
---
## [0.20.0] – 2025-11-12
### Added
- Introduced `Segment` dataclass in `src/dynlib/runtime/sim.py` to represent simulation segments.
- Added `SegmentsView` and `SegmentView` classes in `src/dynlib/runtime/results_api.py` for
accessing recorded simulation segments.
- Implemented `Sim.name_segment` and `Sim.name_last_segment` methods for renaming simulation
segments.
### Changed
- Enhanced `Sim.run` in `src/dynlib/runtime/sim.py` to support tagging and recording simulation
segments.
- Updated `Sim.results` to include segment metadata in `ResultsView`.
### Tests
- Added new tests to `tests/steppers/common/test_sim_session.py` to validate segment functionality.
---
## [0.19.4] – 2025-11-12
### Added
- Added `examples/collatz.py` to demonstrate map simulation with integer dtype and ternary if
usage.
### Changed
- [params] table was mandatory in DSL model declarations. Made it optional.
### Fixed
- Fixed a bug in `src/dynlib/compiler/codegen/runner_discrete.py` where the `header` variable was
causing caching issues. Replaced `textwrap.dedent` with `inspect.cleandoc`during header creation.
Did the same with `runner.py` for symmetry.
- When using `dtype=int64` (or other integer dtypes) for models, the stepper control arrays (dt_next,
t_prop, err_est) were incorrectly being created with the model's dtype instead of float64. This
was causing non-monotone time series. Now they are always float64 alongside tracked time.
### Tests
- Added `test_int_dtype.py` for testing integer data type usage with maps.
---
## [0.19.3] – 2025-11-11
### Added
- Introduced `guards.py` in `src/dynlib/runtime/` to provide universal finiteness checks for
steppers. This guard is applied universally to all steppers inside the runners and adaptive
steppers also use these checks internally inside their step size calculation loops.
- Added `allfinite1d` function and `configure_allfinite_guard` to toggle between Python and
JIT implementations.
### Changed
- Refactored `runner.py` and `runner_discrete.py` to use `guards.allfinite1d` for finiteness
checks.
- Updated `EulerSpec`, `RK4Spec`, `RK45Spec`, and `MapSpec` to remove redundant finiteness
checks and rely on `guards.allfinite1d`.
- Enhanced `build_callables` in `src/dynlib/compiler/build.py` to configure finiteness guards
based on JIT settings.
---
## [0.19.2] – 2025-11-11
### Changed
- Gathered all ode-solver steppers under `src/dynlib/steppers/ode` folder.
---
## [0.19.1] – 2025-11-11
### Added
- Added `examples/logistic_map.py` to demonstrate the logistic map simulation using the new
discrete runner.
- Implemented `RunnerDiskCache` in `src/dynlib/compiler/codegen/_runner_cache.py` for managing
disk-backed runner caching.
### Changed
- Refactored `runner` and `runner_discrete` to use `RunnerDiskCache` for caching.
- Enhanced `src/dynlib/compiler/codegen/runner.py` and `runner_discrete.py` to improve modularity
and maintainability.
### Fixed
- `runner_discrete` jit option was not implemented properly and it was accepting a wrong argument.
Refactored `runner_discrete` so that now it completely mirrors `runner`. The caching was also
not working properly. The new common `RunnerDiskCache` solved that issue.
---
## [0.19.0] – 2025-11-11
### Changed
- Split runners into two: `runner_discrete` for discrete-time models; `runner` (old `runner`
untouched) for continuous-time models.
- Sim.run: replaced legacy `t_end` with `T` (continuous end time) and added `N` for discrete
iteration counts. `Sim` now distinguishes discrete (maps) vs continuous systems and enforces
the correct parameters, improving transient and resume behavior.
- Wrapper: `run_with_wrapper` accepts `discrete`/`target_steps` and passes a general `horizon`
argument to the runner so it can work in iteration (N) or time (T) modes.
- Build/codegen: added and exported a discrete runner (`runner_discrete`) and wired build to
select the discrete runner for `map`-kind steppers; disk-cache configuration was added for
the discrete runner as well.
- Steppers: exported the discrete `map` stepper so it is available for selection/registration.
- Docs/examples: updated usages to call `sim.run(T=...)` where appropriate (replacing
`t_end`).
### Tests
- Reorganized whole tests folder.
- Added `test_discrete_runner.py` test for new `runner_discrete` and maps.
---
## [0.18.0] – 2025-11-11
### Added
- Added source code export functionality for compiled models. All compiled models now store the
generated Python source code for RHS, events, and stepper functions.
- Added `export_model_sources(model, output_dir)` function in `src/dynlib/compiler/build.py` to
export all compiled sources to a directory for inspection and debugging.
- Added source code fields to `FullModel` and `Model` classes:
- `rhs_source`: Generated RHS (right-hand side) function source code
- `events_pre_source`: Pre-step event handler source code
- `events_post_source`: Post-step event handler source code
- `stepper_source`: Numerical integration stepper source code
- Added `examples/export_sources_demo.py` demonstrating basic source export.
- Added comprehensive documentation in `docs/export_sources.md`.
### Changed
- Modified `CompiledPieces` dataclass to store source code from compilation.
- Updated `build_callables()` to preserve source code through the compilation pipeline.
- Enhanced `_StepperCacheEntry` to cache stepper source code for reuse.
- Source code is now available regardless of `disk_cache` setting (always stored in model object).
---
## [0.17.1] – 2025-11-11
### Added
- Added `setup()` helper to `src/dynlib/__init__.py`. It combines `build()` + `Sim()` calls. It is
more convenient for end users.
---
## [0.17.0] – 2025-11-11
### Added
- Added presets feature for quick storage of state/param values. Presets can be defined inside model
file or in external toml files.
- Added `_read_presets` function in `src/dynlib/dsl/parser.py` to parse `[presets.<name>]` blocks
from TOML files.
- Introduced `PresetSpec` dataclass in `src/dynlib/dsl/spec.py` to represent presets in the model
DSL.
- Added `Sim` presets API in `src/dynlib/runtime/sim.py`:
- `list_presets(pattern)`: Lists preset names matching a glob pattern.
- `apply_preset(name)`: Applies a preset to the current session.
- `load_preset(name_or_pattern, path, on_conflict)`: Loads presets from a TOML file.
- `save_preset(name, path, include_states, overwrite)`: Saves a preset to a TOML file.
- Added `examples/presets_demo.py` to demonstrate the presets feature.
### Changed
- Enhanced `build_spec` in `src/dynlib/dsl/spec.py` to validate and include presets in the model
specification.
- Updated `validate_tables` in `src/dynlib/dsl/schema.py` to validate the `[presets]` table.
- Enhanced `Sim` initialization in `src/dynlib/runtime/sim.py` to auto-load inline presets from the
model specification.
### Tests
- Added `tests/unit/test_presets.py` to cover inline and file-based presets, including validation,
loading, saving, and error handling.
---
## [0.16.2] – 2025-11-10
### Added
- Persisted runtime `stepper_config` data in `SessionState`, snapshots, and snapshot metadata so
resumes continue with the exact tolerances last used (plus field-name lists for inspection).
- Added `Sim.stepper_config(**kwargs)` helper and extended `session_state_summary()` diagnostics with
stepper-config previews/digests.
---
## [0.16.1] – 2025-11-10
### Added
- Introduced snapshot export/import functionality in `Sim`:
- `export_snapshot()`: Exports session state to disk as a strict snapshot file.
- `import_snapshot()`: Imports session state from a snapshot file, replacing the current session.
- `inspect_snapshot()`: Returns parsed metadata from a snapshot file without modifying simulation
state.
- Added `examples/snapshot_demo.py` to demonstrate snapshot export/import and inspection.
### Changed
- Updated `Sim` class in `src/dynlib/runtime/sim.py`:
- Added internal helpers for snapshot handling, including `_snapshot_pick_state`,
`_snapshot_build_meta`, `_snapshot_write_npz`, `_snapshot_read_npz`, and `_snapshot_restore`.
- Enhanced `Sim.run()` to support snapshot-based workflows.
### Tests
- Added `tests/integration/test_snapshot_persistence.py` to validate snapshot export/import
functionality.
---
## [0.16.0] – 2025-11-10
### Added
- `Sim` now tracks an internal `SessionState` so `run(resume=True)` continues from the exact last
integrator conditions (time, state, params, dt, workspace).
- Snapshot API: `create_snapshot()`, `reset()`, and `list_snapshots()` capture/restore
SessionState, with an auto `initial` snapshot created before the first run.
- New helpers `session_state_summary()`, `can_resume()`, and `compat_check()` surface resume
diagnostics.
- Seam-aware result stitching drops duplicate seam samples, offsets STEP/EVT indices, and asserts
monotone time; events referencing a dropped seam record are dropped (documented policy).
### Changed
- `Results` contract now includes `final_params_view`, `t_final`, `final_dt`, `step_count_final`,
and `final_stepper_ws`. `run_with_wrapper` captures these values along with snapshots of the
stepper workspace and accepts a `workspace_seed` for resume.
- Runner redefines `EVT_INDEX` as the owning record index (or `-1` when no record exists);
downstream docs/tests updated accordingly.
### Tests
- Added integration coverage for resume stitching, record-off then resume, and snapshot reset in
`tests/integration/test_sim_session.py`.
---
## [0.15.3] – 2025-11-09
### Added
- `Sim.run()` accepts a `transient` warm-up duration that advances the model before recording while
keeping events functional and resetting the public time axis to `t0`.
- `Results` now exposes the final committed state via `final_state_view` for scenarios that need to
reuse the converged state (e.g., transient warm-up, chained simulations).
### Changed
- `run_with_wrapper` captures the final committed state and stores it on the returned `Results`.
---
## [0.15.2] – 2025-11-09
### Changed
- Renamed `run()` args:
- `y0` -> `ic`
- `record_every_step` -> `record_interval`
- Renamed `build()` args:
- `stepper_name` -> `stepper`
- `model_dtype` -> `dtype`
---
## [0.15.1] – 2025-11-09
### Changed
- Forgot to add `**stepper_kwargs` in the previous version. Refactored `Sim.run()` in `sim.py` to
accept `**stepper_kwargs` for runtime overrides instead of explicit stepper parameters.
- Updated `_build_stepper_config()` in `Sim` to construct stepper configuration arrays from
`**stepper_kwargs`.
---
## [0.15.0] – 2025-11-09
### Added
- Introduced runtime stepper configuration system. Now steppers can declare their internal config
values. During build process a read-only struct buffer is filled to pass these values into the
steppers. `Sim.run()` can pass these values at runtime. If not provifed, then model [sim] defaults
are used. If it is also not present, then internal defaults of the stepper are used. Here are the
details:
- `config_spec()`: Returns dataclass type or None for stepper configuration.
- `default_config(model_spec)`: Creates default configuration with model-specific overrides.
- `pack_config(config)`: Packs configuration into a float64 array.
- Added `stepper_config` parameter to `runner` ABI in `src/dynlib/runtime/runner_api.py`.
- Enhanced `Sim.run()` in `src/dynlib/runtime/sim.py` to accept `**stepper_kwargs` for runtime
overrides.
- Added `RK45Config` dataclass in `src/dynlib/steppers/rk45.py` with runtime parameters:
- `atol`, `rtol` (tolerances)
- `safety`, `min_factor`, `max_factor` (step control)
- `max_tries`, `min_step` (failure thresholds)
- Added `_build_stepper_config()` in `Sim` to construct stepper configuration arrays.
### Changed
- Updated `run_with_wrapper` in `src/dynlib/runtime/wrapper.py` to pass `stepper_config` to the
runner.
- Enhanced `_warmup_jit_runner` in `src/dynlib/compiler/build.py` to initialize `stepper_config`
during warmup.
- Refactored `RK45Spec` in `src/dynlib/steppers/rk45.py` to use `RK45Config` for runtime
configuration.
- Updated `runner` in `src/dynlib/compiler/codegen/runner.py` to accept `stepper_config` as a
parameter.
- Modified `EulerSpec` and `RK4Spec` to return `None` for `config_spec()` and handle empty
configurations gracefully.
### Tests
- Added tests for runtime stepper configuration in `tests/unit/test_stepper_config.py`.
---
## [0.14.2] – 2025-11-08
### Added
- Previously only runners were cached. Added disk caching support for stepper and triplet functions
in `runner.py`. This way all jittable parts are cached. This improved build times but compilation
cost is unavoidable. Use numba for long simulations or simulations / analyses that call `run()`
repeatedly. Use disk cache only for fixed models.
### Changed
- Enhanced `build_callables` in `src/dynlib/compiler/build.py` to include disk caching for RHS and
event functions.
- Improved `emit_rhs_and_events` in `src/dynlib/compiler/codegen/emitter.py` to return source code
for RHS and events.
- Refactored `jit_compile` in `src/dynlib/compiler/jit/compile.py` to support disk caching.
---
## [0.14.1] – 2025-11-08
### Added
- Introduced `Timer` utility in `src/dynlib/utils/timer.py` for measuring execution time.
- Added `izhikevich_benchmark.py` to observe build and run times with and without JIT and disk
caching.
### Changed
- Enhanced `build` function in `src/dynlib/compiler/build.py` to support warm-up for JIT runners.
So `build()` also causes numba compilation instead of lazy compilation after `run()` call.
- Improved error handling in `src/dynlib/compiler/codegen/runner.py` for disk cache unavailability.
### Fixed
- Updated `resolve_cache_root` in `src/dynlib/compiler/paths.py` to handle unwritable cache
directories gracefully. Cache root resolution now probes writability and falls back to a temp
directory when the platform default cannot be written to (such as sandboxed environments),
ensuring disk caching actually speeds up repeated builds instead of silently falling back to
in-memory JIT.
---
## [0.14.0] – 2025-11-07
### Added
- Introduced opt-in disk-backed runner caching via `build(..., disk_cache=...)`, including
configurable cache roots, deterministic digesting, and automatic regeneration on corruption.
- `cache_root=True`: persistent on-disk cache.
- `cache_root=False`: in-memory (per-process) JIT only; no files written.
- Config defines the cache root. If not available platform defaults are used:
Linux: ${XDG_CACHE_HOME:-~/.cache}/dynlib
macOS: ~/Library/Caches/dynlib
Windows: %LOCALAPPDATA%\dynlib\Cache
### Tests
- Added `tests/unit/test_runner_diskcache.py` to cover materialization, reuse, recovery, and
fallback scenarios for the new cache layer.
---
## [0.13.2] – 2025-11-07
### Fixed
- The runner was dropping records when buffer growth was triggered. Enhanced `runner` function
in `src/dynlib/compiler/codegen/runner.py`:
- Added logic to handle pending steps before growth.
- Improved recording mechanism for steps during re-entries.
---
## [0.13.1] – 2025-11-07
### Fixed
- Buffer reallocation was resetting the wrapper time value. Refactored `run_with_wrapper` in
`src/dynlib/runtime/wrapper.py` to track committed time and step size for re-entries,
ensuring that reallocation does not corrupt recording.
### Added
- Added `Sim` to `__all__` in `src/dynlib/__init__.py` for better accessibility.
- Added `izhikevich.py` example in `examples/` demonstrating neuron spiking behavior.
### Tests
- `test_euler_growth_matches_reference` in `tests/integration/test_euler_basic.py` now ensures
buffer growth does not alter recorded trajectories.
---
## [0.13.0] – 2025-11-07
### Added
- Introduced `Sim.results` and `Sim.raw_results` methods for accessing simulation results. The
first one returns new `ResultsView` object while the latter returns old low-level `Results` object.
- Added `EventAccessor` and `EventGroupView` in `src/dynlib/runtime/results_api.py` for grouped
event access.
### Changed
- Updated `Sim.run` in `src/dynlib/runtime/sim.py` to return `None` and store results internally.
### Fixed
- Updated tests for the new `ResultsView` API.
- Added `test_sim_results_api.py` test for testing the new `ResultsView` API.
---
## [0.12.5] – 2025-11-07
### Changed
- Removed unused `src/dynlib/utils/arrays.py` and `utils` folder because user inputs are always
copied with `np.array()` and this file is not useful right now.
---
## [0.12.4] – 2025-11-07
### Changed
- Updated `validate_stepper_function` in `src/dynlib/compiler/codegen/validate.py` to include
`StructSpec` validation.
- Enhanced `report_validation_issues` to handle warnings and errors more effectively.
- Modified `build` function in `src/dynlib/compiler/build.py` to pass `StructSpec` to
`validate_stepper_function`.
### Added
- Introduced `test_stepper_guardrails.py` in `tests/unit/` to validate `StructSpec` sizes,
persistence flags, and bank assignments.
- Added new validation rules for `iw0` and `bw0` banks to reject float assignments.
- Added warnings for ephemeral banks being read before write.
---
## [0.12.3] – 2025-11-07
### Added
- Introduced `StepperKindMismatchError` to handle mismatched stepper and model kinds.
### Changed
- Updated `build` function to validate stepper kind against model kind and raise
`StepperKindMismatchError` if incompatible.
---
## [0.12.2] – 2025-11-07
### Changed
- Removed `priority` field from `ModSpec` in `src/dynlib/compiler/mods.py`.
- Updated exclusivity handling in `apply_mods_v2` to enforce stricter group rules.
- Improved error messages for exclusivity conflicts in `src/dynlib/compiler/mods.py`.
### Tests
- Removed priority fields from `tests/unit/test_mods.py` .
- Updated `test_mods_group_exclusive_conflict_raises` to validate stricter exclusivity rules.
---
## [0.12.1] – 2025-11-07
### Added
- TODO.md, ISSUES.md files.
### Changed
- Mods won't change [Sim] defaults in models. This is documented.
---
## [0.12.0] – 2025-11-06
### Added
- Event tagging feature for compile-time metadata and filtering:
- Added `tags: Tuple[str, ...]` field to `EventSpec` dataclass in `src/dynlib/dsl/spec.py`
- Tags are immutable, order-stable (sorted), and deduplicated tuples
- Compile-time only; no ABI or runtime impact
- Added `tag_index: Dict[str, Tuple[str, ...]]` to `ModelSpec` for fast reverse lookup
- Tag index maps each tag to tuple of event names that have that tag
- DSL support for event tags:
- Parser accepts `tags = ["tag1", "tag2", ...]` under each event in TOML files
- Empty or absent tags field defaults to empty tuple (no tags)
- Tags are normalized: duplicates removed, alphabetically sorted
- Tag validation in `src/dynlib/dsl/astcheck.py`:
- Added `validate_event_tags` function with pattern `[A-Za-z_][A-Za-z0-9_-]*`
- Tags must start with letter or underscore, contain only alphanumerics, underscores, hyphens
- Empty tags rejected; non-string tags caught by parser
- Duplicates normalized away (not an error)
### Changed
- Spec hash includes tags for deterministic cache invalidation.
- Updated `_json_canon` in `src/dynlib/dsl/spec.py` to serialize tags in EventSpec and tag_index
in ModelSpec.
- Added helper `_build_tag_index` to construct reverse index during spec building.
### Tests
- Added comprehensive test suite in `tests/unit/test_event_tags.py`:
- Tag parsing, normalization, and validation
- Tag index construction and event lookup
- Format validation (valid slugs, invalid special chars, empty tags)
- Spec hash stability and changes with tags
- Integration with TOML file loading
- Added test data file `tests/data/models/tagged_events.toml` demonstrating tag usage.
---
## [0.11.3] – 2025-11-06
### Changed
- Exclusive groups now raise a ModelLoadError when more than one exclusive mod is supplied,
so conflicts no longer slip through unnoticed.
- `src/dynlib/compiler/mods.py`: updated exclusivity docs and enforce a conflict check
that raises with the conflicting mod names instead of silently selecting a winner.
### Tests
- Replaced the previous “pick a winner” assertion with a conflict-raises check in
`tests/unit/test_mods.py`.
---
## [0.11.2] – 2025-11-06
### Changed
- Updated `_apply_remove` in `src/dynlib/compiler/mods.py`:
- Added validation to raise errors for non-existent `events`, `aux`, and `functions` during
removal.
- Improved error messages for better debugging.
- Enhanced `_apply_replace` in `src/dynlib/compiler/mods.py`:
- Added validation to ensure replaced `events`, `aux`, and `functions` exist.
- Improved error handling for invalid replacements.
### Tests
- Added new test cases in `tests/unit/test_mods.py`:
- `test_mods_remove_nonexistent_event_raises`: Verifies error is raised for non-existent event
removal.
- `test_mods_replace_nonexistent_raises`: Ensures replacement of non-existent entities raises
errors.
- Updated `tests/unit/test_mods_aux_functions.py`:
- `test_remove_aux_nonexistent_raises`: Validates error handling for non-existent aux removal.
- `test_remove_functions_nonexistent_raises`: Ensures error is raised for non-existent function
removal.
---
## [0.11.1] – 2025-11-06
### Changed
- Updated `Results` class in `src/dynlib/runtime/results.py`:
- Added `status` field to store runner exit status.
- Added `ok` property to check if the runner exited cleanly.
- Updated docstrings to reflect the new `status` field.
- Enhanced `run_with_wrapper` function in `src/dynlib/runtime/wrapper.py`:
- Added `status` field to `Results` object returned by the function.
- Improved early termination handling with warnings for specific statuses.
- Refactored status handling logic for clarity.
### Tests
- New assertions for `status` and `ok` properties in `tests/unit/test_wrapper_reentry.py`.
---
## [0.11.0] – 2025-11-06
### Changed
- Removed `REJECT` status code from the stepper/runner contract:
- Clarified architectural contracts: fixed-step steppers do single attempts; adaptive steppers
handle internal accept/reject loops
- Runner never sees rejection codes; adaptive steppers (like RK45) handle retries internally
and only return terminal codes
- Updated status enum in `src/dynlib/runtime/runner_api.py`: removed `REJECT=1`
- Updated all exports in `src/dynlib/__init__.py` to remove `REJECT`
- Simplified runner comments in `src/dynlib/compiler/codegen/runner.py`
- Updated wrapper imports in `src/dynlib/runtime/wrapper.py`
- Stepper contract now clear: return `OK` (step accepted) or terminal codes (`NAN_DETECTED`,
`STEPFAIL`)
### Added
- NaN/Inf detection in fixed-step steppers:
- Added finiteness checks to Euler stepper (`src/dynlib/steppers/euler.py`)
- Added finiteness checks to RK4 stepper (`src/dynlib/steppers/rk4.py`)
- Both now return `NAN_DETECTED` if proposal contains non-finite values
- Maintains consistency with adaptive stepper (RK45) which already had such checks
---
## [0.10.2] – 2025-11-06
### Changed
- Dropped the legacy `EVT_TIME` buffer entirely; logged times live in `EVT_LOG_DATA`.
---
## [0.10.1] – 2025-11-06
### Changed
- Removed `record` key from events in favor of unified `log` mechanism:
- Events no longer support `record=True/False`
- Use `log=["t"]` to capture event occurrence times
- Use `log=["t", "x", ...]` to capture both time and other values
- The `"t"` signal is treated like any other loggable value in `EVT_LOG_DATA`
- Eliminates non-orthogonal design where `record` and `log` overlapped
- Event buffers only grow when events actually have `log` items
- Migration: Replace `record=true` with `log=["t"]`, or add `"t"` to existing log arrays
### Fixed
- Event buffer allocation is now more efficient:
- No buffer space wasted on events without logging
- Events only increment buffer counter when they have actual log data
### Changed
- Updated event function signature in `src/dynlib/compiler/codegen/emitter.py`:
- Old: `events_phase(...) -> (event_code, has_record, log_width)`
- New: `events_phase(...) -> (event_code, log_width)`
- Updated runner in `src/dynlib/compiler/codegen/runner.py`:
- Removed `has_record` conditional logic
- Simplified event recording: only fires when `log_width > 0`
- All log values (including `"t"`) go to `EVT_LOG_DATA`
- Updated `EventSpec` dataclass in `src/dynlib/dsl/spec.py`:
- Removed `record: bool` field
- Updated parsers to reject deprecated `record` key with helpful error:
- `src/dynlib/dsl/parser.py`: Raises error directing users to use `log=["t"]`
- `src/dynlib/compiler/mods.py`: Same validation in mod files
- Updated `src/dynlib/runtime/runner_api.py` documentation
### Tests
- Updated all test models and test cases to use `log=["t"]` instead of `record=True`.
---
## [0.10.0] – 2025-11-06
### Fixed
- Event logging now properly separates `record` and `log` functionality:
- Previously, events only logged if BOTH `record=True` AND `log` was non-empty, silently
ignoring events with `record=True` but empty `log=[]`
- `EVT_INDEX` was stuck at zero and log signal values were never materialized
- New behavior:
- `record=True` → logs event occurrence times to `EVT_TIME` and `EVT_CODE`
- `log=[signals]` → logs signal values to new `EVT_LOG_DATA` buffer (independent of `record`)
- Both features are now orthogonal and can be used independently or together
- `EVT_INDEX` now stores the log width (number of signals logged per event)
- Events with `log` but no `record` set `EVT_TIME=-1.0` as a sentinel
### Added
- New `EVT_LOG_DATA` buffer in `EventPools` to store logged signal values:
- Shape: `(cap_evt, max_log_width)` where `max_log_width` is computed from all events
- Values are model dtype (same as state variables)
- Accessible via `Results.EVT_LOG_DATA_view` property
- Helper function `_parse_log_signal()` in `src/dynlib/compiler/codegen/emitter.py`:
- Supports formats: `"x"` (state), `"param:a"`, `"aux:E"`, `"t"` (time)
- Validates that referenced symbols exist in the model spec
- Event log scratch buffer `evt_log_scratch` passed to runner for temporary log value storage
### Changed
- Event function signature in `src/dynlib/compiler/codegen/emitter.py`:
- Old: `events_phase(t, y_vec, params) -> event_code`
- New: `events_phase(t, y_vec, params, evt_log_scratch) -> (event_code, has_record, log_width)`
- Events now write log values into `evt_log_scratch` before returning
- Runner signature in `src/dynlib/compiler/codegen/runner.py`:
- Added `EVT_LOG_DATA` and `evt_log_scratch` parameters
- Runner now copies log data from scratch buffer to `EVT_LOG_DATA[m, :]` when `log_width > 0`
- Records event time/code only when `has_record=True`
- Updated `allocate_pools()` in `src/dynlib/runtime/buffers.py`:
- Added `max_log_width` parameter
- Allocates `EVT_LOG_DATA` with shape `(cap_evt, max(1, max_log_width))`
- Updated `grow_evt_arrays()` in `src/dynlib/runtime/buffers.py`:
- Added `dtype` parameter for allocating `EVT_LOG_DATA` with correct dtype
- Copies existing log data during growth
- Updated `Sim.run()` in `src/dynlib/runtime/sim.py`:
- Calculates `max_log_width` from event specs before calling wrapper
- Added `EVT_LOG_DATA` to forbidden writes in `src/dynlib/compiler/codegen/validate.py`
### Tests
- Updated `test_event_logging_basic()` in `tests/integration/test_event_logging.py`:
- Verifies `EVT_INDEX` contains log width (not zero)
- Checks that `EVT_LOG_DATA` contains logged signal values
- Validates logged values are within expected ranges
- Fixed `test_codegen_triplet.py` to use new event signature with scratch buffer
- Fixed `test_buffers_growth.py` to pass `max_log_width` and `dtype` parameters
---
## [0.9.0] – 2025-11-06
### Added
- Implemented complete mod support for DSL aux and functions:
- Added `add.aux` and `add.functions` verbs in `src/dynlib/compiler/mods.py`
- Added `replace.aux` and `replace.functions` verbs with existence validation
- Added `remove.aux` and `remove.functions` verbs (silent for non-existent items)
- Added `set.aux` and `set.functions` verbs with upsert semantics
- All verbs follow the same deterministic application order: remove → replace → add → set
### Changed
- Enhanced `_apply_remove`, `_apply_replace`, `_apply_add`, and `_apply_set` in
`src/dynlib/compiler/mods.py`:
- Extended all verb handlers to support aux and functions alongside existing events support
- Added `_normalize_function` helper for consistent function definition validation
- String expression validation for aux values
- Function args and expr validation matching parser requirements
### Tests
- Updated tests and removed redundant ones.
- Added 19 comprehensive tests in `tests/unit/test_mods_aux_functions.py`:
- `test_add_aux` - Adding new auxiliary variables
- `test_add_aux_duplicate_raises` - Duplicate detection
- `test_replace_aux` - Replacing existing aux expressions
- `test_replace_aux_nonexistent_raises` - Error on missing aux
- `test_remove_aux` - Removing aux variables
- `test_remove_aux_nonexistent_silent` - Silent handling of non-existent removals
- `test_set_aux_upsert` - Upsert semantics (create or update)
- `test_add_functions` - Adding new functions
- `test_add_functions_duplicate_raises` - Duplicate detection
- `test_replace_functions` - Replacing existing functions
- `test_replace_functions_nonexistent_raises` - Error on missing function
- `test_remove_functions` - Removing functions
- `test_remove_functions_nonexistent_silent` - Silent handling
- `test_set_functions_upsert` - Upsert semantics
- `test_verb_order_remove_then_add` - Verb ordering validation
- `test_multiple_mods_aux_and_functions` - Sequential application
- `test_invalid_aux_value_type` - Type validation for aux
- `test_invalid_function_args` - Args validation
- `test_invalid_function_expr` - Expr validation
---
## [0.8.0] – 2025-11-06
### Added
- DSL block equations are now parsed (previously they were omitted).
- Introduced `StructSpec` validation in `src/dynlib/steppers/base.py`:
- Ensures all sizes are non-negative integers.
- Validates compatibility with declared dense-output coefficients and history lengths.
- Added `validate_name_collisions` in `src/dynlib/dsl/schema.py`:
- Detects duplicate equation targets across RHS and block forms.
### Changed
- Enhanced `emit_rhs_and_events` in `src/dynlib/compiler/codegen/emitter.py`:
- Improved error messages for invalid LHS in block equations.
- Updated `build_spec` in `src/dynlib/dsl/spec.py`:
- Added stricter validation for unknown states and missing equals in equations.
### Tests
- Added unit tests in `tests/unit/test_equations_block_form.py`:
- Verified detection of duplicate targets and invalid LHS in block equations.
- Tested auxiliary variable usage and user-defined functions in block equations.
- Added integration tests in `tests/integration/test_block_equations_sim.py`:
- Verified simulation correctness with mixed RHS and block equations.
- Tested conservation laws and stepper compatibility.
---
## [0.7.1] – 2025-11-06
### Changed
- Updated `_edges_for_aux_and_functions` in `src/dynlib/dsl/astcheck.py`:
- Function dependencies now include references to auxiliary variables.
- Enhanced `build_spec` in `src/dynlib/dsl/spec.py`:
- Added validation steps for acyclic expressions, event legality, and function signatures.
### Tests
- Added integration tests in `tests/integration/test_semantic_validation.py`:
- Verified detection of cyclic dependencies in auxiliary variables and functions.
- Tested event legality and function argument validation.
---
## [0.7.0] – 2025-11-05
### Changed
- Centralized JIT compilation logic in `src/dynlib/compiler/jit/compile.py`:
- Introduced `jit_compile` function for consistent error handling.
- Updated `maybe_jit_triplet` to use `jit_compile`.
- Enhanced `runner` function in `src/dynlib/compiler/codegen/runner.py`:
- Added event logging for pre/post events.
- Improved event buffer growth handling.
- Updated `emit_rhs_and_events` in `src/dynlib/compiler/codegen/emitter.py`:
- Added event codes for logging-enabled events.
- Ensured default return value for no events fired.
- Modified `run_with_wrapper` in `src/dynlib/runtime/wrapper.py`:
- Preserved event cursor during buffer growth.
### Tests
- Added integration tests in `tests/integration/test_event_logging.py`:
- Verified event logging functionality.
- Tested multiple event firings and state captures.
- Updated `tests/data/models/decay_with_event.toml`:
- Added `log` field to reset event.
---
## [0.6.1] – 2025-11-05
### Changed
- Preceding newlines are removed from the inline model declarations. This way `inline:`
statement can be placed above `[model]` statements.
### Known Issues
- A FIX_PLAN.md file is created to implement planned but missing features. These features
will be added.
---
## [0.6.0] – 2025-11-05
### Added
- Implemented comprehensive path resolution system in `src/dynlib/compiler/paths.py`:
- Platform-specific config file locations (Linux/XDG, macOS, Windows)
- `DYNLIB_CONFIG` environment variable for custom config paths
- `DYN_MODEL_PATH` environment variable with prepend semantics for runtime tag additions
- `TAG://` URI scheme for model resolution from configured directories
- `inline:` URI scheme for embedding model definitions directly
- Support for absolute and relative paths with automatic `.toml` extension resolution
- Fragment selectors (`#mod=NAME`) for selecting embedded mods from files
- Path traversal prevention outside declared roots
- Clear error messages listing all searched candidates on failure
- Enhanced `build()` function in `src/dynlib/compiler/build.py`:
- Accepts both URI strings and ModelSpec objects
- `mods` parameter for applying multiple mod files via URIs
- `config` parameter for custom PathConfig
- Automatically resolves stepper from model's sim defaults if not specified
- Added `load_model_from_uri()` function for loading models with mod application
- New exception classes in `src/dynlib/errors.py`:
- `ModelNotFoundError`: lists all searched paths
- `ConfigError`: configuration file or environment errors
- `PathTraversalError`: security violation detection
- `AmbiguousModelError`: multiple files match extensionless reference
- Updated `src/dynlib/compiler/__init__.py` to export new public API
### Changed
- `build()` signature enhanced with optional `config` parameter
- `build()` now accepts `Union[ModelSpec, str]` for model parameter
### Tests
- Added 37 unit tests in `tests/unit/test_paths.py`:
- Config loading from all platforms
- Environment variable handling
- TAG:// resolution with multiple roots and first-match-wins
- inline:, absolute, relative path handling
- Fragment extraction
- Path traversal security checks
- Error message quality verification
- Added 16 integration tests in `tests/integration/test_uri_loading.py`:
- End-to-end model building from all URI schemes
- Embedded mod selection with fragments
- External mod file application
- Multiple mods in sequence
- Error handling with helpful diagnostics
- Backward compatibility with direct ModelSpec usage
### Documentation
- URI schemes supported:
- `inline: [model]\ntype='ode'\n...` - Direct TOML content
- `/abs/path/model.toml` - Absolute file path
- `relative/model.toml` - Relative to current working directory
- `TAG://model.toml` - Resolve using configured tag roots
- Any of above with `#mod=NAME` - Select embedded mod
- Config file format:
```toml
[paths]
proj = ["/home/user/models", "/opt/shared/models"]
user = "/home/user/personal/models"
```
- Config File Paths:
- Linux: `${XDG_CONFIG_HOME:-~/.config}/dynlib/config.toml`
- macOS: `~/Library/Application Support/dynlib/config.toml`
- Windows: `%APPDATA%\dynlib\config.toml`
- Environment variables:
- `DYNLIB_CONFIG=/custom/path/config.toml` - Override config location
- `DYN_MODEL_PATH=proj=/extra/path,/another:new=/path` - Add paths at runtime
---
## [0.5.0] – 2025-11-05
### Added
- Implemented `RK45Spec` in `src/dynlib/steppers/rk45.py` for Dormand-Prince adaptive stepper
with embedded 4th/5th order error estimation.
### Changed
- Enhanced buffer allocation in `src/dynlib/runtime/buffers.py`:
- Improved `allocate_pools` to handle workspace banks (sp, ss, sw0-sw3) with size 0 convention.
- Size 0 now means "allocate n_state elements", size >= 1 uses spec size as-is.
- Ensures all RK methods get sufficient workspace without ABI changes.
- Updated `RK4Spec` for the new banks allocation rule.
### Tests
- Added comprehensive integration tests in `tests/integration/test_rk4_rk45.py`:
- `test_rk4_accuracy`: Verifies RK4 achieves < 1e-5 relative error for exponential decay.
- `test_rk4_order_convergence`: Confirms 4th-order convergence (16x error reduction when
halving dt).
- `test_rk45_adaptive_accuracy`: Validates RK45 adaptive stepping accuracy.
- `test_rk45_step_adaptation`: Verifies RK45 outperforms Euler significantly.
- `test_rk4_jit_parity` and `test_rk45_jit_parity`: Ensure identical results with JIT on/off.
- `test_stepper_registration`: Confirms all steppers and aliases are properly registered.
- Added test models `tests/data/models/decay_rk4.toml` and `decay_rk45.toml`.
---
## [0.4.1] – 2025-11-05
### Added
- Added `get_runner` in `src/dynlib/compiler/codegen/runner.py` for obtaining a generic runner
function.
- Created `runner` function in `src/dynlib/compiler/codegen/runner.py` for fixed-step execution
with events and recording.
- Introduced `EulerSpec` in `src/dynlib/steppers/euler.py` for explicit Euler stepper
implementation.
### Changed
- Updated `build` in `src/dynlib/compiler/build.py` to use `get_runner` instead of generating
runner source code from string.
- Enhanced `emit` in `src/dynlib/steppers/euler.py` to return a callable Python function instead
of source code.
- Improved `StepperSpec` in `src/dynlib/steppers/base.py` to include detailed docstrings for
`emit` method.
---
## [0.4.0] – 2025-11-04
### Added
- Introduced `Sim.run` method in `src/dynlib/runtime/sim.py` for executing simulations with
compiled models.
- Added `run_with_wrapper` in `src/dynlib/runtime/wrapper.py` for orchestrating simulation runs.
- Created `EulerSpec` in `src/dynlib/steppers/euler.py` for explicit Euler stepper implementation.
### Changed
- Updated `allocate_pools` in `src/dynlib/runtime/buffers.py` to ensure `sw0` size is at least
`n_state` for steppers requiring workspace.
- Enhanced `Model` dataclass in `src/dynlib/runtime/model.py` with additional attributes for
compiled stepper and runner callables.
### Tests
- Added integration tests in `tests/integration/test_euler_basic.py` for Euler simulations, event
handling, and buffer growth.
- Updated unit tests in `tests/unit/test_wrapper_reentry.py` to validate re-entry logic for buffer
growth.
---
## [0.3.1] – 2025-11-04
### Fixed
- Removed redundant validation functions `validate_dtype_rules` and `validate_equation_targets`
from `src/dynlib/dsl/astcheck.py`.
- Fixed unused import `from email.mime import message` in `src/dynlib/errors.py`.
### Tests
- Updated tests in `tests/unit/test_ast_check.py` to reflect changes in validation logic.
---
## [0.3.0] – 2025-11-04
### Added
- Introduced `build_callables` in `src/dynlib/compiler/build.py` for generating RHS and event
callables.
- Added `emit_rhs_and_events` in `src/dynlib/compiler/codegen/emitter.py` for code generation.
- Implemented `JITCache` in `src/dynlib/compiler/jit/cache.py` for caching compiled callables.
- Added `maybe_jit_triplet` in `src/dynlib/compiler/jit/compile.py` for JIT compilation toggle.
- Created `Model` dataclass in `src/dynlib/runtime/model.py` for simulation models.
- Added `Sim` class in `src/dynlib/runtime/sim.py` as a placeholder for simulation runners.
### Changed
- Fixed column-major layout in `tests/unit/test_numba_probe.py` for recording buffers.
- Enhanced `_normalize_event` in `src/dynlib/compiler/mods.py` to handle nested TOML dicts for
actions.
- Improved `_read_events` in `src/dynlib/dsl/parser.py` to support nested TOML keys for event
actions.
### Tests
- Added unit tests for `build_callables` in `tests/unit/test_codegen_triplet.py`.
---
## [0.2.0] – 2025-11-04
### Added
- Implemented `parse_model_v2` in `src/dynlib/dsl/parser.py` for parsing DSL TOML into normalized
models.
- Added validation functions in `src/dynlib/dsl/schema.py` for model headers, tables, and name
collisions.
- Introduced `SimDefaults`, `EventSpec`, and `ModelSpec` dataclasses in
`src/dynlib/dsl/spec.py`.
- Added `build_spec` and `compute_spec_hash` in `src/dynlib/dsl/spec.py` for model specification
and hashing.
- Created `src/dynlib/errors.py` for custom exceptions like `ModelLoadError`.
- Added `allocate_pools`, `grow_rec_arrays`, and `grow_evt_arrays` in `src/dynlib/runtime/buffers.py`
for memory management.
- Introduced `Results` dataclass in `src/dynlib/runtime/results.py` for simulation outputs.
- Added `run_with_wrapper` in `src/dynlib/runtime/wrapper.py` for orchestrating simulation runs.
### Tests
- Added unit tests for `parse_model_v2` in `tests/unit/test_ast_check.py`.
- Added tests for buffer growth in `tests/unit/test_buffers_growth.py`.
- Added schema and parser tests in `tests/unit/test_dsl_schema_parser.py`.
- Added tests for `build_spec` and `compute_spec_hash` in `tests/unit/test_dsl_spec.py`.
- Added tests for `apply_mods_v2` in `tests/unit/test_mods.py`.
- Added re-entry tests for `run_with_wrapper` in `tests/unit/test_wrapper_reentry.py`.
---
## [0.1.0] – 2025-11-04
### Added
- Introduced `src/dynlib/__init__.py` to re-export constants, types, steppers, and utilities
for stable imports.
- Added `src/dynlib/runtime/runner_api.py` defining stable exit/status codes and the frozen
runner ABI.
- Added `src/dynlib/runtime/types.py` for type literals like `Kind`, `TimeCtrl`, and `Scheme`.
- Added `src/dynlib/steppers/base.py` with metadata and struct specifications for steppers.
- Added `src/dynlib/steppers/registry.py` to manage stepper registration and retrieval.
- Added `src/dynlib/utils/arrays.py` with utility functions for array validation and slicing.
### Tests
- Added `tests/unit/test_numba_probe.py` to validate future JIT-compiled runner's numba
compliance.
---
## [0.0.0] – 2025-11-03
### Changed
- Initial commit.