Modelica - how to best adapt import from library to application code - modelica

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;

Related

How to construct a balanced connector for liquids in Modelica?

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

Parametrised Modelica library and possibility to use models as parameters

I try to make a parametrised library. I works fine using packages and connectors as parameters.
Using a model as a parameter is also possible.
However, if the model is used in the library to build new models using extend, then it is not allowed, what I understand.
I wonder if the library contains a model with inner/outer style of connector, is it then allowed to let the inner model to be a parameter of the library?
Below a simple example to illustrate the problem. TEST is the library and Fish3b is an application. When I run the Example in the library TEST it all works, but when I have a separate application file it does not.
The error text is: cannot find class declaration for AquariumType running JModelica 2.4
package TEST
model FishType1
outer Real T;
Real health;
equation
health = 30-T;
end FishType1;
model FishType2
outer Real T;
Real health;
equation
health = 32-T;
end FishType2;
package Equipment
model AquariumType
replaceable model FishType
end FishType;
FishType fish;
inner Real T;
equation
T = 29;
end AquariumType;
end Equipment;
// Adapt AquariumType model to actual fish
model Aquarium
import TEST.Equipment.AquariumType;
extends AquariumType(redeclare model FishType=FishType2);
end Aquarium;
// Example
model Example
Aquarium aquarium;
end Example;
end TEST;
And below an example of application code that import from library above
- and here is some error I think.
encapsulated package Fish3b
model FishType3
outer Real T;
Real health;
equation
health = 34-T;
end FishType3;
// Adapt package Equipment with AquariumType model to actual fish
package Equipment3
import TEST.Equipment;
extends Equipment.AquariumType(redeclare model FishType=FishType3);
end Equipment3;
// Example
model Example
import Fish3b.Equipment3;
Equipment3.AquariumType aquarium;
end Example;
end Fish3b;
Thanks “tbeu” for the comment!
I have modified the code so that package Equipment is not extended from a model. The updated code below also represent my underlying “true” problem much better. And it all works.
Thanks!
The updated library file TEST2.mo:
package TEST2
model FishType1
outer Real T;
Real health;
equation
health = 30-T;
end FishType1;
model FishType2
outer Real T;
Real health;
equation
health = 32-T;
end FishType2;
package Equipment
replaceable model FishType
end FishType;
constant Integer dummy = 1;
model AquariumType
FishType fish;
inner Real T;
equation
T = 29;
end AquariumType;
end Equipment;
// Adapt package Equipment to the actual fish
package Equipment1
import TEST2.Equipment;
extends Equipment(redeclare model FishType=FishType1);
end Equipment1;
// Example
model Example
Equipment1.AquariumType aquarium;
end Example;
end TEST2;
And the application code T2_Fish3 that now uses the above library TEST2:
encapsulated package T2_Fish3
model FishType3
outer Real T;
Real health;
equation
health = 34-T;
end FishType3;
// Adapt package Equipment to the actual fish
package Equipment3
import TEST2.Equipment;
extends Equipment(redeclare model FishType=FishType3);
end Equipment3;
// Example
model Example
Equipment3.AquariumType aquarium;
end Example;
end T2_Fish3;
The answer from janpeter works.
Another alternative that avoids introducing models called "FishType1", "FishType3" etc is to use "redeclare model extends" as follows (the Test2 can be unchanged or same change for Equipment1), but it uses more advanced constructs.
encapsulated package T2_Fish3
// Adapt package Equipment to the actual fish
package Equipment3
import TEST2.Equipment;
extends Equipment;
redeclare model extends FishType
outer Real T;
Real health;
equation
health = 32-T;
end FishType;
end Equipment3;
// Example
model Example
Equipment3.AquariumType aquarium;
end Example;
end T2_Fish3;
Additionally it would be possible to move the common "outer Real T" to the base-model FishType leading to:
package TEST2
package Equipment
replaceable model FishType
outer Real T;
end FishType;
constant Integer dummy = 1;
model AquariumType
FishType fish;
inner Real T;
equation
T = 29;
end AquariumType;
end Equipment;
// Adapt package Equipment to the actual fish
package Equipment1
import TEST2.Equipment;
extends Equipment;
redeclare model extends FishType
Real health;
equation
health = 30 - T;
end FishType;
end Equipment1;
// Example
model Example
Equipment1.AquariumType aquarium;
end Example;
end TEST2;
and
encapsulated package T2_Fish3
// Adapt package Equipment to the actual fish
package Equipment3
import TEST2.Equipment;
extends Equipment;
redeclare model extends FishType
Real health;
equation
health = 32-T;
end FishType;
end Equipment3;
// Example
model Example
Equipment3.AquariumType aquarium;
end Example;
end T2_Fish3;
Updated the code with input from Hans Olsson before, but slightly different with the corrections suggested above.
And I manage to avoid “extend from a replaceable model”, but I am not too sure how critical that is, see my previous comment.
Also, I think I would like to keep the clear identity of the different fishes, and also declare them outside package Equipment.
package TEST3
partial model FishBase
outer Real T;
end FishBase;
model FishType1
extends FishBase;
Real health;
equation
health = 30 - T;
end FishType1;
model FishType2
extends FishBase;
Real health;
equation
health = 32 - T;
end FishType2;
package Equipment
replaceable model FishType
end FishType;
constant Integer dummy = 1;
model AquariumType
FishType fish;
inner Real T;
equation
T = 29;
end AquariumType;
end Equipment;
// Adapt package Equipment to the actual fish
package Equipment3
import TEST3.Equipment;
extends Equipment;
redeclare model FishType=FishType2;
end Equipment3;
// Example
model Example
Equipment3.AquariumType aquarium;
end Example;
end TEST3;
And an example of application code:
encapsulated package T3_Fish3
model FishType3
import TEST3.FishBase;
extends FishBase;
Real health;
equation
health = 34-T;
end FishType3;
// Adapt package Equipment to the actual fish
package Equipment3
import TEST3.Equipment;
extends Equipment(redeclare model FishType=FishType3);
end Equipment3;
// Example
model Example
Equipment3.AquariumType aquarium;
end Example;
end T3_Fish3;

Modelica - how to extend a (minimal) medium package

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.

Repeated components with pattern dependent parameters

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;

Member not replaced as expected, why?

Aims: all the derived classes should inherit some default equations from their base class. When the default equation is not valid for a derived class then it should redeclare it.
Here is a somewhat silly minimalistic example.
package Pkg
class Equations
Real x;
end Equations;
class DefaultEquations
extends Equations;
equation
x = 0.0;
end DefaultEquations;
class Base
replaceable DefaultEquations equations extends Equations;
end Base;
end Pkg;
model DuplicateEquations
import Pkg.*;
class CustomizedClass
extends Base;
redeclare Equations equations;
equation
equations.x = 3;
end CustomizedClass;
CustomizedClass customized;
end DuplicateEquations;
For some mysterious reason, the default equation is not overriden:
omc Test.mo Package.mo
class DuplicateEquations
Real customized.equations.x;
equation
customized.equations.x = 0.0;
customized.equations.x = 3.0;
end DuplicateEquations;
Why is this happening? Why are both x=0 and x=3 generated?
If I comment out the package declaration I get only the expected x=3 equation.
The type has to be redeclared, not the component, as pointed out by Adrian Pop.