@@ -54,12 +54,12 @@ Chronopt provides a comprehensive Python API with full type hints and automatic
The Rust core provides high-performance implementations of all algorithms.
-[:octicons-arrow-right-24: Rust Documentation on docs.rs](https://docs.rs/chronopt/latest/chronopt/)
+[:octicons-arrow-right-24: Rust Documentation on docs.rs](https://docs.rs/diffid/latest/diffid/)
## Module Structure
```
-chronopt/
+diffid/
āāā ScalarBuilder # Direct function optimisation
āāā DiffsolBuilder # ODE fitting with DiffSL/Diffsol
āāā VectorBuilder # Custom solver integration
@@ -80,7 +80,7 @@ chronopt/
All Python functions include comprehensive type hints:
```python
-from chronopt import ScalarBuilder, CMAES, OptimisationResults
+from diffid import ScalarBuilder, CMAES, OptimisationResults
import numpy.typing as npt
def optimize_function(
@@ -106,7 +106,7 @@ All builders maintain parameter order based on the sequence of `.with_parameter(
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_parameter("x", 1.0) # Index 0
.with_parameter("y", 2.0) # Index 1
)
diff --git a/docs/api-reference/python/builders.md b/docs/api-reference/python/builders.md
index c0a7833..80a78db 100644
--- a/docs/api-reference/python/builders.md
+++ b/docs/api-reference/python/builders.md
@@ -12,7 +12,7 @@ Builders provide a fluent API for constructing optimisation problems. Choose the
## ScalarBuilder
-::: chronopt.ScalarBuilder
+::: diffid.ScalarBuilder
options:
show_root_heading: true
show_source: false
@@ -27,13 +27,13 @@ Builders provide a fluent API for constructing optimisation problems. Choose the
```python
import numpy as np
-import chronopt as chron
+import diffid as chron
def rosenbrock(x):
return np.asarray([(1 - x[0])**2 + 100*(x[1] - x[0]**2)**2])
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.5)
.with_parameter("y", -1.5)
@@ -52,7 +52,7 @@ result = problem.optimise()
## DiffsolBuilder
-::: chronopt.DiffsolBuilder
+::: diffid.DiffsolBuilder
options:
show_root_heading: true
show_source: false
@@ -70,7 +70,7 @@ result = problem.optimise()
```python
import numpy as np
-import chronopt as chron
+import diffid as chron
dsl = """
in { r = 1, k = 1 }
@@ -83,14 +83,14 @@ observations = np.exp(-1.3 * t)
data = np.column_stack((t, observations))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
.with_backend("dense")
)
problem = builder.build()
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
result = optimiser.run(problem, [0.5, 0.5])
```
@@ -113,14 +113,14 @@ out_i { state1, state2 } # Optional: output variables
### When to Use
- Fitting ODE parameters to time-series data
-- Using Chronopt's built-in high-performance solver
+- Using Diffid's built-in high-performance solver
- Models expressible in DiffSL syntax
---
## VectorBuilder
-::: chronopt.VectorBuilder
+::: diffid.VectorBuilder
options:
show_root_heading: true
show_source: false
@@ -136,7 +136,7 @@ out_i { state1, state2 } # Optional: output variables
```python
import numpy as np
-import chronopt as chron
+import diffid as chron
def custom_solver(params):
"""Your custom ODE solver (e.g., using JAX/Diffrax)."""
@@ -150,7 +150,7 @@ observations = ... # Your experimental data
data = np.column_stack((t, observations))
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(custom_solver)
.with_data(data)
.with_parameter("alpha", 1.0)
@@ -181,7 +181,7 @@ See the [Custom Solvers Guide](../../guides/custom-solvers.md) for examples with
## Problem
-::: chronopt.Problem
+::: diffid.Problem
options:
show_root_heading: true
show_source: false
@@ -208,11 +208,11 @@ Builders use a fluent interface - chain methods in any order:
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(func)
.with_parameter("x", 1.0)
.with_parameter("y", 2.0)
- .with_cost_metric(chron.RMSE())
+ .with_cost_metric(diffid.RMSE())
)
```
@@ -221,7 +221,7 @@ builder = (
Builders are immutable - each method returns a new builder:
```python
-base = chron.ScalarBuilder().with_objective(func)
+base = diffid.ScalarBuilder().with_objective(func)
problem1 = base.with_parameter("x", 1.0).build()
problem2 = base.with_parameter("x", 2.0).build() # Different initial guess
@@ -233,7 +233,7 @@ Parameters are indexed in the order they're added:
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_parameter("y", 2.0) # Index 0
.with_parameter("x", 1.0) # Index 1
)
diff --git a/docs/api-reference/python/cost-metrics.md b/docs/api-reference/python/cost-metrics.md
index fdebf87..bda1cae 100644
--- a/docs/api-reference/python/cost-metrics.md
+++ b/docs/api-reference/python/cost-metrics.md
@@ -12,7 +12,7 @@ Cost metrics define how model predictions are compared to observations. They det
## CostMetric
-::: chronopt.CostMetric
+::: diffid.CostMetric
options:
show_root_heading: true
show_source: false
@@ -28,15 +28,15 @@ $$\text{SSE} = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2$$
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
# SSE is used by default, but can be explicit:
- # .with_cost_metric(chron.SSE())
+ # .with_cost_metric(diffid.SSE())
)
```
@@ -71,14 +71,14 @@ $$\text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2}$$
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
- .with_cost_metric(chron.RMSE())
+ .with_cost_metric(diffid.RMSE())
)
```
@@ -114,18 +114,18 @@ where $\sigma^2$ is estimated from residuals.
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
- .with_cost_metric(chron.GaussianNLL())
+ .with_cost_metric(diffid.GaussianNLL())
)
# Use with MCMC sampling for Bayesian inference
-sampler = chron.MetropolisHastings().with_max_iter(10000)
+sampler = diffid.MetropolisHastings().with_max_iter(10000)
result = sampler.run(problem, initial_guess)
```
@@ -200,7 +200,7 @@ To implement a custom cost metric:
2. Expose through Python bindings
3. Rebuild the package
-See the [Development Guide](../../development/architecture.md) for details on extending Chronopt.
+See the [Development Guide](../../development/architecture.md) for details on extending Diffid.
---
diff --git a/docs/api-reference/python/optimizers.md b/docs/api-reference/python/optimizers.md
index a12b22f..65e73ae 100644
--- a/docs/api-reference/python/optimizers.md
+++ b/docs/api-reference/python/optimizers.md
@@ -12,7 +12,7 @@ Optimisation algorithms for finding parameter values that minimise the objective
## Nelder-Mead
-::: chronopt.NelderMead
+::: diffid.NelderMead
options:
show_root_heading: true
show_source: false
@@ -20,11 +20,11 @@ Optimisation algorithms for finding parameter values that minimise the objective
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
# Create optimiser with custom settings
optimiser = (
- chron.NelderMead()
+ diffid.NelderMead()
.with_max_iter(5000)
.with_step_size(0.1)
.with_threshold(1e-6)
@@ -75,7 +75,7 @@ See the [Nelder-Mead Algorithm Guide](../../algorithms/optimizers/nelder-mead.md
## CMA-ES
-::: chronopt.CMAES
+::: diffid.CMAES
options:
show_root_heading: true
show_source: false
@@ -83,11 +83,11 @@ See the [Nelder-Mead Algorithm Guide](../../algorithms/optimizers/nelder-mead.md
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
# Create CMA-ES optimiser
optimiser = (
- chron.CMAES()
+ diffid.CMAES()
.with_max_iter(1000)
.with_step_size(0.5)
.with_population_size(20)
@@ -148,7 +148,7 @@ See the [CMA-ES Algorithm Guide](../../algorithms/optimizers/cmaes.md) for more
## Adam
-::: chronopt.Adam
+::: diffid.Adam
options:
show_root_heading: true
show_source: false
@@ -156,11 +156,11 @@ See the [CMA-ES Algorithm Guide](../../algorithms/optimizers/cmaes.md) for more
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
# Create Adam optimiser
optimiser = (
- chron.Adam()
+ diffid.Adam()
.with_max_iter(5000)
.with_step_size(0.01) # Learning rate
.with_betas(0.9, 0.999)
@@ -236,7 +236,7 @@ result = problem.optimise() # Uses Nelder-Mead with defaults
```python
optimiser = (
- chron.CMAES()
+ diffid.CMAES()
.with_max_iter(10000) # Maximum iterations
.with_threshold(1e-8) # Objective threshold
.with_patience(300.0) # Patience in seconds
@@ -253,7 +253,7 @@ The optimiser stops when:
For stochastic optimisers (CMA-ES), set a seed:
```python
-optimiser = chron.CMAES().with_seed(42)
+optimiser = diffid.CMAES().with_seed(42)
result1 = optimiser.run(problem, [0.0, 0.0])
result2 = optimiser.run(problem, [0.0, 0.0])
# result1 == result2 (same random sequence)
diff --git a/docs/api-reference/python/results.md b/docs/api-reference/python/results.md
index 557a05c..926bc60 100644
--- a/docs/api-reference/python/results.md
+++ b/docs/api-reference/python/results.md
@@ -4,7 +4,7 @@ Result objects returned by optimisers and samplers containing optimal parameters
## OptimisationResults
-::: chronopt.OptimisationResults
+::: diffid.OptimisationResults
options:
show_root_heading: true
show_source: false
@@ -14,7 +14,7 @@ All optimisers return an `OptimisationResults` object with the following attribu
### Example Usage
```python
-import chronopt as chron
+import diffid as chron
problem = builder.build()
result = problem.optimise()
@@ -46,7 +46,7 @@ Parameters are ordered according to `.with_parameter()` calls:
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_parameter("alpha", 1.0) # result.x[0]
.with_parameter("beta", 2.0) # result.x[1]
)
@@ -172,7 +172,7 @@ Results don't store parameter names. Track them manually if needed:
```python
param_names = ["alpha", "beta", "gamma"]
-builder = chron.ScalarBuilder().with_objective(func)
+builder = diffid.ScalarBuilder().with_objective(func)
for name in param_names:
builder = builder.with_parameter(name, 1.0)
diff --git a/docs/api-reference/python/samplers.md b/docs/api-reference/python/samplers.md
index bd0b20a..9c271e2 100644
--- a/docs/api-reference/python/samplers.md
+++ b/docs/api-reference/python/samplers.md
@@ -23,11 +23,11 @@ MCMC sampling for exploring parameter posterior distributions.
### Planned API
```python
-import chronopt as chron
+import diffid as chron
# Will be available in a future release
sampler = (
- chron.MetropolisHastings()
+ diffid.MetropolisHastings()
.with_max_iter(10000)
.with_step_size(0.1)
.with_burn_in(1000)
@@ -72,11 +72,11 @@ Nested sampling for calculating model evidence (marginal likelihood) for model c
### Planned API
```python
-import chronopt as chron
+import diffid as chron
# Will be available in a future release
sampler = (
- chron.DynamicNestedSampling()
+ diffid.DynamicNestedSampling()
.with_max_iter(5000)
.with_n_live_points(500)
.with_seed(42)
@@ -147,11 +147,11 @@ graph TD
```python
# Required for samplers
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
- .with_cost_metric(chron.GaussianNLL()) # Required!
+ .with_cost_metric(diffid.GaussianNLL()) # Required!
)
```
@@ -164,26 +164,26 @@ SSE and RMSE cannot be used with samplers as they lack probabilistic interpretat
Typical workflow: optimise first, then sample for uncertainty:
```python
-import chronopt as chron
+import diffid as chron
# 1. Build problem with GaussianNLL
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
- .with_cost_metric(chron.GaussianNLL())
+ .with_cost_metric(diffid.GaussianNLL())
)
problem = builder.build()
# 2. Find MAP estimate with optimiser
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
opt_result = optimiser.run(problem, [1.0])
print(f"MAP estimate: {opt_result.x}")
# 3. Sample around MAP for uncertainty (future API)
-# sampler = chron.MetropolisHastings().with_max_iter(10000)
+# sampler = diffid.MetropolisHastings().with_max_iter(10000)
# sample_result = sampler.run(problem, opt_result.x)
# print(f"Posterior mean: {sample_result.samples.mean(axis=0)}")
# print(f"Posterior std: {sample_result.samples.std(axis=0)}")
@@ -205,8 +205,8 @@ Once samplers are available, see:
Track sampler implementation progress:
-- [GitHub Issue #XXX](https://github.com/bradyplanden/chronopt) - Metropolis-Hastings
-- [GitHub Issue #XXX](https://github.com/bradyplanden/chronopt) - Dynamic Nested Sampling
+- [GitHub Issue #XXX](https://github.com/bradyplanden/diffid) - Metropolis-Hastings
+- [GitHub Issue #XXX](https://github.com/bradyplanden/diffid) - Dynamic Nested Sampling
---
diff --git a/docs/api-reference/rust/index.md b/docs/api-reference/rust/index.md
index f3d5a7a..09d9e1a 100644
--- a/docs/api-reference/rust/index.md
+++ b/docs/api-reference/rust/index.md
@@ -1,12 +1,12 @@
# Rust API Documentation
-Chronopt's Rust core provides high-performance implementations of all optimisation and sampling algorithms.
+Diffid's Rust core provides high-performance implementations of all optimisation and sampling algorithms.
## Official Documentation
The complete Rust API documentation is hosted on docs.rs:
-**[:octicons-arrow-right-24: Chronopt Rust Documentation on docs.rs](https://docs.rs/chronopt/latest/chronopt/)**
+**[:octicons-arrow-right-24: Diffid Rust Documentation on docs.rs](https://docs.rs/diffid/latest/diffid/)**
## When to Use the Rust API
@@ -16,14 +16,14 @@ Consider using the Rust crate directly when:
- Building **Rust-native applications**
- Need **zero-copy** data handling
- Deploying to **embedded systems** or **constrained environments**
-- Building **custom tooling** around Chronopt
+- Building **custom tooling** around Diffid
For most users, the Python API provides excellent performance with easier integration.
## Crate Structure
```
-chronopt/
+diffid/
āāā builders/ # Problem builders (ScalarBuilder, DiffsolBuilder, etc.)
āāā optimisers/ # Optimisation algorithms
ā āāā nelder_mead/ # Nelder-Mead simplex
@@ -38,7 +38,7 @@ chronopt/
## Quick Example
```rust
-use chronopt::prelude::*;
+use diffid::prelude::*;
use ndarray::array;
// Define objective function
@@ -64,13 +64,13 @@ fn main() {
}
```
-## Adding Chronopt to Your Project
+## Adding Diffid to Your Project
Add to your `Cargo.toml`:
```toml
[dependencies]
-chronopt = "0.2"
+diffid = "0.2"
ndarray = "0.15"
```
@@ -78,7 +78,7 @@ For ODE support with DiffSL:
```toml
[dependencies]
-chronopt = { version = "0.2", features = ["diffsol"] }
+diffid = { version = "0.2", features = ["diffsol"] }
```
## Key Rust Features
@@ -149,7 +149,7 @@ Key modules (click through on docs.rs for full details):
Problem construction with fluent API.
```rust
-pub use chronopt::builders::{ScalarBuilder, DiffsolBuilder, VectorBuilder};
+pub use diffid::builders::{ScalarBuilder, DiffsolBuilder, VectorBuilder};
```
### `optimisers`
@@ -157,7 +157,7 @@ pub use chronopt::builders::{ScalarBuilder, DiffsolBuilder, VectorBuilder};
Optimisation algorithms.
```rust
-pub use chronopt::optimisers::{NelderMead, CMAES, Adam};
+pub use diffid::optimisers::{NelderMead, CMAES, Adam};
```
### `cost`
@@ -165,7 +165,7 @@ pub use chronopt::optimisers::{NelderMead, CMAES, Adam};
Cost metrics for objective functions.
```rust
-pub use chronopt::cost::{CostMetric, SSE, RMSE, GaussianNLL};
+pub use diffid::cost::{CostMetric, SSE, RMSE, GaussianNLL};
```
### `problem`
@@ -173,7 +173,7 @@ pub use chronopt::cost::{CostMetric, SSE, RMSE, GaussianNLL};
Problem types and evaluation.
```rust
-pub use chronopt::problem::{Problem, ScalarProblem, VectorProblem};
+pub use diffid::problem::{Problem, ScalarProblem, VectorProblem};
```
## Performance Tips
@@ -194,7 +194,7 @@ cargo run --example rosenbrock
cargo run --example ode_fitting
```
-Browse examples on GitHub: [rust/examples/](https://github.com/bradyplanden/chronopt/tree/main/rust/examples)
+Browse examples on GitHub: [rust/examples/](https://github.com/bradyplanden/diffid/tree/main/rust/examples)
## See Also
diff --git a/docs/assets/diffid.drawio b/docs/assets/diffid.drawio
new file mode 100644
index 0000000..3882c4f
--- /dev/null
+++ b/docs/assets/diffid.drawio
@@ -0,0 +1,188 @@
+
diff --git a/docs/assets/diffid.svg b/docs/assets/diffid.svg
new file mode 100644
index 0000000..ef51290
--- /dev/null
+++ b/docs/assets/diffid.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/development/architecture.md b/docs/development/architecture.md
index 9576393..6c9f9df 100644
--- a/docs/development/architecture.md
+++ b/docs/development/architecture.md
@@ -3,7 +3,7 @@
## High-Level Overview
## Key Design Patterns
diff --git a/docs/development/contributing.md b/docs/development/contributing.md
index 1fe3fce..c28c44e 100644
--- a/docs/development/contributing.md
+++ b/docs/development/contributing.md
@@ -1,4 +1,4 @@
-# Contributing to Chronopt
+# Contributing to Diffid
!!! info "Coming Soon"
Detailed contributing guidelines are being written.
@@ -7,8 +7,8 @@
```bash
# Fork and clone
-git clone https://github.com/YOUR_USERNAME/chronopt.git
-cd chronopt
+git clone https://github.com/YOUR_USERNAME/diffid.git
+cd diffid
# Set up environment
uv sync
@@ -32,4 +32,4 @@ cargo test
## See Also
- [Architecture](architecture.md)
-- [GitHub Issues](https://github.com/bradyplanden/chronopt/issues)
+- [GitHub Issues](https://github.com/bradyplanden/diffid/issues)
diff --git a/docs/development/index.md b/docs/development/index.md
index 67ed534..72422d6 100644
--- a/docs/development/index.md
+++ b/docs/development/index.md
@@ -1,6 +1,6 @@
# Development
-Resources for contributors and developers working with Chronopt.
+Resources for contributors and developers working with Diffid.
## Getting Started with Development
@@ -18,7 +18,7 @@ Resources for contributors and developers working with Chronopt.
---
- Understanding Chronopt's Rust core and PyO3 bindings design.
+ Understanding Diffid's Rust core and PyO3 bindings design.
[:octicons-arrow-right-24: Architecture Overview](architecture.md)
@@ -28,8 +28,8 @@ Resources for contributors and developers working with Chronopt.
```bash
# Clone the repository
-git clone https://github.com/bradyplanden/chronopt.git
-cd chronopt
+git clone https://github.com/bradyplanden/diffid.git
+cd diffid
# Create Python environment
uv sync
@@ -72,7 +72,7 @@ cargo test && uv run pytest -v
If you modified Python bindings:
```bash
-uv run cargo run -p chronopt-py --no-default-features --features stubgen --bin generate_stubs
+uv run cargo run -p diffid-py --no-default-features --features stubgen --bin generate_stubs
```
### 5. Format and Lint
@@ -90,7 +90,7 @@ uv run ruff format .
## Project Structure
```
-chronopt/
+diffid/
āāā rust/ # Rust core implementation
ā āāā src/
ā ā āāā builders/ # Problem builders
@@ -101,10 +101,10 @@ chronopt/
ā āāā Cargo.toml
ā āāā tests/ # Rust tests
āāā python/ # Python bindings
-ā āāā src/chronopt/
+ā āāā src/diffid/
ā ā āāā __init__.py
-ā ā āāā _chronopt.pyi # Generated type stubs
-ā āāā chronopt/ # PyO3 bindings source
+ā ā āāā _diffid.pyi # Generated type stubs
+ā āāā diffid/ # PyO3 bindings source
āāā examples/ # Example scripts
āāā tests/ # Python tests
āāā docs/ # Documentation (this site)
@@ -144,7 +144,7 @@ Python integration tests in `tests/`:
```python
def test_optimisation():
- builder = chron.ScalarBuilder().with_objective(func)
+ builder = diffid.ScalarBuilder().with_objective(func)
# ...
assert result.success
```
diff --git a/docs/examples/gallery.md b/docs/examples/gallery.md
index 42dc673..f59d7ec 100644
--- a/docs/examples/gallery.md
+++ b/docs/examples/gallery.md
@@ -1,9 +1,9 @@
# Examples Gallery
-Visual gallery of Chronopt applications and use cases.
+Visual gallery of Diffid applications and use cases.
!!! info "Gallery Under Construction"
- This gallery is being populated with examples. Check the [examples directory](https://github.com/bradyplanden/chronopt/tree/main/examples) for current code.
+ This gallery is being populated with examples. Check the [examples directory](https://github.com/bradyplanden/diffid/tree/main/examples) for current code.
## Available Examples
@@ -14,8 +14,8 @@ Classic 2D optimisation test problem.
**Files:**
-- [python_problem.py](https://github.com/bradyplanden/chronopt/blob/main/examples/python_problem.py)
-- [python_contour.py](https://github.com/bradyplanden/chronopt/blob/main/examples/python_contour.py)
+- [python_problem.py](https://github.com/bradyplanden/diffid/blob/main/examples/python_problem.py)
+- [python_contour.py](https://github.com/bradyplanden/diffid/blob/main/examples/python_contour.py)
**Topics:** ScalarBuilder, contour plots, optimiser comparison
@@ -26,7 +26,7 @@ Classic 2D optimisation test problem.
#### Logistic Growth
Single-variable ODE with DiffSL.
-**File:** [logistic_growth.py](https://github.com/bradyplanden/chronopt/blob/main/examples/logistic_growth.py)
+**File:** [logistic_growth.py](https://github.com/bradyplanden/diffid/blob/main/examples/logistic_growth.py)
**Topics:** DiffsolBuilder, DiffSL syntax, data fitting
@@ -37,8 +37,8 @@ Physics-based model with event handling.
**Files:**
-- [bouncy_ball.py](https://github.com/bradyplanden/chronopt/blob/main/examples/bouncy_ball.py)
-- [bouncy_ball_sampling.py](https://github.com/bradyplanden/chronopt/blob/main/examples/bouncy_ball_sampling.py)
+- [bouncy_ball.py](https://github.com/bradyplanden/diffid/blob/main/examples/bouncy_ball.py)
+- [bouncy_ball_sampling.py](https://github.com/bradyplanden/diffid/blob/main/examples/bouncy_ball_sampling.py)
**Topics:** Event detection, parameter uncertainty, MCMC
@@ -51,8 +51,8 @@ Comparing different bicycle dynamics formulations.
**Files:**
-- [bicycle_model_diffsol.py](https://github.com/bradyplanden/chronopt/blob/main/examples/bicycle_model_diffsol.py)
-- [bicycle_model_evidence.py](https://github.com/bradyplanden/chronopt/blob/main/examples/bicycle_model_evidence.py)
+- [bicycle_model_diffsol.py](https://github.com/bradyplanden/diffid/blob/main/examples/bicycle_model_diffsol.py)
+- [bicycle_model_evidence.py](https://github.com/bradyplanden/diffid/blob/main/examples/bicycle_model_evidence.py)
**Topics:** Model selection, evidence calculation, Bayes factors
@@ -65,9 +65,9 @@ Lotka-Volterra equations with multiple solver backends.
**Files:**
-- [predator_prey_diffsol.py](https://github.com/bradyplanden/chronopt/blob/main/examples/predator_prey/predator_prey_diffsol.py)
-- [predator_prey_diffrax.py](https://github.com/bradyplanden/chronopt/blob/main/examples/predator_prey/predator_prey_diffrax.py)
-- [predator_prey_diffeqpy.py](https://github.com/bradyplanden/chronopt/blob/main/examples/predator_prey/predator_prey_diffeqpy.py)
+- [predator_prey_diffsol.py](https://github.com/bradyplanden/diffid/blob/main/examples/predator_prey/predator_prey_diffsol.py)
+- [predator_prey_diffrax.py](https://github.com/bradyplanden/diffid/blob/main/examples/predator_prey/predator_prey_diffrax.py)
+- [predator_prey_diffeqpy.py](https://github.com/bradyplanden/diffid/blob/main/examples/predator_prey/predator_prey_diffeqpy.py)
**Topics:** VectorBuilder, JAX/Diffrax, Julia/DifferentialEquations.jl, performance comparison
@@ -78,14 +78,14 @@ Lotka-Volterra equations with multiple solver backends.
Clone the repository:
```bash
-git clone https://github.com/bradyplanden/chronopt.git
-cd chronopt
+git clone https://github.com/bradyplanden/diffid.git
+cd diffid
```
Install dependencies:
```bash
-pip install chronopt matplotlib
+pip install diffid matplotlib
```
Run an example:
diff --git a/docs/getting-started/concepts.md b/docs/getting-started/concepts.md
index 9e559e2..78624ee 100644
--- a/docs/getting-started/concepts.md
+++ b/docs/getting-started/concepts.md
@@ -1,14 +1,14 @@
# Core Concepts
-This guide explains the fundamental concepts and patterns in Chronopt.
+This guide explains the fundamental concepts and patterns in Diffid.
## The Builder Pattern
-Chronopt uses the **builder pattern** for constructing problems. This provides a fluent, chainable API for configuration:
+Diffid uses the **builder pattern** for constructing problems. This provides a fluent, chainable API for configuration:
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(my_function)
.with_parameter("x", 1.0)
.with_parameter("y", 2.0)
@@ -25,7 +25,7 @@ problem = builder.build()
## Problem Types
-Chronopt provides different builders for different problem types:
+Diffid provides different builders for different problem types:
### ScalarBuilder
@@ -36,7 +36,7 @@ def objective(x):
return np.asarray([x[0]**2 + x[1]**2])
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(objective)
.with_parameter("x", 0.0)
.with_parameter("y", 0.0)
@@ -62,7 +62,7 @@ F_i { (r * y) * (1 - (y / k)) }
"""
problem = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
@@ -88,7 +88,7 @@ def solve_ode(params):
return predictions
problem = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(solve_ode)
.with_data(data)
.with_parameter("alpha", 1.0)
@@ -111,7 +111,7 @@ Parameters are the decision variables you want to optimise:
```python
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_parameter("x", initial_value=1.0) # Name and initial guess
.with_parameter("y", initial_value=-1.0)
)
@@ -125,7 +125,7 @@ builder = (
## Optimisers vs Samplers
-Chronopt provides two types of algorithms:
+Diffid provides two types of algorithms:
### Optimisers: Finding the Best Solution
@@ -144,7 +144,7 @@ Chronopt provides two types of algorithms:
result = problem.optimise()
# Specific optimiser
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
result = optimiser.run(problem, initial_guess)
```
@@ -162,7 +162,7 @@ result = optimiser.run(problem, initial_guess)
**Usage:**
```python
-sampler = chron.MetropolisHastings().with_max_iter(10000)
+sampler = diffid.MetropolisHastings().with_max_iter(10000)
result = sampler.run(problem, initial_guess)
# Result contains samples, not a single optimum
@@ -184,10 +184,10 @@ See [Choosing an Optimiser](../guides/choosing-optimiser.md) and [Choosing a Sam
## The Ask/Tell Pattern
-For advanced use cases, Chronopt supports the **ask/tell pattern** for manual control of the optimisation loop:
+For advanced use cases, Diffid supports the **ask/tell pattern** for manual control of the optimisation loop:
```python
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
# Ask for candidates
candidates = optimiser.ask(n_candidates=10)
@@ -219,7 +219,7 @@ result = optimiser.get_result()
Cost metrics define how model predictions are compared to observations:
```python
-from chronopt import SSE, RMSE, GaussianNLL
+from diffid import SSE, RMSE, GaussianNLL
# Sum of squared errors (default)
builder = builder.with_cost_metric(SSE())
@@ -278,7 +278,7 @@ print(result.samples) # Posterior samples
## Parallelisation
-Chronopt automatically parallelises where possible:
+Diffid automatically parallelises where possible:
- **DiffsolBuilder**: Multi-threaded ODE solving
- **CMA-ES**: Parallel candidate evaluation
@@ -291,7 +291,7 @@ Control parallelism:
builder = builder.with_max_threads(4)
# Population size for CMA-ES (larger = more parallel work)
-optimiser = chron.CMAES().with_population_size(20)
+optimiser = diffid.CMAES().with_population_size(20)
```
See the [Parallel Execution Guide](../guides/parallel-execution.md) for details.
diff --git a/docs/getting-started/first-ode-fit.md b/docs/getting-started/first-ode-fit.md
index 3fffa32..ce2fd7c 100644
--- a/docs/getting-started/first-ode-fit.md
+++ b/docs/getting-started/first-ode-fit.md
@@ -1,6 +1,6 @@
# First ODE Fit
-This tutorial demonstrates how to fit ordinary differential equations (ODEs) to data using Chronopt's DiffSL integration with the Diffsol solver.
+This tutorial demonstrates how to fit ordinary differential equations (ODEs) to data using Diffid's DiffSL integration with the Diffsol solver.
## The Problem: Logistic Growth
@@ -14,7 +14,7 @@ where: $r$ is the growth rate, $k$ is the carrying capacity, and $y$ is the popu
```python
import numpy as np
-import chronopt as chron
+import diffid as chron
# Define the ODE model in DiffSL syntax
dsl = """
@@ -37,7 +37,7 @@ data = np.column_stack((t, observations))
# Build the problem
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("r", 0.5) # Initial guess for growth rate
@@ -47,7 +47,7 @@ builder = (
problem = builder.build()
# Run optimisation with CMA-ES
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
result = optimiser.run(problem, [0.5, 1.0])
# Display results
@@ -70,7 +70,7 @@ F_i { (r * y) * (1 - (y / k)) } # Right-hand side of dy/dt = ...
## Data Format
-Chronopt expects data as a 2D NumPy array where:
+Diffid expects data as a 2D NumPy array where:
- **First column**: Time points
- **Remaining columns**: Observed values for each variable
@@ -158,14 +158,14 @@ F_i { k * sin(t) - y }
## Cost Metrics
-By default, Chronopt uses sum of squared errors (SSE). You can specify different cost metrics as shown below. See the [Cost Metrics Guide](../guides/cost-metrics.md) for more details.
+By default, Diffid uses sum of squared errors (SSE). You can specify different cost metrics as shown below. See the [Cost Metrics Guide](../guides/cost-metrics.md) for more details.
```python
-from chronopt import GaussianNLL, RMSE
+from diffid import GaussianNLL, RMSE
# Use Gaussian negative log-likelihood
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
@@ -191,7 +191,7 @@ result = problem.optimise() # Uses Nelder-Mead
### CMA-ES
```python
-optimiser = chron.CMAES().with_max_iter(1000).with_step_size(0.5)
+optimiser = diffid.CMAES().with_max_iter(1000).with_step_size(0.5)
result = optimiser.run(problem, initial_guess)
```
@@ -200,7 +200,7 @@ result = optimiser.run(problem, initial_guess)
### Adam
```python
-optimiser = chron.Adam().with_max_iter(1000).with_step_size(0.01)
+optimiser = diffid.Adam().with_max_iter(1000).with_step_size(0.01)
result = optimiser.run(problem, initial_guess)
```
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index e7e8740..e3280eb 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -1,6 +1,6 @@
-# Getting Started with Chronopt
+# Getting Started with Diffid
-Welcome to Chronopt! This section will help you get up and running with time-series inference and optimisation.
+Welcome to Diffid! This section will help you get up and running with time-series inference and optimisation.
## Learning Path
@@ -22,7 +22,7 @@ We recommend following this sequence:
By the end of this section, you will be able to:
-- Install Chronopt on your platform
+- Install Diffid on your platform
- Create and solve scalar optimisation problems
- Fit differential equations to experimental data
- Understand the builder pattern and problem types
@@ -34,6 +34,6 @@ If you encounter issues:
1. Check the [Troubleshooting](../guides/troubleshooting.md) guide
2. Browse the [examples gallery](../examples/gallery.md)
-3. Open an issue on [GitHub](https://github.com/bradyplanden/chronopt/issues)
+3. Open an issue on [GitHub](https://github.com/bradyplanden/diffid/issues)
Ready to begin? Start with [Installation](installation.md).
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
index 4a645be..59e8ff3 100644
--- a/docs/getting-started/installation.md
+++ b/docs/getting-started/installation.md
@@ -1,6 +1,6 @@
# Installation
-Chronopt is available as a Python package with pre-built wheels for most platforms.
+Diffid is available as a Python package with pre-built wheels for most platforms.
## Installation Methods
@@ -9,44 +9,44 @@ Chronopt is available as a Python package with pre-built wheels for most platfor
[uv](https://docs.astral.sh/uv/) is a fast Python package installer and resolver.
```bash
- uv pip install chronopt
+ uv pip install diffid
```
=== "pip"
```bash
- pip install chronopt
+ pip install diffid
```
### Optional Dependencies
-Chronopt has optional plotting support via matplotlib:
+Diffid has optional plotting support via matplotlib:
=== "uv"
```bash
- uv pip install "chronopt[plotting]"
+ uv pip install "diffid[plotting]"
```
=== "pip"
```bash
- pip install "chronopt[plotting]"
+ pip install "diffid[plotting]"
```
## Verifying Installation
-After installation, verify that Chronopt is working correctly:
+After installation, verify that Diffid is working correctly:
```python
-import chronopt as chron
+import diffid
import numpy as np
# Simple test
def test_func(x):
return np.asarray([(x[0] - 1.0) ** 2])
-builder = chron.ScalarBuilder().with_objective(test_func).with_parameter("x", 0.0)
+builder = diffid.ScalarBuilder().with_objective(test_func).with_parameter("x", 0.0)
problem = builder.build()
result = problem.optimise()
@@ -92,8 +92,8 @@ If you need to build from source (for development or if pre-built wheels aren't
```bash
# Clone the repository
-git clone https://github.com/bradyplanden/chronopt.git
-cd chronopt
+git clone https://github.com/bradyplanden/diffid.git
+cd diffid
# Create Python environment
uv sync
@@ -104,9 +104,9 @@ uv run maturin develop
# Run tests
uv run pytest -v
```
-
+
For additional troubleshooting, see the [Troubleshooting Guide](../guides/troubleshooting.md).
## Next Steps
-Now that Chronopt is installed, proceed to the [5-Minute Quickstart](quickstart.md) to run your first optimisation.
+Now that Diffid is installed, proceed to the [5-Minute Quickstart](quickstart.md) to run your first optimisation.
diff --git a/docs/getting-started/logistic_fit.png b/docs/getting-started/logistic_fit.png
new file mode 100644
index 0000000..bc18c80
Binary files /dev/null and b/docs/getting-started/logistic_fit.png differ
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md
index 97fd9b2..155330b 100644
--- a/docs/getting-started/quickstart.md
+++ b/docs/getting-started/quickstart.md
@@ -14,7 +14,7 @@ The global minimum is at $(x, y) = (1, 1)$ with $f(1, 1) = 0$.
```python
import numpy as np
-import chronopt as chron
+import diffid
def rosenbrock(x):
@@ -25,7 +25,7 @@ def rosenbrock(x):
# Build the problem
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.5) # Initial guess
.with_parameter("y", -1.5) # Initial guess
@@ -70,7 +70,7 @@ You can specify which optimiser to use:
```python
# Use CMA-ES for global search
-optimiser = chron.CMAES().with_max_iter(1000).with_step_size(0.5)
+optimiser = diffid.CMAES().with_max_iter(1000).with_step_size(0.5)
result = optimiser.run(problem, [1.5, -1.5])
print(f"Optimal parameters: {result.x}")
@@ -81,7 +81,7 @@ print(f"Objective value: {result.value:.3e}")
```python
# Use Adam optimiser
-optimiser = chron.Adam().with_max_iter(1000).with_step_size(0.01)
+optimiser = diffid.Adam().with_max_iter(1000).with_step_size(0.01)
result = optimiser.run(problem, [1.5, -1.5])
print(f"Optimal parameters: {result.x}")
@@ -95,7 +95,7 @@ If you installed the `plotting` extra, you can visualise the optimisation landsc
```python
import numpy as np
import matplotlib.pyplot as plt
-import chronopt as chron
+import diffid
def rosenbrock(x):
@@ -124,7 +124,7 @@ plt.plot(1.0, 1.0, 'r*', markersize=20, label='Global minimum')
# Run optimisation and plot path
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", -1.5)
.with_parameter("y", -0.5)
diff --git a/docs/getting-started/rosenbrock_contour.png b/docs/getting-started/rosenbrock_contour.png
new file mode 100644
index 0000000..85f218f
Binary files /dev/null and b/docs/getting-started/rosenbrock_contour.png differ
diff --git a/docs/guides/custom-solvers.md b/docs/guides/custom-solvers.md
index 40e840d..0f04ba2 100644
--- a/docs/guides/custom-solvers.md
+++ b/docs/guides/custom-solvers.md
@@ -19,7 +19,7 @@ def custom_solver(params):
return predictions
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(custom_solver)
.with_data(data)
.with_parameter("alpha", 1.0)
@@ -29,8 +29,8 @@ builder = (
## Examples
See the predator-prey examples:
-- [predator_prey_diffrax.py](https://github.com/bradyplanden/chronopt/blob/main/examples/predator_prey/predator_prey_diffrax.py)
-- [predator_prey_diffeqpy.py](https://github.com/bradyplanden/chronopt/blob/main/examples/predator_prey/predator_prey_diffeqpy.py)
+- [predator_prey_diffrax.py](https://github.com/bradyplanden/diffid/blob/main/examples/predator_prey/predator_prey_diffrax.py)
+- [predator_prey_diffeqpy.py](https://github.com/bradyplanden/diffid/blob/main/examples/predator_prey/predator_prey_diffeqpy.py)
## See Also
diff --git a/docs/guides/diffsol-backend.md b/docs/guides/diffsol-backend.md
index 91925f9..05db37c 100644
--- a/docs/guides/diffsol-backend.md
+++ b/docs/guides/diffsol-backend.md
@@ -14,7 +14,7 @@
```python
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_backend("dense") # or "sparse"
diff --git a/docs/guides/index.md b/docs/guides/index.md
index b371d3a..a1ec664 100644
--- a/docs/guides/index.md
+++ b/docs/guides/index.md
@@ -1,6 +1,6 @@
# User Guides
-In-depth guides for making the most of Chronopt's optimisation and sampling capabilities.
+In-depth guides for making the most of Diffid's optimisation and sampling capabilities.
## Algorithm Selection
diff --git a/docs/guides/parallel-execution.md b/docs/guides/parallel-execution.md
index 8882c80..dc4fa86 100644
--- a/docs/guides/parallel-execution.md
+++ b/docs/guides/parallel-execution.md
@@ -16,7 +16,7 @@
builder = builder.with_max_threads(4)
# Population size for CMA-ES
-optimiser = chron.CMAES().with_population_size(20)
+optimiser = diffid.CMAES().with_population_size(20)
```
## See Also
diff --git a/docs/guides/troubleshooting.md b/docs/guides/troubleshooting.md
index 6b1d8da..fead903 100644
--- a/docs/guides/troubleshooting.md
+++ b/docs/guides/troubleshooting.md
@@ -3,10 +3,10 @@
### Import Error
```python
-ImportError: No module named 'chronopt'
+ImportError: No module named 'diffid'
```
-**Solution**: Install Chronopt: `pip install chronopt`
+**Solution**: Install Diffid: `pip install diffid`
### Poor Fit Quality
@@ -29,4 +29,4 @@ ImportError: No module named 'chronopt'
- [Installation](../getting-started/installation.md)
- [Tuning Optimisers](tuning-optimizers.md)
-- [GitHub Issues](https://github.com/bradyplanden/chronopt/issues)
+- [GitHub Issues](https://github.com/bradyplanden/diffid/issues)
diff --git a/docs/index.md b/docs/index.md
index ad25c0a..5f9481a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,17 @@
-# Chronopt
+# Diffid
-**chron**os-**opt**imum is a Rust-first toolkit for time-series inference and optimisation with ergonomic Python bindings. It couples high-performance solvers with a highly customisable builder API for identification and optimisation of differential systems.
+
entification is a Rust-first toolkit for time-series inference and optimisation with ergonomic Python bindings. It couples high-performance solvers with a highly customisable builder API for identification and optimisation of differential systems.
-## Why Chronopt?
+## Why Diffid?
-Chronopt offers a different paradigm for a parameter inference library. Conventionally, Python-based inference libraries are constructed via python bindings to a high-performance forward model with the inference algorithms implemented in Python. This package instead introduces an alternative, where the Python layer acts purely as a declarative configuration interface,
+Diffid offers a different paradigm for a parameter inference library. Conventionally, Python-based inference libraries are constructed via python bindings to a high-performance forward model with the inference algorithms implemented in Python. This package instead introduces an alternative, where the Python layer acts purely as a declarative configuration interface,
while all computationally intensive work (the optimisation / sampling loop, gradient calculations, etc.) happens entirely within the Rust runtime without crossing the FFI boundary repeatedly. This is architecture is presented visually below,
@@ -30,7 +30,7 @@ while all computationally intensive work (the optimisation / sampling loop, grad
---
- Get started with Chronopt in 5 minutes with a simple scalar optimisation example.
+ Get started with Diffid in 5 minutes with a simple scalar optimisation example.
[:octicons-arrow-right-24: Quickstart](getting-started/quickstart.md)
@@ -62,38 +62,38 @@ while all computationally intensive work (the optimisation / sampling loop, grad
## Installation
-Chronopt targets Python >= 3.11. Windows builds are currently marked experimental.
+Diffid targets Python >= 3.11. Windows builds are currently marked experimental.
=== "pip"
```bash
- pip install chronopt
+ pip install diffid
# Optional extras for plotting
- pip install "chronopt[plotting]"
+ pip install "diffid[plotting]"
```
=== "uv"
```bash
- uv pip install chronopt
+ uv pip install diffid
# Optional extras for plotting
- uv pip install "chronopt[plotting]"
+ uv pip install "diffid[plotting]"
```
## Example: Scalar Optimisation
```python
import numpy as np
-import chronopt as chron
+import diffid
def rosenbrock(x):
value = (1 - x[0]) ** 2 + 100 * (x[1] - x[0] ** 2) ** 2
return np.asarray([value])
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.5)
.with_parameter("y", -1.5)
@@ -110,7 +110,7 @@ print(f"Success: {result.success}")
```python
import numpy as np
-import chronopt as chron
+import diffid
# Logistic growth model in DiffSL
dsl = """
@@ -124,7 +124,7 @@ observations = np.exp(-1.3 * t)
data = np.column_stack((t, observations))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
@@ -132,7 +132,7 @@ builder = (
)
problem = builder.build()
-optimiser = chron.CMAES().with_max_iter(1000)
+optimiser = diffid.CMAES().with_max_iter(1000)
result = optimiser.run(problem, [0.5, 0.5])
print(result.x)
diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md
index 9120487..588f65a 100644
--- a/docs/tutorials/index.md
+++ b/docs/tutorials/index.md
@@ -1,6 +1,6 @@
# Tutorials
-Interactive Jupyter notebooks for hands-on learning with Chronopt.
+Interactive Jupyter notebooks for hands-on learning with Diffid.
## Learning Paths
@@ -8,7 +8,7 @@ Follow these progressive learning paths based on your experience level and goals
### šÆ Beginner Track
-Perfect for those new to Chronopt or optimisation:
+Perfect for those new to Diffid or optimisation:
@@ -101,26 +101,26 @@ Complex problems and advanced techniques:
### Installation
-Install Chronopt with Jupyter and plotting support:
+Install Diffid with Jupyter and plotting support:
=== "pip"
```bash
- pip install chronopt jupyter matplotlib
+ pip install diffid jupyter matplotlib
```
=== "uv"
```bash
- uv pip install chronopt jupyter matplotlib
+ uv pip install diffid jupyter matplotlib
```
### Clone and Run
```bash
# Clone the repository
-git clone https://github.com/bradyplanden/chronopt.git
-cd chronopt/docs/tutorials/notebooks
+git clone https://github.com/bradyplanden/diffid.git
+cd diffid/docs/tutorials/notebooks
# Launch Jupyter
jupyter notebook
@@ -134,13 +134,13 @@ You can also run these notebooks in Google Colab (coming soon with hosted versio
By completing all tutorials, you will:
-ā
Understand Chronopt's builder pattern and API
-ā
Optimize both scalar functions and ODE parameters
-ā
Compare and tune different optimisation algorithms
-ā
Quantify parameter uncertainty with MCMC
-ā
Compare models using Bayesian evidence
-ā
Integrate custom ODE solvers (JAX, Julia)
-ā
Make informed decisions about algorithm selection
+- Understand Diffid's builder pattern and API
+- Optimize both scalar functions and ODE parameters
+- Compare and tune different optimisation algorithms
+- Quantify parameter uncertainty with MCMC
+- Compare models using Bayesian evidence
+- Integrate custom ODE solvers (JAX, Julia)
+- Make informed decisions about algorithm selection
## Notebook Structure
@@ -157,7 +157,7 @@ Each tutorial follows a consistent structure:
## Alternative: Python Scripts
-Prefer scripts to notebooks? Check out the [examples directory](https://github.com/bradyplanden/chronopt/tree/main/examples):
+Prefer scripts to notebooks? Check out the [examples directory](https://github.com/bradyplanden/diffid/tree/main/examples):
- `python_problem.py` - Basic scalar optimisation
- `logistic_growth.py` - ODE fitting
@@ -183,10 +183,10 @@ Feel free to reuse these in your own projects!
### Import Errors
```python
-ModuleNotFoundError: No module named 'chronopt'
+ModuleNotFoundError: No module named 'diffid'
```
-**Solution**: Install Chronopt: `pip install chronopt`
+**Solution**: Install Diffid: `pip install diffid`
### Notebook Kernel Issues
@@ -222,13 +222,13 @@ If MCMC sampling is slow:
- **Documentation**: Browse the [complete docs](../index.md)
- **Examples**: See the [examples gallery](../examples/gallery.md)
- **API Reference**: Check the [API docs](../api-reference/index.md)
-- **Issues**: Report problems on [GitHub](https://github.com/bradyplanden/chronopt/issues)
+- **Issues**: Report problems on [GitHub](https://github.com/bradyplanden/diffid/issues)
## Contributing
Found an issue or want to improve a tutorial?
-1. Fork the [repository](https://github.com/bradyplanden/chronopt)
+1. Fork the [repository](https://github.com/bradyplanden/diffid)
2. Edit notebooks in `docs/tutorials/notebooks/`
3. Test your changes locally
4. Submit a pull request
@@ -253,7 +253,7 @@ After completing the tutorials:
More applications and use cases
-- [:material-github:{ .lg .middle } __GitHub Repository__](https://github.com/bradyplanden/chronopt)
+- [:material-github:{ .lg .middle } __GitHub Repository__](https://github.com/bradyplanden/diffid)
Source code and development
diff --git a/docs/tutorials/notebooks/01_optimization_basics.ipynb b/docs/tutorials/notebooks/01_optimization_basics.ipynb
index d916426..61743db 100644
--- a/docs/tutorials/notebooks/01_optimization_basics.ipynb
+++ b/docs/tutorials/notebooks/01_optimization_basics.ipynb
@@ -28,7 +28,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:26.832609Z",
@@ -40,22 +40,16 @@
"outputs": [],
"source": [
"# Import plotting utilities\n",
- "import chronopt as chron\n",
+ "import diffid\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
- "from chronopt.plotting import contour_2d"
+ "from diffid.plotting import contour_2d"
]
},
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Define the Objective Function\n",
- "\n",
- "In Chronopt, objective functions must:\n",
- "1. Accept a NumPy array as input\n",
- "2. Return a NumPy array as output (even for scalar values)"
- ]
+ "source": "## Define the Objective Function\n\nIn Diffid, objective functions must:\n1. Accept a NumPy array as input\n2. Return a NumPy array as output (even for scalar values)"
},
{
"cell_type": "code",
@@ -87,7 +81,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:27.533691Z",
@@ -96,19 +90,10 @@
"shell.execute_reply": "2026-01-10T22:21:27.537575Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Problem built successfully!\n",
- "Number of parameters: 2\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"builder = (\n",
- " chron.ScalarBuilder()\n",
+ " diffid.ScalarBuilder()\n",
" .with_objective(rosenbrock)\n",
" .with_parameter(\"x\", 10.0) # Initial guess\n",
" .with_parameter(\"y\", 10.0) # Initial guess\n",
@@ -122,12 +107,7 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Optimise with Default Settings\n",
- "\n",
- "The problem class is constructed as the core object in chronopt, as such\n",
- " an `optimise()` method if provided for fast optimisation. This uses Nelder-Mead by default:"
- ]
+ "source": "## Optimise with Default Settings\n\nThe problem class is constructed as the core object in diffid, as such\n an `optimise()` method if provided for fast optimisation. This uses Nelder-Mead by default:"
},
{
"cell_type": "code",
@@ -240,7 +220,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:27.973637Z",
@@ -249,34 +229,16 @@
"shell.execute_reply": "2026-01-10T22:21:28.084924Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "======================================================================\n",
- "OPTIMISER COMPARISON\n",
- "======================================================================\n",
- "Optimiser Success Final Value Iterations Evaluations\n",
- "----------------------------------------------------------------------\n",
- "Nelder-Mead True 2.866e-07 130 290\n",
- "CMA-ES True 3.681e-08 95 571\n",
- "Adam False 6.927e-09 2000 2001\n",
- "\n",
- "Final parameters:\n",
- "Nelder-Mead x = [1.0001253 1.00019857]\n",
- "CMA-ES x = [1.00017279 1.00035395]\n",
- "Adam x = [1.00008317 1.00016666]\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# Define optimisers\n",
"optimisers = {\n",
- " \"Nelder-Mead\": chron.NelderMead().with_max_iter(1000),\n",
- " \"CMA-ES\": chron.CMAES().with_max_iter(300).with_step_size(0.5),\n",
- " \"Adam\": chron.Adam().with_max_iter(2000).with_step_size(0.25).with_threshold(1e-12),\n",
+ " \"Nelder-Mead\": diffid.NelderMead().with_max_iter(1000),\n",
+ " \"CMA-ES\": diffid.CMAES().with_max_iter(300).with_step_size(0.5),\n",
+ " \"Adam\": diffid.Adam()\n",
+ " .with_max_iter(2000)\n",
+ " .with_step_size(0.25)\n",
+ " .with_threshold(1e-12),\n",
"}\n",
"\n",
"# Test starting point\n",
@@ -408,7 +370,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:28.447521Z",
@@ -417,42 +379,7 @@
"shell.execute_reply": "2026-01-10T22:21:28.460670Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "============================================================\n",
- "STARTING POINT SENSITIVITY\n",
- "============================================================\n",
- "\n",
- "Start 1: [-1.5, -0.5]\n",
- " Final: [0.99938601 0.99871559]\n",
- " Iterations: 60\n",
- " Error: 1.424e-03\n",
- " Success: True\n",
- "\n",
- "Start 2: [1.5, 1.5]\n",
- " Final: [1.00023483 1.00043031]\n",
- " Iterations: 79\n",
- " Error: 4.902e-04\n",
- " Success: True\n",
- "\n",
- "Start 3: [0.0, 2.0]\n",
- " Final: [1.00004402 1.00004508]\n",
- " Iterations: 65\n",
- " Error: 6.301e-05\n",
- " Success: True\n",
- "\n",
- "Start 4: [-1.0, 1.0]\n",
- " Final: [1.00040626 1.0008615 ]\n",
- " Iterations: 105\n",
- " Error: 9.525e-04\n",
- " Success: True\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# Try multiple starting points\n",
"starting_points = [\n",
@@ -462,7 +389,7 @@
" [-1.0, 1.0],\n",
"]\n",
"\n",
- "optimiser = chron.NelderMead().with_max_iter(1000)\n",
+ "optimiser = diffid.NelderMead().with_max_iter(1000)\n",
"\n",
"print(\"\\n\" + \"=\" * 60)\n",
"print(\"STARTING POINT SENSITIVITY\")\n",
@@ -536,4 +463,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tutorials/notebooks/02_ode_fitting_diffsol.ipynb b/docs/tutorials/notebooks/02_ode_fitting_diffsol.ipynb
index a59ba93..7a471e9 100644
--- a/docs/tutorials/notebooks/02_ode_fitting_diffsol.ipynb
+++ b/docs/tutorials/notebooks/02_ode_fitting_diffsol.ipynb
@@ -33,7 +33,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:29.582370Z",
@@ -43,13 +43,7 @@
}
},
"outputs": [],
- "source": [
- "# Import plotting utilities\n",
- "import chronopt as chron\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from chronopt.plotting import ode_fit"
- ]
+ "source": "# Import plotting utilities\nimport diffid\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom diffid.plotting import ode_fit"
},
{
"cell_type": "markdown",
@@ -118,7 +112,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:30.052995Z",
@@ -127,48 +121,8 @@
"shell.execute_reply": "2026-01-10T22:21:30.079161Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Generated 100 data points\n",
- "Time range: [0.00, 4.00]\n",
- "Value range: [0.102, 0.856]\n",
- "Noise level: 0.01\n",
- "\n",
- "True parameters: r = 1.0, k = 1.0\n"
- ]
- }
- ],
- "source": [
- "# Time points\n",
- "t_span = np.linspace(0, 4, 100)\n",
- "\n",
- "# True logistic growth solution with known parameters\n",
- "# y(t) = y0 * exp(r*t) / (1 + y0 * (exp(r*t) - 1) / k)\n",
- "y0 = 0.1\n",
- "r_true = 1.0\n",
- "k_true = 1.0\n",
- "\n",
- "# Generate clean data\n",
- "exp_rt = np.exp(r_true * t_span)\n",
- "y_data = y0 * exp_rt / (1 + y0 * (exp_rt - 1) / k_true)\n",
- "\n",
- "# Add some noise\n",
- "np.random.seed(42)\n",
- "noise_level = 0.01\n",
- "y_noisy = y_data + np.random.normal(0, noise_level, size=y_data.shape)\n",
- "\n",
- "# Chronopt expects data as [time, observation] columns\n",
- "data = np.column_stack((t_span, y_noisy))\n",
- "\n",
- "print(f\"Generated {len(t_span)} data points\")\n",
- "print(f\"Time range: [{t_span[0]:.2f}, {t_span[-1]:.2f}]\")\n",
- "print(f\"Value range: [{y_noisy.min():.3f}, {y_noisy.max():.3f}]\")\n",
- "print(f\"Noise level: {noise_level}\")\n",
- "print(f\"\\nTrue parameters: r = {r_true}, k = {k_true}\")"
- ]
+ "outputs": [],
+ "source": "# Time points\nt_span = np.linspace(0, 4, 100)\n\n# True logistic growth solution with known parameters\n# y(t) = y0 * exp(r*t) / (1 + y0 * (exp(r*t) - 1) / k)\ny0 = 0.1\nr_true = 1.0\nk_true = 1.0\n\n# Generate clean data\nexp_rt = np.exp(r_true * t_span)\ny_data = y0 * exp_rt / (1 + y0 * (exp_rt - 1) / k_true)\n\n# Add some noise\nnp.random.seed(42)\nnoise_level = 0.01\ny_noisy = y_data + np.random.normal(0, noise_level, size=y_data.shape)\n\n# Diffid expects data as [time, observation] columns\ndata = np.column_stack((t_span, y_noisy))\n\nprint(f\"Generated {len(t_span)} data points\")\nprint(f\"Time range: [{t_span[0]:.2f}, {t_span[-1]:.2f}]\")\nprint(f\"Value range: [{y_noisy.min():.3f}, {y_noisy.max():.3f}]\")\nprint(f\"Noise level: {noise_level}\")\nprint(f\"\\nTrue parameters: r = {r_true}, k = {k_true}\")"
},
{
"cell_type": "markdown",
@@ -228,7 +182,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:30.232622Z",
@@ -237,36 +191,8 @@
"shell.execute_reply": "2026-01-10T22:21:30.236189Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Problem built successfully!\n",
- "\n",
- "Initial parameter guesses: r = 50.0, k = 50.0\n",
- "(Far from true values: r = 1.0, k = 1.0)\n"
- ]
- }
- ],
- "source": [
- "# Create builder\n",
- "builder = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(dsl_model)\n",
- " .with_data(data)\n",
- " .with_tolerances(1e-6, 1e-8) # Relative and absolute tolerances\n",
- " .with_parameter(\"r\", 10.0) # Initial guess (deliberately wrong)\n",
- " .with_parameter(\"k\", 10.0) # Initial guess (deliberately wrong)\n",
- " .with_parallel(True) # Enable parallel evaluation\n",
- ")\n",
- "\n",
- "problem = builder.build()\n",
- "\n",
- "print(\"Problem built successfully!\")\n",
- "print(\"\\nInitial parameter guesses: r = 50.0, k = 50.0\")\n",
- "print(f\"(Far from true values: r = {r_true}, k = {k_true})\")"
- ]
+ "outputs": [],
+ "source": "# Create builder\nbuilder = (\n diffid.DiffsolBuilder()\n .with_diffsl(dsl_model)\n .with_data(data)\n .with_tolerances(1e-6, 1e-8) # Relative and absolute tolerances\n .with_parameter(\"r\", 10.0) # Initial guess (deliberately wrong)\n .with_parameter(\"k\", 10.0) # Initial guess (deliberately wrong)\n .with_parallel(True) # Enable parallel evaluation\n)\n\nproblem = builder.build()\n\nprint(\"Problem built successfully!\")\nprint(\"\\nInitial parameter guesses: r = 50.0, k = 50.0\")\nprint(f\"(Far from true values: r = {r_true}, k = {k_true})\")"
},
{
"cell_type": "markdown",
@@ -279,7 +205,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:30.238643Z",
@@ -288,62 +214,8 @@
"shell.execute_reply": "2026-01-10T22:21:34.759178Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "============================================================\n",
- "OPTIMIZATION RESULTS\n",
- "============================================================\n",
- "Success: True\n",
- "\n",
- "Fitted parameters:\n",
- " r = 0.996728 (true: 1.0)\n",
- " k = 1.002875 (true: 1.0)\n",
- "\n",
- "Optimization details:\n",
- " Final cost: 8.220e-03\n",
- " Iterations: 545\n",
- " Function evaluations: 3271\n",
- " Time: 514.311 milliseconds\n",
- " Message: Function tolerance met\n",
- "\n",
- "Parameter errors:\n",
- " r: 0.327%\n",
- " k: 0.287%\n"
- ]
- }
- ],
- "source": [
- "# Create optimiser\n",
- "optimiser = chron.CMAES().with_max_iter(1000).with_threshold(1e-12)\n",
- "\n",
- "# Run optimization\n",
- "result = optimiser.run(problem, problem.initial_values())\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"OPTIMIZATION RESULTS\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"Success: {result.success}\")\n",
- "print(\"\\nFitted parameters:\")\n",
- "print(f\" r = {result.x[0]:.6f} (true: {r_true})\")\n",
- "print(f\" k = {result.x[1]:.6f} (true: {k_true})\")\n",
- "print(\"\\nOptimization details:\")\n",
- "print(f\" Final cost: {result.value:.3e}\")\n",
- "print(f\" Iterations: {result.iterations}\")\n",
- "print(f\" Function evaluations: {result.evaluations}\")\n",
- "print(f\" Time: {result.time.microseconds / 1e3:.3f} milliseconds\")\n",
- "print(f\" Message: {result.message}\")\n",
- "\n",
- "# Calculate parameter errors\n",
- "r_error = abs(result.x[0] - r_true) / r_true * 100\n",
- "k_error = abs(result.x[1] - k_true) / k_true * 100\n",
- "print(\"\\nParameter errors:\")\n",
- "print(f\" r: {r_error:.3f}%\")\n",
- "print(f\" k: {k_error:.3f}%\")"
- ]
+ "outputs": [],
+ "source": "# Create optimiser\noptimiser = diffid.CMAES().with_max_iter(1000).with_threshold(1e-12)\n\n# Run optimization\nresult = optimiser.run(problem, problem.initial_values())\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"OPTIMIZATION RESULTS\")\nprint(\"=\" * 60)\nprint(f\"Success: {result.success}\")\nprint(\"\\nFitted parameters:\")\nprint(f\" r = {result.x[0]:.6f} (true: {r_true})\")\nprint(f\" k = {result.x[1]:.6f} (true: {k_true})\")\nprint(\"\\nOptimization details:\")\nprint(f\" Final cost: {result.value:.3e}\")\nprint(f\" Iterations: {result.iterations}\")\nprint(f\" Function evaluations: {result.evaluations}\")\nprint(f\" Time: {result.time.microseconds / 1e3:.3f} milliseconds\")\nprint(f\" Message: {result.message}\")\n\n# Calculate parameter errors\nr_error = abs(result.x[0] - r_true) / r_true * 100\nk_error = abs(result.x[1] - k_true) / k_true * 100\nprint(\"\\nParameter errors:\")\nprint(f\" r: {r_error:.3f}%\")\nprint(f\" k: {k_error:.3f}%\")"
},
{
"cell_type": "markdown",
@@ -513,7 +385,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:42.612961Z",
@@ -522,51 +394,8 @@
"shell.execute_reply": "2026-01-10T22:21:46.725244Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "================================================================================\n",
- "OPTIMISER COMPARISON\n",
- "================================================================================\n",
- "Algorithm r (fitted) k (fitted) Cost Iters Time (s)\n",
- "--------------------------------------------------------------------------------\n",
- "Nelder-Mead 2.527654 0.604107 2.331e+00 37 0.204\n",
- "CMA-ES 0.996760 1.002815 8.220e-03 57 0.529\n",
- "Adam 0.953633 1.070344 1.648e-02 1000 0.374\n",
- "\n",
- "True values: 1.000000 1.000000 \n"
- ]
- }
- ],
- "source": [
- "optimisers = {\n",
- " \"Nelder-Mead\": chron.NelderMead().with_max_iter(1000),\n",
- " \"CMA-ES\": chron.CMAES().with_max_iter(500),\n",
- " \"Adam\": chron.Adam().with_max_iter(1000).with_step_size(0.01),\n",
- "}\n",
- "\n",
- "initial = [2.0, 2.0]\n",
- "\n",
- "print(\"\\n\" + \"=\" * 80)\n",
- "print(\"OPTIMISER COMPARISON\")\n",
- "print(\"=\" * 80)\n",
- "print(\n",
- " f\"{'Algorithm':<15} {'r (fitted)':<15} {'k (fitted)':<15} {'Cost':<15} {'Iters':<8} {'Time (s)'}\"\n",
- ")\n",
- "print(\"-\" * 80)\n",
- "\n",
- "for name, opt in optimisers.items():\n",
- " result = opt.run(problem, initial)\n",
- " print(\n",
- " f\"{name:<15} {result.x[0]:<15.6f} {result.x[1]:<15.6f} \"\n",
- " f\"{result.value:<15.3e} {result.iterations:<8} {result.time.microseconds / 1e6:.3f}\"\n",
- " )\n",
- "\n",
- "print(f\"\\nTrue values: {r_true:<15.6f} {k_true:<15.6f}\")"
- ]
+ "outputs": [],
+ "source": "optimisers = {\n \"Nelder-Mead\": diffid.NelderMead().with_max_iter(1000),\n \"CMA-ES\": diffid.CMAES().with_max_iter(500),\n \"Adam\": diffid.Adam().with_max_iter(1000).with_step_size(0.01),\n}\n\ninitial = [2.0, 2.0]\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"OPTIMISER COMPARISON\")\nprint(\"=\" * 80)\nprint(\n f\"{'Algorithm':<15} {'r (fitted)':<15} {'k (fitted)':<15} {'Cost':<15} {'Iters':<8} {'Time (s)'}\"\n)\nprint(\"-\" * 80)\n\nfor name, opt in optimisers.items():\n result = opt.run(problem, initial)\n print(\n f\"{name:<15} {result.x[0]:<15.6f} {result.x[1]:<15.6f} \"\n f\"{result.value:<15.3e} {result.iterations:<8} {result.time.microseconds / 1e6:.3f}\"\n )\n\nprint(f\"\\nTrue values: {r_true:<15.6f} {k_true:<15.6f}\")"
},
{
"cell_type": "markdown",
@@ -714,4 +543,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tutorials/notebooks/03_parameter_uncertainty.ipynb b/docs/tutorials/notebooks/03_parameter_uncertainty.ipynb
index b7c1c67..51c7ae7 100644
--- a/docs/tutorials/notebooks/03_parameter_uncertainty.ipynb
+++ b/docs/tutorials/notebooks/03_parameter_uncertainty.ipynb
@@ -32,7 +32,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:48.226019Z",
@@ -42,15 +42,7 @@
}
},
"outputs": [],
- "source": [
- "# Import plotting utilities\n",
- "import chronopt as chron\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from chronopt.plotting import parameter_distributions, parameter_traces\n",
- "\n",
- "np.random.seed(42) # For reproducibility"
- ]
+ "source": "# Import plotting utilities\nimport diffid\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom diffid.plotting import parameter_distributions, parameter_traces\n\nnp.random.seed(42) # For reproducibility"
},
{
"cell_type": "markdown",
@@ -83,7 +75,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:48.861095Z",
@@ -92,57 +84,8 @@
"shell.execute_reply": "2026-01-10T22:21:48.866205Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Generated 61 observations\n",
- "Time span: [0, 0.999] seconds\n",
- "Noise level: Ļ = 0.1\n",
- "\n",
- "True parameters:\n",
- " g = 9.81 m/s²\n",
- " h = 10.0 m\n"
- ]
- }
- ],
- "source": [
- "def ball_states(t, g, h):\n",
- " \"\"\"Analytical solution for ball trajectory.\"\"\"\n",
- " height = h - 0.5 * g * t**2\n",
- " height = np.maximum(height, 0.0) # Can't go below ground\n",
- " velocity = -g * t\n",
- " return height, velocity\n",
- "\n",
- "\n",
- "# True parameters\n",
- "g_true = 9.81 # m/s²\n",
- "h_true = 10.0 # meters\n",
- "\n",
- "# Time to hit ground: t = sqrt(2h/g)\n",
- "t_stop = np.sqrt(2.0 * h_true / g_true)\n",
- "t_final = 0.7 * t_stop # Stop before hitting ground\n",
- "t_span = np.linspace(0.0, t_final, 61)\n",
- "\n",
- "# Generate clean data\n",
- "height, velocity = ball_states(t_span, g_true, h_true)\n",
- "\n",
- "# Add measurement noise\n",
- "noise_std = 0.1\n",
- "height_noisy = height + np.random.normal(0, noise_std, len(t_span))\n",
- "velocity_noisy = velocity + np.random.normal(0, noise_std, len(t_span))\n",
- "\n",
- "# Format for Chronopt: [time, height, velocity]\n",
- "data = np.column_stack((t_span, height_noisy, velocity_noisy))\n",
- "\n",
- "print(f\"Generated {len(t_span)} observations\")\n",
- "print(f\"Time span: [0, {t_final:.3f}] seconds\")\n",
- "print(f\"Noise level: Ļ = {noise_std}\")\n",
- "print(\"\\nTrue parameters:\")\n",
- "print(f\" g = {g_true} m/s²\")\n",
- "print(f\" h = {h_true} m\")"
- ]
+ "outputs": [],
+ "source": "def ball_states(t, g, h):\n \"\"\"Analytical solution for ball trajectory.\"\"\"\n height = h - 0.5 * g * t**2\n height = np.maximum(height, 0.0) # Can't go below ground\n velocity = -g * t\n return height, velocity\n\n\n# True parameters\ng_true = 9.81 # m/s²\nh_true = 10.0 # meters\n\n# Time to hit ground: t = sqrt(2h/g)\nt_stop = np.sqrt(2.0 * h_true / g_true)\nt_final = 0.7 * t_stop # Stop before hitting ground\nt_span = np.linspace(0.0, t_final, 61)\n\n# Generate clean data\nheight, velocity = ball_states(t_span, g_true, h_true)\n\n# Add measurement noise\nnoise_std = 0.1\nheight_noisy = height + np.random.normal(0, noise_std, len(t_span))\nvelocity_noisy = velocity + np.random.normal(0, noise_std, len(t_span))\n\n# Format for Diffid: [time, height, velocity]\ndata = np.column_stack((t_span, height_noisy, velocity_noisy))\n\nprint(f\"Generated {len(t_span)} observations\")\nprint(f\"Time span: [0, {t_final:.3f}] seconds\")\nprint(f\"Noise level: Ļ = {noise_std}\")\nprint(\"\\nTrue parameters:\")\nprint(f\" g = {g_true} m/s²\")\nprint(f\" h = {h_true} m\")"
},
{
"cell_type": "markdown",
@@ -267,7 +210,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:49.152830Z",
@@ -276,55 +219,8 @@
"shell.execute_reply": "2026-01-10T22:21:49.634461Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "============================================================\n",
- "OPTIMIZATION RESULTS (MAP Estimate)\n",
- "============================================================\n",
- "Success: True\n",
- "\n",
- "Fitted parameters:\n",
- " g = 9.8035 m/s² (true: 9.81)\n",
- " h = 9.9841 m (true: 10.0)\n",
- "\n",
- "Cost: 0.183349\n",
- "Iterations: 197\n"
- ]
- }
- ],
- "source": [
- "# Build problem\n",
- "builder = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(dsl_model)\n",
- " .with_data(data)\n",
- " .with_parameter(\"g\", 5.0) # Initial guess\n",
- " .with_parameter(\"h\", 5.0) # Initial guess\n",
- " .with_cost(chron.RMSE(2.0)) # 2 observables (height + velocity)\n",
- ")\n",
- "\n",
- "problem = builder.build()\n",
- "\n",
- "# Optimize\n",
- "optimizer = chron.Adam().with_step_size(0.05).with_max_iter(1500)\n",
- "opt_result = optimizer.run(problem, [5.0, 5.0])\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"OPTIMIZATION RESULTS (MAP Estimate)\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"Success: {opt_result.success}\")\n",
- "print(\"\\nFitted parameters:\")\n",
- "print(f\" g = {opt_result.x[0]:.4f} m/s² (true: {g_true})\")\n",
- "print(f\" h = {opt_result.x[1]:.4f} m (true: {h_true})\")\n",
- "print(f\"\\nCost: {opt_result.value:.6f}\")\n",
- "print(f\"Iterations: {opt_result.iterations}\")\n",
- "\n",
- "g_map, h_map = opt_result.x"
- ]
+ "outputs": [],
+ "source": "# Build problem\nbuilder = (\n diffid.DiffsolBuilder()\n .with_diffsl(dsl_model)\n .with_data(data)\n .with_parameter(\"g\", 5.0) # Initial guess\n .with_parameter(\"h\", 5.0) # Initial guess\n .with_cost(diffid.RMSE(2.0)) # 2 observables (height + velocity)\n)\n\nproblem = builder.build()\n\n# Optimize\noptimizer = diffid.Adam().with_step_size(0.05).with_max_iter(1500)\nopt_result = optimizer.run(problem, [5.0, 5.0])\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"OPTIMIZATION RESULTS (MAP Estimate)\")\nprint(\"=\" * 60)\nprint(f\"Success: {opt_result.success}\")\nprint(\"\\nFitted parameters:\")\nprint(f\" g = {opt_result.x[0]:.4f} m/s² (true: {g_true})\")\nprint(f\" h = {opt_result.x[1]:.4f} m (true: {h_true})\")\nprint(f\"\\nCost: {opt_result.value:.6f}\")\nprint(f\"Iterations: {opt_result.iterations}\")\n\ng_map, h_map = opt_result.x"
},
{
"cell_type": "markdown",
@@ -337,7 +233,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-10T22:21:49.638310Z",
@@ -346,72 +242,8 @@
"shell.execute_reply": "2026-01-10T22:21:58.286103Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "============================================================\n",
- "MCMC SAMPLING\n",
- "============================================================\n",
- "\n",
- "Starting from MAP estimate: g = 9.8035, h = 9.9841\n",
- "\n",
- "Running MCMC... (this may take a minute)\n",
- "\n",
- "Sampling complete!\n",
- "Samples shape: (10010, 2)\n",
- "Acceptance rate: [0.284 0.295 0.258 0.24 0.248 0.267 0.284 0.24 0.262 0.277]\n",
- "Target acceptance: 0.20 - 0.40\n",
- "ā Acceptance rate looks good!\n"
- ]
- }
- ],
- "source": [
- "# Rebuild problem with GaussianNLL (required for sampling)\n",
- "builder_sampling = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(dsl_model)\n",
- " .with_data(data)\n",
- " .with_parameter(\"g\", g_map) # Start from MAP\n",
- " .with_parameter(\"h\", h_map)\n",
- " # .with_parallel(True)\n",
- " .with_cost(chron.GaussianNLL(variance=noise_std**2))\n",
- ")\n",
- "\n",
- "problem_sampling = builder_sampling.build()\n",
- "\n",
- "# Setup MCMC sampler\n",
- "sampler = (\n",
- " chron.MetropolisHastings()\n",
- " .with_num_chains(10)\n",
- " .with_iterations(1000)\n",
- " .with_step_size(0.035)\n",
- ")\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"MCMC SAMPLING\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"\\nStarting from MAP estimate: g = {g_map:.4f}, h = {h_map:.4f}\")\n",
- "print(\"\\nRunning MCMC... (this may take a minute)\")\n",
- "\n",
- "# Run sampling\n",
- "mcmc_result = sampler.run(problem_sampling, [g_map, h_map])\n",
- "\n",
- "print(\"\\nSampling complete!\")\n",
- "print(f\"Samples shape: {mcmc_result.samples.shape}\")\n",
- "print(f\"Acceptance rate: {mcmc_result.acceptance_rate}\")\n",
- "print(\"Target acceptance: 0.20 - 0.40\")\n",
- "\n",
- "if np.any(mcmc_result.acceptance_rate < 0.15) or np.any(\n",
- " mcmc_result.acceptance_rate > 0.50\n",
- "):\n",
- " print(\"ā ļø Warning: Acceptance rate is outside optimal range\")\n",
- " print(\" Consider adjusting step_size\")\n",
- "else:\n",
- " print(\"ā Acceptance rate looks good!\")"
- ]
+ "outputs": [],
+ "source": "# Rebuild problem with GaussianNLL (required for sampling)\nbuilder_sampling = (\n diffid.DiffsolBuilder()\n .with_diffsl(dsl_model)\n .with_data(data)\n .with_parameter(\"g\", g_map) # Start from MAP\n .with_parameter(\"h\", h_map)\n # .with_parallel(True)\n .with_cost(diffid.GaussianNLL(variance=noise_std**2))\n)\n\nproblem_sampling = builder_sampling.build()\n\n# Setup MCMC sampler\nsampler = (\n diffid.MetropolisHastings()\n .with_num_chains(10)\n .with_iterations(1000)\n .with_step_size(0.035)\n)\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"MCMC SAMPLING\")\nprint(\"=\" * 60)\nprint(f\"\\nStarting from MAP estimate: g = {g_map:.4f}, h = {h_map:.4f}\")\nprint(\"\\nRunning MCMC... (this may take a minute)\")\n\n# Run sampling\nmcmc_result = sampler.run(problem_sampling, [g_map, h_map])\n\nprint(\"\\nSampling complete!\")\nprint(f\"Samples shape: {mcmc_result.samples.shape}\")\nprint(f\"Acceptance rate: {mcmc_result.acceptance_rate}\")\nprint(\"Target acceptance: 0.20 - 0.40\")\n\nif np.any(mcmc_result.acceptance_rate < 0.15) or np.any(\n mcmc_result.acceptance_rate > 0.50\n):\n print(\"ā ļø Warning: Acceptance rate is outside optimal range\")\n print(\" Consider adjusting step_size\")\nelse:\n print(\"ā Acceptance rate looks good!\")"
},
{
"cell_type": "markdown",
@@ -846,4 +678,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tutorials/notebooks/05_advanced_predator_prey.ipynb b/docs/tutorials/notebooks/05_advanced_predator_prey.ipynb
index 8a7b5e9..165a3b9 100644
--- a/docs/tutorials/notebooks/05_advanced_predator_prey.ipynb
+++ b/docs/tutorials/notebooks/05_advanced_predator_prey.ipynb
@@ -10,7 +10,7 @@
"- Use VectorBuilder for custom ODE solvers\n",
"- Compare Diffsol, Diffrax (JAX), and DifferentialEquations.jl\n",
"- Understand performance trade-offs\n",
- "- Integrate external solvers with Chronopt\n",
+ "- Integrate external solvers with Diffid\n",
"\n",
"**Prerequisites:** Tutorials 1-2, basic JAX or Julia knowledge (optional)\n",
"\n",
@@ -20,136 +20,7 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Introduction\n",
- "\n",
- "Chronopt's **DiffsolBuilder** provides high-performance ODE solving for most cases. But sometimes you need:\n",
- "\n",
- "- **JAX/Diffrax**: Automatic differentiation, GPU acceleration\n",
- "- **Julia/DifferentialEquations.jl**: Specialized solvers, stiff equations\n",
- "- **Custom simulators**: Agent-based models, PDEs, hybrid systems\n",
- "\n",
- "**VectorBuilder** lets you integrate any Python-callable forward model with Chronopt's optimizers.\n",
- "\n",
- "## The Lotka-Volterra Model\n",
- "\n",
- "The predator-prey equations:\n",
- "\n",
- "$$\\begin{aligned}\n",
- "\\frac{dx}{dt} &= \\alpha x - \\beta x y \\\\\n",
- "\\frac{dy}{dt} &= \\delta x y - \\gamma y\n",
- "\\end{aligned}$$\n",
- "\n",
- "where:\n",
- "- $x$ is prey population\n",
- "- $y$ is predator population\n",
- "- $\\alpha, \\beta, \\gamma, \\delta$ are interaction rates\n",
- "\n",
- "This tutorial demonstrates parameter fitting with three different solver backends.\n",
- "\n",
- "## Coming Soon\n",
- "\n",
- "This advanced tutorial is under development. It will cover:\n",
- "\n",
- "### Backend 1: Diffsol (Built-in)\n",
- "```python\n",
- "builder = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(lotka_volterra_dsl)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 1.0)\n",
- " # ... more parameters\n",
- ")\n",
- "```\n",
- "\n",
- "### Backend 2: JAX/Diffrax\n",
- "```python\n",
- "import jax\n",
- "from diffrax import diffeqsolve, ODETerm, Tsit5\n",
- "\n",
- "def diffrax_solver(params):\n",
- " # Your Diffrax integration\n",
- " return predictions\n",
- "\n",
- "builder = (\n",
- " chron.VectorBuilder()\n",
- " .with_objective(diffrax_solver)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 1.0)\n",
- ")\n",
- "```\n",
- "\n",
- "### Backend 3: Julia/DifferentialEquations.jl\n",
- "```python\n",
- "from diffeqpy import de\n",
- "\n",
- "def diffeqpy_solver(params):\n",
- " # Your Julia integration\n",
- " return predictions\n",
- "\n",
- "builder = (\n",
- " chron.VectorBuilder()\n",
- " .with_objective(diffeqpy_solver)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 1.0)\n",
- ")\n",
- "```\n",
- "\n",
- "## Performance Comparison\n",
- "\n",
- "The tutorial will benchmark all three backends:\n",
- "\n",
- "- **Accuracy**: Parameter recovery quality\n",
- "- **Speed**: Time per function evaluation\n",
- "- **Ease of use**: Setup complexity\n",
- "- **Special features**: Gradients, GPU, stiff solvers\n",
- "\n",
- "## When to Use Each Backend\n",
- "\n",
- "| Backend | Best For |\n",
- "|---------|----------|\n",
- "| **Diffsol** | General purpose, fast, built-in |\n",
- "| **JAX/Diffrax** | Gradients, GPU, neural ODEs |\n",
- "| **Julia/DiffEq** | Stiff systems, specialized solvers, DAEs |\n",
- "| **Custom** | Non-ODE models, complex physics |\n",
- "\n",
- "## Example Data\n",
- "\n",
- "The predator-prey examples directory contains:\n",
- "- `generate_data_diffrax.py`: Creates synthetic data\n",
- "- `predator_prey_diffsol.py`: Diffsol backend\n",
- "- `predator_prey_diffrax.py`: JAX/Diffrax backend\n",
- "- `predator_prey_diffeqpy.py`: Julia backend\n",
- "\n",
- "Run these scripts directly to see the backends in action!\n",
- "\n",
- "## Installation\n",
- "\n",
- "For JAX/Diffrax:\n",
- "```bash\n",
- "pip install jax diffrax\n",
- "```\n",
- "\n",
- "For Julia/DifferentialEquations.jl:\n",
- "```bash\n",
- "pip install diffeqpy\n",
- "python -c \"from diffeqpy import de; de.install()\"\n",
- "```\n",
- "\n",
- "## Key Takeaways\n",
- "\n",
- "1. **VectorBuilder** integrates any Python callable\n",
- "2. **Diffsol** is the default - fast and easy\n",
- "3. **JAX/Diffrax** for gradients and GPU\n",
- "4. **Julia/DiffEq** for specialized solvers\n",
- "5. All backends work with Chronopt's optimizers\n",
- "\n",
- "## Next Steps\n",
- "\n",
- "- [Custom Solvers Guide](../../guides/custom-solvers.md) - Detailed integration guide\n",
- "- [VectorBuilder API](../../api-reference/python/builders.md#vectorbuilder)\n",
- "- [Examples Gallery](../../examples/gallery.md) - More backend examples"
- ]
+ "source": "## Introduction\n\nDiffid's **DiffsolBuilder** provides high-performance ODE solving for most cases. But sometimes you need:\n\n- **JAX/Diffrax**: Automatic differentiation, GPU acceleration\n- **Julia/DifferentialEquations.jl**: Specialized solvers, stiff equations\n- **Custom simulators**: Agent-based models, PDEs, hybrid systems\n\n**VectorBuilder** lets you integrate any Python-callable forward model with Diffid's optimizers.\n\n## The Lotka-Volterra Model\n\nThe predator-prey equations:\n\n$$\\begin{aligned}\n\\frac{dx}{dt} &= \\alpha x - \\beta x y \\\\\n\\frac{dy}{dt} &= \\delta x y - \\gamma y\n\\end{aligned}$$\n\nwhere:\n- $x$ is prey population\n- $y$ is predator population\n- $\\alpha, \\beta, \\gamma, \\delta$ are interaction rates\n\nThis tutorial demonstrates parameter fitting with three different solver backends.\n\n## Coming Soon\n\nThis advanced tutorial is under development. It will cover:\n\n### Backend 1: Diffsol (Built-in)\n```python\nbuilder = (\n diffid.DiffsolBuilder()\n .with_diffsl(lotka_volterra_dsl)\n .with_data(data)\n .with_parameter(\"alpha\", 1.0)\n # ... more parameters\n)\n```\n\n### Backend 2: JAX/Diffrax\n```python\nimport jax\nfrom diffrax import diffeqsolve, ODETerm, Tsit5\n\ndef diffrax_solver(params):\n # Your Diffrax integration\n return predictions\n\nbuilder = (\n diffid.VectorBuilder()\n .with_objective(diffrax_solver)\n .with_data(data)\n .with_parameter(\"alpha\", 1.0)\n)\n```\n\n### Backend 3: Julia/DifferentialEquations.jl\n```python\nfrom diffeqpy import de\n\ndef diffeqpy_solver(params):\n # Your Julia integration\n return predictions\n\nbuilder = (\n diffid.VectorBuilder()\n .with_objective(diffeqpy_solver)\n .with_data(data)\n .with_parameter(\"alpha\", 1.0)\n)\n```\n\n## Performance Comparison\n\nThe tutorial will benchmark all three backends:\n\n- **Accuracy**: Parameter recovery quality\n- **Speed**: Time per function evaluation\n- **Ease of use**: Setup complexity\n- **Special features**: Gradients, GPU, stiff solvers\n\n## When to Use Each Backend\n\n| Backend | Best For |\n|---------|----------|\n| **Diffsol** | General purpose, fast, built-in |\n| **JAX/Diffrax** | Gradients, GPU, neural ODEs |\n| **Julia/DiffEq** | Stiff systems, specialized solvers, DAEs |\n| **Custom** | Non-ODE models, complex physics |\n\n## Example Data\n\nThe predator-prey examples directory contains:\n- `generate_data_diffrax.py`: Creates synthetic data\n- `predator_prey_diffsol.py`: Diffsol backend\n- `predator_prey_diffrax.py`: JAX/Diffrax backend\n- `predator_prey_diffeqpy.py`: Julia backend\n\nRun these scripts directly to see the backends in action!\n\n## Installation\n\nFor JAX/Diffrax:\n```bash\npip install jax diffrax\n```\n\nFor Julia/DifferentialEquations.jl:\n```bash\npip install diffeqpy\npython -c \"from diffeqpy import de; de.install()\"\n```\n\n## Key Takeaways\n\n1. **VectorBuilder** integrates any Python callable\n2. **Diffsol** is the default - fast and easy\n3. **JAX/Diffrax** for gradients and GPU\n4. **Julia/DiffEq** for specialized solvers\n5. All backends work with Diffid's optimizers\n\n## Next Steps\n\n- [Custom Solvers Guide](../../guides/custom-solvers.md) - Detailed integration guide\n- [VectorBuilder API](../../api-reference/python/builders.md#vectorbuilder)\n- [Examples Gallery](../../examples/gallery.md) - More backend examples"
}
],
"metadata": {
@@ -173,4 +44,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tutorials/notebooks/06_custom_solver_integration.ipynb b/docs/tutorials/notebooks/06_custom_solver_integration.ipynb
index ba17ab5..2bb03ca 100644
--- a/docs/tutorials/notebooks/06_custom_solver_integration.ipynb
+++ b/docs/tutorials/notebooks/06_custom_solver_integration.ipynb
@@ -20,32 +20,14 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Introduction\n",
- "\n",
- "While `DiffsolBuilder` provides a convenient interface for ODE fitting using the DiffSL language, `VectorBuilder` offers maximum flexibility by allowing you to use **any** ODE solver. This enables:\n",
- "\n",
- "1. **GPU acceleration** via JAX/Diffrax\n",
- "2. **Specialized solvers** from Julia's DifferentialEquations.jl\n",
- "3. **Custom dynamics** that don't fit the DiffSL syntax\n",
- "4. **Pre-existing code** integration\n",
- "\n",
- "In this tutorial, we'll solve the predator-prey model using different backends and compare their performance."
- ]
+ "source": "## Introduction\n\nWhile `DiffsolBuilder` provides a convenient interface for ODE fitting using the DiffSL language, `VectorBuilder` offers maximum flexibility by allowing you to use **any** ODE solver. This enables:\n\n1. **GPU acceleration** via JAX/Diffrax\n2. **Specialized solvers** from Julia's DifferentialEquations.jl\n3. **Custom dynamics** that don't fit the DiffSL syntax\n4. **Pre-existing code** integration\n\nIn this tutorial, we'll solve the predator-prey model using different backends and compare their performance."
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
- "source": [
- "# Import plotting utilities\n",
- "import time\n",
- "\n",
- "import chronopt as chron\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np"
- ]
+ "source": "# Import plotting utilities\nimport time\n\nimport diffid\nimport matplotlib.pyplot as plt\nimport numpy as np"
},
{
"cell_type": "markdown",
@@ -175,56 +157,12 @@
"execution_count": null,
"metadata": {},
"outputs": [],
- "source": [
- "# Fit with DiffsolBuilder\n",
- "start_time = time.time()\n",
- "\n",
- "# Combine times and observations for DiffsolBuilder\n",
- "# Data format: first column is time, remaining columns are observations\n",
- "data = np.column_stack((t_data, y_observed))\n",
- "\n",
- "result_diffsol = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 1.0) # initial guess\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.5)\n",
- " .with_cost(chron.SSE())\n",
- " .with_optimiser(chron.NelderMead().with_max_iter(500))\n",
- " .build()\n",
- " .optimise()\n",
- ")\n",
- "\n",
- "diffsol_time = time.time() - start_time\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"DIFFSOL RESULTS\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"True parameters: {list(true_params.values())}\")\n",
- "print(f\"Estimated parameters: {result_diffsol.x}\")\n",
- "print(f\"Final SSE: {result_diffsol.value:.6f}\")\n",
- "print(f\"Iterations: {result_diffsol.iterations}\")\n",
- "print(f\"Function evals: {result_diffsol.evaluations}\")\n",
- "print(f\"Time: {diffsol_time:.3f}s\")\n",
- "print(f\"Success: {result_diffsol.success}\")"
- ]
+ "source": "# Fit with DiffsolBuilder\nstart_time = time.time()\n\n# Combine times and observations for DiffsolBuilder\n# Data format: first column is time, remaining columns are observations\ndata = np.column_stack((t_data, y_observed))\n\nresult_diffsol = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data)\n .with_parameter(\"alpha\", 1.0) # initial guess\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.5)\n .with_cost(diffid.SSE())\n .with_optimiser(diffid.NelderMead().with_max_iter(500))\n .build()\n .optimise()\n)\n\ndiffsol_time = time.time() - start_time\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"DIFFSOL RESULTS\")\nprint(\"=\" * 60)\nprint(f\"True parameters: {list(true_params.values())}\")\nprint(f\"Estimated parameters: {result_diffsol.x}\")\nprint(f\"Final SSE: {result_diffsol.value:.6f}\")\nprint(f\"Iterations: {result_diffsol.iterations}\")\nprint(f\"Function evals: {result_diffsol.evaluations}\")\nprint(f\"Time: {diffsol_time:.3f}s\")\nprint(f\"Success: {result_diffsol.success}\")"
},
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Method 2: VectorBuilder with JAX/Diffrax\n",
- "\n",
- "`VectorBuilder` accepts any callable that maps parameters to predicted outputs. This enables using **JAX/Diffrax** for GPU-accelerated ODE solving with automatic differentiation.\n",
- "\n",
- "### Advantages:\n",
- "- ā” GPU acceleration\n",
- "- š„ JIT compilation\n",
- "- š Automatic differentiation (gradients for free)\n",
- "- š Fast iteration for gradient-based optimisers"
- ]
+ "source": "## Method 2: VectorBuilder with JAX/Diffrax\n\n`VectorBuilder` accepts any callable that maps parameters to predicted outputs. This enables using **JAX/Diffrax** for GPU-accelerated ODE solving with automatic differentiation.\n\n### Advantages:\n- ā” GPU acceleration\n- š„ JIT compilation\n- š Automatic differentiation (gradients for free)\n- š Fast iteration for gradient-based optimisers"
},
{
"cell_type": "code",
@@ -295,12 +233,12 @@
" return sol.ys # Shape: (n_times, 2)\n",
"\n",
" def simulate_numpy(params):\n",
- " \"\"\"NumPy wrapper for Chronopt compatibility.\"\"\"\n",
+ " \"\"\"NumPy wrapper for Diffid compatibility.\"\"\"\n",
" return np.asarray(simulate_jax(jnp.asarray(params)))\n",
"\n",
" # Warm up JIT compiler\n",
" _ = simulate_numpy([1.0, 0.4, 0.1, 0.4])\n",
- " print(\"ā
JAX/Diffrax solver ready (JIT compiled)\")"
+ " print(\"JAX/Diffrax solver ready (JIT compiled)\")"
]
},
{
@@ -308,39 +246,7 @@
"execution_count": null,
"metadata": {},
"outputs": [],
- "source": [
- "if JAX_AVAILABLE:\n",
- " # Fit with VectorBuilder + JAX/Diffrax\n",
- " start_time = time.time()\n",
- "\n",
- " result_diffrax = (\n",
- " chron.VectorBuilder()\n",
- " .with_objective(simulate_numpy)\n",
- " .with_data(y_observed)\n",
- " .with_parameter(\"alpha\", 1.0)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.5)\n",
- " .with_cost(chron.SSE())\n",
- " .with_optimiser(chron.NelderMead().with_max_iter(500))\n",
- " .build()\n",
- " .optimise()\n",
- " )\n",
- "\n",
- " diffrax_time = time.time() - start_time\n",
- "\n",
- " print(\"\\n\" + \"=\" * 60)\n",
- " print(\"DIFFRAX (JAX) RESULTS\")\n",
- " print(\"=\" * 60)\n",
- " print(f\"True parameters: {list(true_params.values())}\")\n",
- " print(f\"Estimated parameters: {result_diffrax.x}\")\n",
- " print(f\"Final SSE: {result_diffrax.value:.6f}\")\n",
- " print(f\"Iterations: {result_diffrax.iterations}\")\n",
- " print(f\"Function evals: {result_diffrax.evaluations}\")\n",
- " print(f\"Time: {diffrax_time:.3f}s\")\n",
- " print(f\"Success: {result_diffrax.success}\")\n",
- " print(f\"\\nSpeedup vs Diffsol: {diffsol_time / diffrax_time:.2f}x\")"
- ]
+ "source": "if JAX_AVAILABLE:\n # Fit with VectorBuilder + JAX/Diffrax\n start_time = time.time()\n\n result_diffrax = (\n diffid.VectorBuilder()\n .with_objective(simulate_numpy)\n .with_data(y_observed)\n .with_parameter(\"alpha\", 1.0)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.5)\n .with_cost(diffid.SSE())\n .with_optimiser(diffid.NelderMead().with_max_iter(500))\n .build()\n .optimise()\n )\n\n diffrax_time = time.time() - start_time\n\n print(\"\\n\" + \"=\" * 60)\n print(\"DIFFRAX (JAX) RESULTS\")\n print(\"=\" * 60)\n print(f\"True parameters: {list(true_params.values())}\")\n print(f\"Estimated parameters: {result_diffrax.x}\")\n print(f\"Final SSE: {result_diffrax.value:.6f}\")\n print(f\"Iterations: {result_diffrax.iterations}\")\n print(f\"Function evals: {result_diffrax.evaluations}\")\n print(f\"Time: {diffrax_time:.3f}s\")\n print(f\"Success: {result_diffrax.success}\")\n print(f\"\\nSpeedup vs Diffsol: {diffsol_time / diffrax_time:.2f}x\")"
},
{
"cell_type": "markdown",
@@ -404,7 +310,7 @@
"\n",
" # Test\n",
" test_output = simulate_julia([1.0, 0.4, 0.1, 0.4])\n",
- " print(\"ā
Julia/DifferentialEquations.jl ready\")\n",
+ " print(\"Julia/DifferentialEquations.jl ready\")\n",
" print(f\" Output shape: {test_output.shape}\")"
]
},
@@ -413,39 +319,7 @@
"execution_count": null,
"metadata": {},
"outputs": [],
- "source": [
- "if JULIA_AVAILABLE:\n",
- " # Fit with VectorBuilder + Julia\n",
- " start_time = time.time()\n",
- "\n",
- " result_julia = (\n",
- " chron.VectorBuilder()\n",
- " .with_objective(simulate_julia)\n",
- " .with_data(y_observed)\n",
- " .with_parameter(\"alpha\", 1.0)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.5)\n",
- " .with_cost(chron.SSE())\n",
- " .with_optimiser(chron.NelderMead().with_max_iter(500))\n",
- " .build()\n",
- " .optimise()\n",
- " )\n",
- "\n",
- " julia_time = time.time() - start_time\n",
- "\n",
- " print(\"\\n\" + \"=\" * 60)\n",
- " print(\"JULIA DIFFERENTIALEQUATIONS.JL RESULTS\")\n",
- " print(\"=\" * 60)\n",
- " print(f\"True parameters: {list(true_params.values())}\")\n",
- " print(f\"Estimated parameters: {result_julia.x}\")\n",
- " print(f\"Final SSE: {result_julia.value:.6f}\")\n",
- " print(f\"Iterations: {result_julia.iterations}\")\n",
- " print(f\"Function evals: {result_julia.evaluations}\")\n",
- " print(f\"Time: {julia_time:.3f}s\")\n",
- " print(f\"Success: {result_julia.success}\")\n",
- " print(f\"\\nSpeedup vs Diffsol: {diffsol_time / julia_time:.2f}x\")"
- ]
+ "source": "if JULIA_AVAILABLE:\n # Fit with VectorBuilder + Julia\n start_time = time.time()\n\n result_julia = (\n diffid.VectorBuilder()\n .with_objective(simulate_julia)\n .with_data(y_observed)\n .with_parameter(\"alpha\", 1.0)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.5)\n .with_cost(diffid.SSE())\n .with_optimiser(diffid.NelderMead().with_max_iter(500))\n .build()\n .optimise()\n )\n\n julia_time = time.time() - start_time\n\n print(\"\\n\" + \"=\" * 60)\n print(\"JULIA DIFFERENTIALEQUATIONS.JL RESULTS\")\n print(\"=\" * 60)\n print(f\"True parameters: {list(true_params.values())}\")\n print(f\"Estimated parameters: {result_julia.x}\")\n print(f\"Final SSE: {result_julia.value:.6f}\")\n print(f\"Iterations: {result_julia.iterations}\")\n print(f\"Function evals: {result_julia.evaluations}\")\n print(f\"Time: {julia_time:.3f}s\")\n print(f\"Success: {result_julia.success}\")\n print(f\"\\nSpeedup vs Diffsol: {diffsol_time / julia_time:.2f}x\")"
},
{
"cell_type": "markdown",
@@ -656,45 +530,7 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": [
- "## Key Takeaways\n",
- "\n",
- "### When to Use Each Backend\n",
- "\n",
- "**DiffsolBuilder (Diffsol):**\n",
- "- ā
Quick prototyping with DiffSL syntax\n",
- "- ā
Standard ODE problems\n",
- "- ā
No extra dependencies\n",
- "- ā Limited to DiffSL expressiveness\n",
- "\n",
- "**VectorBuilder + JAX/Diffrax:**\n",
- "- ā
GPU acceleration for large problems\n",
- "- ā
Automatic differentiation (enables gradient-based optimisers)\n",
- "- ā
JIT compilation for speed\n",
- "- ā
Excellent for high-dimensional problems\n",
- "- ā Requires JAX ecosystem\n",
- "\n",
- "**VectorBuilder + Julia/DifferentialEquations.jl:**\n",
- "- ā
Largest solver collection (stiff, stochastic, DAE, DDE, etc.)\n",
- "- ā
Advanced features (callbacks, sensitivity analysis)\n",
- "- ā
Best for specialized problems\n",
- "- ā Requires Julia installation\n",
- "\n",
- "### Performance Insights\n",
- "\n",
- "1. **JAX/Diffrax** typically fastest after JIT warmup\n",
- "2. **Diffsol** excellent balance of speed and simplicity\n",
- "3. **Julia/DiffEq** best for problems requiring specialized solvers\n",
- "4. All backends produce equivalent parameter estimates\n",
- "\n",
- "### VectorBuilder Flexibility\n",
- "\n",
- "The key advantage of `VectorBuilder` is **total control**:\n",
- "- Any Python callable works\n",
- "- Integrate pre-existing simulation code\n",
- "- Mix solver backends in the same workflow\n",
- "- Enable advanced features like GPU acceleration"
- ]
+ "source": "## Key Takeaways\n\n### When to Use Each Backend\n\n**DiffsolBuilder (Diffsol):**\n- Quick prototyping with DiffSL syntax\n- ā
Standard ODE problems\n- ā
No extra dependencies\n- ā Limited to DiffSL expressiveness\n\n**VectorBuilder + JAX/Diffrax:**\n- ā
GPU acceleration for large problems\n- ā
Automatic differentiation (enables gradient-based optimisers)\n- ā
JIT compilation for speed\n- ā
Excellent for high-dimensional problems\n- ā Requires JAX ecosystem\n\n**VectorBuilder + Julia/DifferentialEquations.jl:**\n- ā
Largest solver collection (stiff, stochastic, DAE, DDE, etc.)\n- ā
Advanced features (callbacks, sensitivity analysis)\n- ā
Best for specialized problems\n- ā Requires Julia installation\n\n### Performance Insights\n\n1. **JAX/Diffrax** typically fastest after JIT warmup\n2. **Diffsol** excellent balance of speed and simplicity\n3. **Julia/DiffEq** best for problems requiring specialized solvers\n4. All backends produce equivalent parameter estimates\n\n### VectorBuilder Flexibility\n\nThe key advantage of `VectorBuilder` is **total control**:\n- Any Python callable works\n- Integrate pre-existing simulation code\n- Mix solver backends in the same workflow\n- Enable advanced features like GPU acceleration"
},
{
"cell_type": "markdown",
diff --git a/docs/tutorials/notebooks/07_parallel_optimization.ipynb b/docs/tutorials/notebooks/07_parallel_optimization.ipynb
index a542a35..5608a42 100644
--- a/docs/tutorials/notebooks/07_parallel_optimization.ipynb
+++ b/docs/tutorials/notebooks/07_parallel_optimization.ipynb
@@ -21,11 +21,11 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": "## Introduction\n\nChronopt supports **parallel evaluation** for population-based optimisers (CMA-ES, Dynamic Nested Sampling). However, the effectiveness depends on the evaluation cost and backend used.\n\n### Key Insights\n\n1. **DiffsolBuilder** with `.with_parallel(True)` uses Rust's rayon for parallel ODE integration\n2. **Fast evaluations** (< 1ms) may see limited speedup due to efficient solver caching\n3. **Python callables** cannot be parallelised with threads (GIL), but **multiprocessing** works\n\nThis tutorial covers:\n- Parallel ODE fitting with `DiffsolBuilder`\n- Using `multiprocessing` for expensive Python callables\n- Understanding when parallelism helps"
+ "source": "## Introduction\n\nDiffid supports **parallel evaluation** for population-based optimisers (CMA-ES, Dynamic Nested Sampling). However, the effectiveness depends on the evaluation cost and backend used.\n\n### Key Insights\n\n1. **DiffsolBuilder** with `.with_parallel(True)` uses Rust's rayon for parallel ODE integration\n2. **Fast evaluations** (< 1ms) may see limited speedup due to efficient solver caching\n3. **Python callables** cannot be parallelised with threads (GIL), but **multiprocessing** works\n\nThis tutorial covers:\n- Parallel ODE fitting with `DiffsolBuilder`\n- Using `multiprocessing` for expensive Python callables\n- Understanding when parallelism helps"
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:18.792193Z",
@@ -38,28 +38,8 @@
"shell.execute_reply": "2026-01-10T22:22:07.286361Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Available CPU cores: 8\n"
- ]
- }
- ],
- "source": [
- "import multiprocessing\n",
- "import time\n",
- "\n",
- "import chronopt as chron\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from scipy.integrate import solve_ivp\n",
- "\n",
- "# Detect available cores\n",
- "n_cores = multiprocessing.cpu_count()\n",
- "print(f\"Available CPU cores: {n_cores}\")"
- ]
+ "outputs": [],
+ "source": "import multiprocessing\nimport time\n\nimport diffid\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom scipy.integrate import solve_ivp\n\n# Detect available cores\nn_cores = multiprocessing.cpu_count()\nprint(f\"Available CPU cores: {n_cores}\")"
},
{
"cell_type": "markdown",
@@ -155,7 +135,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:20.020454Z",
@@ -168,58 +148,8 @@
"shell.execute_reply": "2026-01-10T22:22:08.804066Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Running CMA-ES (sequential)...\n",
- "\n",
- "============================================================\n",
- "CMA-ES (Sequential)\n",
- "============================================================\n",
- "Solution: [1.14387427 0.51852859 0.48038414 0.75238014]\n",
- "True params: [1.1, 0.4, 0.1, 0.4]\n",
- "Final SSE: 11886.345\n",
- "Evaluations: 801\n",
- "Time: 0.98s\n",
- "Time per eval: 1.22ms\n"
- ]
- }
- ],
- "source": [
- "# Sequential execution (parallel=False)\n",
- "print(\"Running CMA-ES (sequential)...\")\n",
- "\n",
- "start = time.time()\n",
- "\n",
- "result_seq = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 0.8)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.3)\n",
- " .with_cost(chron.SSE())\n",
- " .with_parallel(False) # Sequential evaluation\n",
- " .with_optimiser(chron.CMAES().with_max_iter(100).with_step_size(0.3))\n",
- " .build()\n",
- " .optimise()\n",
- ")\n",
- "\n",
- "time_seq = time.time() - start\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"CMA-ES (Sequential)\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"Solution: {result_seq.x}\")\n",
- "print(f\"True params: {list(true_params.values())}\")\n",
- "print(f\"Final SSE: {result_seq.value:.3f}\")\n",
- "print(f\"Evaluations: {result_seq.evaluations}\")\n",
- "print(f\"Time: {time_seq:.2f}s\")\n",
- "print(f\"Time per eval: {time_seq / result_seq.evaluations * 1000:.2f}ms\")"
- ]
+ "outputs": [],
+ "source": "# Sequential execution (parallel=False)\nprint(\"Running CMA-ES (sequential)...\")\n\nstart = time.time()\n\nresult_seq = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data)\n .with_parameter(\"alpha\", 0.8)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.3)\n .with_cost(diffid.SSE())\n .with_parallel(False) # Sequential evaluation\n .with_optimiser(diffid.CMAES().with_max_iter(100).with_step_size(0.3))\n .build()\n .optimise()\n)\n\ntime_seq = time.time() - start\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"CMA-ES (Sequential)\")\nprint(\"=\" * 60)\nprint(f\"Solution: {result_seq.x}\")\nprint(f\"True params: {list(true_params.values())}\")\nprint(f\"Final SSE: {result_seq.value:.3f}\")\nprint(f\"Evaluations: {result_seq.evaluations}\")\nprint(f\"Time: {time_seq:.2f}s\")\nprint(f\"Time per eval: {time_seq / result_seq.evaluations * 1000:.2f}ms\")"
},
{
"cell_type": "markdown",
@@ -228,7 +158,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:20.380944Z",
@@ -241,67 +171,12 @@
"shell.execute_reply": "2026-01-10T22:22:12.217981Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Running CMA-ES (parallel)...\n",
- "\n",
- "============================================================\n",
- "CMA-ES (Parallel)\n",
- "============================================================\n",
- "Solution: [0.8 0.3 0.05 0.3 ]\n",
- "True params: [1.1, 0.4, 0.1, 0.4]\n",
- "Final SSE: 29190.956\n",
- "Evaluations: 9\n",
- "Time: 0.14s\n",
- "Time per eval: 15.72ms\n",
- "\n",
- "š Speedup: 6.90x (with 8 cores)\n"
- ]
- }
- ],
- "source": [
- "# Parallel execution (parallel=True)\n",
- "print(\"Running CMA-ES (parallel)...\")\n",
- "\n",
- "start = time.time()\n",
- "\n",
- "result_par = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 0.8)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.3)\n",
- " .with_cost(chron.SSE())\n",
- " .with_parallel(True) # Parallel evaluation!\n",
- " .with_optimiser(chron.CMAES().with_max_iter(100).with_step_size(0.3))\n",
- " .build()\n",
- " .optimise()\n",
- ")\n",
- "\n",
- "time_par = time.time() - start\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(\"CMA-ES (Parallel)\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"Solution: {result_par.x}\")\n",
- "print(f\"True params: {list(true_params.values())}\")\n",
- "print(f\"Final SSE: {result_par.value:.3f}\")\n",
- "print(f\"Evaluations: {result_par.evaluations}\")\n",
- "print(f\"Time: {time_par:.2f}s\")\n",
- "print(f\"Time per eval: {time_par / result_par.evaluations * 1000:.2f}ms\")\n",
- "\n",
- "speedup = time_seq / time_par\n",
- "print(f\"\\nš Speedup: {speedup:.2f}x (with {n_cores} cores)\")"
- ]
+ "outputs": [],
+ "source": "# Parallel execution (parallel=True)\nprint(\"Running CMA-ES (parallel)...\")\n\nstart = time.time()\n\nresult_par = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data)\n .with_parameter(\"alpha\", 0.8)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.3)\n .with_cost(diffid.SSE())\n .with_parallel(True) # Parallel evaluation!\n .with_optimiser(diffid.CMAES().with_max_iter(100).with_step_size(0.3))\n .build()\n .optimise()\n)\n\ntime_par = time.time() - start\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"CMA-ES (Parallel)\")\nprint(\"=\" * 60)\nprint(f\"Solution: {result_par.x}\")\nprint(f\"True params: {list(true_params.values())}\")\nprint(f\"Final SSE: {result_par.value:.3f}\")\nprint(f\"Evaluations: {result_par.evaluations}\")\nprint(f\"Time: {time_par:.2f}s\")\nprint(f\"Time per eval: {time_par / result_par.evaluations * 1000:.2f}ms\")\n\nspeedup = time_seq / time_par\nprint(f\"\\nš Speedup: {speedup:.2f}x (with {n_cores} cores)\")"
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:20.695642Z",
@@ -314,68 +189,8 @@
"shell.execute_reply": "2026-01-10T22:22:19.043950Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Running CMA-ES (parallel, large population)...\n",
- "\n",
- "============================================================\n",
- "CMA-ES (Parallel, Population=16)\n",
- "============================================================\n",
- "Solution: [1.18267573 0.602622 0.37072684 0.67821184]\n",
- "True params: [1.1, 0.4, 0.1, 0.4]\n",
- "Final SSE: 8171.282\n",
- "Evaluations: 801\n",
- "Time: 0.26s\n",
- "Time per eval: 0.32ms\n",
- "\n",
- "š Speedup: 3.81x\n"
- ]
- }
- ],
- "source": [
- "# Parallel with larger population (more parallel work per generation)\n",
- "print(\"Running CMA-ES (parallel, large population)...\")\n",
- "\n",
- "start = time.time()\n",
- "\n",
- "result_par_large = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data)\n",
- " .with_parameter(\"alpha\", 0.8)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.3)\n",
- " .with_cost(chron.SSE())\n",
- " .with_parallel(True)\n",
- " .with_optimiser(\n",
- " chron.CMAES()\n",
- " .with_max_iter(50)\n",
- " .with_step_size(0.3)\n",
- " .with_population_size(2 * n_cores) # Match population to available cores\n",
- " )\n",
- " .build()\n",
- " .optimise()\n",
- ")\n",
- "\n",
- "time_par_large = time.time() - start\n",
- "\n",
- "print(\"\\n\" + \"=\" * 60)\n",
- "print(f\"CMA-ES (Parallel, Population={2 * n_cores})\")\n",
- "print(\"=\" * 60)\n",
- "print(f\"Solution: {result_par_large.x}\")\n",
- "print(f\"True params: {list(true_params.values())}\")\n",
- "print(f\"Final SSE: {result_par_large.value:.3f}\")\n",
- "print(f\"Evaluations: {result_par_large.evaluations}\")\n",
- "print(f\"Time: {time_par_large:.2f}s\")\n",
- "print(f\"Time per eval: {time_par_large / result_par_large.evaluations * 1000:.2f}ms\")\n",
- "\n",
- "speedup_large = time_seq / time_par_large\n",
- "print(f\"\\nš Speedup: {speedup_large:.2f}x\")"
- ]
+ "outputs": [],
+ "source": "# Parallel with larger population (more parallel work per generation)\nprint(\"Running CMA-ES (parallel, large population)...\")\n\nstart = time.time()\n\nresult_par_large = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data)\n .with_parameter(\"alpha\", 0.8)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.3)\n .with_cost(diffid.SSE())\n .with_parallel(True)\n .with_optimiser(\n diffid.CMAES()\n .with_max_iter(50)\n .with_step_size(0.3)\n .with_population_size(2 * n_cores) # Match population to available cores\n )\n .build()\n .optimise()\n)\n\ntime_par_large = time.time() - start\n\nprint(\"\\n\" + \"=\" * 60)\nprint(f\"CMA-ES (Parallel, Population={2 * n_cores})\")\nprint(\"=\" * 60)\nprint(f\"Solution: {result_par_large.x}\")\nprint(f\"True params: {list(true_params.values())}\")\nprint(f\"Final SSE: {result_par_large.value:.3f}\")\nprint(f\"Evaluations: {result_par_large.evaluations}\")\nprint(f\"Time: {time_par_large:.2f}s\")\nprint(f\"Time per eval: {time_par_large / result_par_large.evaluations * 1000:.2f}ms\")\n\nspeedup_large = time_seq / time_par_large\nprint(f\"\\nš Speedup: {speedup_large:.2f}x\")"
},
{
"cell_type": "markdown",
@@ -384,7 +199,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:20.944100Z",
@@ -397,82 +212,8 @@
"shell.execute_reply": "2026-01-10T22:22:30.395476Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Testing 20 sequential evaluations...\n",
- "Sequential: 0.20s (9.8ms/eval)\n",
- "\n",
- "š” To parallelise in a script, use multiprocessing:\n",
- "\n",
- "from concurrent.futures import ProcessPoolExecutor\n",
- "\n",
- "def evaluate_batch_parallel(params_list, n_workers=8):\n",
- " with ProcessPoolExecutor(max_workers=n_workers) as executor:\n",
- " return list(executor.map(expensive_objective, params_list))\n",
- "\n",
- "# This provides near-linear speedup for expensive functions\n",
- "\n"
- ]
- }
- ],
- "source": [
- "# Define an expensive objective function\n",
- "def expensive_objective(params):\n",
- " \"\"\"\n",
- " An expensive objective function that simulates computation.\n",
- " In practice, this could be a complex simulation, ML model, etc.\n",
- " \"\"\"\n",
- " alpha, beta, delta, gamma = params\n",
- "\n",
- " # Simulate expensive computation (ODE integration with scipy)\n",
- " sol = solve_ivp(\n",
- " lotka_volterra,\n",
- " [0, 100],\n",
- " [10.0, 5.0],\n",
- " args=(alpha, beta, delta, gamma),\n",
- " t_eval=t_data,\n",
- " method=\"RK45\",\n",
- " )\n",
- "\n",
- " if not sol.success:\n",
- " return 1e10 # Return large value for failed integrations\n",
- "\n",
- " # Compute SSE\n",
- " y_pred = sol.y.T\n",
- " sse = np.sum((y_pred - y_observed) ** 2)\n",
- " return sse\n",
- "\n",
- "\n",
- "# Test single evaluation time\n",
- "n_evals = 20\n",
- "test_params = [[0.8 + i * 0.02, 0.3, 0.05, 0.3] for i in range(n_evals)]\n",
- "\n",
- "print(f\"Testing {n_evals} sequential evaluations...\")\n",
- "\n",
- "start = time.time()\n",
- "seq_results = [expensive_objective(p) for p in test_params]\n",
- "time_seq_python = time.time() - start\n",
- "print(\n",
- " f\"Sequential: {time_seq_python:.2f}s ({time_seq_python / n_evals * 1000:.1f}ms/eval)\"\n",
- ")\n",
- "\n",
- "# Note: Multiprocessing in notebooks has pickling limitations\n",
- "# In a regular Python script, you would use:\n",
- "print(\"\"\"\n",
- "š” To parallelise in a script, use multiprocessing:\n",
- "\n",
- "from concurrent.futures import ProcessPoolExecutor\n",
- "\n",
- "def evaluate_batch_parallel(params_list, n_workers=8):\n",
- " with ProcessPoolExecutor(max_workers=n_workers) as executor:\n",
- " return list(executor.map(expensive_objective, params_list))\n",
- "\n",
- "# This provides near-linear speedup for expensive functions\n",
- "\"\"\")"
- ]
+ "outputs": [],
+ "source": "# Define an expensive objective function\ndef expensive_objective(params):\n \"\"\"\n An expensive objective function that simulates computation.\n In practice, this could be a complex simulation, ML model, etc.\n \"\"\"\n alpha, beta, delta, gamma = params\n\n # Simulate expensive computation (ODE integration with scipy)\n sol = solve_ivp(\n lotka_volterra,\n [0, 100],\n [10.0, 5.0],\n args=(alpha, beta, delta, gamma),\n t_eval=t_data,\n method=\"RK45\",\n )\n\n if not sol.success:\n return 1e10 # Return large value for failed integrations\n\n # Compute SSE\n y_pred = sol.y.T\n sse = np.sum((y_pred - y_observed) ** 2)\n return sse\n\n\n# Test single evaluation time\nn_evals = 20\ntest_params = [[0.8 + i * 0.02, 0.3, 0.05, 0.3] for i in range(n_evals)]\n\nprint(f\"Testing {n_evals} sequential evaluations...\")\n\nstart = time.time()\nseq_results = [expensive_objective(p) for p in test_params]\ntime_seq_python = time.time() - start\nprint(\n f\"Sequential: {time_seq_python:.2f}s ({time_seq_python / n_evals * 1000:.1f}ms/eval)\"\n)\n\n# Note: Multiprocessing in notebooks has pickling limitations\n# In a regular Python script, you would use:\nprint(\"\"\"\nš” To parallelise in a script, use multiprocessing:\n\nfrom concurrent.futures import ProcessPoolExecutor\n\ndef evaluate_batch_parallel(params_list, n_workers=8):\n with ProcessPoolExecutor(max_workers=n_workers) as executor:\n return list(executor.map(expensive_objective, params_list))\n\n# This provides near-linear speedup for expensive functions\n\"\"\")"
},
{
"cell_type": "code",
@@ -573,7 +314,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-22T19:00:23.060489Z",
@@ -586,94 +327,8 @@
"shell.execute_reply": "2026-01-10T22:23:09.260026Z"
}
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Testing DiffsolBuilder scaling with data size...\n",
- "\n",
- "N=100: Sequential=0.11s, Parallel=0.25s, Speedup=0.46x\n",
- "N=200: Sequential=0.67s, Parallel=0.22s, Speedup=3.10x\n",
- "N=500: Sequential=0.17s, Parallel=0.22s, Speedup=0.76x\n"
- ]
- }
- ],
- "source": [
- "# Test DiffsolBuilder scaling with data size\n",
- "data_sizes = [100, 200, 500]\n",
- "scaling_results = []\n",
- "\n",
- "print(\"Testing DiffsolBuilder scaling with data size...\\n\")\n",
- "\n",
- "for n_points in data_sizes:\n",
- " # Generate data with different sizes\n",
- " t_test = np.linspace(0, 100, n_points)\n",
- " sol_test = solve_ivp(\n",
- " lotka_volterra,\n",
- " [t_test[0], t_test[-1]],\n",
- " [10.0, 5.0],\n",
- " args=(\n",
- " true_params[\"alpha\"],\n",
- " true_params[\"beta\"],\n",
- " true_params[\"delta\"],\n",
- " true_params[\"gamma\"],\n",
- " ),\n",
- " t_eval=t_test,\n",
- " method=\"RK45\",\n",
- " )\n",
- " y_test = sol_test.y.T + np.random.normal(0, 0.3, (n_points, 2))\n",
- " data_test = np.column_stack((t_test, y_test))\n",
- "\n",
- " # Sequential\n",
- " start = time.time()\n",
- " _ = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data_test)\n",
- " .with_parameter(\"alpha\", 0.8)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.3)\n",
- " .with_cost(chron.SSE())\n",
- " .with_parallel(False)\n",
- " .with_optimiser(chron.CMAES().with_max_iter(30).with_step_size(0.3))\n",
- " .build()\n",
- " .optimise()\n",
- " )\n",
- " t_seq_scale = time.time() - start\n",
- "\n",
- " # Parallel\n",
- " start = time.time()\n",
- " _ = (\n",
- " chron.DiffsolBuilder()\n",
- " .with_diffsl(model_str)\n",
- " .with_data(data_test)\n",
- " .with_parameter(\"alpha\", 0.8)\n",
- " .with_parameter(\"beta\", 0.3)\n",
- " .with_parameter(\"delta\", 0.05)\n",
- " .with_parameter(\"gamma\", 0.3)\n",
- " .with_cost(chron.SSE())\n",
- " .with_parallel(True)\n",
- " .with_optimiser(chron.CMAES().with_max_iter(30).with_step_size(0.3))\n",
- " .build()\n",
- " .optimise()\n",
- " )\n",
- " t_par_scale = time.time() - start\n",
- "\n",
- " sp = t_seq_scale / t_par_scale\n",
- " scaling_results.append(\n",
- " {\n",
- " \"n_points\": n_points,\n",
- " \"time_seq\": t_seq_scale,\n",
- " \"time_par\": t_par_scale,\n",
- " \"speedup\": sp,\n",
- " }\n",
- " )\n",
- " print(\n",
- " f\"N={n_points:3d}: Sequential={t_seq_scale:.2f}s, Parallel={t_par_scale:.2f}s, Speedup={sp:.2f}x\"\n",
- " )"
- ]
+ "outputs": [],
+ "source": "# Test DiffsolBuilder scaling with data size\ndata_sizes = [100, 200, 500]\nscaling_results = []\n\nprint(\"Testing DiffsolBuilder scaling with data size...\\n\")\n\nfor n_points in data_sizes:\n # Generate data with different sizes\n t_test = np.linspace(0, 100, n_points)\n sol_test = solve_ivp(\n lotka_volterra,\n [t_test[0], t_test[-1]],\n [10.0, 5.0],\n args=(\n true_params[\"alpha\"],\n true_params[\"beta\"],\n true_params[\"delta\"],\n true_params[\"gamma\"],\n ),\n t_eval=t_test,\n method=\"RK45\",\n )\n y_test = sol_test.y.T + np.random.normal(0, 0.3, (n_points, 2))\n data_test = np.column_stack((t_test, y_test))\n\n # Sequential\n start = time.time()\n _ = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data_test)\n .with_parameter(\"alpha\", 0.8)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.3)\n .with_cost(diffid.SSE())\n .with_parallel(False)\n .with_optimiser(diffid.CMAES().with_max_iter(30).with_step_size(0.3))\n .build()\n .optimise()\n )\n t_seq_scale = time.time() - start\n\n # Parallel\n start = time.time()\n _ = (\n diffid.DiffsolBuilder()\n .with_diffsl(model_str)\n .with_data(data_test)\n .with_parameter(\"alpha\", 0.8)\n .with_parameter(\"beta\", 0.3)\n .with_parameter(\"delta\", 0.05)\n .with_parameter(\"gamma\", 0.3)\n .with_cost(diffid.SSE())\n .with_parallel(True)\n .with_optimiser(diffid.CMAES().with_max_iter(30).with_step_size(0.3))\n .build()\n .optimise()\n )\n t_par_scale = time.time() - start\n\n sp = t_seq_scale / t_par_scale\n scaling_results.append(\n {\n \"n_points\": n_points,\n \"time_seq\": t_seq_scale,\n \"time_par\": t_par_scale,\n \"speedup\": sp,\n }\n )\n print(\n f\"N={n_points:3d}: Sequential={t_seq_scale:.2f}s, Parallel={t_par_scale:.2f}s, Speedup={sp:.2f}x\"\n )"
},
{
"cell_type": "code",
@@ -749,7 +404,7 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": "## When to Use Each Approach\n\n### DiffsolBuilder with `.with_parallel(True)`\n\nBest for:\n- ODE fitting problems where evaluations are moderately expensive\n- When you want automatic parallelism without code changes\n- Lower overhead than multiprocessing\n\n```python\nproblem = (\n chron.DiffsolBuilder()\n .with_diffsl(model)\n .with_data(data)\n .with_parallel(True) # Enable rayon parallelism\n .build()\n)\n```\n\n### Multiprocessing for Python Callables\n\nBest for:\n- Expensive Python simulations (>10ms per evaluation)\n- Complex custom objective functions\n- When DiffsolBuilder isn't applicable\n\n```python\nfrom concurrent.futures import ProcessPoolExecutor\n\ndef evaluate_parallel(params_list):\n with ProcessPoolExecutor(max_workers=n_cores) as executor:\n return list(executor.map(expensive_objective, params_list))\n```"
+ "source": "## When to Use Each Approach\n\n### DiffsolBuilder with `.with_parallel(True)`\n\nBest for:\n- ODE fitting problems where evaluations are moderately expensive\n- When you want automatic parallelism without code changes\n- Lower overhead than multiprocessing\n\n```python\nproblem = (\n diffid.DiffsolBuilder()\n .with_diffsl(model)\n .with_data(data)\n .with_parallel(True) # Enable rayon parallelism\n .build()\n)\n```\n\n### Multiprocessing for Python Callables\n\nBest for:\n- Expensive Python simulations (>10ms per evaluation)\n- Complex custom objective functions\n- When DiffsolBuilder isn't applicable\n\n```python\nfrom concurrent.futures import ProcessPoolExecutor\n\ndef evaluate_parallel(params_list):\n with ProcessPoolExecutor(max_workers=n_cores) as executor:\n return list(executor.map(expensive_objective, params_list))\n```"
},
{
"cell_type": "markdown",
@@ -850,4 +505,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tutorials/notebooks/08_advanced_cost_functions.ipynb b/docs/tutorials/notebooks/08_advanced_cost_functions.ipynb
index 5257a88..edf95a9 100644
--- a/docs/tutorials/notebooks/08_advanced_cost_functions.ipynb
+++ b/docs/tutorials/notebooks/08_advanced_cost_functions.ipynb
@@ -24,7 +24,7 @@
"source": [
"## Introduction\n",
"\n",
- "The **cost function** (or loss function) quantifies how well model predictions match observations. Chronopt provides built-in metrics, but real-world problems often require:\n",
+ "The **cost function** (or loss function) quantifies how well model predictions match observations. Diffid provides built-in metrics, but real-world problems often require:\n",
"\n",
"- **Weighted fitting** when measurement errors vary\n",
"- **Custom metrics** for domain-specific requirements\n",
@@ -54,7 +54,7 @@
"# Import plotting utilities\n",
"from functools import partial\n",
"\n",
- "import chronopt as chron\n",
+ "import diffid\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
@@ -67,7 +67,7 @@
"source": [
"## Built-in Cost Metrics\n",
"\n",
- "Chronopt provides three standard metrics:\n",
+ "Diffid provides three standard metrics:\n",
"\n",
"### 1. Sum of Squared Errors (SSE)\n",
"\n",
@@ -193,7 +193,7 @@
"\n",
"# Define problem\n",
"builder = (\n",
- " chron.VectorBuilder()\n",
+ " diffid.VectorBuilder()\n",
" .with_objective(linear_model)\n",
" .with_data(y_observed)\n",
" .with_parameter(\"slope\", 1.0)\n",
@@ -201,7 +201,11 @@
")\n",
"\n",
"# Test each metric\n",
- "metrics = {\"SSE\": chron.SSE(), \"RMSE\": chron.RMSE(), \"GaussianNLL\": chron.GaussianNLL()}\n",
+ "metrics = {\n",
+ " \"SSE\": diffid.SSE(),\n",
+ " \"RMSE\": diffid.RMSE(),\n",
+ " \"GaussianNLL\": diffid.GaussianNLL(),\n",
+ "}\n",
"\n",
"results = {}\n",
"for name, metric in metrics.items():\n",
@@ -386,19 +390,19 @@
"\n",
"# Fit WITHOUT weights (standard SSE)\n",
"result_unweighted = (\n",
- " chron.VectorBuilder()\n",
+ " diffid.VectorBuilder()\n",
" .with_objective(linear_model_hetero)\n",
" .with_data(y_hetero)\n",
" .with_parameter(\"slope\", 1.0)\n",
" .with_parameter(\"intercept\", 0.0)\n",
- " .with_cost(chron.SSE()) # Standard SSE\n",
+ " .with_cost(diffid.SSE()) # Standard SSE\n",
" .build()\n",
" .optimise()\n",
")\n",
"\n",
"# Fit with weights\n",
"result_weighted = (\n",
- " chron.ScalarBuilder()\n",
+ " diffid.ScalarBuilder()\n",
" .with_objective(lambda x: weighted_sse(linear_model_hetero(x), y_hetero))\n",
" .with_parameter(\"slope\", 1.0)\n",
" .with_parameter(\"intercept\", 0.0)\n",
@@ -626,21 +630,21 @@
" return sse\n",
"\n",
"\n",
- "# Note: Chronopt's built-in costs don't support parameter access yet,\n",
+ "# Note: Diffid's built-in costs don't support parameter access yet,\n",
"# so we'll compare by manually adding regularisation to parameter update\n",
"\n",
"# Unregularised fit\n",
"initial_params = np.zeros(degree + 1)\n",
"initial_params[0] = np.mean(y_poly)\n",
"\n",
- "builder_poly = chron.VectorBuilder().with_objective(polynomial_model).with_data(y_poly)\n",
+ "builder_poly = diffid.VectorBuilder().with_objective(polynomial_model).with_data(y_poly)\n",
"\n",
"for i in range(degree + 1):\n",
" builder_poly = builder_poly.with_parameter(f\"c{i}\", initial_params[i])\n",
"\n",
"result_unreg = (\n",
- " builder_poly.with_cost(chron.SSE())\n",
- " .with_optimiser(chron.NelderMead().with_max_iter(2000))\n",
+ " builder_poly.with_cost(diffid.SSE())\n",
+ " .with_optimiser(diffid.NelderMead().with_max_iter(2000))\n",
" .build()\n",
" .optimise()\n",
")\n",
@@ -836,7 +840,7 @@
"for w_smooth in weights_smooth:\n",
" # Construct custom cost\n",
" multi_cost = MultiObjectiveCost(weight_fit=1.0, weight_smooth=w_smooth)\n",
- " result = chron.ScalarBuilder().with_objective(\n",
+ " result = diffid.ScalarBuilder().with_objective(\n",
" partial(wrapper, y_poly=y_poly, cost_func=multi_cost)\n",
" )\n",
"\n",
@@ -844,7 +848,9 @@
" result = result.with_parameter(f\"c{i}\", initial_params[i])\n",
"\n",
" result = (\n",
- " result.with_optimiser(chron.NelderMead().with_max_iter(2000)).build().optimise()\n",
+ " result.with_optimiser(diffid.NelderMead().with_max_iter(2000))\n",
+ " .build()\n",
+ " .optimise()\n",
" )\n",
"\n",
" results_multi[w_smooth] = result\n",
diff --git a/examples/bicycle_model_diffsol.py b/examples/bicycle_model_diffsol.py
index 1d8808b..a03f2a7 100644
--- a/examples/bicycle_model_diffsol.py
+++ b/examples/bicycle_model_diffsol.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-import chronopt as chron
+import diffid
import numpy as np
TRUE_L = 2.5 # wheelbase
@@ -36,10 +36,10 @@
stacked_data = np.column_stack((t_span, x_obs, y_obs, psi_obs))
-optimiser = chron.CMAES().with_max_iter(500).with_threshold(1e-10)
+optimiser = diffid.CMAES().with_max_iter(500).with_threshold(1e-10)
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-8)
diff --git a/examples/bicycle_model_evidence.py b/examples/bicycle_model_evidence.py
index 33769ba..00998bd 100644
--- a/examples/bicycle_model_evidence.py
+++ b/examples/bicycle_model_evidence.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-import chronopt as chron
+import diffid
import numpy as np
TRUE_L = 2.5 # wheelbase
@@ -36,11 +36,11 @@
stacked_data = np.column_stack((t_span, x_obs, y_obs, psi_obs))
-optimiser = chron.CMAES().with_max_iter(500).with_threshold(1e-10)
-cost = chron.GaussianNLL(0.05)
+optimiser = diffid.CMAES().with_max_iter(500).with_threshold(1e-10)
+cost = diffid.GaussianNLL(0.05)
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-8)
@@ -52,7 +52,7 @@
results = problem.optimise()
print(results)
-sampler = chron.DynamicNestedSampler().with_live_points(128)
+sampler = diffid.DynamicNestedSampler().with_live_points(128)
samples = sampler.run(problem, initial=results.x)
print("time :", samples.time)
diff --git a/examples/bouncy_ball.py b/examples/bouncy_ball.py
index 2965422..bc8b98e 100644
--- a/examples/bouncy_ball.py
+++ b/examples/bouncy_ball.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -30,14 +30,14 @@ def ball_states(t: np.ndarray, g: float, h: float) -> tuple[np.ndarray, np.ndarr
# Configure the problem
initial_values = [4.0, 4.0]
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("g", initial_values[0])
.with_parameter("h", initial_values[1])
- .with_optimiser(chron.Adam().with_step_size(0.05).with_max_iter(1500))
- # .with_cost(chron.GaussianNLL(variance=0.01))
- .with_cost(chron.RMSE(2.0))
+ .with_optimiser(diffid.Adam().with_step_size(0.05).with_max_iter(1500))
+ # .with_cost(diffid.GaussianNLL(variance=0.01))
+ .with_cost(diffid.RMSE(2.0))
)
problem = builder.build()
diff --git a/examples/bouncy_ball_sampling.py b/examples/bouncy_ball_sampling.py
index 70294d4..48cc9ef 100644
--- a/examples/bouncy_ball_sampling.py
+++ b/examples/bouncy_ball_sampling.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -30,20 +30,20 @@ def ball_states(t: np.ndarray, g: float, h: float) -> tuple[np.ndarray, np.ndarr
# Configure the problem
initial_values = [4.0, 4.0]
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("g", initial_values[0])
.with_parameter("h", initial_values[1])
.with_parallel(True)
- .with_cost(chron.GaussianNLL(variance=0.01))
+ .with_cost(diffid.GaussianNLL(variance=0.01))
)
problem = builder.build()
# Setup sampler
sampler = (
- chron.MetropolisHastings()
+ diffid.MetropolisHastings()
.with_num_chains(100)
.with_iterations(1000)
.with_step_size(0.25)
diff --git a/examples/logistic_growth.py b/examples/logistic_growth.py
index c55ed85..3c362f4 100644
--- a/examples/logistic_growth.py
+++ b/examples/logistic_growth.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
# Example diffsol ODE (logistic growth)
@@ -15,11 +15,11 @@
# Create an optimiser
-optimiser = chron.CMAES().with_max_iter(1000).with_threshold(1e-12)
+optimiser = diffid.CMAES().with_max_iter(1000).with_threshold(1e-12)
# Simple API
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_tolerances(1e-6, 1e-8)
diff --git a/examples/model_evidence.py b/examples/model_evidence.py
index 69a95a9..4ac136a 100644
--- a/examples/model_evidence.py
+++ b/examples/model_evidence.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-import chronopt as chron
+import diffid
def rosenbrock(x: list[float]) -> float:
@@ -11,17 +11,17 @@ def rosenbrock(x: list[float]) -> float:
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", initial_value=1.2)
.with_parameter("y", initial_value=1.4)
- .with_optimiser(chron.NelderMead().with_max_iter(2000))
+ .with_optimiser(diffid.NelderMead().with_max_iter(2000))
)
problem = builder.build()
optimised = problem.optimise()
-sampler = chron.DynamicNestedSampler().with_live_points(256).with_seed(1234)
+sampler = diffid.DynamicNestedSampler().with_live_points(256).with_seed(1234)
samples = sampler.run(problem, initial=optimised.x)
print("time :", samples.time)
diff --git a/examples/model_evidence_diffsol.py b/examples/model_evidence_diffsol.py
index 8919b3a..955f9c6 100644
--- a/examples/model_evidence_diffsol.py
+++ b/examples/model_evidence_diffsol.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-import chronopt as chron
+import diffid
import numpy as np
# Example diffsol ODE (logistic growth)
@@ -18,7 +18,7 @@
stacked_data = np.column_stack((t_span, data))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_parameter("r", initial_value=1.2)
@@ -29,7 +29,7 @@
optimised = problem.optimise()
-sampler = chron.DynamicNestedSampler().with_live_points(256).with_seed(1234)
+sampler = diffid.DynamicNestedSampler().with_live_points(256).with_seed(1234)
samples = sampler.run(problem, initial=optimised.x)
print("time :", samples.time)
diff --git a/examples/predator_prey/README.md b/examples/predator_prey/README.md
index 767e29a..5388abb 100644
--- a/examples/predator_prey/README.md
+++ b/examples/predator_prey/README.md
@@ -1,7 +1,7 @@
# Predator-Prey Parameter Identification Examples
This directory showcases how to recover the parameters of the LotkaāVolterra
-predator-prey model using Chronopt with several ODE solver backends. All
+predator-prey model using Diffid with several ODE solver backends. All
examples share a synthetic dataset so that the optimisation results can be
compared side by side.
@@ -12,7 +12,7 @@ compared side by side.
- `predator_prey_diffrax.py` ā fits the model using the JAX/Diffrax simulator.
- `predator_prey_diffeqpy.py` ā fits the model using the Julia
DifferentialEquations.jl stack via diffeqpy.
-- `predator_prey_diffsol.py` ā fits the model using Chronopt's Diffsol backend.
+- `predator_prey_diffsol.py` ā fits the model using Diffid's Diffsol backend.
- `synthetic_data.npz` ā cached dataset generated by the script above
(re-created on demand).
@@ -24,7 +24,7 @@ compared side by side.
```bash
# Base requirements for all examples
- pip install chronopt numpy
+ pip install diffid numpy
# Diffrax example
pip install jax diffrax
@@ -35,7 +35,7 @@ compared side by side.
```
The Diffsol example only needs the base requirements because the solver is
- provided by Chronopt.
+ provided by Diffid.
## Generating the dataset
@@ -69,10 +69,10 @@ performance across solver backends.
platform has compatible JAX wheels.
- Diffeqpy: the `de.install()` command downloads a Julia runtime if one is not
already configured. This can take a few minutes.
-- Diffsol: Chronopt bundles the solver; no extra setup is required beyond the
+- Diffsol: Diffid bundles the solver; no extra setup is required beyond the
base dependencies.
## Further reading
-Check the top-level project README for more background on Chronopt and links to
+Check the top-level project README for more background on Diffid and links to
additional examples.
\ No newline at end of file
diff --git a/examples/predator_prey/predator_prey_diffeqpy.py b/examples/predator_prey/predator_prey_diffeqpy.py
index 1d67721..a31620f 100644
--- a/examples/predator_prey/predator_prey_diffeqpy.py
+++ b/examples/predator_prey/predator_prey_diffeqpy.py
@@ -1,12 +1,12 @@
-"""Predator-prey parameter identification using DifferentialEquations.jl and Chronopt.
+"""Predator-prey parameter identification using DifferentialEquations.jl and diffid.
Demonstrates parameter estimation for the Lotka-Volterra model by:
1. Defining the predator-prey ODE in Julia via diffeqpy
2. Generating synthetic noisy observations
-3. Recovering parameters using Chronopt optimization
+3. Recovering parameters using diffid optimization
Prerequisites:
- pip install diffeqpy chronopt numpy
+ pip install diffeqpy diffid numpy
python -c "from diffeqpy import de; de.install()"
"""
@@ -15,7 +15,7 @@
import importlib.util
import pathlib
-import chronopt as chron
+import diffid
import numpy as np
from diffeqpy import de
@@ -67,15 +67,15 @@ def simulate(params):
# Parameter identification
result = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(simulate)
.with_data(observed)
.with_parameter("alpha", 0.8)
.with_parameter("beta", 0.3)
.with_parameter("delta", 0.05)
.with_parameter("gamma", 0.6)
- .with_cost(chron.SSE())
- .with_optimiser(chron.NelderMead().with_max_iter(1000))
+ .with_cost(diffid.SSE())
+ .with_optimiser(diffid.NelderMead().with_max_iter(1000))
.build()
.optimise()
)
diff --git a/examples/predator_prey/predator_prey_diffrax.py b/examples/predator_prey/predator_prey_diffrax.py
index 51e4ab0..98cfae5 100644
--- a/examples/predator_prey/predator_prey_diffrax.py
+++ b/examples/predator_prey/predator_prey_diffrax.py
@@ -1,12 +1,12 @@
-"""Predator-prey parameter identification using JAX/Diffrax and Chronopt.
+"""Predator-prey parameter identification using JAX/Diffrax and diffid.
Demonstrates parameter estimation for the Lotka-Volterra model by:
1. Defining the predator-prey ODE in JAX
2. Generating synthetic noisy observations
-3. Recovering parameters using Chronopt optimization
+3. Recovering parameters using diffid optimization
Prerequisites:
- pip install chronopt diffrax jax numpy
+ pip install diffid diffrax jax numpy
"""
from __future__ import annotations
@@ -14,7 +14,7 @@
import importlib.util
import pathlib
-import chronopt as chron
+import diffid
import diffrax as dfx
import jax.numpy as jnp
import numpy as np
@@ -67,7 +67,7 @@ def simulate_jax(params):
def simulate(params):
- """NumPy wrapper for Chronopt compatibility."""
+ """NumPy wrapper for diffid compatibility."""
return np.asarray(simulate_jax(jnp.asarray(params)))
@@ -89,15 +89,15 @@ def simulate(params):
# Parameter identification
result = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(simulate)
.with_data(observed)
.with_parameter("alpha", 1.3)
.with_parameter("beta", 0.3)
.with_parameter("delta", 0.05)
.with_parameter("gamma", 0.6)
- .with_cost(chron.SSE())
- .with_optimiser(chron.NelderMead().with_max_iter(1000))
+ .with_cost(diffid.SSE())
+ .with_optimiser(diffid.NelderMead().with_max_iter(1000))
.build()
.optimise()
)
diff --git a/examples/predator_prey/predator_prey_diffsol.py b/examples/predator_prey/predator_prey_diffsol.py
index 4dc015e..b3eda65 100644
--- a/examples/predator_prey/predator_prey_diffsol.py
+++ b/examples/predator_prey/predator_prey_diffsol.py
@@ -1,7 +1,7 @@
import importlib.util
import pathlib
-import chronopt as chron
+import diffid
import numpy as np
# Example diffsol ODE (logistic growth)
@@ -32,7 +32,7 @@
# Simple API
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ode)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-8)
@@ -42,7 +42,7 @@
.with_parameter("d", 0.6)
.with_parallel(True)
.with_optimiser(
- chron.NelderMead().with_max_iter(1000)
+ diffid.NelderMead().with_max_iter(1000)
) # Override default optimiser
)
problem = builder.build()
diff --git a/examples/python_contour.png b/examples/python_contour.png
new file mode 100644
index 0000000..24ab72e
Binary files /dev/null and b/examples/python_contour.png differ
diff --git a/examples/python_contour.py b/examples/python_contour.py
index 6c5503e..8144922 100644
--- a/examples/python_contour.py
+++ b/examples/python_contour.py
@@ -2,7 +2,7 @@
from pathlib import Path
-import chronopt as chron
+import diffid
import matplotlib.pyplot as plt
import numpy as np
@@ -15,7 +15,7 @@ def rosenbrock(x: np.ndarray) -> float:
# Setup
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
@@ -23,11 +23,11 @@ def rosenbrock(x: np.ndarray) -> float:
problem = builder.build()
# Optimise
-optimiser = chron.NelderMead().with_max_iter(500).with_threshold(1e-8)
+optimiser = diffid.NelderMead().with_max_iter(500).with_threshold(1e-8)
result = optimiser.run(problem, [-1.5, 1.5])
# Plot
-contour_set = chron.plotting.contour(
+contour_set = diffid.plotting.contour(
problem,
x_bounds=(-2.0, 2.0),
y_bounds=(-1.0, 3.0),
diff --git a/examples/python_problem.py b/examples/python_problem.py
index 65552e2..4de56e3 100644
--- a/examples/python_problem.py
+++ b/examples/python_problem.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -10,11 +10,11 @@ def rosenbrock(x):
# Simple API
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
- .with_optimiser(chron.NelderMead().with_max_iter(1000))
+ .with_optimiser(diffid.NelderMead().with_max_iter(1000))
)
problem = builder.build()
result = problem.optimise(initial=[10.0, 10.0])
diff --git a/mkdocs.yml b/mkdocs.yml
index 6b7ab6e..d83766f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,9 +1,9 @@
-site_name: Chronopt
+site_name: Diffid
site_description: High-performance time-series inference and optimisation toolkit with Rust core and ergonomic Python bindings
site_author: Brady Planden
-site_url: https://bradyplanden.github.io/chronopt/
-repo_name: bradyplanden/chronopt
-repo_url: https://github.com/bradyplanden/chronopt
+site_url: https://bradyplanden.github.io/diffid/
+repo_name: bradyplanden/diffid
+repo_url: https://github.com/bradyplanden/diffid
edit_uri: edit/main/docs/
theme:
@@ -124,9 +124,9 @@ markdown_extensions:
extra:
social:
- icon: fontawesome/brands/github
- link: https://github.com/bradyplanden/chronopt
+ link: https://github.com/bradyplanden/diffid
- icon: fontawesome/brands/python
- link: https://pypi.org/project/chronopt/
+ link: https://pypi.org/project/diffid/
version:
provider: mike
default: latest
@@ -195,5 +195,5 @@ nav:
strict: true # Fail on warnings
watch:
- - python/src/chronopt
+ - python/src/diffid
- docs
diff --git a/pyproject.toml b/pyproject.toml
index 24d034c..ecde2dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ requires = ["maturin>=1.9,<2.0"]
build-backend = "maturin"
[project]
-name = "chronopt"
+name = "diffid"
requires-python = ">=3.11"
dynamic = ["version"]
license = { file = "LICENSE" }
@@ -31,7 +31,7 @@ diffeqpy = [
[tool.maturin]
manifest-path = "python/Cargo.toml"
-module-name = "chronopt._chronopt"
+module-name = "diffid._diffid"
features = ["extension-module"]
python-source = "python/src"
sdist-include = ["LICENSE", "README.md"]
diff --git a/python/Cargo.toml b/python/Cargo.toml
index 32f36cd..f7f26fb 100644
--- a/python/Cargo.toml
+++ b/python/Cargo.toml
@@ -1,12 +1,12 @@
[package]
-name = "chronopt-py"
+name = "diffid-py"
version.workspace = true
edition.workspace = true
license.workspace = true
readme = "../README.md"
[package.metadata.maturin]
-name = "chronopt"
+name = "diffid"
python-source = "src"
artifact = "dist"
@@ -16,7 +16,7 @@ extension-module = ["pyo3/extension-module"]
stubgen = ["clap", "pyo3-stub-gen", "pyo3-stub-gen-derive"]
[lib]
-name = "_chronopt"
+name = "_diffid"
crate-type = ["cdylib", "rlib"]
[dependencies]
pyo3 = { workspace = true, default-features = false, features = ["macros"] }
@@ -27,10 +27,10 @@ pyo3-stub-gen-derive = { version = "0.17.2", optional = true }
clap = { version = "4.5", optional = true, features = ["derive"] }
[target.'cfg(not(windows))'.dependencies]
-chronopt_core = { package = "chronopt", path = "../rust" }
+diffid_core = { package = "diffid", path = "../rust" }
[target.'cfg(windows)'.dependencies]
-chronopt_core = { package = "chronopt", path = "../rust", default-features = false, features = ["cranelift-backend"] }
+diffid_core = { package = "diffid", path = "../rust", default-features = false, features = ["cranelift-backend"] }
[build-dependencies]
pyo3-build-config = "0.27.1"
diff --git a/python/src/bin/generate_stubs.rs b/python/src/bin/generate_stubs.rs
index 254f01f..fe6dc72 100644
--- a/python/src/bin/generate_stubs.rs
+++ b/python/src/bin/generate_stubs.rs
@@ -4,7 +4,7 @@ use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
-use chronopt::{stub_info, stub_info_from};
+use _diffid::{stub_info, stub_info_from};
use clap::Parser;
use pyo3_stub_gen::Result;
@@ -38,7 +38,7 @@ fn main() -> Result<()> {
}
fn post_process_sampler_stub() -> Result<()> {
- let sampler_stub_path = resolve_workspace_root()?.join("python/src/chronopt/sampler.pyi");
+ let sampler_stub_path = resolve_workspace_root()?.join("python/src/diffid/sampler.pyi");
let contents = fs::read_to_string(&sampler_stub_path)?;
let mut lines: Vec<&str> = contents.lines().collect();
diff --git a/python/src/builders.rs b/python/src/builders.rs
index 52229a3..b95c9ec 100644
--- a/python/src/builders.rs
+++ b/python/src/builders.rs
@@ -6,11 +6,11 @@ use pyo3::types::PyDict;
use std::collections::HashMap;
use std::sync::Arc;
-use chronopt_core::builders::{
+use diffid_core::builders::{
DiffsolBackend, DiffsolProblemBuilder, ScalarProblemBuilder, VectorProblemBuilder,
};
-use chronopt_core::common::Unbounded;
-use chronopt_core::problem::{NoFunction, NoGradient};
+use diffid_core::common::Unbounded;
+use diffid_core::problem::{NoFunction, NoGradient};
#[cfg(feature = "stubgen")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
@@ -333,7 +333,7 @@ impl PyScalarBuilder {
.push((name.clone(), initial_value, bounds));
// Convert Option<(f64, f64)> to ParameterRange
- let range: chronopt_core::problem::ParameterRange =
+ let range: diffid_core::problem::ParameterRange =
bounds.map(|b| b.into()).unwrap_or_else(|| Unbounded.into());
slf.state = match std::mem::replace(
diff --git a/python/src/chronopt/_chronopt.pyi b/python/src/chronopt/_chronopt.pyi
deleted file mode 100644
index fff8acb..0000000
--- a/python/src/chronopt/_chronopt.pyi
+++ /dev/null
@@ -1,441 +0,0 @@
-# This file is automatically generated by pyo3_stub_gen
-# ruff: noqa: E501, F401
-
-import builtins
-import datetime
-import typing
-
-import numpy
-import numpy.typing
-
-@typing.final
-class Adam:
- r"""
- Adaptive Moment Estimation (Adam) gradient-based optimiser.
- """
- def __new__(cls) -> Adam:
- r"""
- Create an Adam optimiser with library defaults.
- """
- def with_max_iter(self, max_iter: builtins.int) -> Adam:
- r"""
- Limit the maximum number of optimisation iterations.
- """
- def with_threshold(self, threshold: builtins.float) -> Adam:
- r"""
- Set the stopping threshold on the gradient norm.
- """
- def with_step_size(self, step_size: builtins.float) -> Adam:
- r"""
- Configure the base learning rate / step size.
- """
- def with_betas(self, beta1: builtins.float, beta2: builtins.float) -> Adam:
- r"""
- Override the exponential decay rates for the first and second moments.
- """
- def with_eps(self, eps: builtins.float) -> Adam:
- r"""
- Override the numerical stability constant added to the denominator.
- """
- def with_patience(self, patience_seconds: builtins.float) -> Adam:
- r"""
- Abort the run once the patience window has elapsed.
- """
- def run(
- self, problem: Problem, initial: typing.Sequence[builtins.float]
- ) -> OptimisationResults:
- r"""
- Optimise the given problem using Adam starting from the provided point.
- """
-
-@typing.final
-class CMAES:
- r"""
- Covariance Matrix Adaptation Evolution Strategy optimiser.
- """
- def __new__(cls) -> CMAES:
- r"""
- Create a CMA-ES optimiser with library defaults.
- """
- def with_max_iter(self, max_iter: builtins.int) -> CMAES:
- r"""
- Limit the number of iterations/generations before termination.
- """
- def with_threshold(self, threshold: builtins.float) -> CMAES:
- r"""
- Set the stopping threshold on the best objective value.
- """
- def with_step_size(self, step_size: builtins.float) -> CMAES:
- r"""
- Set the initial global step-size (standard deviation).
- """
- def with_patience(self, patience_seconds: builtins.float) -> CMAES:
- r"""
- Abort the run if no improvement occurs for the given wall-clock duration.
- """
- def with_population_size(self, population_size: builtins.int) -> CMAES:
- r"""
- Specify the number of offspring evaluated per generation.
- """
- def with_seed(self, seed: builtins.int) -> CMAES:
- r"""
- Initialise the internal RNG for reproducible runs.
- """
- def run(
- self, problem: Problem, initial: typing.Sequence[builtins.float]
- ) -> OptimisationResults:
- r"""
- Optimise the given problem starting from the provided mean vector.
- """
-
-@typing.final
-class CostMetric:
- @property
- def name(self) -> builtins.str:
- r"""
- Name of the cost metric.
- """
- def __repr__(self) -> builtins.str: ...
-
-@typing.final
-class DiffsolBuilder:
- r"""
- Differential equation solver builder.
- """
- def __new__(cls) -> DiffsolBuilder:
- r"""
- Create an empty differential solver builder.
- """
- def __copy__(self) -> DiffsolBuilder: ...
- def __deepcopy__(self, _memo: dict) -> DiffsolBuilder: ...
- def with_diffsl(self, dsl: builtins.str) -> DiffsolBuilder:
- r"""
- Register the DiffSL program describing the system dynamics.
- """
- def remove_diffsl(self) -> DiffsolBuilder:
- r"""
- Remove any registered DiffSL program.
- """
- def with_data(self, data: numpy.typing.NDArray[numpy.float64]) -> DiffsolBuilder:
- r"""
- Attach observed data used to fit the differential equation.
-
- The first column must contain the time samples (t_span) and the remaining
- columns the observed trajectories.
- """
- def remove_data(self) -> DiffsolBuilder:
- r"""
- Remove any previously attached data along with its time span.
- """
- def with_backend(self, backend: builtins.str) -> DiffsolBuilder:
- r"""
- Choose whether to use dense or sparse diffusion solvers.
- """
- def with_parallel(self, parallel: builtins.bool | None = None) -> DiffsolBuilder:
- r"""
- Opt into parallel proposal generation when supported by the backend.
- """
- def with_config(
- self, config: typing.Mapping[builtins.str, builtins.float]
- ) -> DiffsolBuilder: ...
- def with_rtol(self, rtol: builtins.float) -> DiffsolBuilder:
- r"""
- Adjust the relative integration tolerance.
- """
- def with_atol(self, atol: builtins.float) -> DiffsolBuilder:
- r"""
- Adjust the absolute integration tolerance.
- """
- def with_parameter(
- self,
- name: builtins.str,
- initial_value: builtins.float,
- bounds: tuple[builtins.float, builtins.float] | None = None,
- ) -> DiffsolBuilder:
- r"""
- Register a named optimisation variable in the order it appears in vectors.
- """
- def clear_parameters(self) -> DiffsolBuilder:
- r"""
- Remove previously provided parameter defaults.
- """
- def with_cost(self, cost: CostMetric) -> DiffsolBuilder:
- r"""
- Select the error metric used to compare simulated and observed data.
- """
- def remove_cost(self) -> DiffsolBuilder:
- r"""
- Reset the cost metric to the default sum of squared errors.
- """
- def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> DiffsolBuilder:
- r"""
- Configure the default optimiser used when `Problem.optimise` omits one.
- """
- def build(self) -> Problem:
- r"""
- Create a `Problem` representing the differential solver model.
- """
-
-@typing.final
-class NelderMead:
- r"""
- Classic simplex-based direct search optimiser.
- """
- def __new__(cls) -> NelderMead:
- r"""
- Create a Nelder-Mead optimiser with default coefficients.
- """
- def with_max_iter(self, max_iter: builtins.int) -> NelderMead:
- r"""
- Limit the number of simplex iterations.
- """
- def with_threshold(self, threshold: builtins.float) -> NelderMead:
- r"""
- Set the stopping threshold on simplex size or objective reduction.
- """
- def with_position_tolerance(self, tolerance: builtins.float) -> NelderMead:
- r"""
- Stop once simplex vertices fall within the supplied positional tolerance.
- """
- def with_max_evaluations(self, max_evaluations: builtins.int) -> NelderMead:
- r"""
- Abort after evaluating the objective `max_evaluations` times.
- """
- def with_coefficients(
- self,
- alpha: builtins.float,
- gamma: builtins.float,
- rho: builtins.float,
- sigma: builtins.float,
- ) -> NelderMead:
- r"""
- Override the reflection, expansion, contraction, and shrink coefficients.
- """
- def with_patience(self, patience_seconds: builtins.float) -> NelderMead:
- r"""
- Abort if the objective fails to improve within the allotted time.
- """
- def run(
- self, problem: Problem, initial: typing.Sequence[builtins.float]
- ) -> OptimisationResults:
- r"""
- Optimise the given problem starting from the provided initial simplex centre.
- """
-
-@typing.final
-class OptimisationResults:
- r"""
- Container for optimiser outputs and diagnostic metadata.
- """
- @property
- def x(self) -> builtins.list[builtins.float]:
- r"""
- Decision vector corresponding to the best-found objective value.
- """
- @property
- def fun(self) -> builtins.float:
- r"""
- Objective value evaluated at `x`.
- """
- @property
- def nit(self) -> builtins.int:
- r"""
- Number of iterations performed by the optimiser.
- """
- @property
- def evaluations(self) -> builtins.int:
- r"""
- Total number of objective function evaluations.
- """
- @property
- def time(self) -> datetime.timedelta:
- r"""
- Total number of objective function evaluations.
- """
- @property
- def success(self) -> builtins.bool:
- r"""
- Whether the run satisfied its convergence criteria.
- """
- @property
- def message(self) -> builtins.str:
- r"""
- Human-readable status message summarising the termination state.
- """
- @property
- def termination_reason(self) -> builtins.str:
- r"""
- Structured termination flag describing why the run ended.
- """
- @property
- def final_simplex(self) -> builtins.list[builtins.list[builtins.float]]:
- r"""
- Simplex vertices at termination, when provided by the optimiser.
- """
- @property
- def final_simplex_values(self) -> builtins.list[builtins.float]:
- r"""
- Objective values corresponding to `final_simplex`.
- """
- @property
- def covariance(
- self,
- ) -> builtins.list[builtins.list[builtins.float]] | None:
- r"""
- Estimated covariance of the search distribution, if available.
- """
- def __repr__(self) -> builtins.str:
- r"""
- Render a concise summary of the optimisation outcome.
- """
-
-@typing.final
-class Problem:
- r"""
- Executable optimisation problem wrapping the Chronopt core implementation.
- """
- def evaluate(self, x: typing.Sequence[builtins.float]) -> builtins.float:
- r"""
- Evaluate the configured objective function at `x`.
- """
- def evaluate_gradient(
- self, x: typing.Sequence[builtins.float]
- ) -> builtins.list[builtins.float] | None:
- r"""
- Evaluate the gradient of the objective function at `x` if available.
- """
- def optimise(
- self,
- initial: typing.Sequence[builtins.float] | None = None,
- optimiser: NelderMead | CMAES | Adam | None = None,
- ) -> OptimisationResults:
- r"""
- Solve the problem starting from `initial` using the supplied optimiser.
- """
- def get_config(self, key: builtins.str) -> builtins.float | None:
- r"""
- Return the numeric configuration value stored under `key` if present.
- """
- def dimension(self) -> builtins.int:
- r"""
- Return the number of parameters the problem expects.
- """
- def parameters(
- self,
- ) -> builtins.list[
- tuple[
- builtins.str,
- builtins.float,
- tuple[builtins.float, builtins.float] | None,
- ]
- ]: ...
- def default_parameters(self) -> builtins.list[builtins.float]:
- r"""
- Return the default parameter vector implied by the builder.
- """
- def config(self) -> builtins.dict[builtins.str, builtins.float]:
- r"""
- Return a copy of the problem configuration dictionary.
- """
-
-@typing.final
-class ScalarBuilder:
- r"""
- High-level builder for optimisation `Problem` instances exposed to Python.
- """
- def __new__(cls) -> ScalarBuilder:
- r"""
- Create an empty builder with no objective, parameters, or default optimiser.
- """
- def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> ScalarBuilder:
- r"""
- Configure the default optimiser used when `Problem.optimise` omits one.
- """
- def with_callable(self, obj: typing.Any) -> ScalarBuilder:
- r"""
- Attach the objective function callable executed during optimisation.
- """
- def with_gradient(self, obj: typing.Any) -> ScalarBuilder:
- r"""
- Attach the gradient callable returning derivatives of the objective.
- """
- def with_parameter(
- self,
- name: builtins.str,
- initial_value: builtins.float,
- bounds: tuple[builtins.float, builtins.float] | None = None,
- ) -> ScalarBuilder:
- r"""
- Register a named optimisation variable in the order it appears in vectors.
- """
- def build(self) -> Problem:
- r"""
- Finalize the builder into an executable `Problem`.
- """
-
-@typing.final
-class VectorBuilder:
- r"""
- Time-series problem builder for vector-valued objectives.
- """
- def __new__(cls) -> VectorBuilder:
- r"""
- Create an empty vector problem builder.
- """
- def with_objective(self, objective: typing.Any) -> VectorBuilder:
- r"""
- Register a callable that produces predictions matching the data shape.
-
- The callable should accept a parameter vector and return a numpy array
- of the same shape as the observed data.
- """
- def with_data(self, data: numpy.typing.NDArray[numpy.float64]) -> VectorBuilder:
- r"""
- Attach observed data used to fit the model.
-
- The data should be a 1D numpy array. The shape will be inferred
- from the data length.
- """
- def with_config(self, key: builtins.str, value: builtins.float) -> VectorBuilder:
- r"""
- Stores an optimisation configuration value keyed by name.
- """
- def with_parameter(
- self,
- name: builtins.str,
- initial_value: builtins.float,
- bounds: tuple[builtins.float, builtins.float] | None = None,
- ) -> VectorBuilder:
- r"""
- Register a named optimisation variable in the order it appears in vectors.
- """
- def clear_parameters(self) -> VectorBuilder:
- r"""
- Remove previously provided parameter defaults.
- """
- def with_cost(self, cost: CostMetric) -> VectorBuilder:
- r"""
- Select the error metric used to compare predictions and observed data.
- """
- def remove_cost(self) -> VectorBuilder:
- r"""
- Reset the cost metric to the default sum of squared errors.
- """
- def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> VectorBuilder:
- r"""
- Configure the default optimiser used when `Problem.optimise` omits one.
- """
- def build(self) -> Problem:
- r"""
- Create a `Problem` representing the vector optimisation model.
- """
-
-def GaussianNLL(
- variance: builtins.float, weight: builtins.float = 1.0
-) -> CostMetric: ...
-def RMSE(weight: builtins.float = 1.0) -> CostMetric: ...
-def SSE(weight: builtins.float = 1.0) -> CostMetric: ...
-def builder_factory_py() -> ScalarBuilder:
- r"""
- Return a convenience factory for creating `Builder` instances.
- """
diff --git a/python/src/chronopt/sampler.pyi b/python/src/chronopt/sampler.pyi
deleted file mode 100644
index e5b66d2..0000000
--- a/python/src/chronopt/sampler.pyi
+++ /dev/null
@@ -1,83 +0,0 @@
-# This file is automatically generated by pyo3_stub_gen
-# ruff: noqa: E501, F401
-
-import builtins
-import datetime
-import typing
-
-from chronopt._chronopt import Problem
-
-@typing.final
-class DynamicNestedSampler:
- r"""
- Dynamic nested sampler binding exposing DNS configuration knobs.
- """
- def __new__(cls) -> DynamicNestedSampler: ...
- def with_live_points(self, live_points: builtins.int) -> DynamicNestedSampler: ...
- def with_expansion_factor(
- self, expansion_factor: builtins.float
- ) -> DynamicNestedSampler: ...
- def with_termination_tolerance(
- self, tolerance: builtins.float
- ) -> DynamicNestedSampler: ...
- def with_seed(self, seed: builtins.int) -> DynamicNestedSampler: ...
- def run(
- self,
- problem: Problem,
- initial: typing.Sequence[builtins.float] | None = None,
- ) -> NestedSamples: ...
-
-@typing.final
-class MetropolisHastings:
- r"""
- Basic Metropolis-Hastings sampler binding mirroring the optimiser API.
- """
- def __new__(cls) -> MetropolisHastings: ...
- def with_num_chains(self, num_chains: builtins.int) -> MetropolisHastings: ...
- def set_number_of_chains(self, num_chains: builtins.int) -> MetropolisHastings: ...
- def with_iterations(self, iterations: builtins.int) -> MetropolisHastings: ...
- def with_num_steps(self, steps: builtins.int) -> MetropolisHastings: ...
- def with_step_size(self, step_size: builtins.float) -> MetropolisHastings: ...
- def with_seed(self, seed: builtins.int) -> MetropolisHastings: ...
- def run(
- self, problem: Problem, initial: typing.Sequence[builtins.float]
- ) -> Samples: ...
-
-@typing.final
-class NestedSamples:
- r"""
- Nested sampling results including evidence estimates.
- """
- @property
- def posterior(
- self,
- ) -> builtins.list[
- tuple[builtins.list[builtins.float], builtins.float, builtins.float]
- ]: ...
- @property
- def mean(self) -> builtins.list[builtins.float]: ...
- @property
- def draws(self) -> builtins.int: ...
- @property
- def log_evidence(self) -> builtins.float: ...
- @property
- def information(self) -> builtins.float: ...
- @property
- def time(self) -> datetime.timedelta: ...
- def to_samples(self) -> Samples: ...
- def __repr__(self) -> builtins.str: ...
-
-@typing.final
-class Samples:
- r"""
- Container for sampler draws and diagnostics.
- """
- @property
- def chains(self) -> builtins.list[builtins.list[builtins.list[builtins.float]]]: ...
- @property
- def mean_x(self) -> builtins.list[builtins.float]: ...
- @property
- def draws(self) -> builtins.int: ...
- @property
- def time(self) -> datetime.timedelta: ...
- def __repr__(self) -> builtins.str: ...
diff --git a/python/src/chronopt/__init__.py b/python/src/diffid/__init__.py
similarity index 87%
rename from python/src/chronopt/__init__.py
rename to python/src/diffid/__init__.py
index ebb5280..8ca3963 100644
--- a/python/src/chronopt/__init__.py
+++ b/python/src/diffid/__init__.py
@@ -1,22 +1,22 @@
-"""Chronopt public Python API."""
+"""Diffid public Python API."""
from __future__ import annotations
# Error hierarchy
-from chronopt.errors import (
+from diffid.errors import (
AlreadyTerminated,
BuildError,
- ChronoptError,
+ DiffidError,
EvaluationError,
ResultCountMismatch,
TellError,
)
# Plotting module
-from chronopt import plotting
+from diffid import plotting
# Core bindings - optimisers
-from chronopt._chronopt import (
+from diffid._diffid import (
Adam,
AdamState,
CMAES,
@@ -26,7 +26,7 @@
)
# Core bindings - samplers
-from chronopt._chronopt import (
+from diffid._diffid import (
DynamicNestedSampler,
DynamicNestedSamplerState,
MetropolisHastings,
@@ -36,14 +36,14 @@
)
# Core bindings - builders
-from chronopt._chronopt import (
+from diffid._diffid import (
DiffsolBuilder,
ScalarBuilder,
VectorBuilder,
)
# Core bindings - results and problems
-from chronopt._chronopt import (
+from diffid._diffid import (
CostMetric,
Done,
Evaluate,
@@ -52,9 +52,9 @@
)
# Cost metric factory functions
-from chronopt._chronopt import RMSE as _RMSE
-from chronopt._chronopt import SSE as _SSE
-from chronopt._chronopt import GaussianNLL as _GaussianNLL
+from diffid._diffid import RMSE as _RMSE
+from diffid._diffid import SSE as _SSE
+from diffid._diffid import GaussianNLL as _GaussianNLL
def SSE(weight: float = 1.0) -> CostMetric:
@@ -116,7 +116,7 @@ def GaussianNLL(variance: float = 1.0, weight: float = 1.0) -> CostMetric:
# Modules
"plotting",
# Errors
- "ChronoptError",
+ "DiffidError",
"EvaluationError",
"BuildError",
"TellError",
diff --git a/python/src/diffid/_diffid.pyi b/python/src/diffid/_diffid.pyi
new file mode 100644
index 0000000..ab356e0
--- /dev/null
+++ b/python/src/diffid/_diffid.pyi
@@ -0,0 +1,956 @@
+# This file is automatically generated by pyo3_stub_gen
+# ruff: noqa: E501, F401
+
+import builtins
+import datetime
+import typing
+
+import numpy
+import numpy.typing
+
+from diffid.sampler import DynamicNestedSampler, MetropolisHastings
+
+@typing.final
+class Adam:
+ r"""
+ Adaptive Moment Estimation (Adam) gradient-based optimiser.
+ """
+ def __new__(cls) -> Adam:
+ r"""
+ Create an Adam optimiser with library defaults.
+ """
+ def with_max_iter(self, max_iter: builtins.int) -> Adam:
+ r"""
+ Limit the maximum number of optimisation iterations.
+ """
+ def with_threshold(self, threshold: builtins.float) -> Adam:
+ r"""
+ Set the stopping threshold on the gradient norm.
+ """
+ def with_step_size(self, step_size: builtins.float) -> Adam:
+ r"""
+ Configure the base learning rate / step size.
+ """
+ def with_betas(self, beta1: builtins.float, beta2: builtins.float) -> Adam:
+ r"""
+ Override the exponential decay rates for the first and second moments.
+ """
+ def with_eps(self, eps: builtins.float) -> Adam:
+ r"""
+ Override the numerical stability constant added to the denominator.
+ """
+ def with_patience(self, patience: typing.Any) -> Adam:
+ r"""
+ Abort the run once the patience window has elapsed.
+
+ Parameters
+ ----------
+ patience : float or timedelta
+ Either seconds (float) or a timedelta object
+ """
+ def run(
+ self, problem: Problem, initial: typing.Sequence[builtins.float]
+ ) -> OptimisationResults:
+ r"""
+ Optimise the given problem using Adam starting from the provided point.
+ """
+ def init(
+ self,
+ initial: typing.Sequence[builtins.float],
+ bounds: typing.Sequence[tuple[builtins.float, builtins.float]] | None = None,
+ ) -> AdamState:
+ r"""
+ Initialize ask-tell optimization state.
+
+ Returns an AdamState object that can be used for incremental optimization
+ via the ask-tell interface.
+
+ Parameters
+ ----------
+ initial : list[float]
+ Initial parameter vector
+ bounds : list[tuple[float, float]], optional
+ Parameter bounds as [(lower, upper), ...]. If None, unbounded.
+
+ Returns
+ -------
+ AdamState
+ State object for ask-tell optimization
+
+ Examples
+ --------
+ >>> optimiser = diffid.Adam()
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... break
+ ... values = [evaluate_with_gradient(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+
+@typing.final
+class AdamState:
+ r"""
+ Ask-tell state for incremental Adam optimization.
+
+ This state object allows step-by-step control over the optimization process.
+ Use `ask()` to get points to evaluate, and `tell()` to provide results.
+
+ Examples
+ --------
+ >>> optimiser = diffid.Adam().with_max_iter(100)
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Final result: {result.result}")
+ ... break
+ ... # Adam requires gradient information
+ ... values = [(f(pt), grad_f(pt)) for pt in result.points]
+ ... state.tell(values)
+ """
+ def ask(self) -> typing.Any:
+ r"""
+ Get the next action: evaluate points or optimization complete.
+
+ Returns
+ -------
+ Evaluate | Done
+ Either Evaluate(points) requiring function evaluations,
+ or Done(result) indicating completion.
+
+ Examples
+ --------
+ >>> result = state.ask()
+ >>> if isinstance(result, diffid.Evaluate):
+ ... print(f"Need to evaluate {len(result.points)} points")
+ >>> elif isinstance(result, diffid.Done):
+ ... print(f"Optimization complete: {result.result}")
+ """
+ def tell(
+ self, result: tuple[builtins.float, typing.Sequence[builtins.float]]
+ ) -> None:
+ r"""
+ Provide evaluation results (value and gradient) for the requested points.
+
+ Parameters
+ ----------
+ result : tuple[float, list[float]]
+ Tuple of (value, gradient) where gradient is a list of partial derivatives.
+ Adam requires gradient information.
+
+ Raises
+ ------
+ TellError
+ If called after optimization has terminated or if result format is invalid
+ EvaluationError
+ If the evaluation failed or contained invalid values
+
+ Examples
+ --------
+ >>> result = state.ask()
+ >>> if isinstance(result, diffid.Evaluate):
+ ... point = result.points[0]
+ ... value = objective(point)
+ ... gradient = compute_gradient(point)
+ ... state.tell((value, gradient))
+ """
+ def iterations(self) -> builtins.int:
+ r"""
+ Get the current iteration count.
+
+ Returns
+ -------
+ int
+ Number of iterations completed
+ """
+ def evaluations(self) -> builtins.int:
+ r"""
+ Get the total number of function evaluations.
+
+ Returns
+ -------
+ int
+ Number of function evaluations performed
+ """
+ def best(
+ self,
+ ) -> tuple[builtins.list[builtins.float], builtins.float] | None:
+ r"""
+ Get the current best point and value found so far.
+
+ Returns
+ -------
+ tuple[list[float], float] | None
+ (best_point, best_value) or None if no valid evaluations yet
+ """
+ def current_position(self) -> builtins.list[builtins.float]:
+ r"""
+ Get the current parameter position.
+
+ Returns
+ -------
+ list[float]
+ Current parameter vector
+ """
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class CMAES:
+ r"""
+ Covariance Matrix Adaptation Evolution Strategy optimiser.
+ """
+ def __new__(cls) -> CMAES:
+ r"""
+ Create a CMA-ES optimiser with library defaults.
+ """
+ def with_max_iter(self, max_iter: builtins.int) -> CMAES:
+ r"""
+ Limit the number of iterations/generations before termination.
+ """
+ def with_threshold(self, threshold: builtins.float) -> CMAES:
+ r"""
+ Set the stopping threshold on the best objective value.
+ """
+ def with_step_size(self, step_size: builtins.float) -> CMAES:
+ r"""
+ Set the initial global step-size (standard deviation).
+ """
+ def with_patience(self, patience: typing.Any) -> CMAES:
+ r"""
+ Abort the run if no improvement occurs for the given wall-clock duration.
+
+ Parameters
+ ----------
+ patience : float or timedelta
+ Either seconds (float) or a timedelta object
+ """
+ def with_population_size(self, population_size: builtins.int) -> CMAES:
+ r"""
+ Specify the number of offspring evaluated per generation.
+ """
+ def with_seed(self, seed: builtins.int) -> CMAES:
+ r"""
+ Initialise the internal RNG for reproducible runs.
+ """
+ def run(
+ self, problem: Problem, initial: typing.Sequence[builtins.float]
+ ) -> OptimisationResults:
+ r"""
+ Optimise the given problem starting from the provided mean vector.
+ """
+ def init(
+ self,
+ initial: typing.Sequence[builtins.float],
+ bounds: typing.Sequence[tuple[builtins.float, builtins.float]] | None = None,
+ ) -> CMAESState:
+ r"""
+ Initialize ask-tell optimization state.
+
+ Returns a CMAESState object that can be used for incremental optimization
+ via the ask-tell interface.
+
+ Parameters
+ ----------
+ initial : list[float]
+ Initial mean vector for the search distribution
+ bounds : list[tuple[float, float]], optional
+ Parameter bounds as [(lower, upper), ...]. If None, unbounded.
+
+ Returns
+ -------
+ CMAESState
+ State object for ask-tell optimization
+
+ Examples
+ --------
+ >>> optimiser = diffid.CMAES()
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... break
+ ... values = [evaluate(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+
+@typing.final
+class CMAESState:
+ r"""
+ Ask-tell state for incremental CMA-ES optimization.
+
+ This state object allows step-by-step control over the optimization process.
+ Use `ask()` to get a population of points to evaluate, and `tell()` to provide results.
+
+ Examples
+ --------
+ >>> optimiser = diffid.CMAES().with_max_iter(100)
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Final result: {result.result}")
+ ... break
+ ... values = [f(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+ def ask(self) -> typing.Any:
+ r"""
+ Get the next action: evaluate points or optimization complete.
+
+ Returns
+ -------
+ Evaluate | Done
+ Either Evaluate(points) requiring function evaluations,
+ or Done(result) indicating completion.
+
+ Notes
+ -----
+ CMA-ES evaluates a population of points each iteration. The number
+ of points returned depends on the population_size setting.
+ """
+ def tell(self, results: typing.Sequence[builtins.float]) -> None:
+ r"""
+ Provide evaluation results for the requested population of points.
+
+ Parameters
+ ----------
+ results : list[float]
+ List of objective function values corresponding to the points
+ from the last ask() call. Must match the number of points.
+
+ Raises
+ ------
+ TellError
+ If called after optimization has terminated or if wrong number
+ of results provided
+ EvaluationError
+ If evaluations failed or contained invalid values
+ """
+ def iterations(self) -> builtins.int:
+ r"""
+ Get the current iteration (generation) count.
+
+ Returns
+ -------
+ int
+ Number of generations completed
+ """
+ def evaluations(self) -> builtins.int:
+ r"""
+ Get the total number of function evaluations.
+
+ Returns
+ -------
+ int
+ Number of function evaluations performed
+ """
+ def best(
+ self,
+ ) -> tuple[builtins.list[builtins.float], builtins.float] | None:
+ r"""
+ Get the current best point and value found so far.
+
+ Returns
+ -------
+ tuple[list[float], float] | None
+ (best_point, best_value) or None if no valid evaluations yet
+ """
+ def mean(self) -> builtins.list[builtins.float]:
+ r"""
+ Get the current mean of the search distribution.
+
+ Returns
+ -------
+ list[float]
+ Current mean vector
+ """
+ def sigma(self) -> builtins.float:
+ r"""
+ Get the current step size (sigma).
+
+ Returns
+ -------
+ float
+ Current global step size
+ """
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class CostMetric:
+ @property
+ def name(self) -> builtins.str:
+ r"""
+ Name of the cost metric.
+ """
+ def __repr__(self) -> builtins.str: ...
+
+@typing.final
+class DiffsolBuilder:
+ r"""
+ Differential equation solver builder.
+ """
+ def __new__(cls) -> DiffsolBuilder:
+ r"""
+ Create an empty differential solver builder.
+ """
+ def __copy__(self) -> DiffsolBuilder: ...
+ def __deepcopy__(self, _memo: dict) -> DiffsolBuilder: ...
+ def with_diffsl(self, dsl: builtins.str) -> DiffsolBuilder:
+ r"""
+ Register the DiffSL program describing the system dynamics.
+ """
+ def with_data(self, data: numpy.typing.NDArray[numpy.float64]) -> DiffsolBuilder:
+ r"""
+ Attach observed data used to fit the differential equation.
+
+ The first column must contain the time samples (t_span) and the remaining
+ columns the observed trajectories.
+ """
+ def remove_data(self) -> DiffsolBuilder:
+ r"""
+ Remove any previously attached data along with its time span.
+ """
+ def with_backend(self, backend: builtins.str) -> DiffsolBuilder:
+ r"""
+ Choose whether to use dense or sparse diffusion solvers.
+ """
+ def with_parallel(self, parallel: builtins.bool | None = None) -> DiffsolBuilder:
+ r"""
+ Opt into parallel proposal generation when supported by the backend.
+ """
+ def with_config(
+ self, config: typing.Mapping[builtins.str, builtins.float]
+ ) -> DiffsolBuilder: ...
+ def with_tolerances(
+ self, rtol: builtins.float, atol: builtins.float
+ ) -> DiffsolBuilder:
+ r"""
+ Adjust the relative and absolute integration tolerances.
+ """
+ def with_parameter(
+ self,
+ name: builtins.str,
+ initial_value: builtins.float,
+ bounds: tuple[builtins.float, builtins.float] | None = None,
+ ) -> DiffsolBuilder:
+ r"""
+ Register a named optimisation variable in the order it appears in vectors.
+ """
+ def clear_parameters(self) -> DiffsolBuilder:
+ r"""
+ Clear all previously registered parameters while preserving other configuration.
+ """
+ def with_cost(self, cost: CostMetric) -> DiffsolBuilder:
+ r"""
+ Select the error metric used to compare simulated and observed data.
+ """
+ def remove_cost(self) -> DiffsolBuilder:
+ r"""
+ Reset the cost metric to the default sum of squared errors.
+ """
+ def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> DiffsolBuilder:
+ r"""
+ Configure the default optimiser used when `Problem.optimise` omits one.
+ """
+ def build(self) -> Problem:
+ r"""
+ Create a `Problem` representing the differential solver model.
+ """
+
+@typing.final
+class Done:
+ r"""
+ Optimization/sampling is complete with final results.
+
+ This is returned by `ask()` when the algorithm has terminated.
+ Access the results via the `result` attribute.
+
+ Examples
+ --------
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Optimization complete: {result.result}")
+ ... break
+ """
+ @property
+ def result(self) -> typing.Any: ...
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class Evaluate:
+ r"""
+ Request to evaluate objective function at specific points.
+
+ This is returned by `ask()` when the optimiser/sampler needs function
+ evaluations. Call `tell()` with the results after evaluation.
+
+ Examples
+ --------
+ >>> state = optimiser.init(problem, initial=[1.0, 2.0])
+ >>> result = state.ask()
+ >>> if isinstance(result, diffid.Evaluate):
+ ... values = [problem.evaluate(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+ @property
+ def points(self) -> builtins.list[builtins.list[builtins.float]]: ...
+ def __new__(
+ cls, points: typing.Sequence[typing.Sequence[builtins.float]]
+ ) -> Evaluate: ...
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class NelderMead:
+ r"""
+ Classic simplex-based direct search optimiser.
+ """
+ def __new__(cls) -> NelderMead:
+ r"""
+ Create a Nelder-Mead optimiser with default coefficients.
+ """
+ def with_step_size(self, step_size: builtins.float) -> NelderMead:
+ r"""
+ Set the initial global step-size (standard deviation).
+ """
+ def with_max_iter(self, max_iter: builtins.int) -> NelderMead:
+ r"""
+ Limit the number of simplex iterations.
+ """
+ def with_threshold(self, threshold: builtins.float) -> NelderMead:
+ r"""
+ Set the stopping threshold on simplex size or objective reduction.
+ """
+ def with_position_tolerance(self, tolerance: builtins.float) -> NelderMead:
+ r"""
+ Stop once simplex vertices fall within the supplied positional tolerance.
+ """
+ def with_max_evaluations(self, max_evaluations: builtins.int) -> NelderMead:
+ r"""
+ Abort after evaluating the objective `max_evaluations` times.
+ """
+ def with_coefficients(
+ self,
+ alpha: builtins.float,
+ gamma: builtins.float,
+ rho: builtins.float,
+ sigma: builtins.float,
+ ) -> NelderMead:
+ r"""
+ Override the reflection, expansion, contraction, and shrink coefficients.
+ """
+ def with_patience(self, patience: typing.Any) -> NelderMead:
+ r"""
+ Abort if the objective fails to improve within the allotted time.
+
+ Parameters
+ ----------
+ patience : float or timedelta
+ Either seconds (float) or a timedelta object
+ """
+ def run(
+ self, problem: Problem, initial: typing.Sequence[builtins.float]
+ ) -> OptimisationResults:
+ r"""
+ Optimise the given problem starting from the provided initial simplex centre.
+ """
+ def init(
+ self,
+ initial: typing.Sequence[builtins.float],
+ bounds: typing.Sequence[tuple[builtins.float, builtins.float]] | None = None,
+ ) -> NelderMeadState:
+ r"""
+ Initialize ask-tell optimization state.
+
+ Returns a NelderMeadState object that can be used for incremental optimization
+ via the ask-tell interface.
+
+ Parameters
+ ----------
+ initial : list[float]
+ Initial parameter vector (simplex center)
+ bounds : list[tuple[float, float]], optional
+ Parameter bounds as [(lower, upper), ...]. If None, unbounded.
+
+ Returns
+ -------
+ NelderMeadState
+ State object for ask-tell optimization
+
+ Examples
+ --------
+ >>> optimiser = diffid.NelderMead()
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... break
+ ... values = [evaluate(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+
+@typing.final
+class NelderMeadState:
+ r"""
+ Ask-tell state for incremental Nelder-Mead optimization.
+
+ This state object allows step-by-step control over the optimization process.
+ Use `ask()` to get points to evaluate, and `tell()` to provide results.
+
+ Examples
+ --------
+ >>> optimiser = diffid.NelderMead().with_max_iter(100)
+ >>> state = optimiser.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Final result: {result.result}")
+ ... break
+ ... values = [f(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+ def ask(self) -> typing.Any:
+ r"""
+ Get the next action: evaluate points or optimization complete.
+
+ Returns
+ -------
+ Evaluate | Done
+ Either Evaluate(points) requiring function evaluations,
+ or Done(result) indicating completion.
+ """
+ def tell(self, result: builtins.float) -> None:
+ r"""
+ Provide evaluation result (scalar value) for the requested point.
+
+ Parameters
+ ----------
+ result : float
+ Scalar objective function value
+
+ Raises
+ ------
+ TellError
+ If called after optimization has terminated
+ EvaluationError
+ If the evaluation failed or contained invalid values
+ """
+ def iterations(self) -> builtins.int:
+ r"""
+ Get the current iteration count.
+
+ Returns
+ -------
+ int
+ Number of iterations completed
+ """
+ def evaluations(self) -> builtins.int:
+ r"""
+ Get the total number of function evaluations.
+
+ Returns
+ -------
+ int
+ Number of function evaluations performed
+ """
+ def best(
+ self,
+ ) -> tuple[builtins.list[builtins.float], builtins.float] | None:
+ r"""
+ Get the current best point and value from the simplex.
+
+ Returns
+ -------
+ tuple[list[float], float] | None
+ (best_point, best_value) or None if no valid evaluations yet
+ """
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class NestedSamplesIterator:
+ r"""
+ Iterator for NestedSamples posterior
+ """
+ def __iter__(self) -> NestedSamplesIterator: ...
+ def __next__(
+ self,
+ ) -> (
+ tuple[builtins.list[builtins.float], builtins.float, builtins.float] | None
+ ): ...
+
+@typing.final
+class OptimisationResults:
+ r"""
+ Container for optimiser outputs and diagnostic metadata.
+ """
+ @property
+ def x(self) -> numpy.typing.NDArray[numpy.float64]:
+ r"""
+ Decision vector corresponding to the best-found objective value.
+
+ Returns
+ -------
+ numpy.ndarray
+ Best parameter vector as a NumPy array
+ """
+ @property
+ def value(self) -> builtins.float:
+ r"""
+ Objective value evaluated at `x`.
+ """
+ @property
+ def iterations(self) -> builtins.int:
+ r"""
+ Number of iterations performed by the optimiser.
+ """
+ @property
+ def evaluations(self) -> builtins.int:
+ r"""
+ Total number of objective function evaluations.
+ """
+ @property
+ def time(self) -> datetime.timedelta:
+ r"""
+ Total number of objective function evaluations.
+ """
+ @property
+ def success(self) -> builtins.bool:
+ r"""
+ Whether the run satisfied its convergence criteria.
+ """
+ @property
+ def message(self) -> builtins.str:
+ r"""
+ Human-readable status message summarising the termination state.
+ """
+ @property
+ def termination_reason(self) -> builtins.str:
+ r"""
+ Structured termination flag describing why the run ended.
+ """
+ @property
+ def final_simplex(self) -> builtins.list[builtins.list[builtins.float]]:
+ r"""
+ Simplex vertices at termination, when provided by the optimiser.
+ """
+ @property
+ def final_simplex_values(self) -> builtins.list[builtins.float]:
+ r"""
+ Objective values corresponding to `final_simplex`.
+ """
+ @property
+ def covariance(
+ self,
+ ) -> builtins.list[builtins.list[builtins.float]] | None:
+ r"""
+ Estimated covariance of the search distribution, if available.
+ """
+ def __repr__(self) -> builtins.str:
+ r"""
+ Render a concise summary of the optimisation outcome.
+ """
+ def __str__(self) -> builtins.str:
+ r"""
+ Return a human-readable summary of the result.
+ """
+ def __bool__(self) -> builtins.bool:
+ r"""
+ Return truthiness based on optimization success.
+
+ Allows using `if result:` instead of `if result.success:`.
+ """
+
+@typing.final
+class Problem:
+ r"""
+ Executable optimisation problem wrapping the Diffid core implementation.
+ """
+ def evaluate(self, x: typing.Sequence[builtins.float]) -> builtins.float:
+ r"""
+ Evaluate the configured objective function at `x`.
+ """
+ def evaluate_gradient(
+ self, x: typing.Sequence[builtins.float]
+ ) -> builtins.list[builtins.float] | None:
+ r"""
+ Evaluate the gradient of the objective function at `x` if available.
+ """
+ def optimise(
+ self,
+ initial: typing.Sequence[builtins.float] | None = None,
+ optimiser: NelderMead | CMAES | Adam | None = None,
+ ) -> OptimisationResults:
+ r"""
+ Solve the problem starting from `initial` using the supplied optimiser.
+ """
+ def sample(
+ self,
+ initial: typing.Sequence[builtins.float] | None = None,
+ sampler: MetropolisHastings | DynamicNestedSampler | None = None,
+ ) -> typing.Any:
+ r"""
+ Sample from the problem starting from `initial` using the supplied sampler.
+ """
+ def get_config(self, _key: builtins.str) -> builtins.float | None:
+ r"""
+ Return the numeric configuration value stored under `key` if present.
+ """
+ def dimension(self) -> builtins.int:
+ r"""
+ Return the number of parameters the problem expects.
+ """
+ def bounds(self) -> builtins.list[tuple[builtins.float, builtins.float]]:
+ r"""
+ Return the parameter bounds for the problem as a list of (lower, upper) tuples.
+ """
+ def parameters(
+ self,
+ ) -> builtins.list[
+ tuple[
+ builtins.str,
+ builtins.float,
+ tuple[builtins.float, builtins.float] | None,
+ ]
+ ]: ...
+ def initial_values(self) -> builtins.list[builtins.float]: ...
+ def default_parameters(self) -> builtins.list[builtins.float]:
+ r"""
+ Return the default parameter vector implied by the builder.
+ """
+ def config(self) -> builtins.dict[builtins.str, builtins.float]:
+ r"""
+ Return a copy of the problem configuration dictionary.
+ """
+ def __call__(self, x: typing.Sequence[builtins.float]) -> builtins.float:
+ r"""
+ Call the problem as a function (shorthand for evaluate).
+
+ Allows using `problem(x)` instead of `problem.evaluate(x)`.
+ """
+ def __repr__(self) -> builtins.str:
+ r"""
+ Return a detailed string representation of the problem.
+ """
+ def __str__(self) -> builtins.str:
+ r"""
+ Return a concise string representation of the problem.
+ """
+
+@typing.final
+class SamplesIterator:
+ r"""
+ Iterator for Samples chains
+ """
+ def __iter__(self) -> SamplesIterator: ...
+ def __next__(
+ self,
+ ) -> builtins.list[builtins.list[builtins.float]] | None: ...
+
+@typing.final
+class ScalarBuilder:
+ r"""
+ High-level builder for optimisation `Problem` instances exposed to Python.
+ """
+ def __new__(cls) -> ScalarBuilder:
+ r"""
+ Create an empty builder with no objective, parameters, or default optimiser.
+ """
+ def __copy__(self) -> ScalarBuilder: ...
+ def __deepcopy__(self, _memo: dict) -> ScalarBuilder: ...
+ def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> ScalarBuilder:
+ r"""
+ Configure the default optimiser used when `Problem.optimise` omits one.
+ """
+ def with_objective(self, obj: typing.Any) -> ScalarBuilder:
+ r"""
+ Attach the objective function callable executed during optimisation.
+ """
+ def with_gradient(self, obj: typing.Any) -> ScalarBuilder:
+ r"""
+ Attach the gradient callable returning derivatives of the objective.
+ """
+ def with_parameter(
+ self,
+ name: builtins.str,
+ initial_value: builtins.float,
+ bounds: tuple[builtins.float, builtins.float] | None = None,
+ ) -> ScalarBuilder:
+ r"""
+ Register a named optimisation variable in the order it appears in vectors.
+ """
+ def build(self) -> Problem:
+ r"""
+ Finalize the builder into an executable `Problem`.
+ """
+
+@typing.final
+class VectorBuilder:
+ r"""
+ Time-series problem builder for vector-valued objectives.
+ """
+ def __new__(cls) -> VectorBuilder:
+ r"""
+ Create an empty vector problem builder.
+ """
+ def __copy__(self) -> VectorBuilder: ...
+ def __deepcopy__(self, _memo: dict) -> VectorBuilder: ...
+ def with_objective(self, objective: typing.Any) -> VectorBuilder:
+ r"""
+ Register a callable that produces predictions matching the data shape.
+
+ The callable should accept a parameter vector and return a numpy array
+ of the same shape as the observed data.
+ """
+ def with_data(self, data: numpy.typing.NDArray[numpy.float64]) -> VectorBuilder:
+ r"""
+ Attach observed data used to fit the model.
+
+ The data should be a 1D numpy array. The shape will be inferred
+ from the data length.
+ """
+ def with_parameter(
+ self,
+ name: builtins.str,
+ initial_value: builtins.float,
+ bounds: tuple[builtins.float, builtins.float] | None = None,
+ ) -> VectorBuilder:
+ r"""
+ Register a named optimisation variable in the order it appears in vectors.
+ """
+ def with_cost(self, cost: CostMetric) -> VectorBuilder:
+ r"""
+ Select the error metric used to compare predictions and observed data.
+ """
+ def remove_cost(self) -> VectorBuilder:
+ r"""
+ Reset the cost metric to the default sum of squared errors.
+ """
+ def with_optimiser(self, optimiser: NelderMead | CMAES | Adam) -> VectorBuilder:
+ r"""
+ Configure the default optimiser used when `Problem.optimise` omits one.
+ """
+ def with_config(self, key: builtins.str, value: builtins.float) -> VectorBuilder:
+ r"""
+ Attach an arbitrary configuration value to the problem.
+ """
+ def build(self) -> Problem:
+ r"""
+ Create a `Problem` representing the vector optimisation model.
+ """
+
+def GaussianNLL(
+ variance: builtins.float, weight: builtins.float = 1.0
+) -> CostMetric: ...
+def RMSE(weight: builtins.float = 1.0) -> CostMetric: ...
+def SSE(weight: builtins.float = 1.0) -> CostMetric: ...
+def builder_factory_py() -> ScalarBuilder:
+ r"""
+ Return a convenience factory for creating `Builder` instances.
+ """
diff --git a/python/src/chronopt/errors.py b/python/src/diffid/errors.py
similarity index 90%
rename from python/src/chronopt/errors.py
rename to python/src/diffid/errors.py
index 6946247..e22c963 100644
--- a/python/src/chronopt/errors.py
+++ b/python/src/diffid/errors.py
@@ -1,30 +1,30 @@
-"""Custom exception hierarchy for Chronopt.
+"""Custom exception hierarchy for Diffid.
-This module defines the exception hierarchy used throughout the chronopt library,
+This module defines the exception hierarchy used throughout the diffid library,
providing clear and actionable error messages for different failure modes.
"""
from __future__ import annotations
-class ChronoptError(Exception):
- """Base exception class for all Chronopt errors.
+class DiffidError(Exception):
+ """Base exception class for all Diffid errors.
- All custom exceptions in the chronopt library inherit from this class,
- making it easy to catch any chronopt-specific error.
+ All custom exceptions in the diffid library inherit from this class,
+ making it easy to catch any diffid-specific error.
Examples
--------
>>> try:
... optimiser.run(problem, initial=[1.0, 2.0])
- ... except ChronoptError as e:
- ... print(f"Chronopt error occurred: {e}")
+ ... except DiffidError as e:
+ ... print(f"Diffid error occurred: {e}")
"""
pass
-class EvaluationError(ChronoptError):
+class EvaluationError(DiffidError):
"""Raised when objective function evaluation fails.
This exception is raised when the objective function (or callback) throws
@@ -65,7 +65,7 @@ def __init__(
self.original_error = original_error
-class BuildError(ChronoptError):
+class BuildError(DiffidError):
"""Raised when problem or optimiser construction fails.
This exception is raised during the build phase when invalid parameters
@@ -84,7 +84,7 @@ class BuildError(ChronoptError):
pass
-class TellError(ChronoptError):
+class TellError(DiffidError):
"""Base exception for errors during the 'tell' phase of ask-tell interface.
This exception is raised when providing results back to an optimiser or
@@ -161,7 +161,7 @@ def __init__(self):
__all__ = [
- "ChronoptError",
+ "DiffidError",
"EvaluationError",
"BuildError",
"TellError",
diff --git a/python/src/chronopt/plotting/__init__.py b/python/src/diffid/plotting/__init__.py
similarity index 97%
rename from python/src/chronopt/plotting/__init__.py
rename to python/src/diffid/plotting/__init__.py
index 2c73e1a..8335511 100644
--- a/python/src/chronopt/plotting/__init__.py
+++ b/python/src/diffid/plotting/__init__.py
@@ -1,4 +1,4 @@
-"""Plotting utilities for Chronopt.
+"""Plotting utilities for Diffid.
This module provides convenience helpers for visualising optimisation and sampling
results. The implementation only depends on ``numpy`` at import time and lazily
@@ -14,7 +14,7 @@
import numpy as np
if TYPE_CHECKING: # pragma: no cover - type checking only
- from chronopt import Problem
+ from diffid import Problem
__all__ = [
"contour",
@@ -70,7 +70,7 @@ def contour(
----------
objective:
A callable mapping a two-dimensional input to a scalar value, or a
- :class:`chronopt.Problem` instance whose ``evaluate`` method will be
+ :class:`diffid.Problem` instance whose ``evaluate`` method will be
invoked.
x_bounds, y_bounds:
Inclusive ranges ``(min, max)`` spanning the region to sample along each
@@ -116,7 +116,7 @@ def contour(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc: # pragma: no cover - import guard
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
xs = np.linspace(x_min, x_max, grid_size)
@@ -193,7 +193,7 @@ def contour_2d(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
@@ -294,7 +294,7 @@ def ode_fit(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
@@ -359,7 +359,7 @@ def convergence(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
@@ -420,7 +420,7 @@ def parameter_traces(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
@@ -502,7 +502,7 @@ def parameter_distributions(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
@@ -598,7 +598,7 @@ def compare_models(
import matplotlib.pyplot as plt
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
- "matplotlib is required for plotting; install it via 'pip install chronopt[plotting]'"
+ "matplotlib is required for plotting; install it via 'pip install diffid[plotting]'"
) from exc
_setup_plotting()
diff --git a/python/src/chronopt/plotting/__init__.pyi b/python/src/diffid/plotting/__init__.pyi
similarity index 100%
rename from python/src/chronopt/plotting/__init__.pyi
rename to python/src/diffid/plotting/__init__.pyi
diff --git a/python/src/chronopt/py.typed b/python/src/diffid/py.typed
similarity index 100%
rename from python/src/chronopt/py.typed
rename to python/src/diffid/py.typed
diff --git a/python/src/diffid/sampler.pyi b/python/src/diffid/sampler.pyi
new file mode 100644
index 0000000..6054e40
--- /dev/null
+++ b/python/src/diffid/sampler.pyi
@@ -0,0 +1,353 @@
+# This file is automatically generated by pyo3_stub_gen
+# ruff: noqa: E501, F401
+
+import builtins
+import datetime
+import typing
+
+import numpy
+import numpy.typing
+
+from diffid._diffid import NestedSamplesIterator, Problem, SamplesIterator
+
+@typing.final
+class DynamicNestedSampler:
+ r"""
+ Dynamic nested sampler binding exposing DNS configuration knobs.
+ """
+ def __new__(cls) -> DynamicNestedSampler: ...
+ def with_live_points(self, live_points: builtins.int) -> DynamicNestedSampler: ...
+ def with_expansion_factor(
+ self, expansion_factor: builtins.float
+ ) -> DynamicNestedSampler: ...
+ def with_termination_tolerance(
+ self, tolerance: builtins.float
+ ) -> DynamicNestedSampler: ...
+ def with_seed(self, seed: builtins.int) -> DynamicNestedSampler: ...
+ def run(
+ self,
+ problem: Problem,
+ initial: typing.Sequence[builtins.float] | None = None,
+ ) -> NestedSamples: ...
+ def init(
+ self,
+ initial: typing.Sequence[builtins.float],
+ bounds: typing.Sequence[tuple[builtins.float, builtins.float]] | None = None,
+ ) -> DynamicNestedSamplerState:
+ r"""
+ Initialize ask-tell sampling state.
+
+ Returns a DynamicNestedSamplerState object that can be used for incremental
+ sampling via the ask-tell interface.
+
+ Parameters
+ ----------
+ initial : list[float]
+ Initial point for the sampler
+ bounds : list[tuple[float, float]], optional
+ Parameter bounds as [(lower, upper), ...]. If None, unbounded.
+
+ Returns
+ -------
+ DynamicNestedSamplerState
+ State object for ask-tell sampling
+
+ Examples
+ --------
+ >>> sampler = diffid.DynamicNestedSampler()
+ >>> state = sampler.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... break
+ ... values = [evaluate(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+
+@typing.final
+class DynamicNestedSamplerState:
+ r"""
+ Ask-tell state for incremental Dynamic Nested Sampling.
+
+ This state object allows step-by-step control over the sampling process.
+ Use `ask()` to get points to evaluate, and `tell()` to provide results.
+
+ Examples
+ --------
+ >>> sampler = diffid.DynamicNestedSampler()
+ >>> state = sampler.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Sampling complete: {result.result}")
+ ... break
+ ... values = [negative_log_likelihood(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+ def ask(self) -> typing.Any:
+ r"""
+ Get the next action: evaluate points or sampling complete.
+
+ Returns
+ -------
+ Evaluate | Done
+ Either Evaluate(points) requiring function evaluations,
+ or Done(result) indicating completion with NestedSamples.
+ """
+ def tell(self, results: typing.Sequence[builtins.float]) -> None:
+ r"""
+ Provide evaluation results for the requested points.
+
+ Parameters
+ ----------
+ results : list[float]
+ Negative log-likelihood values for each requested point.
+
+ Raises
+ ------
+ TellError
+ If called after sampling has terminated or if wrong number
+ of results provided
+ """
+ def iterations(self) -> builtins.int:
+ r"""
+ Get the current iteration count.
+
+ Returns
+ -------
+ int
+ Number of iterations completed
+ """
+ def num_live_points(self) -> builtins.int:
+ r"""
+ Get the number of live points.
+
+ Returns
+ -------
+ int
+ Current number of live points in the sampler
+ """
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class MetropolisHastings:
+ r"""
+ Basic Metropolis-Hastings sampler binding mirroring the optimiser API.
+ """
+ def __new__(cls) -> MetropolisHastings: ...
+ def with_num_chains(self, num_chains: builtins.int) -> MetropolisHastings: ...
+ def with_iterations(self, iterations: builtins.int) -> MetropolisHastings: ...
+ def with_step_size(self, step_size: builtins.float) -> MetropolisHastings: ...
+ def with_seed(self, seed: builtins.int) -> MetropolisHastings: ...
+ def run(
+ self, problem: Problem, initial: typing.Sequence[builtins.float]
+ ) -> Samples: ...
+ def init(
+ self,
+ initial: typing.Sequence[builtins.float],
+ bounds: typing.Sequence[tuple[builtins.float, builtins.float]] | None = None,
+ ) -> MetropolisHastingsState:
+ r"""
+ Initialize ask-tell sampling state.
+
+ Returns a MetropolisHastingsState object that can be used for incremental
+ sampling via the ask-tell interface.
+
+ Parameters
+ ----------
+ initial : list[float]
+ Initial point for all chains
+ bounds : list[tuple[float, float]], optional
+ Parameter bounds as [(lower, upper), ...]. If None, unbounded.
+
+ Returns
+ -------
+ MetropolisHastingsState
+ State object for ask-tell sampling
+
+ Examples
+ --------
+ >>> sampler = diffid.MetropolisHastings().with_num_chains(4)
+ >>> state = sampler.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... break
+ ... values = [evaluate(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+
+@typing.final
+class MetropolisHastingsState:
+ r"""
+ Ask-tell state for incremental Metropolis-Hastings MCMC sampling.
+
+ This state object allows step-by-step control over the sampling process.
+ Use `ask()` to get proposal points to evaluate, and `tell()` to provide results.
+
+ Examples
+ --------
+ >>> sampler = diffid.MetropolisHastings().with_num_chains(4)
+ >>> state = sampler.init(initial=[1.0, 2.0])
+ >>> while True:
+ ... result = state.ask()
+ ... if isinstance(result, diffid.Done):
+ ... print(f"Sampling complete: {result.result}")
+ ... break
+ ... values = [negative_log_likelihood(pt) for pt in result.points]
+ ... state.tell(values)
+ """
+ def ask(self) -> typing.Any:
+ r"""
+ Get the next action: evaluate proposal points or sampling complete.
+
+ Returns
+ -------
+ Evaluate | Done
+ Either Evaluate(points) requiring function evaluations,
+ or Done(result) indicating completion with Samples.
+
+ Notes
+ -----
+ Returns one proposal point per chain.
+ """
+ def tell(self, results: typing.Sequence[builtins.float]) -> None:
+ r"""
+ Provide evaluation results for the proposed points.
+
+ Parameters
+ ----------
+ results : list[float]
+ Negative log-likelihood values for each proposal point.
+ Must match the number of chains.
+
+ Raises
+ ------
+ TellError
+ If called after sampling has terminated or if wrong number
+ of results provided
+ """
+ def iterations(self) -> builtins.int:
+ r"""
+ Get the current iteration count.
+
+ Returns
+ -------
+ int
+ Number of iterations completed
+ """
+ def num_chains(self) -> builtins.int:
+ r"""
+ Get the number of chains being run.
+
+ Returns
+ -------
+ int
+ Number of parallel MCMC chains
+ """
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str: ...
+
+@typing.final
+class NestedSamples:
+ r"""
+ Nested sampling results including evidence estimates.
+ """
+ @property
+ def posterior(
+ self,
+ ) -> builtins.list[
+ tuple[builtins.list[builtins.float], builtins.float, builtins.float]
+ ]: ...
+ @property
+ def mean(self) -> numpy.typing.NDArray[numpy.float64]: ...
+ @property
+ def draws(self) -> builtins.int: ...
+ @property
+ def log_evidence(self) -> builtins.float: ...
+ @property
+ def information(self) -> builtins.float: ...
+ @property
+ def time(self) -> datetime.timedelta: ...
+ def to_samples(self) -> Samples: ...
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str:
+ r"""
+ Return a human-readable summary of the nested samples.
+ """
+ def __len__(self) -> builtins.int:
+ r"""
+ Return the number of posterior samples.
+ """
+ def __iter__(self) -> NestedSamplesIterator:
+ r"""
+ Iterate over posterior samples.
+
+ Yields tuples of (position, log_likelihood, log_weight).
+ """
+ def __getitem__(
+ self, idx: builtins.int
+ ) -> tuple[builtins.list[builtins.float], builtins.float, builtins.float]:
+ r"""
+ Get a specific posterior sample by index.
+
+ Parameters
+ ----------
+ idx : int
+ Sample index (0 to num_samples - 1)
+
+ Returns
+ -------
+ tuple[list[float], float, float]
+ Tuple of (position, log_likelihood, log_weight)
+ """
+
+@typing.final
+class Samples:
+ r"""
+ Container for sampler draws and diagnostics.
+ """
+ @property
+ def chains(self) -> numpy.typing.NDArray[numpy.float64]: ...
+ @property
+ def samples(self) -> numpy.typing.NDArray[numpy.float64]: ...
+ @property
+ def mean_x(self) -> numpy.typing.NDArray[numpy.float64]: ...
+ @property
+ def acceptance_rate(self) -> numpy.typing.NDArray[numpy.float64]: ...
+ @property
+ def draws(self) -> builtins.int: ...
+ @property
+ def time(self) -> datetime.timedelta: ...
+ def __repr__(self) -> builtins.str: ...
+ def __str__(self) -> builtins.str:
+ r"""
+ Return a human-readable summary of the samples.
+ """
+ def __len__(self) -> builtins.int:
+ r"""
+ Return the number of chains.
+ """
+ def __iter__(self) -> SamplesIterator:
+ r"""
+ Iterate over chains.
+
+ Yields each chain as a list of samples.
+ """
+ def __getitem__(
+ self, idx: builtins.int
+ ) -> builtins.list[builtins.list[builtins.float]]:
+ r"""
+ Get a specific chain by index.
+
+ Parameters
+ ----------
+ idx : int
+ Chain index (0 to num_chains - 1)
+
+ Returns
+ -------
+ list[list[float]]
+ The requested chain
+ """
diff --git a/python/src/errors.rs b/python/src/errors.rs
index 157da4a..de4a67c 100644
--- a/python/src/errors.rs
+++ b/python/src/errors.rs
@@ -2,11 +2,11 @@ use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyModule;
-use chronopt_core::errors::{EvaluationError as CoreEvaluationError, TellError as CoreTellError};
+use diffid_core::errors::{EvaluationError as CoreEvaluationError, TellError as CoreTellError};
-/// Get the custom exception class from chronopt.errors module
+/// Get the custom exception class from diffid.errors module
fn get_exception_class<'py>(py: Python<'py>, name: &str) -> PyResult
> {
- let errors_module = PyModule::import(py, "chronopt.errors")?;
+ let errors_module = PyModule::import(py, "diffid.errors")?;
errors_module.getattr(name)
}
diff --git a/python/src/lib.rs b/python/src/lib.rs
index 828d30c..d2452f5 100644
--- a/python/src/lib.rs
+++ b/python/src/lib.rs
@@ -10,9 +10,9 @@ use std::env;
#[cfg(feature = "stubgen")]
use std::path::PathBuf;
-use chronopt_core::cost::{CostMetric, GaussianNll, RootMeanSquaredError, SumSquaredError};
-use chronopt_core::prelude::*;
-use chronopt_core::sampler::SamplingResults;
+use diffid_core::cost::{CostMetric, GaussianNll, RootMeanSquaredError, SumSquaredError};
+use diffid_core::prelude::*;
+use diffid_core::sampler::SamplingResults;
#[cfg(feature = "stubgen")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyfunction, gen_stub_pymethods};
@@ -39,7 +39,7 @@ use samplers::Sampler;
type ParameterSpecEntry = (String, f64, Option<(f64, f64)>);
// Import objective types for the problem enum
-use chronopt_core::problem::{DiffsolObjective, ScalarObjective, VectorObjective};
+use diffid_core::problem::{DiffsolObjective, ScalarObjective, VectorObjective};
// Enum to hold different Problem types internally
pub(crate) enum DynProblem {
@@ -57,7 +57,7 @@ pub(crate) enum DynProblem {
}
impl DynProblem {
- fn evaluate(&self, x: &[f64]) -> Result {
+ fn evaluate(&self, x: &[f64]) -> Result {
match self {
Self::Scalar(p) => p.evaluate(x),
Self::ScalarWithGradient(p) => p.evaluate(x),
@@ -210,7 +210,7 @@ fn gaussian_nll(variance: f64, weight: f64) -> PyResult {
}
// Problem
-/// Executable optimisation problem wrapping the Chronopt core implementation.
+/// Executable optimisation problem wrapping the Diffid core implementation.
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
#[pyclass(name = "Problem")]
pub struct PyProblem {
@@ -227,7 +227,7 @@ impl PyProblem {
/// Evaluate the configured objective function at `x`.
fn evaluate(&self, x: Vec) -> PyResult {
self.inner.evaluate(&x).map_err(|e| {
- crate::errors::evaluation_error_to_py(chronopt_core::errors::EvaluationError::message(
+ crate::errors::evaluation_error_to_py(diffid_core::errors::EvaluationError::message(
format!("{}", e),
))
})
@@ -239,7 +239,7 @@ impl PyProblem {
DynProblem::ScalarWithGradient(p) => match p.evaluate_with_gradient(&x) {
Ok((_val, grad_opt)) => Ok(grad_opt),
Err(e) => Err(crate::errors::evaluation_error_to_py(
- chronopt_core::errors::EvaluationError::message(format!("{}", e)),
+ diffid_core::errors::EvaluationError::message(format!("{}", e)),
)),
},
_ => Ok(None),
@@ -399,7 +399,7 @@ fn builder_factory_py() -> PyScalarBuilder {
}
#[pymodule]
-fn _chronopt(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
+fn _diffid(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
// Main classes
m.add_class::()?;
m.add_class::()?;
@@ -461,11 +461,11 @@ fn _chronopt(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_submodule(&sampler_module)?;
m.setattr("sampler", &sampler_module)?;
- // Register submodules for `import chronopt.builder` and `chronopt.cost`
+ // Register submodules for `import diffid.builder` and `diffid.cost`
let sys_modules = py.import("sys")?.getattr("modules")?;
- sys_modules.set_item("chronopt.builder", &builder_module)?;
- sys_modules.set_item("chronopt.cost", &cost_module)?;
- sys_modules.set_item("chronopt.sampler", &sampler_module)?;
+ sys_modules.set_item("diffid.builder", &builder_module)?;
+ sys_modules.set_item("diffid.cost", &cost_module)?;
+ sys_modules.set_item("diffid.sampler", &sampler_module)?;
// Factory function
m.add_function(wrap_pyfunction!(builder_factory_py, m)?)?;
diff --git a/python/src/optimisers.rs b/python/src/optimisers.rs
index 5043383..4cf537b 100644
--- a/python/src/optimisers.rs
+++ b/python/src/optimisers.rs
@@ -2,9 +2,9 @@ use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use std::time::Duration;
-use chronopt_core::common::{AskResult, Bounds};
-use chronopt_core::optimisers::{AdamState, CMAESState, NelderMeadState};
-use chronopt_core::prelude::*;
+use diffid_core::common::{AskResult, Bounds};
+use diffid_core::optimisers::{AdamState, CMAESState, NelderMeadState};
+use diffid_core::prelude::*;
#[cfg(feature = "stubgen")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
@@ -28,9 +28,9 @@ pub(crate) enum Optimiser {
#[cfg(feature = "stubgen")]
#[allow(dead_code)]
pub(crate) fn optimiser_type_info() -> TypeInfo {
- TypeInfo::unqualified("chronopt._chronopt.NelderMead")
- | TypeInfo::unqualified("chronopt._chronopt.CMAES")
- | TypeInfo::unqualified("chronopt._chronopt.Adam")
+ TypeInfo::unqualified("diffid._diffid.NelderMead")
+ | TypeInfo::unqualified("diffid._diffid.CMAES")
+ | TypeInfo::unqualified("diffid._diffid.Adam")
}
impl FromPyObject<'_, '_> for Optimiser {
@@ -53,7 +53,7 @@ impl FromPyObject<'_, '_> for Optimiser {
impl Optimiser {
/// Convert to the core Optimiser enum
- pub(crate) fn to_core(&self) -> chronopt_core::optimisers::Optimiser {
+ pub(crate) fn to_core(&self) -> diffid_core::optimisers::Optimiser {
match self {
Optimiser::NelderMead(nm) => nm.clone().into(),
Optimiser::Cmaes(cma) => cma.clone().into(),
@@ -178,11 +178,11 @@ impl PyNelderMead {
///
/// Examples
/// --------
- /// >>> optimiser = chronopt.NelderMead()
+ /// >>> optimiser = diffid.NelderMead()
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
- /// ... if isinstance(result, chronopt.Done):
+ /// ... if isinstance(result, diffid.Done):
/// ... break
/// ... values = [evaluate(pt) for pt in result.points]
/// ... state.tell(values)
@@ -305,11 +305,11 @@ impl PyCMAES {
///
/// Examples
/// --------
- /// >>> optimiser = chronopt.CMAES()
+ /// >>> optimiser = diffid.CMAES()
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
- /// ... if isinstance(result, chronopt.Done):
+ /// ... if isinstance(result, diffid.Done):
/// ... break
/// ... values = [evaluate(pt) for pt in result.points]
/// ... state.tell(values)
@@ -423,11 +423,11 @@ impl PyAdam {
///
/// Examples
/// --------
- /// >>> optimiser = chronopt.Adam()
+ /// >>> optimiser = diffid.Adam()
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
- /// ... if isinstance(result, chronopt.Done):
+ /// ... if isinstance(result, diffid.Done):
/// ... break
/// ... values = [evaluate_with_gradient(pt) for pt in result.points]
/// ... state.tell(values)
@@ -450,11 +450,11 @@ impl PyAdam {
///
/// Examples
/// --------
-/// >>> optimiser = chronopt.Adam().with_max_iter(100)
+/// >>> optimiser = diffid.Adam().with_max_iter(100)
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Final result: {result.result}")
/// ... break
/// ... # Adam requires gradient information
@@ -480,9 +480,9 @@ impl PyAdamState {
/// Examples
/// --------
/// >>> result = state.ask()
- /// >>> if isinstance(result, chronopt.Evaluate):
+ /// >>> if isinstance(result, diffid.Evaluate):
/// ... print(f"Need to evaluate {len(result.points)} points")
- /// >>> elif isinstance(result, chronopt.Done):
+ /// >>> elif isinstance(result, diffid.Done):
/// ... print(f"Optimization complete: {result.result}")
fn ask(&self, py: Python<'_>) -> Py {
match self.inner.ask() {
@@ -511,7 +511,7 @@ impl PyAdamState {
/// Examples
/// --------
/// >>> result = state.ask()
- /// >>> if isinstance(result, chronopt.Evaluate):
+ /// >>> if isinstance(result, diffid.Evaluate):
/// ... point = result.points[0]
/// ... value = objective(point)
/// ... gradient = compute_gradient(point)
@@ -588,11 +588,11 @@ impl PyAdamState {
///
/// Examples
/// --------
-/// >>> optimiser = chronopt.NelderMead().with_max_iter(100)
+/// >>> optimiser = diffid.NelderMead().with_max_iter(100)
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Final result: {result.result}")
/// ... break
/// ... values = [f(pt) for pt in result.points]
@@ -699,11 +699,11 @@ impl PyNelderMeadState {
///
/// Examples
/// --------
-/// >>> optimiser = chronopt.CMAES().with_max_iter(100)
+/// >>> optimiser = diffid.CMAES().with_max_iter(100)
/// >>> state = optimiser.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Final result: {result.result}")
/// ... break
/// ... values = [f(pt) for pt in result.points]
diff --git a/python/src/results.rs b/python/src/results.rs
index 6ae8bdd..902ef66 100644
--- a/python/src/results.rs
+++ b/python/src/results.rs
@@ -2,7 +2,7 @@ use numpy::{PyArray1, ToPyArray};
use pyo3::prelude::*;
use std::time::Duration;
-use chronopt_core::prelude::OptimisationResults;
+use diffid_core::prelude::OptimisationResults;
#[cfg(feature = "stubgen")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
@@ -18,7 +18,7 @@ use crate::{PyNestedSamples, PySamples};
/// --------
/// >>> state = optimiser.init(problem, initial=[1.0, 2.0])
/// >>> result = state.ask()
-/// >>> if isinstance(result, chronopt.Evaluate):
+/// >>> if isinstance(result, diffid.Evaluate):
/// ... values = [problem.evaluate(pt) for pt in result.points]
/// ... state.tell(values)
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
@@ -55,7 +55,7 @@ impl PyEvaluate {
/// --------
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Optimization complete: {result.result}")
/// ... break
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
diff --git a/python/src/samplers.rs b/python/src/samplers.rs
index a51a2e9..1bfc918 100644
--- a/python/src/samplers.rs
+++ b/python/src/samplers.rs
@@ -3,8 +3,8 @@ use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use std::time::Duration;
-use chronopt_core::common::{AskResult, Bounds};
-use chronopt_core::sampler::{
+use diffid_core::common::{AskResult, Bounds};
+use diffid_core::sampler::{
DynamicNestedSampler as CoreDynamicNestedSampler,
DynamicNestedSamplerState as CoreDynamicNestedSamplerState,
MetropolisHastings as CoreMetropolisHastings,
@@ -34,8 +34,8 @@ pub(crate) enum Sampler {
#[cfg(feature = "stubgen")]
#[allow(dead_code)]
pub(crate) fn sampler_type_info() -> TypeInfo {
- TypeInfo::unqualified("chronopt._chronopt.MetropolisHastings")
- | TypeInfo::unqualified("chronopt._chronopt.DynamicNestedSampler")
+ TypeInfo::unqualified("diffid._diffid.MetropolisHastings")
+ | TypeInfo::unqualified("diffid._diffid.DynamicNestedSampler")
}
impl FromPyObject<'_, '_> for Sampler {
@@ -70,7 +70,7 @@ impl Sampler {
/// Container for sampler draws and diagnostics.
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "Samples")]
+#[pyclass(module = "diffid.sampler", name = "Samples")]
pub struct PySamples {
pub(crate) inner: CoreSamples,
}
@@ -225,7 +225,7 @@ impl SamplesIterator {
/// Nested sampling results including evidence estimates.
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "NestedSamples")]
+#[pyclass(module = "diffid.sampler", name = "NestedSamples")]
#[derive(Clone)]
pub struct PyNestedSamples {
pub(crate) inner: CoreNestedSamples,
@@ -394,7 +394,7 @@ impl NestedSamplesIterator {
/// Basic Metropolis-Hastings sampler binding mirroring the optimiser API.
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "MetropolisHastings")]
+#[pyclass(module = "diffid.sampler", name = "MetropolisHastings")]
#[derive(Clone)]
pub struct PyMetropolisHastings {
pub(crate) inner: CoreMetropolisHastings,
@@ -457,11 +457,11 @@ impl PyMetropolisHastings {
///
/// Examples
/// --------
- /// >>> sampler = chronopt.MetropolisHastings().with_num_chains(4)
+ /// >>> sampler = diffid.MetropolisHastings().with_num_chains(4)
/// >>> state = sampler.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
- /// ... if isinstance(result, chronopt.Done):
+ /// ... if isinstance(result, diffid.Done):
/// ... break
/// ... values = [evaluate(pt) for pt in result.points]
/// ... state.tell(values)
@@ -482,7 +482,7 @@ impl PyMetropolisHastings {
/// Dynamic nested sampler binding exposing DNS configuration knobs.
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "DynamicNestedSampler")]
+#[pyclass(module = "diffid.sampler", name = "DynamicNestedSampler")]
#[derive(Clone)]
pub struct PyDynamicNestedSampler {
pub(crate) inner: CoreDynamicNestedSampler,
@@ -553,11 +553,11 @@ impl PyDynamicNestedSampler {
///
/// Examples
/// --------
- /// >>> sampler = chronopt.DynamicNestedSampler()
+ /// >>> sampler = diffid.DynamicNestedSampler()
/// >>> state = sampler.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
- /// ... if isinstance(result, chronopt.Done):
+ /// ... if isinstance(result, diffid.Done):
/// ... break
/// ... values = [evaluate(pt) for pt in result.points]
/// ... state.tell(values)
@@ -583,17 +583,17 @@ impl PyDynamicNestedSampler {
///
/// Examples
/// --------
-/// >>> sampler = chronopt.MetropolisHastings().with_num_chains(4)
+/// >>> sampler = diffid.MetropolisHastings().with_num_chains(4)
/// >>> state = sampler.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Sampling complete: {result.result}")
/// ... break
/// ... values = [negative_log_likelihood(pt) for pt in result.points]
/// ... state.tell(values)
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "MetropolisHastingsState")]
+#[pyclass(module = "diffid.sampler", name = "MetropolisHastingsState")]
pub struct PyMetropolisHastingsState {
inner: CoreMetropolisHastingsState,
}
@@ -685,17 +685,17 @@ impl PyMetropolisHastingsState {
///
/// Examples
/// --------
-/// >>> sampler = chronopt.DynamicNestedSampler()
+/// >>> sampler = diffid.DynamicNestedSampler()
/// >>> state = sampler.init(initial=[1.0, 2.0])
/// >>> while True:
/// ... result = state.ask()
-/// ... if isinstance(result, chronopt.Done):
+/// ... if isinstance(result, diffid.Done):
/// ... print(f"Sampling complete: {result.result}")
/// ... break
/// ... values = [negative_log_likelihood(pt) for pt in result.points]
/// ... state.tell(values)
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
-#[pyclass(module = "chronopt.sampler", name = "DynamicNestedSamplerState")]
+#[pyclass(module = "diffid.sampler", name = "DynamicNestedSamplerState")]
pub struct PyDynamicNestedSamplerState {
inner: CoreDynamicNestedSamplerState,
}
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 9b890ec..c27fb3a 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -1,12 +1,12 @@
[package]
-name = "chronopt"
+name = "diffid"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Time-series optimisation and inference toolkit with differential-system support"
-repository = "https://github.com/BradyPlanden/chronopt"
-homepage = "https://github.com/BradyPlanden/chronopt"
-documentation = "https://docs.rs/chronopt"
+repository = "https://github.com/BradyPlanden/diffid"
+homepage = "https://github.com/BradyPlanden/diffid"
+documentation = "https://docs.rs/diffid"
readme = "../README.md"
keywords = ["optimisation", "time-series", "differential-equations", "sampler", "inference"]
categories = ["algorithms", "science", "simulation"]
diff --git a/rust/benches/diffsol_benches.rs b/rust/benches/diffsol_benches.rs
index bb1a20d..7a8b94f 100644
--- a/rust/benches/diffsol_benches.rs
+++ b/rust/benches/diffsol_benches.rs
@@ -1,6 +1,6 @@
-use chronopt::builders::DiffsolBackend;
-use chronopt::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use diffid::builders::DiffsolBackend;
+use diffid::prelude::*;
use nalgebra::DMatrix;
use std::time::Duration;
diff --git a/rust/benches/optimisers_benches.rs b/rust/benches/optimisers_benches.rs
index 1f4e56e..03375e3 100644
--- a/rust/benches/optimisers_benches.rs
+++ b/rust/benches/optimisers_benches.rs
@@ -1,6 +1,6 @@
-use chronopt::common::Bounds;
-use chronopt::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use diffid::common::Bounds;
+use diffid::prelude::*;
use std::time::Duration;
fn bench_nelder_mead_quadratic(c: &mut Criterion) {
diff --git a/rust/benches/samplers_benches.rs b/rust/benches/samplers_benches.rs
index fd30d3a..8d74010 100644
--- a/rust/benches/samplers_benches.rs
+++ b/rust/benches/samplers_benches.rs
@@ -1,5 +1,5 @@
-use chronopt::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use diffid::prelude::*;
use std::time::Duration;
fn bench_metropolis_hastings_gaussian(c: &mut Criterion) {
diff --git a/rust/src/common.rs b/rust/src/common.rs
index 5346843..2c45560 100644
--- a/rust/src/common.rs
+++ b/rust/src/common.rs
@@ -32,7 +32,7 @@ pub struct Unbounded;
/// # Examples
///
/// ```
-/// use chronopt::common::Bounds;
+/// use diffid::common::Bounds;
///
/// // Create bounds for a two-dimensional parameter-space
/// let bounds = Bounds::new(vec![(0.0, 1.0), (-5.0, 5.0)]);
@@ -56,7 +56,7 @@ impl Bounds {
/// # Examples
///
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
///
/// let bounds = Bounds::new(vec![(0.0, 1.0), (-10.0, 10.0)]);
/// ```
@@ -69,7 +69,7 @@ impl Bounds {
/// # Examples
///
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
///
/// let bounds = Bounds::new(vec![(0.0, 1.0), (-5.0, 5.0), (0.0, 100.0)]);
/// assert_eq!(bounds.dimension(), 3);
@@ -89,7 +89,7 @@ impl Bounds {
///
/// # Examples
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
///
/// let initial = [0.0];
/// let bounds = Bounds::unbounded_like(&initial);
@@ -123,7 +123,7 @@ impl Bounds {
/// # Examples
///
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
///
/// let bounds = Bounds::new(vec![(0.0, 1.0), (-5.0, 5.0)]);
/// let mut pos = vec![1.5, -10.0];
@@ -156,7 +156,7 @@ impl Bounds {
/// # Examples
///
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
/// use rand::SeedableRng;
/// use rand::prelude::StdRng;
///
@@ -205,7 +205,7 @@ impl From> for Bounds {
/// # Examples
///
/// ```
- /// use chronopt::common::Bounds;
+ /// use diffid::common::Bounds;
///
/// let bounds: Bounds = vec![(0.0, 1.0), (-5.0, 5.0)].into();
/// assert_eq!(bounds.dimension(), 2);
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index d6ede1d..e57bdc5 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -8,7 +8,7 @@ pub mod problem;
pub mod sampler;
mod types;
-// Convenience re-exports so users can `use chronopt::prelude::*;`
+// Convenience re-exports so users can `use diffid::prelude::*;`
pub mod prelude {
pub use crate::builders::{
DiffsolConfig, DiffsolProblemBuilder, ScalarProblemBuilder, VectorProblemBuilder,
diff --git a/rust/src/optimisers/mod.rs b/rust/src/optimisers/mod.rs
index d712461..8d81fc4 100644
--- a/rust/src/optimisers/mod.rs
+++ b/rust/src/optimisers/mod.rs
@@ -52,8 +52,8 @@ impl ScalarOptimiser {
///
/// # Example
/// ```
- /// use chronopt::common::Bounds;
- /// use chronopt::optimisers::{ScalarOptimiser, NelderMead};
+ /// use diffid::common::Bounds;
+ /// use diffid::optimisers::{ScalarOptimiser, NelderMead};
///
/// let optimiser = ScalarOptimiser::from(NelderMead::new());
/// let result = optimiser.run(
@@ -86,8 +86,8 @@ impl ScalarOptimiser {
///
/// # Example
/// ```
- /// use chronopt::common::Bounds;
- /// use chronopt::optimisers::{ScalarOptimiser, NelderMead};
+ /// use diffid::common::Bounds;
+ /// use diffid::optimisers::{ScalarOptimiser, NelderMead};
///
/// let optimiser = ScalarOptimiser::from(NelderMead::new());
/// let result = optimiser.run_batch(
@@ -137,8 +137,8 @@ impl GradientOptimiser {
///
/// # Example
/// ```
- /// use chronopt::common::Bounds;
- /// use chronopt::optimisers::{GradientOptimiser, Adam};
+ /// use diffid::common::Bounds;
+ /// use diffid::optimisers::{GradientOptimiser, Adam};
///
/// let optimiser = GradientOptimiser::from(Adam::new());
/// let result = optimiser.run(
diff --git a/rust/tests/diffsol_optimisation.rs b/rust/tests/diffsol_optimisation.rs
index 300ed33..6003559 100644
--- a/rust/tests/diffsol_optimisation.rs
+++ b/rust/tests/diffsol_optimisation.rs
@@ -1,4 +1,4 @@
-use chronopt::prelude::*;
+use diffid::prelude::*;
use nalgebra::DMatrix;
#[test]
diff --git a/rust/tests/dynamic_nested.rs b/rust/tests/dynamic_nested.rs
index 0b977f5..e584042 100644
--- a/rust/tests/dynamic_nested.rs
+++ b/rust/tests/dynamic_nested.rs
@@ -1,5 +1,5 @@
-use chronopt::builders::DiffsolBackend;
-use chronopt::prelude::*;
+use diffid::builders::DiffsolBackend;
+use diffid::prelude::*;
use nalgebra::DMatrix;
#[test]
diff --git a/rust/tests/dynamic_nested_advanced.rs b/rust/tests/dynamic_nested_advanced.rs
index 7de0c13..d130439 100644
--- a/rust/tests/dynamic_nested_advanced.rs
+++ b/rust/tests/dynamic_nested_advanced.rs
@@ -1,5 +1,5 @@
-use chronopt::prelude::*;
-use chronopt::problem::ParameterRange;
+use diffid::prelude::*;
+use diffid::problem::ParameterRange;
/// Test evidence calculation against known analytical result.
/// For a Gaussian N(x | 0, ϲ) with uniform prior U(a, b),
diff --git a/tests/integration/test_diffsol_dynamic_nested_parallel.py b/tests/integration/test_diffsol_dynamic_nested_parallel.py
index 56a7543..cf5d431 100644
--- a/tests/integration/test_diffsol_dynamic_nested_parallel.py
+++ b/tests/integration/test_diffsol_dynamic_nested_parallel.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -16,10 +16,10 @@ def _logistic_data(n: int = 40) -> np.ndarray:
return np.column_stack((t_span, y))
-def _build_diffsol_problem(parallel: bool) -> chron.Problem:
+def _build_diffsol_problem(parallel: bool) -> diffid.Problem:
data = _logistic_data(40)
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(_logistic_dsl())
.with_data(data)
.with_parameter("r", 1.0, bounds=(0.1, 3.0))
@@ -38,7 +38,7 @@ def test_dynamic_nested_diffsol_parallel_vs_sequential():
initial = [1.0, 1.0]
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(32)
.with_expansion_factor(0.3)
.with_termination_tolerance(1e-3)
@@ -66,14 +66,14 @@ def test_dynamic_nested_diffsol_parallel_vs_sequential():
def test_dynamic_nested_sampler_parallel_fallback_for_non_parallel_problems():
# Scalar problem does not support parallel evaluation; sampler should still work
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(lambda x: 0.5 * (x[0] - 0.5) ** 2)
.with_parameter("x", 0.5, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(24)
.with_expansion_factor(0.2)
.with_termination_tolerance(1e-3)
diff --git a/tests/integration/test_diffsol_sampling.py b/tests/integration/test_diffsol_sampling.py
index 84c550c..00fad37 100644
--- a/tests/integration/test_diffsol_sampling.py
+++ b/tests/integration/test_diffsol_sampling.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -27,12 +27,12 @@ def test_diffsol_sampling_tracks_bouncy_ball_parameters():
data = np.column_stack((t_span, height, velocity))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("g", g_true)
.with_parameter("h", h_true)
- .with_cost(chron.SSE())
+ .with_cost(diffid.SSE())
)
problem = builder.build()
@@ -41,7 +41,7 @@ def test_diffsol_sampling_tracks_bouncy_ball_parameters():
initial_cost = problem.evaluate(initial_guess)
sampler = (
- chron.MetropolisHastings()
+ diffid.MetropolisHastings()
.with_num_chains(2)
.with_iterations(250)
.with_step_size(0.25)
@@ -84,19 +84,19 @@ def test_diffsol_dynamic_nested_sampler_produces_evidence():
data = np.column_stack((t_span, height, velocity))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("g", g_true)
.with_parameter("h", h_true)
.with_tolerances(rtol=1e-6, atol=1e-6)
- .with_cost(chron.SSE())
+ .with_cost(diffid.SSE())
)
problem = builder.build()
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.15)
.with_termination_tolerance(1e-3)
diff --git a/tests/integration/test_mathematical_suite.py b/tests/integration/test_mathematical_suite.py
index a61b21e..515d924 100644
--- a/tests/integration/test_mathematical_suite.py
+++ b/tests/integration/test_mathematical_suite.py
@@ -4,7 +4,7 @@
from functools import partial
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -59,9 +59,9 @@ def ridge(x: list[float], alpha: float = 1.0) -> np.ndarray:
return np.asarray([value], dtype=float)
-def make_nelder_mead() -> chron.NelderMead:
+def make_nelder_mead() -> diffid.NelderMead:
return (
- chron.NelderMead()
+ diffid.NelderMead()
.with_max_iter(800)
.with_step_size(0.2)
.with_threshold(1e-8)
@@ -69,9 +69,9 @@ def make_nelder_mead() -> chron.NelderMead:
)
-def make_cmaes() -> chron.CMAES:
+def make_cmaes() -> diffid.CMAES:
return (
- chron.CMAES()
+ diffid.CMAES()
.with_max_iter(1500)
.with_threshold(1e-8)
.with_step_size(0.8)
@@ -161,7 +161,7 @@ def test_python_objectives_converge(
):
"""Ensure optimisation reaches known minima for several analytic functions."""
- builder = chron.ScalarBuilder().with_objective(objective)
+ builder = diffid.ScalarBuilder().with_objective(objective)
for idx in range(dimension):
builder = builder.with_parameter(f"x{idx}", 1.0)
@@ -196,7 +196,7 @@ def test_diffsol_logistic_convergence():
stacked_data = np.column_stack((time_points, data))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(_LOGISTIC_DSL)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-6)
@@ -207,7 +207,7 @@ def test_diffsol_logistic_convergence():
problem = builder.build()
optimiser = (
- chron.NelderMead()
+ diffid.NelderMead()
.with_max_iter(400)
.with_threshold(1e-7)
.with_position_tolerance(1e-6)
diff --git a/tests/test_docs.py b/tests/test_docs.py
index 9c770e0..3ac02b0 100644
--- a/tests/test_docs.py
+++ b/tests/test_docs.py
@@ -1,15 +1,12 @@
-"""Documentation quality and coverage tests.
+"""Documentation quality tests.
-This module validates that:
-1. All public APIs are documented
-2. Documentation builds without errors
-3. All internal links resolve
-4. Code examples in docs are valid
+Validates that:
+1. Documentation builds without errors
+2. Notebooks are valid and well-structured
"""
-import inspect
+import json
import pathlib
-import re
import subprocess
import pytest
@@ -23,16 +20,14 @@
class TestDocumentationBuild:
"""Test that documentation builds successfully."""
- def test_mkdocs_config_exists(self):
- """Check that mkdocs.yml exists."""
- assert MKDOCS_YML.exists(), "mkdocs.yml not found"
-
- def test_docs_directory_exists(self):
- """Check that docs/ directory exists."""
- assert DOCS_DIR.exists(), "docs/ directory not found"
-
def test_mkdocs_build_succeeds(self):
- """Test that mkdocs builds without errors."""
+ """Test that mkdocs builds without errors.
+
+ mkdocs strict mode validates:
+ - All internal links resolve
+ - No broken references
+ - Proper navigation structure
+ """
result = subprocess.run(
["mkdocs", "build", "--strict"],
cwd=ROOT,
@@ -40,268 +35,34 @@ def test_mkdocs_build_succeeds(self):
text=True,
)
- # Check return code
assert result.returncode == 0, (
- f"mkdocs build failed with:\nSTDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
- )
-
-
-class TestAPIDocumentation:
- """Test that all public APIs are documented."""
-
- def _get_public_apis(self, module) -> set[str]:
- """Extract public API names from a module."""
- apis = set()
-
- for name, obj in inspect.getmembers(module):
- # Skip private members
- if name.startswith("_"):
- continue
-
- # Include classes, functions, and constants
- if (
- inspect.isclass(obj)
- or inspect.isfunction(obj)
- or isinstance(obj, (int, float, str))
- ):
- apis.add(name)
-
- return apis
-
- def _find_documented_apis(self, doc_path: pathlib.Path) -> set[str]:
- """Extract API names mentioned in documentation."""
- if not doc_path.exists():
- return set()
-
- content = doc_path.read_text()
-
- # Find patterns like `ClassName`, `function_name()`, etc.
- # This is a simple heuristic - could be improved
- patterns = [
- r"`(\w+)`", # Inline code
- r"##\s+(\w+)", # Headers
- r"class:\s+(\w+)", # Class references
- ]
-
- documented = set()
- for pattern in patterns:
- matches = re.findall(pattern, content)
- documented.update(matches)
-
- return documented
-
- @pytest.mark.skipif(
- not (ROOT / "python" / "src" / "chronopt").exists(),
- reason="chronopt package not installed",
- )
- def test_core_apis_documented(self):
- """Check that core chronopt APIs are documented."""
- try:
- import chronopt as chron
- except ImportError:
- pytest.skip("chronopt not installed")
-
- # Get public APIs from chronopt
- self._get_public_apis(chron)
-
- # Find documentation files
- api_docs = list((DOCS_DIR / "api-reference" / "python").rglob("*.md"))
-
- # Collect documented APIs
- documented_apis = set()
- for doc_file in api_docs:
- documented_apis.update(self._find_documented_apis(doc_file))
-
- # Core APIs that should be documented
- expected_apis = {
- "ScalarBuilder",
- "DiffsolBuilder",
- "VectorBuilder",
- "NelderMead",
- "CMAES",
- "Adam",
- "SSE",
- "RMSE",
- "GaussianNLL",
- }
-
- # Check coverage
- missing = expected_apis - documented_apis
-
- if missing:
- print(f"\nā ļø APIs missing from documentation: {missing}")
- print(f"Documented APIs: {documented_apis}")
-
- # We expect at least 80% coverage
- coverage = len(expected_apis - missing) / len(expected_apis)
- assert coverage >= 0.8, (
- f"API documentation coverage too low: {coverage:.0%}\nMissing: {missing}"
+ f"mkdocs build failed:\n{result.stdout}\n{result.stderr}"
)
-class TestNotebookQuality:
- """Test Jupyter notebook quality."""
-
- def test_all_notebooks_exist(self):
- """Check that all referenced notebooks exist."""
- # Parse mkdocs.yml for notebook references
- content = MKDOCS_YML.read_text()
-
- # Find .ipynb references
- notebook_refs = re.findall(r"tutorials/notebooks/(\d+_\w+\.ipynb)", content)
-
- # Check each exists
- for notebook_name in notebook_refs:
- notebook_path = DOCS_DIR / "tutorials" / "notebooks" / notebook_name
- assert notebook_path.exists(), f"Notebook not found: {notebook_path}"
-
- def test_notebooks_have_metadata(self):
- """Check that notebooks have proper metadata."""
- import json
+class TestNotebookStructure:
+ """Test that notebooks are valid and well-structured."""
+ def test_all_notebooks_are_valid_json(self):
+ """Verify all notebooks are valid JSON with proper structure."""
notebooks = list((DOCS_DIR / "tutorials" / "notebooks").glob("*.ipynb"))
- for notebook_path in notebooks:
- if notebook_path.name == "utils.py": # Skip utility file
- continue
+ assert len(notebooks) > 0, "No notebooks found"
- with open(notebook_path) as f:
+ for notebook_path in notebooks:
+ with open(notebook_path, encoding="utf-8") as f:
nb_data = json.load(f)
- # Check structure
+ # Validate basic notebook structure
assert "cells" in nb_data, f"{notebook_path.name} missing cells"
assert "metadata" in nb_data, f"{notebook_path.name} missing metadata"
-
- # Check that it has markdown cells (documentation)
- has_markdown = any(
- cell.get("cell_type") == "markdown" for cell in nb_data["cells"]
- )
- assert has_markdown, f"{notebook_path.name} has no markdown cells"
-
- def test_notebooks_have_objectives(self):
- """Check that notebooks start with objectives."""
- import json
-
- notebooks = list((DOCS_DIR / "tutorials" / "notebooks").glob("[0-9]*.ipynb"))
-
- for notebook_path in notebooks:
- with open(notebook_path) as f:
- nb_data = json.load(f)
-
- # First cell should be markdown with objectives
- first_cell = nb_data["cells"][0]
- assert first_cell["cell_type"] == "markdown", (
- f"{notebook_path.name} first cell is not markdown"
- )
-
- # Check for objectives
- content = "".join(first_cell["source"])
- has_objectives = "Objectives" in content or "objectives" in content
- assert has_objectives, (
- f"{notebook_path.name} missing objectives in first cell"
- )
-
-
-class TestDocumentationStructure:
- """Test documentation structure and organization."""
-
- def test_required_sections_exist(self):
- """Check that required documentation sections exist."""
- required = [
- "getting-started",
- "tutorials",
- "guides",
- "algorithms",
- "api-reference",
- "examples",
- "development",
- ]
-
- for section in required:
- section_path = DOCS_DIR / section
- assert section_path.exists(), f"Required section missing: {section}"
- assert section_path.is_dir(), f"Section is not a directory: {section}"
-
- def test_index_pages_exist(self):
- """Check that all sections have index pages."""
- sections = [
- "getting-started",
- "tutorials",
- "guides",
- "algorithms",
- "api-reference",
- "development",
- ]
-
- for section in sections:
- index_path = DOCS_DIR / section / "index.md"
- assert index_path.exists(), f"Missing index page: {section}/index.md"
-
- def test_navigation_completeness(self):
- """Check that mkdocs.yml nav includes all main sections."""
- content = MKDOCS_YML.read_text()
-
- required_nav = [
- "Getting Started",
- "Tutorials",
- "User Guides",
- "Algorithms",
- "API Reference",
- "Examples",
- "Development",
- ]
-
- for section in required_nav:
- assert section in content, f"Navigation missing section: {section}"
-
-
-class TestInternalLinks:
- """Test that internal links are valid."""
-
- def _extract_md_links(self, content: str) -> list[str]:
- """Extract markdown links from content."""
- # Match [text](path) but not [text](http://...)
- pattern = r"\[([^\]]+)\]\((?!http)([^)]+)\)"
- matches = re.findall(pattern, content)
- return [path for _, path in matches]
-
- def test_getting_started_links(self):
- """Test links in getting started guides."""
- getting_started = DOCS_DIR / "getting-started"
-
- for md_file in getting_started.glob("*.md"):
- content = md_file.read_text()
- links = self._extract_md_links(content)
-
- for link in links:
- # Resolve relative link
- if link.startswith("#"): # Anchor link
- continue
-
- if link.startswith("../../"):
- # Relative to docs root
- target = DOCS_DIR / link.replace("../../", "")
- elif link.startswith("../"):
- # Relative to parent
- target = getting_started.parent / link.replace("../", "")
- else:
- target = getting_started / link
-
- # Check if target exists (handle .md vs .html)
- if not target.exists() and target.suffix == "":
- target = target.with_suffix(".md")
-
- assert target.exists(), (
- f"Broken link in {md_file.name}: {link} -> {target}"
- )
+ assert len(nb_data["cells"]) > 0, f"{notebook_path.name} has no cells"
def test_readme_not_in_docs():
"""Ensure README doesn't conflict with index.md."""
readme = DOCS_DIR / "README.md"
- assert not readme.exists(), (
- "docs/README.md conflicts with index.md (causes mkdocs warnings)"
- )
+ assert not readme.exists(), "docs/README.md conflicts with index.md"
if __name__ == "__main__":
diff --git a/tests/unit/optimisers/test_adam.py b/tests/unit/optimisers/test_adam.py
index dabe27a..c937133 100644
--- a/tests/unit/optimisers/test_adam.py
+++ b/tests/unit/optimisers/test_adam.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -16,7 +16,7 @@ def quadratic_grad(x):
def build_quadratic_problem_with_gradient():
return (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic)
.with_gradient(quadratic_grad)
.with_parameter("x", 1.0)
@@ -28,7 +28,9 @@ def build_quadratic_problem_with_gradient():
def test_adam_direct_run_minimises_quadratic():
problem = build_quadratic_problem_with_gradient()
- optimiser = chron.Adam().with_step_size(0.1).with_max_iter(500).with_threshold(1e-8)
+ optimiser = (
+ diffid.Adam().with_step_size(0.1).with_max_iter(500).with_threshold(1e-8)
+ )
result = optimiser.run(problem, [5.0, -4.0])
@@ -39,14 +41,16 @@ def test_adam_direct_run_minimises_quadratic():
def test_python_builder_optimise_with_adam_default():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic)
.with_gradient(quadratic_grad)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
)
- optimiser = chron.Adam().with_step_size(0.1).with_max_iter(400).with_threshold(1e-8)
+ optimiser = (
+ diffid.Adam().with_step_size(0.1).with_max_iter(400).with_threshold(1e-8)
+ )
builder.with_optimiser(optimiser)
problem = builder.build()
@@ -60,7 +64,7 @@ def test_python_builder_optimise_with_adam_default():
def test_adam_falls_back_with_numerical_grad():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic)
.with_parameter("x", 0.0)
.with_parameter("y", 0.0)
@@ -68,7 +72,7 @@ def test_adam_falls_back_with_numerical_grad():
problem = builder.build()
- optimiser = chron.Adam().with_max_iter(10)
+ optimiser = diffid.Adam().with_max_iter(10)
result = optimiser.run(problem, [0.0, 0.0])
assert result.iterations == 10
diff --git a/tests/unit/optimisers/test_cmaes.py b/tests/unit/optimisers/test_cmaes.py
index 0fe839a..1f0baa2 100644
--- a/tests/unit/optimisers/test_cmaes.py
+++ b/tests/unit/optimisers/test_cmaes.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -10,7 +10,7 @@ def rosenbrock(x):
def build_rosenbrock_problem():
return (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
@@ -22,7 +22,7 @@ def test_cmaes_direct_run_minimises_rosenbrock():
problem = build_rosenbrock_problem()
optimiser = (
- chron.CMAES()
+ diffid.CMAES()
.with_max_iter(400)
.with_threshold(1e-8)
.with_step_size(0.6)
@@ -38,14 +38,14 @@ def test_cmaes_direct_run_minimises_rosenbrock():
def test_python_builder_optimise_with_cmaes_default():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
)
optimiser = (
- chron.CMAES()
+ diffid.CMAES()
.with_max_iter(300)
.with_threshold(1e-8)
.with_step_size(0.5)
@@ -63,7 +63,7 @@ def test_python_builder_optimise_with_cmaes_default():
def test_set_optimiser_rejects_unknown_type():
- builder = chron.ScalarBuilder()
+ builder = diffid.ScalarBuilder()
with pytest.raises(TypeError):
builder.with_optimiser(object())
@@ -72,7 +72,7 @@ def test_set_optimiser_rejects_unknown_type():
def test_cmaes_result_covariance_available():
problem = build_rosenbrock_problem()
- optimiser = chron.CMAES().with_max_iter(50).with_seed(123)
+ optimiser = diffid.CMAES().with_max_iter(50).with_seed(123)
result = optimiser.run(problem, [1.5, -1.5])
diff --git a/tests/unit/test_diffsol.py b/tests/unit/test_diffsol.py
index 5ed2c83..404162d 100644
--- a/tests/unit/test_diffsol.py
+++ b/tests/unit/test_diffsol.py
@@ -1,6 +1,6 @@
import copy
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -22,14 +22,14 @@ def test_diffsol_builder():
# Build the problem
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-6)
.with_parameter("r", 1.0, None)
.with_parameter("k", 1.0, None)
- .with_cost(chron.SSE())
- .with_cost(chron.RMSE())
+ .with_cost(diffid.SSE())
+ .with_cost(diffid.RMSE())
)
problem = builder.build()
@@ -44,7 +44,7 @@ def test_diffsol_builder():
# Test that we can optimise the problem
optimiser = (
- chron.NelderMead().with_max_iter(500).with_threshold(1e-7).with_patience(10)
+ diffid.NelderMead().with_max_iter(500).with_threshold(1e-7).with_patience(10)
)
result = optimiser.run(problem, x0)
assert result.success
@@ -62,10 +62,10 @@ def test_diffsol_builder_remove_methods():
data = t_span**2
stacked_data = np.column_stack((t_span, data))
- metric = chron.RMSE()
+ metric = diffid.RMSE()
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_parameter("a", 1.0, None)
@@ -94,7 +94,7 @@ def test_diffsol_builder_remove_methods():
# Change cost
builder = builder.remove_cost()
- builder = builder.with_cost(chron.SSE())
+ builder = builder.with_cost(diffid.SSE())
problem_5 = builder.build()
# Check that problems are different
@@ -120,7 +120,7 @@ def test_problem_optimise_defaults_to_builder_params():
stacked_data = np.column_stack((t_span, data))
problem = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_tolerances(rtol=1e-4, atol=1e-4)
.with_data(stacked_data)
@@ -128,7 +128,7 @@ def test_problem_optimise_defaults_to_builder_params():
.build()
)
- optimiser = chron.NelderMead().with_max_iter(0)
+ optimiser = diffid.NelderMead().with_max_iter(0)
result = problem.optimise(optimiser=optimiser)
@@ -154,7 +154,7 @@ def test_diffsol_cost_metrics(variance: float) -> None:
def build_problem(cost_metric=None):
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-6)
@@ -166,9 +166,9 @@ def build_problem(cost_metric=None):
return builder.build()
sse_problem = build_problem()
- sse_problem_explicit = build_problem(chron.SSE())
- rmse_problem = build_problem(chron.RMSE())
- gaussian_problem = build_problem(chron.GaussianNLL(variance))
+ sse_problem_explicit = build_problem(diffid.SSE())
+ rmse_problem = build_problem(diffid.RMSE())
+ gaussian_problem = build_problem(diffid.GaussianNLL(variance))
test_params = [0.8, 1.2]
sse_cost = sse_problem.evaluate(test_params)
@@ -189,7 +189,7 @@ def build_problem(cost_metric=None):
assert pytest.approx(expected_gaussian, rel=1e-6, abs=1e-9) == gaussian_cost
with pytest.raises(ValueError):
- chron.GaussianNLL(0.0)
+ diffid.GaussianNLL(0.0)
def test_diffsol_bicycle_model_neldermead_recovers_wheelbase() -> None:
@@ -217,7 +217,7 @@ def test_diffsol_bicycle_model_neldermead_recovers_wheelbase() -> None:
stacked_data = np.column_stack((t_span, y_true, psi_true))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_tolerances(rtol=1e-6, atol=1e-8)
@@ -227,7 +227,7 @@ def test_diffsol_bicycle_model_neldermead_recovers_wheelbase() -> None:
problem = builder.build()
optimiser = (
- chron.NelderMead()
+ diffid.NelderMead()
.with_max_iter(500)
.with_threshold(1e-10)
.with_position_tolerance(1e-8)
diff --git a/tests/unit/test_dynamic_nested_sampler.py b/tests/unit/test_dynamic_nested_sampler.py
index e79739e..50d4819 100644
--- a/tests/unit/test_dynamic_nested_sampler.py
+++ b/tests/unit/test_dynamic_nested_sampler.py
@@ -4,7 +4,7 @@
import math
-import chronopt as chron
+import diffid
import numpy as np
import pytest
from scipy.stats import norm
@@ -36,14 +36,14 @@ def gaussian_nll(x: list[float]) -> float:
return 0.5 * (diff / sigma) ** 2 + np.log(sigma) + 0.5 * np.log(2 * np.pi)
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(gaussian_nll)
.with_parameter("x", mu, bounds=(prior_lower, prior_upper))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.2)
.with_termination_tolerance(1e-5)
@@ -84,14 +84,14 @@ def exponential_nll(x: list[float]) -> float:
return lambda_param * x[0]
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(exponential_nll)
.with_parameter("x", 1.0, bounds=(0.0, x_max))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.01)
.with_termination_tolerance(1e-6)
@@ -123,14 +123,14 @@ def bimodal_nll(x: list[float]) -> float:
return -(log_sum - np.log(2) - np.log(sigma) - 0.5 * np.log(2 * np.pi))
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(bimodal_nll)
.with_parameter("x", 0.0, bounds=(-10.0, 10.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.2)
.with_termination_tolerance(1e-4)
@@ -163,14 +163,14 @@ def pathological_nll(x: list[float]) -> float:
return 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(pathological_nll)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(32)
.with_expansion_factor(0.2)
.with_seed(456)
@@ -195,7 +195,7 @@ def high_dim_quadratic(x: list[float]) -> float:
"""Simple quadratic in high dimensions."""
return 0.5 * sum(xi**2 for xi in x)
- problem = chron.ScalarBuilder().with_objective(high_dim_quadratic)
+ problem = diffid.ScalarBuilder().with_objective(high_dim_quadratic)
for i in range(dimension):
problem = problem.with_parameter(f"x{i}", 0.0, bounds=(-3.0, 3.0))
@@ -203,7 +203,7 @@ def high_dim_quadratic(x: list[float]) -> float:
problem = problem.build()
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.15)
.with_termination_tolerance(1e-4)
@@ -227,14 +227,14 @@ def sharp_peak(x: list[float]) -> float:
return 0.5 * (x[0] / sigma) ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(sharp_peak)
.with_parameter("x", 0.0, bounds=(-1.0, 1.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.05) # Small expansion for narrow peak
.with_termination_tolerance(1e-3)
@@ -257,14 +257,14 @@ def large_offset(x: list[float]) -> float:
return 100.0 + 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(large_offset)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.2)
.with_seed(111)
@@ -285,14 +285,14 @@ def simple_quadratic(x: list[float]) -> float:
return 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(simple_quadratic)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.2)
.with_seed(555)
@@ -320,14 +320,14 @@ def quadratic(x: list[float]) -> float:
return 0.5 * (x[0] - 1.0) ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic)
.with_parameter("x", 1.0, bounds=(-3.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.2)
.with_seed(777)
@@ -359,14 +359,14 @@ def narrow_gaussian(x: list[float]) -> float:
return 0.5 * (x[0] / sigma) ** 2 + np.log(sigma) + 0.5 * np.log(2 * np.pi)
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(narrow_gaussian)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.1)
.with_termination_tolerance(1e-3)
@@ -388,21 +388,21 @@ def simple_problem(x: list[float]) -> float:
return 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(simple_problem)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler1 = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.2)
.with_seed(12345)
)
sampler2 = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.2)
.with_seed(12345)
@@ -430,14 +430,14 @@ def multimodal(x: list[float]) -> float:
)
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(multimodal)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(32)
.with_expansion_factor(0.5) # Allow significant expansion
.with_seed(999)
@@ -458,14 +458,14 @@ def bounded_quadratic(x: list[float]) -> float:
return 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(bounded_quadratic)
.with_parameter("x", 0.0, bounds=(lower, upper))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.2)
.with_seed(444)
@@ -486,14 +486,14 @@ def simple_problem(x: list[float]) -> float:
return 0.5 * x[0] ** 2
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(simple_problem)
.with_parameter("x", 0.0, bounds=(-5.0, 5.0))
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(64)
.with_expansion_factor(0.2)
.with_seed(666)
@@ -513,7 +513,7 @@ def nll(x: list[float]) -> float:
return 0.5 * (x[0] / sigma) ** 2 + np.log(sigma) + 0.5 * np.log(2 * np.pi)
return (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(nll)
.with_parameter("x", 0.0, bounds=(-10.0, 10.0))
.build()
@@ -521,7 +521,7 @@ def nll(x: list[float]) -> float:
def sampler_config(seed):
return (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(128)
.with_expansion_factor(0.2)
.with_seed(seed)
diff --git a/tests/unit/test_optimisation.py b/tests/unit/test_optimisation.py
index fe4ec1f..f857506 100644
--- a/tests/unit/test_optimisation.py
+++ b/tests/unit/test_optimisation.py
@@ -1,10 +1,10 @@
-import chronopt as chron
+import diffid
import numpy as np
def test_builder_exposes_config_and_parameters():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(lambda x: np.asarray([float(x[0]) ** 2]))
.with_parameter("x", 3.5, bounds=(0.0, 10.0))
)
@@ -30,7 +30,7 @@ def bounded_quadratic(x):
def test_python_builder_rosenbrock():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.2, None)
.with_parameter("y", -1.2, None)
@@ -39,7 +39,7 @@ def test_python_builder_rosenbrock():
# Create the optimisation
optimiser = (
- chron.NelderMead().with_max_iter(500).with_threshold(1e-6).with_step_size(0.15)
+ diffid.NelderMead().with_max_iter(500).with_threshold(1e-6).with_step_size(0.15)
)
results = optimiser.run(problem, [1.5, -1.5])
@@ -51,14 +51,14 @@ def test_python_builder_rosenbrock():
def test_python_builder_bounds_respected():
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(bounded_quadratic)
.with_parameter("x", 0.0, bounds=(0.0, 1.0))
.with_parameter("y", 0.0, bounds=(0.0, 2.0))
)
problem = builder.build()
- optimiser = chron.NelderMead().with_max_iter(200).with_threshold(1e-8)
+ optimiser = diffid.NelderMead().with_max_iter(200).with_threshold(1e-8)
results = optimiser.run(problem, [0.5, 1.0])
assert results.success
diff --git a/tests/unit/test_optimisation_api.py b/tests/unit/test_optimisation_api.py
index eb9e279..9f7db58 100644
--- a/tests/unit/test_optimisation_api.py
+++ b/tests/unit/test_optimisation_api.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
@@ -19,7 +19,7 @@ def _test_optimisation_api():
# Build the problem
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_config({"rtol": 1e-6})
@@ -47,7 +47,7 @@ def test_diffsol_builder_allows_multiple_builds():
stacked_data = np.column_stack((t_span, data))
builder = (
- chron.DiffsolBuilder()
+ diffid.DiffsolBuilder()
.with_diffsl(ds)
.with_data(stacked_data)
.with_parameter("r", 1.0)
@@ -70,7 +70,7 @@ def rosenbrock(x):
return (1 - x[0]) ** 2 + 100 * (x[1] - x[0] ** 2) ** 2
builder = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.0)
.with_parameter("y", 1.0)
@@ -92,7 +92,7 @@ def exponential_model(params):
return y0 * np.exp(rate * t_span)
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(exponential_model)
.with_data(data)
.with_parameter("rate", 1.0)
@@ -110,17 +110,17 @@ def test_all_builders_support_copy():
import copy
# ScalarBuilder
- scalar_builder = chron.ScalarBuilder().with_objective(lambda x: x[0] ** 2)
+ scalar_builder = diffid.ScalarBuilder().with_objective(lambda x: x[0] ** 2)
scalar_copy = copy.copy(scalar_builder)
copy.deepcopy(scalar_builder) # Test deepcopy
# DiffsolBuilder
- diffsol_builder = chron.DiffsolBuilder().with_diffsl("in { a }")
+ diffsol_builder = diffid.DiffsolBuilder().with_diffsl("in { a }")
diffsol_copy = copy.copy(diffsol_builder)
copy.deepcopy(diffsol_builder) # Test deepcopy
# VectorBuilder
- vector_builder = chron.VectorBuilder().with_objective(lambda x: [x[0]])
+ vector_builder = diffid.VectorBuilder().with_objective(lambda x: [x[0]])
vector_copy = copy.copy(vector_builder)
copy.deepcopy(vector_builder) # Test deepcopy
diff --git a/tests/unit/test_python_autodiff.py b/tests/unit/test_python_autodiff.py
index 9b6e129..4e516cb 100644
--- a/tests/unit/test_python_autodiff.py
+++ b/tests/unit/test_python_autodiff.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -26,7 +26,7 @@ def _quadratic_gradient(x):
def quadratic_problem():
"""Creates a 3D quadratic optimization problem using ScalarBuilder"""
return (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(_quadratic_objective)
.with_gradient(_quadratic_gradient)
.with_parameter("x1", 1.0)
diff --git a/tests/unit/test_samplers.py b/tests/unit/test_samplers.py
index 43f846c..dba5fca 100644
--- a/tests/unit/test_samplers.py
+++ b/tests/unit/test_samplers.py
@@ -1,6 +1,6 @@
import math
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -12,14 +12,14 @@ def quadratic_potential(x: np.ndarray) -> np.ndarray:
def test_metropolis_hastings_runs_and_returns_samples():
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic_potential)
.with_parameter("x", 1.0)
.build()
)
sampler = (
- chron.MetropolisHastings()
+ diffid.MetropolisHastings()
.with_num_chains(3)
.with_iterations(400)
.with_step_size(0.4)
@@ -44,14 +44,14 @@ def test_metropolis_hastings_runs_and_returns_samples():
def test_dynamic_nested_sampler_runs_on_scalar_problem():
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic_potential)
.with_parameter("x", 0.5)
.build()
)
sampler = (
- chron.DynamicNestedSampler()
+ diffid.DynamicNestedSampler()
.with_live_points(32)
.with_expansion_factor(0.1)
.with_seed(99)
@@ -67,20 +67,20 @@ def test_dynamic_nested_sampler_runs_on_scalar_problem():
def test_dynamic_nested_invalid_live_points_are_clamped():
problem = (
- chron.ScalarBuilder()
+ diffid.ScalarBuilder()
.with_objective(quadratic_potential)
.with_parameter("x", 1.0)
.build()
)
- sampler = chron.DynamicNestedSampler().with_live_points(1)
+ sampler = diffid.DynamicNestedSampler().with_live_points(1)
nested = sampler.run(problem)
assert nested.draws >= 0
def test_dynamic_nested_requires_problem_instance():
- sampler = chron.DynamicNestedSampler()
+ sampler = diffid.DynamicNestedSampler()
with pytest.raises(TypeError):
sampler.run(object()) # type: ignore[arg-type]
diff --git a/tests/unit/test_vector.py b/tests/unit/test_vector.py
index 8629500..c49298e 100644
--- a/tests/unit/test_vector.py
+++ b/tests/unit/test_vector.py
@@ -1,4 +1,4 @@
-import chronopt as chron
+import diffid
import numpy as np
import pytest
@@ -17,12 +17,12 @@ def exponential_model(params):
# Build the problem
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(exponential_model)
.with_data(data)
.with_parameter("rate", 1.0, None)
.with_parameter("y0", 1.0, None)
- .with_cost(chron.SSE())
+ .with_cost(diffid.SSE())
)
problem = builder.build()
@@ -35,7 +35,7 @@ def exponential_model(params):
assert cost >= 0, f"Cost should be non-negative, got {cost}"
# Test optimization
- optimiser = chron.NelderMead().with_max_iter(1000).with_threshold(1e-8)
+ optimiser = diffid.NelderMead().with_max_iter(1000).with_threshold(1e-8)
result = problem.optimise(x0, optimiser)
assert result.success
@@ -55,7 +55,7 @@ def model(params):
return params[0] * np.ones(n_points) + params[1]
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(model)
.with_data(data)
.with_parameter("scale", 1.0)
@@ -80,18 +80,18 @@ def sinusoid(params):
return amp * np.sin(freq * t + phase)
problem = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(sinusoid)
.with_data(data)
.with_parameter("amplitude", 2.0, (0.0, 5.0))
.with_parameter("frequency", 1.0, (0.1, 3.0))
.with_parameter("phase", 0.0, (-np.pi, np.pi))
- .with_cost(chron.RMSE())
+ .with_cost(diffid.RMSE())
.build()
)
x0 = [2.0, 1.0, 0.0]
- optimiser = chron.NelderMead().with_max_iter(2000).with_threshold(1e-9)
+ optimiser = diffid.NelderMead().with_max_iter(2000).with_threshold(1e-9)
result = problem.optimise(x0, optimiser)
# Note: Sinusoidal fitting can be challenging due to local minima
@@ -114,7 +114,7 @@ def quadratic(params):
def build_problem(cost_metric=None):
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(quadratic)
.with_data(data)
.with_parameter("scale", 1.5)
@@ -124,9 +124,9 @@ def build_problem(cost_metric=None):
return builder.build()
sse_problem = build_problem()
- sse_problem_explicit = build_problem(chron.SSE())
- rmse_problem = build_problem(chron.RMSE())
- gaussian_problem = build_problem(chron.GaussianNLL(1.0))
+ sse_problem_explicit = build_problem(diffid.SSE())
+ rmse_problem = build_problem(diffid.RMSE())
+ gaussian_problem = build_problem(diffid.GaussianNLL(1.0))
test_params = [1.5]
sse_cost = sse_problem.evaluate(test_params)
@@ -157,21 +157,21 @@ def model(params):
# Build with SSE
builder1 = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(model)
.with_data(data)
.with_parameter("a", 1.0)
- .with_cost(chron.SSE())
+ .with_cost(diffid.SSE())
)
problem1 = builder1.build()
# Build with RMSE
builder2 = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(model)
.with_data(data)
.with_parameter("a", 1.0)
- .with_cost(chron.RMSE())
+ .with_cost(diffid.RMSE())
)
problem2 = builder2.build()
@@ -188,10 +188,10 @@ def test_vector_builder_with_default_optimiser():
def linear(params):
return params[0] * np.arange(4) + params[1]
- optimiser = chron.NelderMead().with_max_iter(100)
+ optimiser = diffid.NelderMead().with_max_iter(100)
problem = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(linear)
.with_data(data)
.with_parameter("slope", 0.5)
@@ -214,7 +214,7 @@ def wrong_size(params):
return params[0] * np.ones(5) # Wrong size!
problem = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(wrong_size)
.with_data(data)
.with_parameter("a", 1.0)
@@ -222,7 +222,7 @@ def wrong_size(params):
)
with pytest.raises(
- chron.errors.EvaluationError,
+ diffid.errors.EvaluationError,
match="Evaluation failed: Evaluation failed:: expected 3 elements, got 5",
):
problem.evaluate([1.0])
@@ -237,16 +237,16 @@ def model(params):
# Build two problems with same configuration
builder = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(model)
.with_data(data)
.with_parameter("scale", 1.0)
- .with_cost(chron.RMSE())
+ .with_cost(diffid.RMSE())
)
problem1 = builder.build()
builder.remove_cost()
- builder.with_cost(chron.SSE())
+ builder.with_cost(diffid.SSE())
problem2 = builder.build()
# Should produce same results
@@ -261,7 +261,7 @@ def model(params):
return params[0] * data
problem = (
- chron.VectorBuilder()
+ diffid.VectorBuilder()
.with_objective(model)
.with_data(data)
.with_parameter("a", 1.0)
diff --git a/uv.lock b/uv.lock
index 5b9b837..de4eaaf 100644
--- a/uv.lock
+++ b/uv.lock
@@ -259,86 +259,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
-[[package]]
-name = "chronopt"
-source = { editable = "." }
-dependencies = [
- { name = "numpy" },
-]
-
-[package.optional-dependencies]
-diffeqpy = [
- { name = "diffeqpy" },
-]
-jax = [
- { name = "diffrax" },
- { name = "jax", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
- { name = "jaxlib", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
-]
-plotting = [
- { name = "matplotlib" },
-]
-
-[package.dev-dependencies]
-dev = [
- { name = "pytest" },
- { name = "scipy" },
-]
-docs = [
- { name = "matplotlib" },
- { name = "mkdocs" },
- { name = "mkdocs-git-revision-date-localized-plugin" },
- { name = "mkdocs-jupyter" },
- { name = "mkdocs-material" },
- { name = "mkdocs-minify-plugin" },
- { name = "mkdocstrings", extra = ["python"] },
- { name = "nbconvert" },
- { name = "pymdown-extensions" },
-]
-examples = [
- { name = "diffeqpy" },
- { name = "diffrax", marker = "(python_full_version < '3.15' and platform_machine != 'x86_64') or (python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform != 'darwin')" },
- { name = "jax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'" },
- { name = "jaxlib", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'" },
- { name = "matplotlib" },
-]
-
-[package.metadata]
-requires-dist = [
- { name = "diffeqpy", marker = "extra == 'diffeqpy'" },
- { name = "diffrax", marker = "extra == 'jax'", specifier = ">=0.7.0" },
- { name = "jax", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'jax'", specifier = "==0.4.38" },
- { name = "jaxlib", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'jax'", specifier = "==0.4.38" },
- { name = "matplotlib", marker = "extra == 'plotting'", specifier = ">=3.8" },
- { name = "numpy", specifier = ">=1.24.4" },
-]
-provides-extras = ["diffeqpy", "jax", "plotting"]
-
-[package.metadata.requires-dev]
-dev = [
- { name = "pytest", specifier = ">=8.3.5" },
- { name = "scipy", specifier = ">=1.16.2" },
-]
-docs = [
- { name = "matplotlib", specifier = ">=3.8" },
- { name = "mkdocs", specifier = ">=1.5.0" },
- { name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.2.0" },
- { name = "mkdocs-jupyter", specifier = ">=0.24.0" },
- { name = "mkdocs-material", specifier = ">=9.5.0" },
- { name = "mkdocs-minify-plugin", specifier = ">=0.8.0" },
- { name = "mkdocstrings", extras = ["python"], specifier = ">=0.24.0" },
- { name = "nbconvert", specifier = ">=7.0" },
- { name = "pymdown-extensions", specifier = ">=10.7" },
-]
-examples = [
- { name = "diffeqpy" },
- { name = "diffrax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = ">=0.7.0" },
- { name = "diffrax", marker = "(python_full_version < '3.15' and platform_machine != 'x86_64') or (python_full_version < '3.15' and sys_platform != 'darwin')", specifier = ">=0.7.0" },
- { name = "jax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==0.4.38" },
- { name = "jaxlib", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==0.4.38" },
- { name = "matplotlib", specifier = ">=3.8" },
-]
-
[[package]]
name = "click"
version = "8.3.0"
@@ -522,6 +442,86 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/5b/3866770a8e01811e767bd1b560c39f65dc4b57f74e70a2865526f7177999/diffeqpy-2.5.4-py3-none-any.whl", hash = "sha256:8214d747dfacfb5fc49690e0a3b8182c14d82e1b609d045086c036a1bceb9370", size = 17342, upload-time = "2025-09-08T01:33:37.032Z" },
]
+[[package]]
+name = "diffid"
+source = { editable = "." }
+dependencies = [
+ { name = "numpy" },
+]
+
+[package.optional-dependencies]
+diffeqpy = [
+ { name = "diffeqpy" },
+]
+jax = [
+ { name = "diffrax" },
+ { name = "jax", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
+ { name = "jaxlib", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
+]
+plotting = [
+ { name = "matplotlib" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "scipy" },
+]
+docs = [
+ { name = "matplotlib" },
+ { name = "mkdocs" },
+ { name = "mkdocs-git-revision-date-localized-plugin" },
+ { name = "mkdocs-jupyter" },
+ { name = "mkdocs-material" },
+ { name = "mkdocs-minify-plugin" },
+ { name = "mkdocstrings", extra = ["python"] },
+ { name = "nbconvert" },
+ { name = "pymdown-extensions" },
+]
+examples = [
+ { name = "diffeqpy" },
+ { name = "diffrax", marker = "(python_full_version < '3.15' and platform_machine != 'x86_64') or (python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform != 'darwin')" },
+ { name = "jax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'" },
+ { name = "jaxlib", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'" },
+ { name = "matplotlib" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "diffeqpy", marker = "extra == 'diffeqpy'" },
+ { name = "diffrax", marker = "extra == 'jax'", specifier = ">=0.7.0" },
+ { name = "jax", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'jax'", specifier = "==0.4.38" },
+ { name = "jaxlib", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'jax'", specifier = "==0.4.38" },
+ { name = "matplotlib", marker = "extra == 'plotting'", specifier = ">=3.8" },
+ { name = "numpy", specifier = ">=1.24.4" },
+]
+provides-extras = ["diffeqpy", "jax", "plotting"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=8.3.5" },
+ { name = "scipy", specifier = ">=1.16.2" },
+]
+docs = [
+ { name = "matplotlib", specifier = ">=3.8" },
+ { name = "mkdocs", specifier = ">=1.5.0" },
+ { name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.2.0" },
+ { name = "mkdocs-jupyter", specifier = ">=0.24.0" },
+ { name = "mkdocs-material", specifier = ">=9.5.0" },
+ { name = "mkdocs-minify-plugin", specifier = ">=0.8.0" },
+ { name = "mkdocstrings", extras = ["python"], specifier = ">=0.24.0" },
+ { name = "nbconvert", specifier = ">=7.0" },
+ { name = "pymdown-extensions", specifier = ">=10.7" },
+]
+examples = [
+ { name = "diffeqpy" },
+ { name = "diffrax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = ">=0.7.0" },
+ { name = "diffrax", marker = "(python_full_version < '3.15' and platform_machine != 'x86_64') or (python_full_version < '3.15' and sys_platform != 'darwin')", specifier = ">=0.7.0" },
+ { name = "jax", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==0.4.38" },
+ { name = "jaxlib", marker = "python_full_version < '3.14' and platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==0.4.38" },
+ { name = "matplotlib", specifier = ">=3.8" },
+]
+
[[package]]
name = "diffrax"
version = "0.7.0"