diff --git a/docs/src/basics/Variable_metadata.md b/docs/src/basics/Variable_metadata.md index e9bdabfe38..d36c91697e 100644 --- a/docs/src/basics/Variable_metadata.md +++ b/docs/src/basics/Variable_metadata.md @@ -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 @@ -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 diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 743dc8ce76..b4533f72a0 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -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 diff --git a/src/variables.jl b/src/variables.jl index c45c4b0f00..049dac790b 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -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 @@ -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) @@ -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 == "" @@ -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, name, + variable_type, shape, unit, connect, - noise, input, output, irreducible, @@ -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) @@ -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) @@ -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 diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index 7b093be366..8fca96a23d 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -1,4 +1,5 @@ using ModelingToolkit +using DynamicQuantities # Bounds @variables u [bounds = (-1, 1)] @@ -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