Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add metadata getters #3334

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion docs/src/basics/Variable_metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ using ModelingToolkit: t_nounits as t, D_nounits as D

@variables i(t) [connect = Flow]
@variables k(t) [connect = Stream]
hasconnect(i)
```
```@example connect
getconnect(k)
```

## Input or output
Expand Down Expand Up @@ -177,8 +181,44 @@ A variable can be marked `irreducible` to prevent it from being moved to an
`observed` state. This forces the variable to be computed during solving so that
it can be accessed in [callbacks](@ref events)

```julia
```@example metadata
@variable important_value [irreducible = true]
isirreducible(important_value)
```

## State Priority

When a model is structurally simplified, the algorithm will try to ensure that the variables with higher state priority become states of the system. A variable's state priority is a number set using the `state_priority` metadata.

```@example metadata
@variable important_dof [state_priority = 10] unimportant_dof [state_priority = 2]
state_priority(important_dof)
```

## Units

Units for variables can be designated using symbolic metadata. For more information, please see the [model validation and units](@ref units) section of the docs. Note that `getunit` is not equivalent to `get_unit` - the former is a metadata getter for individual variables (and is provided so the same interface function for `unit` exists like other metadata), while the latter is used to handle more general symbolic expressions.

```@example metadata
@variable speed [unit=u"m/s"]
hasunit(speed)
```
```@example metadata
getunit(speed)
```

## Miscellaneous metadata

User-defined metadata can be added using the `misc` metadata. This can be queried
using the `hasmisc` and `getmisc` functions.

```@example metadata
@variables u [misc = :conserved_parameter] y [misc = [2, 4, 6]]
hasmisc(u)
```

```@example metadata
getmisc(y)
```

## Additional functions
Expand Down
4 changes: 3 additions & 1 deletion src/ModelingToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ export initial_state, transition, activeState, entry, ticksInState, timeInState
export @component, @mtkmodel, @mtkbuild
export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbance,
istunable, getdist, hasdist,
tunable_parameters, isirreducible, getdescription, hasdescription
tunable_parameters, isirreducible, getdescription, hasdescription,
hasunit, getunit, hasconnect, getconnect,
hasmisc, getmisc
export ode_order_lowering, dae_order_lowering, liouville_transform
export PDESystem
export Differential, expand_derivatives, @derivatives
Expand Down
82 changes: 67 additions & 15 deletions src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ struct VariableStatePriority end
struct VariableMisc end
Symbolics.option_to_metadata_type(::Val{:unit}) = VariableUnit
Symbolics.option_to_metadata_type(::Val{:connect}) = VariableConnectType
Symbolics.option_to_metadata_type(::Val{:noise}) = VariableNoiseType
Symbolics.option_to_metadata_type(::Val{:input}) = VariableInput
Symbolics.option_to_metadata_type(::Val{:output}) = VariableOutput
Symbolics.option_to_metadata_type(::Val{:irreducible}) = VariableIrreducible
Expand All @@ -29,7 +28,7 @@ ModelingToolkit.dump_variable_metadata(p)
"""
function dump_variable_metadata(var)
uvar = unwrap(var)
vartype, name = get(uvar.metadata, VariableSource, (:unknown, :unknown))
variable_source, name = Symbolics.getmetadata(uvar, VariableSource, (:unknown, :unknown))
type = symtype(uvar)
if type <: AbstractArray
shape = Symbolics.shape(var)
Expand All @@ -39,14 +38,13 @@ function dump_variable_metadata(var)
else
shape = nothing
end
unit = get(uvar.metadata, VariableUnit, nothing)
connect = get(uvar.metadata, VariableConnectType, nothing)
noise = get(uvar.metadata, VariableNoiseType, nothing)
unit = getunit(uvar)
connect = getconnect(uvar)
input = isinput(uvar) || nothing
output = isoutput(uvar) || nothing
irreducible = get(uvar.metadata, VariableIrreducible, nothing)
state_priority = get(uvar.metadata, VariableStatePriority, nothing)
misc = get(uvar.metadata, VariableMisc, nothing)
irreducible = isirreducible(var)
state_priority = Symbolics.getmetadata(uvar, VariableStatePriority, nothing)
misc = getmisc(uvar)
bounds = hasbounds(uvar) ? getbounds(uvar) : nothing
desc = getdescription(var)
if desc == ""
Expand All @@ -57,16 +55,16 @@ function dump_variable_metadata(var)
disturbance = isdisturbance(uvar) || nothing
tunable = istunable(uvar, isparameter(uvar))
dist = getdist(uvar)
type = symtype(uvar)
variable_type = getvariabletype(uvar)

meta = (
var = var,
vartype,
variable_source,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing this key is breaking

name,
variable_type,
shape,
unit,
connect,
noise,
input,
output,
irreducible,
Expand All @@ -85,11 +83,28 @@ function dump_variable_metadata(var)
return NamedTuple(k => v for (k, v) in pairs(meta) if v !== nothing)
end

### Connect
abstract type AbstractConnectType end
struct Equality <: AbstractConnectType end # Equality connection
struct Flow <: AbstractConnectType end # sum to 0
struct Stream <: AbstractConnectType end # special stream connector

"""
getconnect(x)

