Skip to content

Commit df1728a

Browse files
SebastianM-Cclaude
andcommitted
add docs for parameter estimation with the dynamic optimization interface
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3039293 commit df1728a

File tree

1 file changed

+88
-1
lines changed

1 file changed

+88
-1
lines changed

docs/src/tutorials/dynamic_optimization.md

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ The `tf` mapping in the parameter map is treated as an initial guess.
101101
Please note that, at the moment, free final time problems cannot support constraints defined at definite time values, like `x(3) ~ 2`.
102102

103103
!!! warning
104-
104+
105105
The Pyomo collocation methods (LagrangeRadau, LagrangeLegendre) currently are bugged for free final time problems. Strongly suggest using BackwardEuler() for such problems when using Pyomo as the backend.
106106

107107
When declaring the problem in this case we need to provide the number of steps, since dt can't be known in advanced. Let's solve plot our final solution and the controller for the block, using InfiniteOpt as the backend:
@@ -126,3 +126,90 @@ axislegend(ax1)
126126
axislegend(ax2)
127127
fig
128128
```
129+
130+
### Parameter estimation
131+
132+
The dynamic optimization framework can also be used for parameter estimation. In this approach, we treat unknown parameters as tunable variables and minimize the difference between model predictions and observed data.
133+
134+
Let's demonstrate this with the Lotka-Volterra equations. First, we'll generate some synthetic data by solving the system with known parameter values:
135+
136+
```@example dynamic_opt
137+
@parameters α = 1.5 β = 1.0 [tunable=false] γ = 3.0 δ = 1.0
138+
@variables x_pe(t) y_pe(t)
139+
140+
eqs_pe = [D(x_pe) ~ α * x_pe - β * x_pe * y_pe,
141+
D(y_pe) ~ -γ * y_pe + δ * x_pe * y_pe]
142+
143+
@mtkcompile sys0_pe = System(eqs_pe, t)
144+
tspan_pe = (0.0, 1.0)
145+
u0map_pe = [x_pe => 4.0, y_pe => 2.0]
146+
147+
# True parameter values (these are what we'll try to recover)
148+
parammap_pe = [α => 2.5, δ => 1.8]
149+
150+
oprob_pe = ODEProblem(sys0_pe, [u0map_pe; parammap_pe], tspan_pe)
151+
osol_pe = solve(oprob_pe, Tsit5())
152+
153+
# Generate synthetic data at 51 time points
154+
ts_pe = range(tspan_pe..., length=51)
155+
data_pe = osol_pe(ts_pe, idxs=x_pe).u
156+
```
157+
158+
Now we'll set up the parameter estimation problem. We use `EvalAt` to evaluate the state at specific time points and construct a least-squares cost function:
159+
160+
```@example dynamic_opt
161+
costs_pe = [abs2(EvalAt(ti)(x_pe) - data_pe[i]) for (i, ti) in enumerate(ts_pe)]
162+
163+
@mtkcompile sys_pe = System(eqs_pe, t; costs = costs_pe)
164+
```
165+
166+
By default the cost values are sumed up, if a different behaviour is desired, the `consolidate` keyword can be set in the `System` definition.
167+
168+
Next, we select which parameters to tune using `subset_tunables`. Here we'll estimate `α` and `δ` while keeping `β` and `γ` fixed:
169+
170+
```@example dynamic_opt
171+
sys_pe′ = subset_tunables(sys_pe, [α, δ])
172+
```
173+
174+
Now we can solve the parameter estimation problem. Note the `tune_parameters=true` flag:
175+
176+
```@example dynamic_opt
177+
iprob_pe = InfiniteOptDynamicOptProblem(sys_pe′, u0map_pe, tspan_pe; dt=1/50, tune_parameters=true)
178+
isol_pe = solve(iprob_pe, InfiniteOptCollocation(Ipopt.Optimizer, InfiniteOpt.OrthogonalCollocation(3)))
179+
180+
println("Estimated α = ", isol_pe.sol.ps[α], " (true value: 2.5)")
181+
println("Estimated δ = ", isol_pe.sol.ps[δ], " (true value: 1.8)")
182+
```
183+
184+
Let's visualize the fit:
185+
186+
```@example dynamic_opt
187+
fig = Figure(resolution = (800, 400))
188+
ax = Axis(fig[1, 1], title = "Parameter Estimation Results", xlabel = "Time", ylabel = "Prey Population")
189+
scatter!(ax, ts_pe, data_pe, label = "Data", markersize = 8)
190+
lines!(ax, isol_pe.sol.t, isol_pe.sol[x_pe], label = "Fitted Model", linewidth = 2)
191+
axislegend(ax)
192+
fig
193+
```
194+
195+
!!! note "Time Alignment for Cost Evaluation"
196+
When using `EvalAt` for parameter estimation, different backends handle the case when evaluation times don't align with collocation points differently:
197+
198+
- **JuMP**: Will throw an error asking you to adjust `dt` if evaluation times don't match collocation points exactly.
199+
- **CasADi**: Uses linear interpolation between collocation points for cost evaluations at intermediate times.
200+
- **InfiniteOpt**: Automatically adds support points for the evaluation times, handling mismatched grids gracefully.
201+
202+
For example, InfiniteOpt can use a different `dt` than what the data spacing requires:
203+
204+
```@example dynamic_opt
205+
# With InfiniteOpt, dt doesn't need to match the data points:
206+
iprob_pe2 = InfiniteOptDynamicOptProblem(sys_pe′, u0map_pe, tspan_pe,
207+
dt = 1/120, tune_parameters=true)
208+
isol_pe2 = solve(iprob_pe2, InfiniteOptCollocation(Ipopt.Optimizer,
209+
InfiniteOpt.OrthogonalCollocation(3)))
210+
211+
println("With dt=1/120: Estimated α = ", isol_pe2.sol.ps[α], " (true value: 2.5)")
212+
println("With dt=1/120: Estimated δ = ", isol_pe2.sol.ps[δ], " (true value: 1.8)")
213+
```
214+
215+
This flexibility makes InfiniteOpt particularly convenient for parameter estimation when your data points don't naturally align with a uniform collocation grid.

0 commit comments

Comments
 (0)