Builders¶
Builders provide a fluent API for constructing optimisation problems. Choose the appropriate builder based on your problem type.
Overview¶
| Builder | Use Case | Example |
|---|---|---|
ScalarBuilder | Direct function optimisation | Rosenbrock, Rastrigin |
DiffsolBuilder | ODE fitting with DiffSL | Parameter identification |
VectorBuilder | Custom solver integration | JAX, Julia, external simulators |
ScalarBuilder¶
ScalarBuilder ¶
Example Usage¶
import numpy as np
import diffid as chron
def rosenbrock(x):
return np.asarray([(1 - x[0])**2 + 100*(x[1] - x[0]**2)**2])
builder = (
diffid.ScalarBuilder()
.with_objective(rosenbrock)
.with_parameter("x", 1.5)
.with_parameter("y", -1.5)
)
problem = builder.build()
result = problem.optimise()
When to Use¶
- You have a Python function to minimise directly
- No differential equations involved
- Simple parameter optimisation or test functions
DiffsolBuilder¶
DiffsolBuilder ¶
Differential equation solver builder.
Example Usage¶
import numpy as np
import diffid as chron
dsl = """
in { r = 1, k = 1 }
u_i { y = 0.1 }
F_i { (r * y) * (1 - (y / k)) }
"""
t = np.linspace(0.0, 5.0, 51)
observations = np.exp(-1.3 * t)
data = np.column_stack((t, observations))
builder = (
diffid.DiffsolBuilder()
.with_diffsl(dsl)
.with_data(data)
.with_parameter("k", 1.0)
.with_backend("dense")
)
problem = builder.build()
optimiser = diffid.CMAES().with_max_iter(1000)
result = optimiser.run(problem, [0.5, 0.5])
Backend Options¶
"dense"(default): For systems with < 100 variables, dense Jacobian"sparse": For large systems (> 100 variables), sparse Jacobian
DiffSL Syntax¶
DiffSL is a domain-specific language for ODEs:
in_i { param1 = default1, param2 = default 2 } # Parameters to fit with defaults
u_i { state1 = init1 } # Initial conditions
F_i { derivative_expr } # dy/dt expressions
out_i { state1, state2 } # Optional: output variables
When to Use¶
- Fitting ODE parameters to time-series data
- Using Diffid's built-in high-performance solver
- Models expressible in DiffSL syntax
VectorBuilder¶
VectorBuilder ¶
Time-series problem builder for vector-valued objectives.
with_objective ¶
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.
with_data ¶
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.
with_parameter ¶
Register a named optimisation variable in the order it appears in vectors.
Example Usage¶
import numpy as np
import diffid as chron
def custom_solver(params):
"""Your custom ODE solver (e.g., using JAX/Diffrax)."""
# Solve ODE with params
# Return predictions at observation times
return predictions # NumPy array
# Observed data
t = np.linspace(0, 10, 100)
observations = ... # Your experimental data
data = np.column_stack((t, observations))
builder = (
diffid.VectorBuilder()
.with_objective(custom_solver)
.with_data(data)
.with_parameter("alpha", 1.0)
.with_parameter("beta", 0.5)
)
problem = builder.build()
result = problem.optimise()
Callable Requirements¶
Your callable must:
- Accept a NumPy array of parameters
- Return a NumPy array of predictions
- Predictions must match observation times in the data
When to Use¶
- Need specific solver features (stiff solvers, event detection, etc.)
- Using JAX/Diffrax for automatic differentiation
- Using Julia/DifferentialEquations.jl via diffeqpy
- Custom forward models beyond ODEs (PDEs, agent-based models, etc.)
See the Custom Solvers Guide for examples with Diffrax and DifferentialEquations.jl.
Problem¶
Problem ¶
Executable optimisation problem wrapping the Diffid core implementation.
evaluate_gradient ¶
Evaluate the gradient of the objective function at x if available.
optimise ¶
Solve the problem starting from initial using the supplied optimiser.
sample ¶
Sample from the problem starting from initial using the supplied sampler.
default_parameters ¶
Return the default parameter vector implied by the builder.
__call__ ¶
Call the problem as a function (shorthand for evaluate).
Allows using problem(x) instead of problem.evaluate(x).
The Problem class is created by calling .build() on a builder. It represents a fully configured optimisation problem.
Common Methods¶
# Optimise with default settings (Nelder-Mead)
result = problem.optimise()
# Evaluate objective at specific parameters
value = problem.evaluate(params)
Common Patterns¶
Chaining Methods¶
Builders use a fluent interface - chain methods in any order:
builder = (
diffid.ScalarBuilder()
.with_objective(func)
.with_parameter("x", 1.0)
.with_parameter("y", 2.0)
.with_cost_metric(diffid.RMSE())
)
Reusing Builders¶
Builders are immutable - each method returns a new builder:
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
Parameter Order Matters¶
Parameters are indexed in the order they're added:
builder = (
diffid.ScalarBuilder()
.with_parameter("y", 2.0) # Index 0
.with_parameter("x", 1.0) # Index 1
)
result = problem.optimise()
print(result.x) # [optimal_y, optimal_x]