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:
During the last 15–20 years, many attempts have been made to create good thermo-hydraulic connectors in Modelica. The effort resulted in 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.
Adhering to e.g. 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:
You'll need to learn the syntax and the workings of stream connectors. You can find inspiration in https://github.com/justnielsen/ModelicaTutorials
You must implement a medium model based on 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:
If you want the concentration if the medium were flowing out use c_outflow
If you want the concentration if the medium were flowing in use inStream(c_outflow)
If you just want the actual concentration in the flow use 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: git#github.com:kabdelhak/TankSystem.git
Related
I would like to have a package that extends from another basic package. The basic package contains a vector type and constant integers with name for each index number of the vector. It also contains a vector constant that gives each element a value (describing some property fo the element). In the extended package I would like to add one element of the vector type and add one new name for the new index, and also add to the vector constant one element with a specific value (describing some property of the substance).
Using the technique with replaceable - extend - redeclare it is straight forward to extend the vector type with an element and also append to the package a new constant integer for the for the appended element. But I am not sure how to append the constant vector with a new element.
The code below works in JModelica (2.4) but involves both redeclaration and copy of values from the original basic package. However in Medium3 JModelica does not accept a fourth redeclare statement for the constant vector mw. But if I instead make the redeclare statement as first line after extension is done, it does work (see Fritzson section 4.3.1). However, redeclaration should be a subtype of the original and Real3] is not a subtype of Real[2], but the compiler seems to manage anyway.
When I try the same code in OpenModelica (1.13) I get error message due to the fact that I redeclare a constant, and error already on the first redeclaration in Medium3. I am not sure this is a correct error message, and does not appear in JModelica.
Otherwise OpenModelica (and JModelica) accept both Medium2 without any warnings or errors. And these tests by just changing the medium used in LiquidCon.
My main question is if here is a more direct solution to do the extension of the medium package with one substance as described above, than my code, and that is more standard (and do work with both JModelica and OpenModelica and Modelica in general of course).
It is of course of interest to sort out what the Modelica standard say here and then we can bring that information to the organisations behind JModelica and OpenModelica as bug-reports.
Would appreciate your input / Jan Peter
Below an extract of package DEMO_v8
package Medium2
replaceable constant String name = "Two components" "Medium name";
replaceable constant Integer nc = 2 "Number of substances";
replaceable type Concentration = Real[nc] "Substance conc";
replaceable constant Real[nc] mw = {10, 20} "Substance weight";
constant Integer A = 1 "Substance index";
constant Integer B = 2 "Substance index";
end Medium2;
package Medium3
import M2 = DEMO_v8.Medium2;
extends M2
(redeclare constant String name="Three components" "Medium name",
redeclare constant Integer nc=3 "Number of substances",
redeclare type Concentration = Real[nc] "Substance conc");
redeclare constant Real[nc] mw = cat(1,M2.mw,{30}) "Substance weight";
constant Integer C = 3 "Substance index";
end Medium3;
connector LiquidCon
replaceable package medium=DEMO_v8.Medium3;
medium.Concentration c "Substance conc";
flow Real F (unit="m3/s") "Flow rate";
end LiquidCon;
You can (since Modelica Language 3.2 - it was illegal in 3.1) just modify the value of the constant as follows:
package Demo_v8
package Medium2
replaceable constant String name="Two components" "Medium name";
constant Integer nc=2 "Number of substances";
replaceable type Concentration = Real[nc] "Substance conc";
constant Real[nc] mw={10,20} "Substance weight";
constant Integer A=1 "Substance index";
constant Integer B=2 "Substance index";
end Medium2;
package Medium3
import M2 = Demo_v8.Medium2;
extends M2(
name="Three components" "Medium name",
nc=3 "Number of substances",
mw=cat(1, M2.mw, {30}),
redeclare type Concentration = Real[nc] "Substance conc");
constant Integer C=3 "Substance index";
end Medium3;
connector LiquidCon
replaceable package medium = Demo_v8.Medium3;
medium.Concentration c "Substance conc";
flow Real F(unit="m3/s") "Flow rate";
end LiquidCon;
end Demo_v8;
However, I have not verified that JModelica.org or OpenModelica can handle it.
BTW: The error message is correct, as redeclaring a constant has been illegal since Modelica 1.2.
I have a question around structuring Modelica code in a re-usable library part and a specific application part. The question concerns medium and equipment that depends on medium and I am inspired by some of the structure in MSL fluid library but I want to make something much smaller and adapted to my needs, but that I also can grow with.
The question is about how to conveniently adapt the library to a new medium defined in the application code. Since there are several models of different pieces of equipment it is natural to have a partial model that defines the type of connectors the equipment should have and then one only make changes in the partial model when adaptation of connectors are needed.
To me it looks like I need a three-step adaptation process of the library, instead of one-step that I hope for. I have a detailed example below that makes it possible ask the question more clearly.
The example is a model for pumping liquid from one vessel to another, i.e. we have a feed tank, a pump and a harvest tank. The liquid medium contains originally two substances and now in the application we want to model seven substances.
In the application code the new medium with seven substances are declared as a package Medium7. The adaptation of the library models for pump, feed and harvest tanks are made in the following three steps:
Define a connector LiquidCon7 as an extension of import of standard connector LiquidCon from the library and redeclare the medium to Medium7
Define a partial model EquipmentMedium7 as an extension of import of standard partial model EquipmentMedium and where the connector is redeclared LiquidCon to LiquidCon7
Define a package Equipment7 as an extension of import of the standard package Equipment where the partial model is redeclared from EquipmentMedium to EquipmentMedium7.
First now a system can be defined in the application code that is tailored to Medium7 using equipment from Equipment7.
—
I wish I could do the adaptation more direct than described above. If I avoid dividing the code in library and application like I do here then it is much more easy to switch from Medium2 to Medium7, by just changing the medium used in the LiquidConType and then that change propagate through the whole system.
When I read text book material on the subject by Tiller and Fritzson or when I try to understand MSL code I find similar structures but still not what I have here. I also think my questions of how to effectively adapt a library to changes in interfaces called for by a new application is not limited to medium, but a much wider range of code.
Just read Tillers paper "Patterns and anti-patterns in Modelica" from 2008 and in section 2.3 "Medium Model Pattern" here is a discussion that relate to my question and think of the last few lines on pg 649.
I just realised that my model structure breaks the Modelica definition, because you are not allowed to extend PumpType, FeedtankType etc from the partial model EquipmentMedium since I need the EquipmentMedium to be replaceable. See Modelica def 3.2 rev 2 section 6.2.1 “Transitively non-Replaceable”.
I would appreciate some comments on the subject and perhaps reading advice. Alternative solutions to my toy-problem is also very wellcome!
Thanks, Jan Peter
I do not know how to append a code file but below I show the application code described above. The library DATA_v04 is straight forward. But note that I need to define models PumpType, FeedtankType etc using extend from a partial model EquipmentMedium...and not allowed.
encapsulated package d4_app7
// ------------------------------------------------------------------------
// Interfaces
// ------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
package Medium7
constant String name = "Seven components" "Medium name";
constant Integer nc = 7 "Number of substances;
type Concentration
= Real[nc] (each min=0, each unit="kg/m3") "Substance conc";
end Medium7;
// ------------------------------------------------------------------------
// Adaptation of library DEMO to Medium7
// ------------------------------------------------------------------------
connector LiquidCon7
import DEMO_v4.LiquidCon;
extends LiquidCon(redeclare package medium=Medium7);
end LiquidCon7;
partial model EquipmentMedium7
connector LiquidConType=LiquidCon7;
end EquipmentMedium7;
package Equipment7
import DEMO_v4.Equipment;
extends Equipment
(redeclare partial model EquipmentMedium=EquipmentMedium7);
end Equipment7;
import DEMO_v4.Control;
// ------------------------------------------------------------------------
// Examples of systems
// ------------------------------------------------------------------------
model Test
LiquidCon7.medium medium;
Equipment7.PumpType pump;
Equipment7.FeedtankType feedtank;
Equipment7.HarvesttankType harvesttank;
Control.FixValueType Fsp(val=0.2);
equation
connect(feedtank.outlet, pump.inlet);
connect(pump.outlet, harvesttank.inlet);
connect(Fsp.out, pump.Fsp);
end Test;
end d4_app7;
I have got some input to this problem to simplify the adaptation of the library code to application both from JModelica and OpenModelica support and I share it here.
The code in the original questions do work in JModelica and OpenModelica but I found it “clumsy” and also actually has a central flaw that people in the OpenModelica community has pointed out to me. I use the partial model EquipmentMedium as parameter for the package Equipment and in the package extend from it. To extend from a replaceable model gives too much flexibility here and gives in OpenModelica 2.0-beta error (but not in earlier versions of OM).
In the updated code below of both library DEMO_v11.mo and application code d11_app7.mo I have simplified the the adaptation of the library and also avoid to extend from a replaceable model. After all it is only the connector LiquidCon that need to be adapted to the application, and to be precise, it is only the medium part of the connector that needs adaptation. So I use Medium as formal parameter of the package Equipment and define the connector inside the package based on the actual Medium. Then the different Equipment use this connector. This way of “extending” the models in the package from a parametrised connector is thus regarded as acceptable while extending from a parametrised model is not.
In the updated code I have also brought in more information in the Medium package and show how to extend that for a new Medium - result from another of my posts here.
For more information about the degree of flexibility there is in parametrised packages in the way, see Modelica def 6.2.1 and 7.3.1 as pointed out by Hans Olsson. There is also section 4.15 in Peter Fritzsons book (2nd ed 2015) that discuss this.
Library code DEMO_v11.mo:
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
package Medium2
replaceable constant String name = "Two components" "Medium name";
replaceable constant Integer nc = 2 "Number of substances";
replaceable type Concentration = Real[nc] "Substance conc";
replaceable constant Real[nc] mw = {10, 20} "Substance weight";
constant Integer A = 1 "Substance index";
constant Integer B = 2 "Substance index";
end Medium2;
package Medium3
import M2 = DEMO_v11.Medium2;
extends M2
(name="Three components" "Medium name",
nc=3 "Number of substances",
mw = cat(1,M2.mw,{30}) "Substance weight",
redeclare type Concentration = Real[nc] "Substance conc");
constant Integer C = 3 "Substance index";
end Medium3;
// ---------------------------------------------------------------------------------------------
// Equipment dependent on the medium
// ---------------------------------------------------------------------------------------------
package Equipment
replaceable package Medium
end Medium;
connector LiquidCon
Medium.Concentration c "Substance conc";
flow Real F (unit="m3/s") "Flow rate";
end LiquidCon;
model PumpType
LiquidCon inlet, outlet;
input RealInput Fsp;
equation
inlet.F = Fsp;
connect(outlet, inlet);
end PumpType;
model FeedtankType
LiquidCon outlet;
constant Integer medium_nc = size(outlet.c,1);
parameter Real[medium_nc] c_in (each unit="kg/m3")
= {1.0*k for k in 1:medium_nc} "Feed inlet conc";
parameter Real V_0 (unit="m3") = 100 "Initial feed volume";
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;
der(V) = outlet.F;
end FeedtankType;
model HarvesttankType
LiquidCon inlet;
constant Integer medium_nc = size(inlet.c,1);
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] c "Substance conc";
Real[medium_nc] m
(start=m_0, each fixed=true) "Substance mass";
Real V(start=V_0, fixed=true, unit="m3") "Harvest liquid volume";
equation
for i in 1:medium_nc loop
der(m[i]) = inlet.c[i]*inlet.F;
c[i] = m[i]/V;
end for;
der(V) = inlet.F;
end HarvesttankType;
end Equipment;
// ---------------------------------------------------------------------------------------------
// Control
// ---------------------------------------------------------------------------------------------
package Control
block FixValueType
output RealOutput out;
parameter Real val=0;
equation
out = val;
end FixValueType;
end Control;
// ---------------------------------------------------------------------------------------------
// Examples of systems
// ---------------------------------------------------------------------------------------------
// package Equipment3 = Equipment(redeclare package Medium=Medium3); // Just shorter version
package Equipment3
import DEMO_v11.Equipment;
extends Equipment(redeclare package Medium=Medium3);
end Equipment3;
model Test
Equipment3.Medium medium;
Equipment3.FeedtankType feedtank;
Equipment3.HarvesttankType harvesttank;
Equipment3.PumpType pump;
Control.FixValueType Fsp(val=0.2);
equation
connect(feedtank.outlet, pump.inlet);
connect(pump.outlet, harvesttank.inlet);
connect(Fsp.out, pump.Fsp);
end Test;
end DEMO_v11;
And application code d11_app7.mo:
// ---------------------------------------------------------------------------------------------
// Interfaces
// ---------------------------------------------------------------------------------------------
import Modelica.Blocks.Interfaces.RealInput;
import Modelica.Blocks.Interfaces.RealOutput;
package Medium7
import M2 = DEMO_v11.Medium2;
extends M2
(name = "Seven components" "Medium name",
nc = 7 "Number of substances",
mw = cat(1,M2.mw,{30,40,50,60,70}) "Substance weight",
redeclare type Concentration = Real[nc] "Substance conc");
constant Integer C = 3 "Substance index";
constant Integer D = 4 "Substance index";
constant Integer E = 5 "Substance index";
constant Integer F = 6 "Substance index";
constant Integer G = 7 "Substance index";
end Medium7;
// ---------------------------------------------------------------------------------------------
// Adaptation of library DEMO_v11 to Medium7
// ---------------------------------------------------------------------------------------------
package Equipment7
import DEMO_v11.Equipment;
extends Equipment(redeclare package Medium=Medium7);
end Equipment7;
// ---------------------------------------------------------------------------------------------
// Examples of systems
// ---------------------------------------------------------------------------------------------
import DEMO_v11.Control;
model Test
Equipment7.Medium medium; // Instance not necessary but helpful for user interface
Equipment7.PumpType pump;
Equipment7.FeedtankType feedtank;
Equipment7.HarvesttankType harvesttank;
Control.FixValueType Fsp(val=0.2);
equation
connect(feedtank.outlet, pump.inlet);
connect(pump.outlet, harvesttank.inlet);
connect(Fsp.out, pump.Fsp);
end Test;
end d11_app7;
I am modelling a concentrated solar thermal power plant using OpenModelica. I have created a model for a pump and a condenser, and they successfully calculate the outlet conditions when tested individually. However for testing purposes, when I connect the pump and condenser models in a closed loop the model will not simulate and I receive the following errors:
[1] 22:16:26 Translation Error
Internal error Circular Equalities Detected for Variables:
condenser2.o.mdot
condenser2.i.mdot
----------------------------------
pump2.i.mdot
condenser2.o.mdot
----------------------------------
pump2.o.mdot
pump2.i.mdot
----------------------------------
[2] 22:16:26 Symbolic Error
[PTC: 995:5-995:122]: Model is structurally singular, error found sorting equations
This most likely has something to do with the connectors and the mass flow rate in particular, however, I do not know what I need to change to solve this issue.
The system model is shown below:
model Pump_condenser
PTC.Pump2 pump2 annotation();
PTC.Condenser2 condenser2 annotation();
equation
connect(condenser2.o, pump2.i) annotation();
connect(pump2.o, condenser2.i) annotation();
end Pump_condenser;
The model for the pump is shown below:
model Pump2
extends PTC.PartialModels.PartialCSTComponentSISO;
// Imports
import Modelica.SIunits.*;
import ThermoCycle.Media.*;
import Modelica.Constants;
// Outlet isentropic state
Medium.ThermodynamicState state_isentropic;
// Parameters
parameter Length D = 2 "Diameter of pump";
parameter Length w = 1 "Width of pump";
parameter Real eta(unit = "1") = 0.7 "Isentropic efficiency of pump";
constant Real pi = 2 * Modelica.Math.asin(1.0) "3.14159265358979";
// Variables
Power Wdot "Power input to pump";
SpecificEntropy s_i "Specific entropy at inlet";
SpecificEnthalpy hs "Specific enthalpy after isentropic compression";
initial equation
medium_i.T = 363;
medium_i.p = 80000;
medium_o.T = 450;
medium_o.p = 800000;
i.h = Medium.specificEnthalpy(medium_i.state);
equation
// Energy balance
m * der(u) = (-Wdot) + i.mdot * actualStream(i.h) + o.mdot * actualStream(o.h);
// Isentropic efficiency
s_i = Medium.specificEntropy(medium_i.state) "Get inlet entropy";
state_isentropic = Medium.setState_ps(medium_o.p, s_i) "Isentropic state";
hs = Medium.specificEnthalpy(state_isentropic);
eta = (hs - medium_i.h) / (medium_o.h - medium_i.h) "Isentropic efficiency";
// Inlet pressure
medium_i.p = 80000;
// Mass
m = pi * D ^ 2 * w * d / 4 "Mass of fluid in control volume";
// Simulation parameters
annotation();
annotation();
end Pump2;
The model for the condenser is shown below:
model Condenser2
extends PTC.PartialModels.PartialCSTComponentSISO;
// Imports
import Modelica.SIunits.*;
// Parameters
parameter Length L = 5 "Length of one tube";
parameter Area A = 0.02 "Cross sectional area of tubes";
parameter Integer N(unit = "1") = 10 "Number of tubes";
parameter Temperature Tinf = 298 "Ambient temperature";
parameter CoefficientOfHeatTransfer k = 3000 "Convection coefficient";
// Variables
HeatFlowRate Qdot "Heat flow rate from power block to condenser fluid";
Temperature T "Average temp in CV";
initial equation
medium_i.T = 450;
medium_i.p = 8000000;
medium_o.T = 363;
medium_o.p = 80000;
i.h = Medium.specificEnthalpy(medium_i.state);
equation
// Energy balance
m * der(u) = Qdot + i.mdot * actualStream(i.h) + o.mdot * actualStream(o.h);
// Inlet pressure
medium_i.p = 8000000;
// Average temperature
T = (medium_i.T + medium_o.T) / 2;
// Heat flow rate
Qdot = -k * (T - Tinf) "Newtons law of cooling";
// Mass
m = N * L * A * d "Mass of fluid in CV";
// Simulation parameters
annotation();
annotation();
end Condenser2;
Both models extend the following partial model:
partial model PartialCSTComponentSISO
// Imports
import Modelica.SIunits.*;
// Fluid imports
replaceable package Medium = Modelica.Media.Water.StandardWater annotation(
choicesAllMatching = true);
// Set up inlet and outlet media
Medium.BaseProperties medium_i;
Medium.BaseProperties medium_o;
// Connectors
PTC.inlet i(redeclare package Medium = Medium) annotation();
PTC.outlet o(redeclare package Medium = Medium) annotation();
// Variables
SpecificInternalEnergy u "Average specific internal energy in CV (control volume)";
Mass m "Mass contained in CV";
Density d "Average density of fluid in CV";
// Parameters
parameter MassFlowRate mdotCV = 3.0 "Mass flow rate through CV";
equation
// Fluid equations
medium_i.h = i.h;
medium_i.p = i.p;
medium_o.p = o.p;
medium_o.h = o.h;
// Mass flow rate
i.mdot = mdotCV;
o.mdot + i.mdot = 0 "Mass balance";
// Internal energy
u = (medium_i.u + medium_o.u) / 2 "Internal energy in CV is average of inlet and outlet u";
// Miscellaneous
d = (medium_i.d + medium_o.d) / 2 "d in CV is average of inlet and outlet densities";
end PartialCSTComponentSISO;
The inlet and outlet connectors have the following structure:
connector inlet
// Imports //
import Modelica.SIunits.*;
// Fluid imports //
replaceable package Medium = Modelica.Media.Interfaces.PartialMedium "Medium model" annotation(
choicesAllMatching = true);
// Energy variables //
Medium.AbsolutePressure p;
stream Medium.SpecificEnthalpy h;
// Flow variables //
flow Medium.MassFlowRate mdot;
annotation();
end inlet;
Imagine there is a repeating pattern of components (e.g., dynamic pipe) which has a parameter (e.g., length) which will change depending on where in the pattern it resides. I proposed that it might be possible by omitting the "each" prefix before the parameter of interest.
For example. Lets take a n pipes and add them to the model in the following way:
parameter Integer n;
Modelica.Fluid.Pipes.DynamicPipe[n] pipe;
For a very simple example lets specify that we want the length of the 1st pipe in the pattern to have some length (length_1) and all others to have length_n. My thought is that it may be possible to put an if statement when defining the parameter in the following way such that length_1 is assigned to the n=1 pipe component and all others get assigned length_n:
parameter Integer n;
Modelica.Fluid.Pipes.DynamicPipe[n] pipe(
length=if n<2 then length_1 else cat(1,length_1,fill(length_n,n-1)));
A simple model in the framework mentioned is presented below:
model Test
parameter Integer n(min=1);
parameter Modelica.SIunits.Length length_1 = 0.1;
parameter Modelica.SIunits.Length length_n = 0.2;
Modelica.Fluid.Pipes.DynamicPipe[n] pipe(
redeclare each package Medium = Modelica.Media.Water.StandardWater,
each modelStructure=Modelica.Fluid.Types.ModelStructure.av_b,
each diameter=1,
length=if n==1 then length_1 else cat(1,length_1,fill(length_n,n-1)));
Modelica.Fluid.Sources.Boundary_pT boundary(
nPorts=1,
redeclare package Medium = Modelica.Media.Water.StandardWater,
p=100000,
T=293.15);
Modelica.Fluid.Sources.MassFlowSource_T boundary1(
nPorts=1,
redeclare package Medium = Modelica.Media.Water.StandardWater,
m_flow=1,
T=293.15);
inner Modelica.Fluid.System system(m_flow_start=1, T_start=293.15);
equation
if n == 1 then
connect(boundary1.ports[1], pipe[1].port_a);
connect(boundary.ports[1], pipe[1].port_b);
else
connect(boundary1.ports[1], pipe[1].port_a);
for i in 1:n-1 loop
connect(pipe[i].port_b, pipe[i+1].port_a);
end for;
connect(boundary.ports[1], pipe[n].port_b);
end if;
end Test;
As it stands the model does not work. I am not sure if the way in which the if statement is constructed is in error or if this is simply not allowable (which may be the case if my interpretation of the "each" prefix is in error).
Any thoughts?
First of all thanks to matth for pointing me towards the pendulum example from Mike Tiller's book.
Below, are two possible ways of modifying the code to achieve a repeated component pattern where the parameters change in some predetermined fashion. The error was associated with how the array for length was being defined.
Option #1:
Replaced
length=if n==1 then length_1 else cat(1,length_1,fill(length_n,n-1))
with
length={if i==1 then length_1 else length_n for i in 1:n}
Option #2:
Inserted
final parameter Modelica.SIunits.Length[n]
lengths_pipe = {if i==1 then length_1 else length_n for i in 1:n};
and replaced
... length=lengths_pipe
Below is the complete working code using the second option:
model Test
parameter Integer n(min=1)=2;
parameter Modelica.SIunits.Length length_1 = 0.1;
parameter Modelica.SIunits.Length length_n = 0.2;
final parameter Modelica.SIunits.Length[n] lengths_pipe = {if i==1 then length_1 else length_n for i in 1:n};
Modelica.Fluid.Pipes.DynamicPipe[n] pipe(
redeclare each package Medium = Modelica.Media.Water.StandardWater,
each modelStructure=Modelica.Fluid.Types.ModelStructure.av_b,
each diameter=1,
length=lengths_pipe);
Modelica.Fluid.Sources.Boundary_pT boundary(
nPorts=1,
redeclare package Medium = Modelica.Media.Water.StandardWater,
p=100000,
T=293.15);
Modelica.Fluid.Sources.MassFlowSource_T boundary1(
nPorts=1,
redeclare package Medium = Modelica.Media.Water.StandardWater,
m_flow=1,
T=293.15);
inner Modelica.Fluid.System system(m_flow_start=1, T_start=293.15);
equation
if n == 1 then
connect(boundary1.ports[1], pipe[1].port_a);
connect(boundary.ports[1], pipe[1].port_b);
else
connect(boundary1.ports[1], pipe[1].port_a);
for i in 1:n-1 loop
connect(pipe[i].port_b, pipe[i+1].port_a);
end for;
connect(boundary.ports[1], pipe[n].port_b);
end if;
annotation (uses(Modelica(version="3.2.1")));
end Test;
I try to set up a simple model with electrical or thermal power flows between sources and sinks.
I seem to have the same problem as treated in this topic although I used only one pair of flow and potential variables in my connector:
connector PowerPortE
flow SI.Power P;
SI.Voltage v "Dummy potential-variable to balance flow-variable P";
end PowerPortE;
A simple example with a signal responsed power sink looks like this:
model PowerSinkE
SimplePowerSystem.PowerPortE Port;
Modelica.Blocks.Interfaces.RealInput P(unit = "W");
SI.Voltage v(start = 230);
equation
Port.P = P;
Port.v = v;
end PowerSinkE;
model Test
SimplePowerSystem.PowerSinkE Verbraucher ;
Modelica.Blocks.Sources.Sine sine1(freqHz = 50) ;
equation
connect(sine1.y,Verbraucher.P);
end Test;
Checking PowerSinkE goes well, but when trying to simulate, I get the following errors:
Internal error pre-optimization module removeSimpleEquations failed.
Internal error Found Equation without time dependent variables Verbraucher.Port.P = const.k
An independent subset of the model has imbalanced number of equations (1) and variables (2).
variables:
Verbraucher.v
Verbraucher.Port.v
equations:
1 : Verbraucher.Port.v = Verbraucher.v
An independent subset of the model has imbalanced number of equations (4) and variables (3).
variables:
sine1.y
Verbraucher.P
Verbraucher.Port.P
equations:
1 : Verbraucher.Port.P = Verbraucher.P
2 : sine1.y = sine1.offset + (if time < sine1.startTime then 0.0 else sine1.amplitude * sin(6.283185307179586 * sine1.freqHz * (time - sine1.startTime) + sine1.phase))
3 : Verbraucher.Port.P = 0.0
4 : Verbraucher.P = sine1.y
Initially I wanted to leave the variable v completely out of the model (though I had to leave it in the connector to be balanced) but this didn't work out either:
Model is structurally singular, error found sorting equations
1: 0.0 = sine1.offset + (if time < sine1.startTime then 0.0 else sine1.amplitude * sin(6.283185307179586 * sine1.freqHz * (time - sine1.startTime) + sine1.phase));
for variables
Verbraucher.Port.v(1)
The problem seems to be that I need the flow variable power but don't have a corresponding potential variable. I am running out of ideas how to fix this, so thanks for the help.
Why do you try to use a connector in this case? If you don't need the "pyhsical meaning" of flow and potential variables inside the connector, you just can use real inputs and outputs to handle signals.
package SimplePowerSystem
model PowerSinkE
import SI = Modelica.SIunits;
SI.Power P;
Modelica.Blocks.Interfaces.RealInput P_in(unit="W");
equation
P = P_in;
end PowerSinkE;
model Test
SimplePowerSystem.PowerSinkE Verbraucher;
Modelica.Blocks.Sources.Sine sine1(freqHz = 50);
equation
connect(sine1.y, Verbraucher.P_in);
end Test;
end SimplePowerSystem;
My initial thought is that port in the consumer is unconnected. This adds the equation consumer.port.P = 0.0. But what you really need is an equation for the voltage in the port.
You need to use voltage and current on your electrical connector and you need an electrical ground. I suggest you have a look at Modelica by Example for more about both electrical and thermal component modeling.