Constructing ODEProblem parameter map from JSON data

I posted before here regarding this when my project was less fleshed out. Now that Ive actually got things mostly working I’m still struggling with this last task.

Ive got some JSON that looks something like this:

[
  "model1": {
     "system": {
     "parameters": [
        {name:  "a", value: "1"},
        {name:  "b", value: "2"},
        {name:  "c", value: "3"},
      ]
    }
  },
  "model2": {
     "system": {
     "parameters": [
        {name:  "d", value: "1"},
        {name:  "e", value: "2"},
        {name:  "f", value: "3"},
      ]
    }
  }
]


I need to somehow get this into a parameter map programatically such that the parameter map is:

ps = [
model1.a => 1,
model1.b => 2,
model1.c => 3,
model2.d => 1,
model2.e => 2,
model2.f => 3,
]

the models (with model names matching the names in the JSON) have already been generated and composed using modeling toolkit, now its just a matter of making this map. But im kind of stumped on how to go about it. It seems like a good job for julias metaprogramming but I tried some simple stuff and im not sure how to generate the modelname.parameter name portion. Any ideas?

Edit:
Ah, Meta.parse is exactly what I wanted. I was trying to convert the string directly to a symbol and was running into problems there

Edit2: Hmm. Ive run into an issue. My models dont exist in the global scope (they are stored in a dictionary, and in my function that builds the parameter map I need to call eval on my expression. But eval only evaluates in the global scope:

function build_parameter_map(systems_map::Dict{String, ModelingToolkit.AbstractSystem}, model_nodes) 
    ps = []
    for model_node in model_nodes
        node_data = model_node.data
        # this corresponds to one of the model objects in the JSON above
        model = node_data.model

        for parameter in model.system.parameters
            p_name = parameter.name
            p_value = Float64(parameter.value)
            # this is the problematic part, model_node does not exist in the global scope
            expression = Meta.parse("systems_map[model_node.id].$p_name => $p_value")
            push!(ps, eval(expression))
        end

    end

    return ps

end

@ChrisRackauckas any tips? I thought it would be easy to supply parameters programatically to a problem constructor but I cant figure it out.

I don’t understand what you’re saying :sweat_smile:. Can you show a reproducer in terms of the MTK code?

alright ive found a similar issue here. His code is this:

using ModelingToolkit

function decay(;name)
    @parameters t a
    @variables x(t) f(t)
    D = Differential(t)
    ODESystem([
        D(x) ~ -a*x + f
    ];
    name=name)
end

function system()
    # Subsystems
    @named decay1 = decay()
    @named decay2 = decay()

    # Connect Subsystems
    @variables t
    D = Differential(t)
    connections = [
        decay2.f ~ decay1.x,
        D(decay1.f) ~ 0,
    ]
    ODESystem(connections; systems = [decay1, decay2])
end

# x, f, decay1 and decay2 are out of scope here
x0 = Dict(
    :decay1 => Dict(:x => 1.0, f => 0.01),
    :decay2 => Dict(:x => 1.0),
)
p = Dict(
    :decay1 => Dict( :a => 0.1 ),
    :decay2 => Dict( :a => 0.2 ),
)

using OrdinaryDiffEq
prob = ODEProblem(system(), x0, (0.0, 100.0), p)
sol = solve(prob, Tsit5())

Essentially the issue is that Ive got a system composed and returned from a function. I need to set the parameters of that system, but the system (and all its subsystems) was composed and built in another function, so I am not sure how to pass parameters to the ODEProblem. In the case above the parameter map would be

[
decay1.a => .1,
decay2.a => .2
]

but none of those are in scope (and even if they were, I would still need to do it programatically based on the JSON), so how do you pass the parameters? I can not for the life of me figure out how to make this map programatically haha. My JSON contains all the parameter information (subsystem names, parameters names, and parameter values.

alright I finally got it working!

The key thing I was missing was a map from a symbol to the parameter. Alex (who made the github issue) was able to share his solution with me which included the use of the get_var_to_name function which supplies such a mapping. It only works for systems that have been structurally simplified however as it only returns states otherwise.

Also I was missing that the dot in model_name.parameter_name needs to be the little plus sign.

I ended up doing something like this to build my map:

p_name_sym = Symbol(parameter.name)
p_value = Float64(parameter.value)
sys_name_sym = Symbol(system_name)
sys_p_name_sym = Symbol(sys_name_sym, :₊, p_name_sym)
namespace_map = ModelingToolkit.get_var_to_name(simplified_system)
sys_p_name = namespace_map[sys_p_name_sym]
ps[sys_p_name] = p_value

I looped through all my systems and parameters from the JSON and built up the ps dict as above.