Skip to content

One way stream connections (no backflow) #3678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
cstjean opened this issue May 30, 2025 · 3 comments
Open

One way stream connections (no backflow) #3678

cstjean opened this issue May 30, 2025 · 3 comments
Assignees

Comments

@cstjean
Copy link
Contributor

cstjean commented May 30, 2025

A lot of chemical plants have the product flowing in only one direction. Modeling those with stream connectors is natural, but under MTK (and vanilla Modelica) it requires also specifying the backflow version of all stream quantities.

To wit, here I have one source of product flowing into two reactors, in parallel:

using ModelingToolkit, ModelingToolkitStandardLibrary
using ModelingToolkit: t_nounits as t, D_nounits as D

@connector FlowPin begin
    flow(t), [connect=Flow]
    conc(t), [connect=Stream]
    dummy(t)   # otherwise MTK won't accept it
end

@mtkmodel Source begin
    @components begin
        inlet = FlowPin()
    end
end

@mtkmodel Reactor begin  # mass-less reactor
    @components begin
        inlet = FlowPin()
    end
    @variables begin
        conc(t)
    end
    @equations begin
        conc ~ instream(inlet.conc) + 0.1
        inlet.conc ~ -999999
    end
end

@mtkmodel Room begin
    @components begin
        source = Source()
        reactor1 = Reactor()
        reactor2 = Reactor()
    end
    @equations begin
        connect(source.inlet, reactor1.inlet)
        connect(source.inlet, reactor2.inlet)

        source.inlet.conc ~ 0.2

        reactor1.inlet.flow ~ 2
        reactor2.inlet.flow ~ 3
    end
end

@mtkbuild room = Room()
prob = ODEProblem(room, [], (0, 100.0), [])
sol = solve(prob)

Without the inlet.conc ~ 9999 equation, MTK complains that it has more unknowns than equations. This is understandable, but it has been a major source of headache on our side since we've started using MTK. If a reactor operates in the forward direction, are we expected to always specify a hypothetical backflow case? Consider this simple equation (in the forward direction):

D(mass_product) ~ instream(inlet.concentration) * inlet.flow - outlet.concentration * outlet.flow

Complicating it with a physically-impossible backflow is quite depressing, not to mention error-prone. The user-side alternatives are:

  • inlet.conc ~ -999999
  • inlet.conc ~ 0
  • inlet.conc ~ outlet.conc

I use the first one. I sprinkle inlet.conc ~ -999999 in my components when MTK complains of a lack of equations, and I delete them when there is a surplus of equations. It usually works 😱

Proposal

connect(a.inlet, b.inlet, backflow=false). Then expand_connection could ignore the backflow equations, and ideally add an assertion that the flow is in the right direction.

Related

According to https://stackoverflow.com/questions/78580149/how-to-turn-off-the-possibility-of-media-back-flow-when-using-the-modelica-strea

Regarding the concept of "back-flow can be turned-off" in the MSL: This is done in many of the components of the Fluid package. One example is the pipe in Modelica.Fluid.Examples.PumpingSystem. There you can find the parameter allowFlowReversal. This should be what you are looking for.

I am quite interested to hear about how others are approaching this. Perhaps there is a simpler solution that I missed?

@ChrisRackauckas
Copy link
Member

Stream connectors effectively correspond to upwinding discretizations of advection dominated PDEs:

https://en.wikipedia.org/wiki/Upwind_scheme

In that sense, you numerically always need a backflow equation if you're going to integrate the system, since not switching equations would be numerically unstable. But yes, if you can guarantee that a certain direction is taken then the system can be greatly simplified: then we only need to generate for one direction. So I think this is possible and it would definitely need to have an assertion in there that the user's assumption is correct as otherwise bad things can happen.

@mtiller-jh
Copy link

I'd like to understand...is your concern the need to specify the inlet.conc with a value that is never used or is it the unnecessary complexity of the resulting equation? Or both?

Part of my concern here is that in building such a system, you may want to reuse components that are built to handle flow in both directions. If you didn't do that, then you would potentially end up having to create two libraries, one that supports reversing flow and one that didn't...each with potentially the same components.

But if you leave aside the need to specify an unused value for inlet.conc but instead focus on being able to reason about the flow direction, then it seems doable to have a system where MTK might be able to reason about the direction of flow (due to an assertion or min bound) and simplify the equations under that assumption. This would lead to component models being more reusable.

@cstjean
Copy link
Contributor Author

cstjean commented Jun 2, 2025

Broadly, it's that stream connectors are error prone and hard to debug:

  • Because I use inlet.conc ~ -999999, I've had InitialValueProblems converge to a backflow solution. Yes, I should have used assertions, but it's one more footgun.
  • It is so oh-so-very-easy to accidentally write inlet.conc * inlet.flow instead of instream(inlet.conc) * inlet.flow. With forward-flow connections, I would get a build-time error, but instead I get non-sensical values. It would also help if we used instream(x) / outstream(x) instead of instream(x) / x.
  • In the above MWE, inlet.conc ~ -999999 is not required in the single-reactor case. With two reactors, the N_equations < N_variables error appears. I now get the mathematical reason for this (after N hours of frustration) but it adds to the dificulty.
  • The forward flow stream connection equations could be plainly displayed (no conditional behaviour, no instream_rt(Val{2}(), Val{0}(), tank₊outlet₊flow)

Part of my concern here is that in building such a system, you may want to reuse components that are built to handle flow in both directions.

A big part of the appeal for us is that MTK's equations are understandable by the chemical engineers working with me. A physical backflow would destroy our reactor. What kind of equations should I write to represent that? To my colleagues, this is just noise.

I get the appeal of component libraries, but at the current POC stage this is not what we are aiming for.

This would lead to component models being more reusable.

Yes, ideally a user with a forward-flow model should be able to integrate bidirectional flow components from a library. The backflow equations would just be ignored. Not sure how to implement that in practice.

By the way, thank you for Modelica By Example! I was pulling my hair reading the MTK docs until I found it. It should be prominently featured IMO, or translated to MTK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants