Skip to content

Commit d1dfcb5

Browse files
Merge pull request #4425 from SebastianM-C/smc/nominal_value
Add nominal value metadata
2 parents 039ac74 + 6eddd71 commit d1dfcb5

4 files changed

Lines changed: 128 additions & 1 deletion

File tree

docs/src/API/variables.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,33 @@ getbounds
173173
ModelingToolkit.VariableBounds
174174
```
175175

176+
## Nominal Value
177+
178+
A nominal value represents the characteristic magnitude of a variable. This is useful
179+
for scaling constraints in optimal control problems, preventing ill-conditioning when
180+
variables have vastly different magnitudes. The default nominal value is `1.0`.
181+
182+
```@repl metadata
183+
@variables x [nominal = 1000.0];
184+
hasnominal(x)
185+
getnominal(x)
186+
```
187+
188+
Nominal values can also be specified for array variables:
189+
190+
```@repl metadata
191+
@variables x[1:3] [nominal = [100.0, 200.0, 300.0]];
192+
getnominal(x)
193+
getnominal(x[1])
194+
```
195+
196+
```@docs
197+
hasnominal
198+
getnominal
199+
setnominal
200+
ModelingToolkit.VariableNominal
201+
```
202+
176203
## Guess
177204

178205
Specify an initial guess for variables of a `System`. This is used when building the

lib/ModelingToolkitBase/src/ModelingToolkitBase.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export flatten
257257
export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream,
258258
instream
259259
export @component, @mtkcompile, @mtkbuild, @mtkcomplete
260-
export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbance,
260+
export isinput, isoutput, getbounds, hasbounds, getnominal, hasnominal, setnominal, getguess, hasguess, isdisturbance,
261261
istunable, getdist, hasdist,
262262
tunable_parameters, isirreducible, getdescription, hasdescription,
263263
hasunit, getunit, hasconnect, getconnect,

lib/ModelingToolkitBase/src/variables.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,40 @@ function setbounds(x::Num, bounds)
465465
return setmetadata(x, VariableBounds, (lb, ub))
466466
end
467467

468+
## Nominal =====================================================================
469+
struct VariableNominal end
470+
Symbolics.option_to_metadata_type(::Val{:nominal}) = VariableNominal
471+
472+
"""
473+
getnominal(x)
474+
475+
Get the nominal value associated with symbolic variable `x`. Returns `1.0` if no nominal value is set.
476+
Create variables with a nominal value like this
477+
478+
```
479+
@variables x [nominal = 4785.0]
480+
```
481+
"""
482+
getnominal(x::Union{Num, Symbolics.Arr}) = getnominal(unwrap(x))
483+
function getnominal(x::SymbolicT)
484+
s = Symbolics.getmetadata_maybe_indexed(x, VariableNominal, nothing)
485+
s === nothing ? 1.0 : s
486+
end
487+
488+
"""
489+
hasnominal(x)
490+
491+
Determine whether symbolic variable `x` has a nominal value associated with it.
492+
See also [`getnominal`](@ref).
493+
"""
494+
function hasnominal(x)
495+
Symbolics.getmetadata_maybe_indexed(unwrap(x), VariableNominal, nothing) !== nothing
496+
end
497+
498+
function setnominal(x::Num, val)
499+
return setmetadata(x, VariableNominal, val)
500+
end
501+
468502
## Disturbance =================================================================
469503
struct VariableDisturbance end
470504
Symbolics.option_to_metadata_type(::Val{:disturbance}) = VariableDisturbance
@@ -608,6 +642,26 @@ function getbounds(p::AbstractVector)
608642
return (; lb, ub)
609643
end
610644

645+
"""
646+
getnominal(sys::ModelingToolkitBase.AbstractSystem, vars = parameters(sys))
647+
648+
Returns a dict with pairs `var => nominal` mapping variables of `sys` to their nominal values.
649+
Create variables with a nominal value like this
650+
651+
```
652+
@variables x [nominal = 40.0]
653+
```
654+
655+
To obtain unknown variable nominal values, call `getnominal(sys, unknowns(sys))`
656+
"""
657+
function getnominal(sys::ModelingToolkitBase.AbstractSystem, p = parameters(sys))
658+
return Dict(p .=> getnominal.(p))
659+
end
660+
661+
function getnominal(p::AbstractVector)
662+
return getnominal.(p)
663+
end
664+
611665
## Description =================================================================
612666
"""
613667
$TYPEDEF

lib/ModelingToolkitBase/test/test_variable_metadata.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,49 @@ x = ModelingToolkitBase.toparam(x)
263263

264264
@brownians z
265265
@test ModelingToolkitBase.getvariabletype(z) == ModelingToolkitBase.BROWNIAN
266+
267+
# Nominal
268+
@variables x [nominal = 100.0]
269+
@test getnominal(x) == 100.0
270+
@test hasnominal(x)
271+
272+
@variables y
273+
@test !hasnominal(y)
274+
@test getnominal(y) == 1.0
275+
276+
y2 = setnominal(y, 50.0)
277+
@test getnominal(y2) == 50.0
278+
@test hasnominal(y2)
279+
280+
@variables z [nominal = 0.001]
281+
@test getnominal(z) == 0.001
282+
283+
# Vector of variables
284+
vals = getnominal([x, y, z])
285+
@test vals == [100.0, 1.0, 0.001]
286+
287+
# Vector variables
288+
@variables yv[1:3] [nominal = [100.0, 200.0, 300.0]]
289+
@test hasnominal(yv)
290+
@test getnominal(yv) == [100.0, 200.0, 300.0]
291+
for i in 1:3
292+
@test hasnominal(yv[i])
293+
end
294+
@test getnominal(yv[1]) == 100.0
295+
@test getnominal(yv[2]) == 200.0
296+
@test getnominal(yv[3]) == 300.0
297+
298+
@variables wv[1:3]
299+
@test !hasnominal(wv)
300+
@test getnominal(wv[1]) == 1.0
301+
302+
# System-level accessor
303+
@independent_variables t5
304+
Dₜ5 = Differential(t5)
305+
@variables x5(t5) [nominal = 100.0] y5(t5) z5(t5) [nominal = 0.001]
306+
@parameters α5 = 1.5 β5 = 1.0
307+
eqs5 = [Dₜ5(x5) ~ α5 * x5, Dₜ5(y5) ~ -β5 * y5, Dₜ5(z5) ~ x5 - z5]
308+
sys5 = System(eqs5, t5, name = :nominal_test)
309+
nv = getnominal(sys5, unknowns(sys5))
310+
@test nv isa Dict
311+
@test length(nv) == length(unknowns(sys5))

0 commit comments

Comments
 (0)