sol[inlet.conc] does not work in flow connector
Describe the bug 🐞
If I have a stream connector for a concentration, sol[inlet.concentration] should work. It doesn't, but it works for sol.outlet.concentration.
Minimal Reproducible Example 👇
using ModelingToolkit, DifferentialEquations
using ModelingToolkit: t_nounits as t, D_nounits as D
@connector SolutionPin begin
flow(t), [connect=Flow]
conc(t), [connect=Stream]
dummy(t)
end
@mtkmodel Source begin
@components begin
outlet = SolutionPin()
end
@equations begin
outlet.flow ~ -10
outlet.conc ~ 0.3
end
end
@mtkmodel Tank begin
@components begin
inlet = SolutionPin()
end
@variables begin
product_mass(t) = 0
conc(t)
end
@equations begin
D(product_mass) ~ inlet.flow * instream(inlet.conc)
conc ~ instream(inlet.conc) # obviously not physically correct
end
end
@mtkmodel Room begin
@components begin
src = Source()
tank = Tank()
end
@equations begin
connect(src.outlet, tank.inlet)
end
end
@mtkbuild room = Room()
prob = ODEProblem(room, [], (0, 10.0), [])
sol = solve(prob)
sol[room.src.outlet.conc] # works
sol[room.src.inlet.flow] # works
sol[room.tank.inlet.conc] # ERROR: ArgumentError: tank₊inlet₊conc(t) is neither an observed nor an unknown
sol[room.tank.conc] # works
Adding instream() does not work either (it shouldn't be required anyway).
The problem is that tank.inlet.conc is not present in the simplified system, so MTK doesn't know how to compute it.
julia> room
Model room:
Equations (1):
1 standard: see equations(room)
Unknowns (1): see unknowns(room)
tank₊product_mass(t) [defaults to 0]
Observed (6): see observed(room)
julia> observed(room)
6-element Vector{Equation}:
src₊outlet₊flow(t) ~ -10.0
src₊outlet₊conc(t) ~ 0.3
tank₊inlet₊dummy(t) ~ -0.0
tank₊inlet₊flow(t) ~ -src₊outlet₊flow(t)
tank₊conc(t) ~ src₊outlet₊conc(t)
src₊outlet₊dummy(t) ~ tank₊inlet₊dummy(t)
julia> equations(room)
1-element Vector{Equation}:
Differential(t)(tank₊product_mass(t)) ~ tank₊inlet₊flow(t)*src₊outlet₊conc(t)
Seems like a stream connector bug?
Yeah likely. I'm looking into it.
The problem is that tank.inlet.conc is not present in the simplified system, so MTK doesn't know how to compute it.
That's correct. room.tank.inlet.conc is not present in the original system either, so in that sense ModelingToolkit is not wrong for my posted MWE.
Nevertheless, there is still a problem: there is no way to get instream(room.tank.inlet.conc). plot(sol, idxs=[instream(room.tank.inlet.conc)]) fails with ERROR: MethodError: no method matching iterate(::SymbolicUtils.BasicSymbolic{Float64}). actualStream would also work for us, but it is not implemented (https://github.com/SciML/ModelingToolkit.jl/issues/1666).
julia> sol(10, idxs=[instream(test_room.reactor.inlet.conc)])
1-element Vector{SymbolicUtils.BasicSymbolic{Float64}}:
instream(-10.0)
MTK treated instream as a wrapper, and evaluated test_room.reactor.inlet.conc to -10, whereas it should be treating instream(test_room.reactor.inlet.conc) as a variable in itself, distinct from test_room.reactor.inlet.conc
I believe I am encountering the same issue, it's really frustrating, did anyone find a workaround? (My case is pretty much the mixing tank example from above)
I did! I stopped using stream connectors and my life got so much better. If your flow is unidirectional, you can do it too! Just replace connect with connect_flow, and adapt the code below to cover whatever variables are in your connectors.
""" Connect the `from` components to the `to` components. _The flow must be in the `from -> to` direction_.
"""
connect_flow(from, to::Vector) = # Connect one component to N components
vcat([from.conc ~ t.conc for t in to],
from.flow + sum(t.flow for t in to) ~ 0)
connect_flow(from::Vector, to) = # Connect N components to one component
vcat([to.conc ~ sum(f.conc * f.flow for f in from) / sum(f.flow for f in from)],
to.flow + sum(f.flow for f in from) ~ 0)
connect_flow(from, to) = connect_flow([from], to) # (from, [to]) would also be fine
Another work-around is to introduce an inlet_conc ~ instream(inlet.conc) variable (observable)
I see! That's relatively elegant, even if it's frustrating to have to resort to these workarounds It'd be nice to be ablew to use the normal Stream connectors, but well, things are moving
Talking about directionality, I was also struggling with switching mechanism (aka AON valves in my case), I realized that "discrete variables" (@parameters with (t) ) together with callbacks worked pretty well. Not sure you'll find this one useful, but maybe someone else that'll read :)