SciML / ModelingToolkit.jl

An acausal modeling framework for automatically parallelized scientific machine learning (SciML) in Julia. A computer algebra system for integrated symbolics for physics-informed machine learning and automated transformations of differential equations
https://mtk.sciml.ai/dev/
Other
1.43k stars 209 forks source link

Replacing subsystems #2038

Open baggepinnen opened 1 year ago

baggepinnen commented 1 year ago

Modelica has the replaceable keyword to enable models that have replaceable components. This is useful in model libraries, where the user might want to replace one or several of the components in a larger model.

In MTK, our current option is to require the author of the model library to ensure that there are input arguments to the constructor of every model to allow the user to select model components.

As an example, consider a model component that models a heat pump, this model is likely to have hundreds of sub-models. Deep inside this model, I as a user want to replace a friction model to something more detailed, how do I do this? If I own the model library, I could make all the required changes to all constructors to take the friction model as an argument, but if I do not own the model library?

wang890 commented 4 months ago

I also encountered the same situation. I needed to replace the type of a component in a model. For this , Modelica has two keywords that can be used together: replaceable, redeclare.

ModelicaStandardLibrary use extensivly the keywords: replaceable , redeclare, are they nessary forModelingToolkit?

redeclare can also be used alone, like:

class Parent  
  Real x;  
end Parent;  

class Child extends Parent  
  redeclare Real x(start=1.0); 
end Child;

@ChrisRackauckas @YingboMa , ... , Thanks for your great work, I am a new user of Julia and ModelingToolkit.

hersle commented 3 months ago

I also want this. Here is a hacky solution I have cooked up:

using ModelingToolkit
using ModelingToolkit: t_nounits as t

# transform a system with a function f while preserving the hierarchical structure of its subsystems
# f must modify fields local to each system, and not its subsystems (e.g. get_eqs() instead of equations())!
function transform(f::Function, sys::ODESystem; fullname=string(sys.name), kwargs...)
    subs = [transform(f, sub; fullname = fullname * "₊" * string(sub.name)) for sub in sys.systems]
    sys = f(sys, fullname)
    return compose(sys, subs; kwargs...)
end

# return a local copy of a system (excluding fields that are part of its subsystems)
function identity(sys)
    eqs = ModelingToolkit.get_eqs(sys) # not equations()!
    ieqs = ModelingToolkit.get_initialization_eqs(sys)
    vars = ModelingToolkit.get_unknowns(sys)
    pars = ModelingToolkit.get_ps(sys)
    # TODO: forward more fields
    return ODESystem(eqs, t, vars, pars; initialization_eqs=ieqs, name=sys.name)
end

# replace one subsystem by another
function replace(sys::ODESystem, old_new_subsys::Pair{ODESystem, ODESystem}; kwargs...)
    old_subsys, new_subsys = old_new_subsys # unpack
    fullname_target = ModelingToolkit.get_name(old_subsys) |> string
    return transform((sys, fullname) -> (fullname == fullname_target ? new_subsys : identity(sys)), sys; kwargs...)
end

# factory for a stupid component
function stupidcomponent(xval; kwargs...)
    @variables x(t)
    return ODESystem([x ~ xval], t; kwargs...)
end

# create a hierarchical system
@named A = stupidcomponent(1)
@named B = stupidcomponent(1)
@named C = stupidcomponent(1)
@named S1 = compose(A, compose(A, B, C), C)

# now want to replace S1.A.C where x ~ 1 with a different C where x ~ 2
@named C = stupidcomponent(2)
@named S2 = replace(S1, S1.A.C => C)

Run this, and the component A.C is changed:

julia> equations(S1)
5-element Vector{Equation}:
 x(t) ~ 1
 A₊x(t) ~ 1
 A₊B₊x(t) ~ 1
 A₊C₊x(t) ~ 1
 C₊x(t) ~ 1

julia> equations(S2)
5-element Vector{Equation}:
 x(t) ~ 1
 A₊x(t) ~ 1
 A₊B₊x(t) ~ 1
 A₊C₊x(t) ~ 2
 C₊x(t) ~ 1

Would love to see anyone improve on this! Can this or something similar be incorporated into the library?

SebastianM-C commented 3 months ago

If you want to replace all occurances of a subsystem, you can use substitute with a Dict of system names to new systems and that will replace all the occurrences of the un-namespaced system name with the new system.

For example, in the above, you would have

S2 = substitute(S1, Dict(:C => C))

and this will give

julia> equations(S2)
5-element Vector{Equation}:
 x(t) ~ 1
 A₊x(t) ~ 1
 A₊B₊x(t) ~ 1
 A₊C₊x(t) ~ 2
 C₊x(t) ~ 2

Note that both A₊C₊x and C₊x were changed.

baggepinnen commented 3 months ago

Beware, this is a super dangerous feature that I wish we didn't have :O

SebastianM-C commented 3 months ago

Yeah, I'm not sure if this is public API. It's not documented as far as I can tell 😅