How to tell ModelingToolkit.jl which variables are parameters

I have a Symbolics.Equation that I got from an external source. Now I want to use that equation in ModelingToolkit.jl, but I don’t know how to tell ModelingToolkit.jl which variables are parameters.

For example, here I get my equation from python, and SymPyPythonCall’s conversion from sympy variables to Symbolics.jl variables does not support provenance.

julia> using Symbolics, SymbolicUtils, PythonCall, SymPyPythonCall, ModelingToolkit

julia> pyexec("import sympy", Main)

julia> eq = pyconvert(Equation, pyeval("sympy.core.sympify('Eq(-x*k, diff(diff(f(x), x), x))')", Main))
Differential(x)(Differential(x)(f(x))) ~ -k*x

julia> ODESystem(eq, name="MyODESystem")
ERROR: ArgumentError: Variable k is not a function of independent variable x.
Stacktrace:
 [1] check_variables(dvs::Vector{SymbolicUtils.BasicSymbolic{Real}}, iv::SymbolicUtils.BasicSymbolic{Real})
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/utils.jl:132
 [2] ODESystem(tag::UInt64, deqs::Vector{Equation}, iv::SymbolicUtils.BasicSymbolic{Real}, dvs::Vector{SymbolicUtils.BasicSymbolic{Real}}, ps::Vector{Any}, tspan::Nothing, var_to_name::Dict{Any, Any}, ctrls::Vector{Any}, observed::Vector{Equation}, tgrad::Base.RefValue{Vector{Num}}, jac::Base.RefValue{Any}, ctrl_jac::Base.RefValue{Any}, Wfact::Base.RefValue{Matrix{Num}}, Wfact_t::Base.RefValue{Matrix{Num}}, name::String, systems::Vector{ODESystem}, defaults::Dict{Any, Any}, torn_matching::Nothing, connector_type::Nothing, preface::Nothing, cevents::Vector{ModelingToolkit.SymbolicContinuousCallback}, devents::Vector{ModelingToolkit.SymbolicDiscreteCallback}, metadata::Nothing, gui_metadata::Nothing, tearing_state::Nothing, substitutions::Nothing, complete::Bool, discrete_subsystems::Nothing, unknown_states::Nothing; checks::Bool)
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:152
 [3] ODESystem(deqs::Vector{Equation}, iv::SymbolicUtils.BasicSymbolic{Real}, dvs::Vector{Any}, ps::OrderedCollections.OrderedSet{Any}; controls::Vector{Num}, observed::Vector{Equation}, systems::Vector{ODESystem}, tspan::Nothing, name::String, default_u0::Dict{Any, Any}, default_p::Dict{Any, Any}, defaults::Dict{Any, Any}, connector_type::Nothing, preface::Nothing, continuous_events::Nothing, discrete_events::Nothing, checks::Bool, metadata::Nothing, gui_metadata::Nothing)
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:218
 [4] ODESystem(eqs::Vector{Equation}, iv::Nothing; kwargs::Base.Pairs{Symbol, String, Tuple{Symbol}, NamedTuple{(:name,), Tuple{String}}})
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:268
 [5] ODESystem(::Equation; kwargs::Base.Pairs{Symbol, String, Tuple{Symbol}, NamedTuple{(:name,), Tuple{String}}})
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:303
 [6] top-level scope
   @ REPL[91]:1

julia> fx, k, x = get_variables(eq)
3-element Vector{Any}:
 f(x)
 k
 x

