How to make R, C, etc. parameters in Acausal Component-Based Model (ModelingToolkit example)


I am trying to move my models to the new ModelingToolkit framework and so far everything works nicely and it creates the models nicely.

For simplicity I refer here to the example as outlined in Acausal Component-Based Modeling the RC Circuit · ModelingToolkit.jl - my model is similar enough (more complex network, and a driving function), but the issue arises in the tutorial case as well.

Obviously the first run takes longer due to compile time, and the second run will be blisteringly fast.

However, I have not found a way yet to change the parameters (R, C) and rerun the model without incurring a recompilation.

My optimisation problem will need to change the model parameters (R and C) and rerun the model many times, so I need to find a way to make these parameters act in a similar way than when I define the ODEs manually.

I hope that it’s just my lack of understanding of how the model generation works and it is a rather simple solution.

The driving function is defined as:

systcos(t) = sin(2pi * t) < 0 ? 0 : 1 - cos(2pi * t)^2
@register systcos(t)

and has a similar problem. If I change the function or one of the parameters, then I need to go through the compose, structural_simplify, and ODAEProblem steps, which means it will recompile the solve step.

The driving function component looks like this:

function DrivenCurrent(;name, I=1.0, fun)
    @named oneport = OnePort()
    @unpack i = oneport
    ps = @parameters I = I
    eqs = [
           i ~ - I * fun(t)
    extend(ODESystem(eqs, t, [], ps; name=name), oneport)
@named source = DrivenCurrent(I=I, fun=systcos)

Using the example from the documentation, you can get a list of parameters from the system object:

julia> parameters(sys)
3-element Vector{Sym{Real, Base.ImmutableDict{DataType, Any}}}:

There is a helper in the FAQ to turn this name into an index in prob.p, indexof. Then you can do

params = copy(prob.p)
params[indexof(capacitor.C, parameters(sys))] = 2.0
newprob = remake(prob; p=params)

newprob is now a copy of prob with just the parameters changed, so it re-uses all the compilation done for prob.

1 Like

Thanks a lot, this does work.

But is a bit awkward just to change a parameter, in the otherwise elegant ModelingToolkit.

The varmap_to_vars function on the same FAQ would be more elegant. However, it gives me a varmap_to_vars not defined error.

Is there an equivalent to remake that takes the variable map, rather than an array?

You need to import varmap_to_vars explicitly, it is not an exported function, then you could do this:

using ModelingToolkit
using ModelingToolkit: varmap_to_vars

function reparameterize(sys, prob, parameter_map)
    params = varmap_to_vars(parameter_map, parameters(sys))
    remake(prob; p=params)

Looking at the implementation of remake it seems like a version that accepted maps and only updated the mapped variables wouldn’t be too hard to implement.

1 Like

I completely agree with you and it’s an open issue to fix it. We need to handle it because it’s hacky to require users to ever have to deal with the index maps.


Thanks a lot. I was just starting to write my own function. But this is a lot more elegant.

I assume there will be a remake soon that will take the parameter maps, but for the time being this will do nicely.

I don’t necessarily mind hacky solutions :slight_smile:

But in the otherwise very elegant toolkit, it seemed a bit off.

BTW. I just want to take the opportunity to thank you and the other developers for the amazing work you do on the whole scientific modelling ecosystem. It is mindblowing how much is already possible in Julia and how consistent and streamlined everything (well, almost everything) feels.

BTW2: Also amazing: My first acausal model produces an ODAE system that solves faster than the code you optimised in another thread on here*. I was prepared for a penalty for not deriving the ODEs myself, but that did not materialise at all. To the contrary.

  • I need to double check that, but without going through the old work in detail, it seems like the new code is about 25% faster (and about 4 times faster than my initial unoptimised code).
1 Like