Status of the post:
200313 Got an answer with code DEMO_v42 that I accept for the bounty!
200310 I comment on two key papers suggested yesterday. Still do not understand how to update DEMO_v41.
200309 I want to underline that the key problem is how to introduce the concept of stream in the code DEMO_v41 (if possible) and in this way make the connector balanced. The variable c that is the concentration should be declared stream, but how should equations be updated with inStream or actualStream - that I would be glad to see!
200226 The post example DEMO_v41 was added and is a simplified and I hope more readable code than the first DEMO_v40.
200225 I gave some comments to the answers given and to tried to focus the readers on the actual problems, but little happened.
200224 I got some input to the post both general and detailed. The detailed comments was of less value and partly an effect of misunderstanding the problem. The more general answer from Rene is good but too general. I really like to understand how to use the concept of stream with small examples, before I think about using Modelica.Media etc. It is a learning process.
I want to know how to correctly define a connector for a liquid that has a number of components with different concentrations in solution and then that solution has a flow rate. Pressure in the liquid is negligible. The standard connector I have used for long time is:
connector LiquidCon
nc=5;
Real c[nc] “Component concentrations”;
flow Real F “Flow rate”;
end LiquidCon;
The connector works well in JModelica and OpenModelica, but I get warnings in OpenModelica that the connector is not balanced. In the Modelica language specification, section 9.3.1 I see that my construct is actually not legal, see https://www.modelica.org/documents/ModelicaSpec34.pdf. How can I make a connector that satisfy the demands?
I have spent some time reading chapter 5.10 on the concept of “stream” in Fritzons book 2n edition, but I need to study it in more detail.
The reason my simple connector brings a warning is that when you declare a flow variable the compiler assumes that the other variable is a potential variable to that flow variable, i.e. at least the number of flow and potential variables must be the same in a connector. Then of course in my case the component concentration is not a potential variable, but that the compiler cannot detect.
In the introductory section of chapter 5.10 the scope of the concept of “stream" seems to be “...application of bidirectional flows of matter with associated properties…”. In my area of applications I doubt that I need to consider bidirectional flows. This means that use of stream is an “overkill”. But that seems also to imply that I should not use the concept of “flow” either, which is a bit of a pity. Should we really stop using the concept “flow” here?
Anyway, I have tried to put together a more basic example than could be found in the book of Fritzson on this subject to see how use of the concept of “stream” would look and also what overhead in computation time etc you get. In the example below I model flow of a liquid from feed tank to harvest tank. The flow is governed by a pressure difference now. The code DEMO_v41 works and gives a warning that the connector is not balanced. If I now declare substrate concentration c to be “stream”, how should I now update the code with use of inStream and actualStream to make it work the same way, but now with this balanced connector?
package DEMO_v41
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
// ---------------------------------------------------------------------------------------------
// Equipment
// ---------------------------------------------------------------------------------------------
package EquipmentLib
connector LiquidCon
Real P "Pressure";
flow Real F "Flow rate";
Real c "Substance conc";
end LiquidCon;
model PipeType
LiquidCon inlet, outlet;
parameter Real area = 1;
equation
inlet.F = -outlet.F;
outlet.F = -area^2*(inlet.P - outlet.P); // Linearized Bernoulli equation
outlet.c = inlet.c;
end PipeType;
model FeedtankType
LiquidCon outlet;
parameter Real P = 0.1 "Pressure";
parameter Real V_0 = 100 "Initial feed volume";
parameter Real c_in = 1.0 "Feedtank conc";
Real V(start=V_0, fixed=true) "Feed volume";
equation
outlet.c = c_in;
outlet.P = P;
der(V) = outlet.F;
end FeedtankType;
model HarvesttankType
LiquidCon inlet;
parameter Real P = 0.0 "Pressure";
parameter Real V_0 = 1.0 "Initial harvest liquid volume";
parameter Real m_0 = 0.0 "Initial substance mass";
Real V(start=V_0, fixed=true) "Harvest liquid volume";
Real m(start=m_0, fixed=true) "Substance mass";
Real c "Substance conc";
equation
inlet.P = P;
der(V) = inlet.F;
der(m) = inlet.c*inlet.F;
c = m/V;
end HarvesttankType;
end EquipmentLib;
// ---------------------------------------------------------------------------------------------
// Example of system
// ---------------------------------------------------------------------------------------------
model Test
EquipmentLib.FeedtankType feedtank;
EquipmentLib.HarvesttankType harvesttank;
EquipmentLib.PipeType pipe;
equation
connect(feedtank.outlet, pipe.inlet);
connect(pipe.outlet, harvesttank.inlet);
end Test;
end DEMO_v41;
The older example DEMO_v40 below is more general and harder to read, but kept for reference since one early answer around this example.
The compilation (JModelica 2.14) error message I get is: “Error in flattened model: The system is structurally singular. The following variables(s) could not be matched to an equation: harvesttank.inlet.c[1], pipe.outlet.c[1]. OpenModelica (1.16) gives about the same message. What is wrong here?
package DEMO_v40
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
partial package MediumBase
constant String name "Medium name";
constant Integer nc "Number of substances";
replaceable type Concentration = Real[nc] "Substance conc";
end MediumBase;
package Medium1
extends MediumBase
(name="One component medium",
nc=1);
constant Real[nc] mw = {10} "Substance weight";
constant Integer A = 1 "Substance index";
end Medium1;
record Medium_data
constant String name = Medium1.name;
constant Integer nc = Medium1.nc;
constant Real[nc] mw = Medium1.mw;
constant Integer A = Medium1.A;
end Medium_data;
// ---------------------------------------------------------------------------------------------
// Equipment dependent on the medium
// ---------------------------------------------------------------------------------------------
package EquipmentLib
replaceable package Medium = MediumBase // formal parameter - EquipmentLib
constrainedby MediumBase;
connector LiquidCon
Real P "Pressure";
flow Real F (unit="m3/s") "Flow rate";
stream Medium.Concentration c "Substance conc";
end LiquidCon;
model PipeType
LiquidCon inlet, outlet;
parameter Real area = 1;
equation
inlet.F = -outlet.F;
outlet.F = area^2*(inlet.P - outlet.P); // Linearized Bernoulli equation
for i in 1:Medium.nc loop
outlet.c[i] = inlet.c[i];
end for;
end PipeType;
model FeedtankType
LiquidCon outlet;
parameter Real P = 0.1 "Pressure";
parameter Real V_0 (unit="m3") = 100 "Initial feed volume";
parameter Real[Medium.nc] c_in (each unit="kg/m3")
= {1.0*k for k in 1:Medium.nc} "Feed inlet conc";
Real V(start=V_0, fixed=true, unit="m3") "Feed volume";
equation
for i in 1:Medium.nc loop
outlet.c[i] = c_in[i];
end for;
outlet.P = P;
der(V) = outlet.F;
end FeedtankType;
model HarvesttankType
LiquidCon inlet;
parameter Real P = 0.0 "Pressure";
parameter Real V_0 (unit="m3") = 1.0 "Initial harvest liquid volume";
parameter Real[Medium.nc] m_0
(each unit="kg/m3") = zeros(Medium.nc) "Initial substance mass";
Real[Medium.nc] m
(start=m_0, each fixed=true) "Substance mass";
Real[Medium.nc] c "Substance conc";
Real V(start=V_0, fixed=true, unit="m3") "Harvest liquid volume";
equation
inlet.P = P;
der(V) = inlet.F;
for i in 1:Medium.nc loop
der(m[i]) = inStream(inlet.c[i])*inlet.F;
c[i] = m[i]/V;
end for;
end HarvesttankType;
end EquipmentLib;
// ---------------------------------------------------------------------------------------------
// Adaptation of package Equipment to Medium1
// ---------------------------------------------------------------------------------------------
package Equipment
import DEMO_v40.EquipmentLib;
extends EquipmentLib(redeclare package Medium=Medium1);
end Equipment;
// ---------------------------------------------------------------------------------------------
// Examples of systems
// ---------------------------------------------------------------------------------------------
model Test
Medium_data medium;
Equipment.FeedtankType feedtank;
Equipment.HarvesttankType harvesttank;
Equipment.PipeType pipe;
equation
connect(feedtank.outlet, pipe.inlet);
connect(pipe.outlet, harvesttank.inlet);
end Test;
end DEMO_v40;
Personally, I would "go all the way" and use stream connectors for the following reasons:
stream
connectors in 2008 which is currently state-of-the-art in Modelica. It allows you to transport specific enthalpy and substance fractions (or species concentrations) with one flow
variable and it enables flow reversal. Using stream
connectors is not overkill. Modelica.Fluid.Interfaces.FluidPort
, your work will be compatible with many existing libraries and models and you don't need to make pumps, pipes, valves, tank models etc. yourself.You will be faced with a couple of challenges, though:
stream
connectors. You can find inspiration in https://github.com/justnielsen/ModelicaTutorials
Modelica.Media
for the fluid that you will be transporting in the stream connectors. A medium models doesn't have to be very complex if you can assume constant density and/or specific heat capacity, for example. If the medium model is simple, it is computationally easy to switch between volume/mass flow rate when you specify the boundary conditions (source/sinks).After some thought I believe the following is your example converted to use stream variables directly (although I agree that making it compatible with MSL would be good, as @ReneJustNielsen suggested).
package DEMO_v42
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
// ---------------------------------------------------------------------------------------------
// Equipment
// ---------------------------------------------------------------------------------------------
package EquipmentLib
connector LiquidCon
Real P "Pressure";
flow Real F "Flow rate";
stream Real c_outflow "Substance conc";
end LiquidCon;
model PipeType
LiquidCon inlet, outlet;
parameter Real area = 1;
equation
inlet.F = -outlet.F;
outlet.F = -area^2*(inlet.P - outlet.P); // Linearized Bernoulli equation
outlet.c_outflow = inStream(inlet.c_outflow);
inlet.c_outflow=inStream(outlet.c_outflow);
end PipeType;
model FeedtankType
LiquidCon outlet;
parameter Real P = 0.1 "Pressure";
parameter Real V_0 = 100 "Initial feed volume";
parameter Real c_in = 1.0 "Feedtank conc";
Real V(start=V_0, fixed=true) "Feed volume";
equation
outlet.c_outflow = c_in;
outlet.P = P;
der(V) = outlet.F;
end FeedtankType;
model HarvesttankType
LiquidCon inlet;
parameter Real P = 0.0 "Pressure";
parameter Real V_0 = 1.0 "Initial harvest liquid volume";
parameter Real m_0 = 0.0 "Initial substance mass";
Real V(start=V_0, fixed=true) "Harvest liquid volume";
Real m(start=m_0, fixed=true) "Substance mass";
Real c "Substance conc";
Real inletC=actualStream(inlet.c_outflow);
equation
inlet.P = P;
inlet.c_outflow=c;
der(V) = inlet.F;
der(m) = actualStream(inlet.c_outflow)*inlet.F;
c = m/V;
end HarvesttankType;
end EquipmentLib;
// ---------------------------------------------------------------------------------------------
// Example of system
// ---------------------------------------------------------------------------------------------
model Test
EquipmentLib.FeedtankType feedtank;
EquipmentLib.HarvesttankType harvesttank;
EquipmentLib.PipeType pipe;
equation
connect(feedtank.outlet, pipe.inlet);
connect(pipe.outlet, harvesttank.inlet);
end Test;
end DEMO_v42;
I added inletC to be able to compare the concentrations with the previous models.
The major change is that for a stream variable, c_outflow, there are in fact two/three different variables to use:
c_outflow
inStream(c_outflow)
actualStream(c_outflow)
So for the pipe you write that the concentration flowing out from one port equals the concentration flowing into the other port, and vice versa.
For the tank you just write the equation for c_outflow
, but use actualStream
to get the actual concentration in the flow.
The correct way would be either
connector LiquidCon
nc=5;
Real c[nc] “Component concentrations”;
flow Real F[nc] “Flow rate”;
end LiquidCon;
or
connector LiquidCon
Real c “Component concentrations”;
flow Real F “Flow rate”;
end LiquidCon;
Depending on what you want to model. Rule of thumb is: number of potentials = number of flows
. Since you only use one flow and multiple concentrations it implies that you have multiple tanks-like components each with some concentration, connected by pipe-like components that allow a flow rate.
For these i would recommend the second version i posted!
Some background information:
A connector is never balanced, it is assumed to provide half the number of equations compared to its unknowns. Whenever you add a connector to a component, that component has to balance it. The reason is quite simple: E.g. a connector with one potential and one flow. The direction in which the information flows is unclear, but certain is, that either the flow
variable is considered known or the potential
is considered known, the other one will be computed by the equations of the component. For the tank the concentration is computed by its own equations and the flow is passed by the connector (vice versa for the pipe).
Whenever two or more connector are connected all potentials are set equal and all flows sum up to be zero (Modelica Language Specification section 9.2).
I changed your example such that i can actually individually test the components. Note that i added a default value to nc
, otherwise it is not possible to check single components for consistency.
package DEMO_v40
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
partial package MediumBase
constant String name "Medium name";
constant Integer nc = 1 "Number of substances";
replaceable type Concentration = Real[nc] "Substance conc";
end MediumBase;
package Medium1
extends MediumBase
(name="One component medium",
nc=1);
constant Real[nc] mw = {10} "Substance weight";
constant Integer A = 1 "Substance index";
end Medium1;
record Medium_data
constant String name = Medium1.name;
constant Integer nc = Medium1.nc;
constant Real[nc] mw = Medium1.mw;
constant Integer A = Medium1.A;
end Medium_data;
// ---------------------------------------------------------------------------------------------
// Equipment dependent on the medium
// ---------------------------------------------------------------------------------------------
package EquipmentLib
replaceable package Medium = MediumBase "formal parameter EquipmentLib";
connector LiquidCon
Real P "Pressure";
flow Real F (unit="m3/s") "Flow rate";
stream Medium.Concentration c "Substance conc";
end LiquidCon;
model PipeType
LiquidCon inlet, outlet;
parameter Real area = 1;
equation
inlet.F = -outlet.F;
outlet.F = area^2*(inlet.P - outlet.P); // Linearized Bernoulli equation
for i in 1:Medium.nc loop
outlet.c[i] = inlet.c[i];
end for;
end PipeType;
model FeedtankType
LiquidCon outlet;
parameter Real P = 0.1 "Pressure";
parameter Real V_0 (unit="m3") = 100 "Initial feed volume";
parameter Real[Medium.nc] c_in (each unit="kg/m3")
= {1.0*k for k in 1:Medium.nc} "Feed inlet conc";
Real V(start=V_0, fixed=true, unit="m3") "Feed volume";
equation
for i in 1:Medium.nc loop
outlet.c[i] = c_in[i];
end for;
outlet.P = P;
der(V) = outlet.F;
end FeedtankType;
model HarvesttankType
LiquidCon inlet;
parameter Real P = 0.0 "Pressure";
parameter Real V_0 (unit="m3") = 1.0 "Initial harvest liquid volume";
parameter Real[Medium.nc] m_0
(each unit="kg/m3") = zeros(Medium.nc) "Initial substance mass";
Real[Medium.nc] m
(start=m_0, each fixed=true) "Substance mass";
Real[Medium.nc] c "Substance conc";
Real V(start=V_0, fixed=true, unit="m3") "Harvest liquid volume";
equation
inlet.P = P;
der(V) = inlet.F;
for i in 1:Medium.nc loop
der(m[i]) = inStream(inlet.c[i])*inlet.F;
c[i] = m[i]/V;
end for;
end HarvesttankType;
end EquipmentLib;
// ---------------------------------------------------------------------------------------------
// Adaptation of package Equipment to Medium1
// ---------------------------------------------------------------------------------------------
package Equipment
import DEMO_v40.EquipmentLib;
extends EquipmentLib(redeclare package Medium=Medium1);
end Equipment;
// ---------------------------------------------------------------------------------------------
// Examples of systems
// ---------------------------------------------------------------------------------------------
model Test
Medium_data medium;
Equipment.FeedtankType feedtank;
Equipment.HarvesttankType harvesttank;
Equipment.PipeType pipe;
equation
connect(feedtank.outlet, pipe.inlet);
connect(pipe.outlet, harvesttank.inlet);
end Test;
end DEMO_v40;
With this i went to OMEdit and checked every component individually with the CheckModel button (Single check mark on green circle in the top middle of OMEdit). I realized that your connector has 3 unknowns and 1 equation which is illegal (as i said it should be 2:1 ratio). That also results in all your other components being illegal.
Since it would be quite the work to debug all your stuff i can only provide something i made some time ago for a student project but it should showcase what has to be done. You don't need to pass both pressure
and concentration
since they should be algebraically connected anyways. I used the height
instead.
See following answer for model, it does not fit in this (and i can't add files here).
EDIT: I just made a git repository. Much easier actually:
HTTPS: https://github.com/kabdelhak/TankSystem
SSH: [email protected]:kabdelhak/TankSystem.git
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With