julia> ODESystem(eq,x,[fx],[k], name="MyODESystem")
ERROR: ArgumentError: k is not a parameter.
Stacktrace:
 [1] check_parameters(ps::Vector{SymbolicUtils.BasicSymbolic{Real}}, iv::SymbolicUtils.BasicSymbolic{Real})
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/utils.jl:108
 [2] ODESystem(tag::UInt64, deqs::Vector{Equation}, iv::SymbolicUtils.BasicSymbolic{Real}, dvs::Vector{SymbolicUtils.BasicSymbolic{Real}}, ps::Vector{SymbolicUtils.BasicSymbolic{Real}}, tspan::Nothing, var_to_name::Dict{Any, Any}, ctrls::Vector{Any}, observed::Vector{Equation}, tgrad::Base.RefValue{Vector{Num}}, jac::Base.RefValue{Any}, ctrl_jac::Base.RefValue{Any}, Wfact::Base.RefValue{Matrix{Num}}, Wfact_t::Base.RefValue{Matrix{Num}}, name::String, systems::Vector{ODESystem}, defaults::Dict{Any, Any}, torn_matching::Nothing, connector_type::Nothing, preface::Nothing, cevents::Vector{ModelingToolkit.SymbolicContinuousCallback}, devents::Vector{ModelingToolkit.SymbolicDiscreteCallback}, metadata::Nothing, gui_metadata::Nothing, tearing_state::Nothing, substitutions::Nothing, complete::Bool, discrete_subsystems::Nothing, unknown_states::Nothing; checks::Bool)
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:153
 [3] ODESystem(deqs::Vector{Equation}, iv::SymbolicUtils.BasicSymbolic{Real}, dvs::Vector{SymbolicUtils.BasicSymbolic{Real}}, ps::Vector{SymbolicUtils.BasicSymbolic{Real}}; controls::Vector{Num}, observed::Vector{Equation}, systems::Vector{ODESystem}, tspan::Nothing, name::String, default_u0::Dict{Any, Any}, default_p::Dict{Any, Any}, defaults::Dict{Any, Any}, connector_type::Nothing, preface::Nothing, continuous_events::Nothing, discrete_events::Nothing, checks::Bool, metadata::Nothing, gui_metadata::Nothing)
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:218
 [4] ODESystem(::Equation, ::SymbolicUtils.BasicSymbolic{Real}, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, String, Tuple{Symbol}, NamedTuple{(:name,), Tuple{String}}})
   @ ModelingToolkit ~/.julia/packages/ModelingToolkit/brJDK/src/systems/diffeqs/odesystem.jl:303
 [5] top-level scope
   @ REPL[93]:1

How should I tell ModelingToolkit.jl which variables are parameters and which are variables?

I am developing a package/codebase and I needed exactly this information. As far as I can tell, it doens’t exist in the public API. I\ve coded up the following function:


is_variable(x::Num) = is_variable(x.val)
function is_variable(x)
    if x isa ModelingToolkit.SymbolicUtils.Symbolic
        if haskey(x.metadata, ModelingToolkit.Symbolics.VariableSource)
            src = x.metadata[ModelingToolkit.Symbolics.VariableSource]
            return first(src) == :variables
        end
    end
    return false
end

This will return false for “variables” defined as @parameters p = 0.2 c = 0.1 etc.

So you can copy this and change the :variables to :parameters. I think it will work.

1 Like

Edit: ah, I misunderstood what you need. You need the other-way around. Not to examine if something is a parameter, but to enforce that something is… I know you can create symbolically parameters, so maybe just use their symbol and re-create them using @parameters ?

like

"""
    make_new_named_parameter(variable, value::Num, extra::String, suffix = true)

If `value isa Num`, return `value`. Otherwise, create a new MTK `@parameter`
whose name is created from `variable` by adding the `extra` string.
If `suffix = true` the extra is added at the end after a `_`. Otherwise
it is added at the start, then a `_` and then the variable name.
"""
make_new_named_parameter(v, value::Num, extra, suffix = true) = value
function make_new_named_parameter(v, value::Real, extra, suffix = true)
    # create new string:
    n = string(ModelingToolkit.getname(v))
    newstring = if suffix
        n*"_"*extra
    else
        extra*"_"*n
    end
    varsymbol = Symbol(newstring)
    dummy = (@parameters $(varsymbol) = value)
    return first(dummy)
end

(another function I’ve coded up for the aforementioned project, you don’t need the suffix stsuff)

I figured out a way to do this without using internals:

function mark_parameters(eq::Equation, symbols)
    parameters = Dict(sy => only(@parameters($sy)) for sy in symbols)
    substitutions = Dict((var => parameters[Symbol(var)] for var in get_variables(eq) if Symbol(var) in keys(parameters)))
    substitute(eq, substitutions)
end

It’s still not as nice as it could theoretically be, but it’s not too bad. I’m still open to hearing alternative approaches.

1 Like

I’m not sure what the code snippets you have shared are for. Would you show how I could use them to pass my equation to ODESystem?