Get the connect type of x. See also [`hasconnect`](@ref).
"""
getconnect(x) = getconnect(unwrap(x))
getconnect(x::Symbolic) = Symbolics.getmetadata(x, VariableConnectType, nothing)
"""
hasconnect(x)

Determine whether variable `x` has a connect type. See also [`getconnect`](@ref).
"""
hasconnect(x) = getconnect(x) !== nothing
setconnect(x, t::Type{T}) where T <: AbstractConnectType = setmetadata(x, VariableConnectType, t)

### Input, Output, Irreducible
isvarkind(m, x::Union{Num, Symbolics.Arr}) = isvarkind(m, value(x))
function isvarkind(m, x)
iskind = getmetadata(x, m, nothing)
Expand All @@ -98,15 +113,17 @@ function isvarkind(m, x)
getmetadata(x, m, false)
end

setinput(x, v) = setmetadata(x, VariableInput, v)
setoutput(x, v) = setmetadata(x, VariableOutput, v)
setio(x, i, o) = setoutput(setinput(x, i), o)
setinput(x, v::Bool) = setmetadata(x, VariableInput, v)
setoutput(x, v::Bool) = setmetadata(x, VariableOutput, v)
setio(x, i::Bool, o::Bool) = setoutput(setinput(x, i), o)

isinput(x) = isvarkind(VariableInput, x)
isoutput(x) = isvarkind(VariableOutput, x)

# Before the solvability check, we already have handled IO variables, so
# irreducibility is independent from IO.
isirreducible(x) = isvarkind(VariableIrreducible, x)
setirreducible(x, v) = setmetadata(x, VariableIrreducible, v)
setirreducible(x, v::Bool) = setmetadata(x, VariableIrreducible, v)
state_priority(x) = convert(Float64, getmetadata(x, VariableStatePriority, 0.0))::Float64

function default_toterm(x)
Expand Down Expand Up @@ -545,3 +562,38 @@ function get_default_or_guess(x)
return getguess(x)
end
end

## Miscellaneous metadata ======================================================================
"""
getmisc(x)

Fetch any miscellaneous data associated with symbolic variable `x`.
See also [`hasmisc(x)`](@ref).
"""
getmisc(x) = getmisc(unwrap(x))
getmisc(x::Symbolic) = Symbolics.getmetadata(x, VariableMisc, nothing)
"""
hasmisc(x)

Determine whether a symbolic variable `x` has misc
metadata associated with it.

See also [`getmisc(x)`](@ref).
"""
hasmisc(x) = getmisc(x) !== nothing
setmisc(x, miscdata) = setmetadata(x, VariableMisc, miscdata)

## Units ======================================================================
"""
getunit(x)

Fetch the unit associated with variable `x`. This function is a metadata getter for an individual variable, while `get_unit` is used for unit inference on more complicated sdymbolic expressions.
"""
getunit(x) = getunit(unwrap(x))
getunit(x::Symbolic) = Symbolics.getmetadata(x, VariableUnit, nothing)
"""
hasunit(x)

Check if the variable `x` has a unit.
"""
hasunit(x) = getunit(x) !== nothing
40 changes: 40 additions & 0 deletions test/test_variable_metadata.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ModelingToolkit
using DynamicQuantities

# Bounds
@variables u [bounds = (-1, 1)]
Expand Down Expand Up @@ -185,3 +186,42 @@ params_meta = ModelingToolkit.dump_parameters(sys)
params_meta = Dict([ModelingToolkit.getname(meta.var) => meta for meta in params_meta])
@test params_meta[:p].default == 3.0
@test isequal(params_meta[:q].dependency, 2p)

# Connect
@variables x [connect = Flow]
@test hasconnect(x)
@test getconnect(x) == Flow
@test ModelingToolkit.dump_variable_metadata(x).connect == Flow
x = ModelingToolkit.setconnect(x, ModelingToolkit.Stream)
@test getconnect(x) == ModelingToolkit.Stream

struct BadConnect end
@test_throws Exception ModelingToolkit.setconnect(x, BadConnect)

# Unit
@variables x [unit = u"s"]
@test hasunit(x)
@test getunit(x) == u"s"
@test ModelingToolkit.dump_variable_metadata(x).unit == u"s"

# Misc data
@variables x [misc = [:good]]
@test hasmisc(x)
@test getmisc(x) == [:good]
x = ModelingToolkit.setmisc(x, "okay")
@test getmisc(x) == "okay"

# Variable Type
@variables x
@test ModelingToolkit.getvariabletype(x) == ModelingToolkit.VARIABLE
@test ModelingToolkit.dump_variable_metadata(x).variable_type == ModelingToolkit.VARIABLE
@test ModelingToolkit.dump_variable_metadata(x).variable_source == :variables
x = ModelingToolkit.toparam(x)
@test ModelingToolkit.getvariabletype(x) == ModelingToolkit.PARAMETER
@test ModelingToolkit.dump_variable_metadata(x).variable_source == :variables

@parameters y
@test ModelingToolkit.getvariabletype(y) == ModelingToolkit.PARAMETER

@brownian z
@test ModelingToolkit.getvariabletype(z) == ModelingToolkit.BROWNIAN
Loading