Reputation: 467
I am building a library to support System Dynamics (SD) like modeling in Modelica. Unlike the freely available library by Cellier et al. I am convinced that one may well make use of acausal connectors: Transmitting the stock value as "potential" via connectors enables to build compact components (e.g. flows = processes).
In SD we would maybe distinguish material ("mass") stocks from informational stocks that can become negative. To support this I am using the following definitions for a mass port (here giving the definition for a StockPort
- its the counterpar, a FlowPort
, would simply have Boolean input variables instead of output variables and is given later on):
connector StockPort "Used to represent stock and flow connections"
Real stock "Current value of material in the stock";
flow Real rate "Flow that affects the stock";
// Boolean switches
output Boolean stopInflow "True indicates that nothing can flow into the stock";
output Boolean stopOutflow "True indicates that nothing can flow out of the stock";
end StockPort;
The Boolean switches indicate for each port of a stock whether filling or draining is allowed.
For a "material stock" the stopOutflow
switch should prevent the stock from being drained below zero. Unfortunately in the following example this will not work out: The stock will be drained just slightly below zero.
The following TestModel
uses these building blocks:
function constrainedRate( indicated rate, stopInflow, stopOutflow)
is used to return a rate that will comply with the given constraints (i.e. boolean switches)
connector StockPort
as described above
connector FlowPort
the counterpart to the StockPort
model MaterialStock
a stock-component with one StockPort
that shall not be drainable below zeromodel LinearDecline
a flow-element with one FlowPort
(i.e. a Sink) that models draining a connected stock at a constant rate (here set to 1)The main model simply initiates a stock
with initialValue = 5
that is connected to a process
of linear decline with declineRate = 1
.
model TestModel "Stop draining a stock below zero"
function constrainedRate "Set rate for a port according to signals from stock"
input Real indicatedRate "Proposed rate for port of flow element";
input Boolean stopInflow "Signal from connected stock";
input Boolean stopOutflow "Signal from connected stock";
output Real actualRate "The rate to use";
protected
// check whether indicated rate is negative (e.g. an inflow to the connected stock)
Boolean indRateIsInflow = indicatedRate < 0;
algorithm
// set rate to zero if stopSignal matches character of flow
actualRate := if indRateIsInflow and stopInflow
then 0
elseif not indRateIsInflow and stopOutflow
then 0
else indicatedRate;
end constrainedRate;
connector FlowPort "Used to represent stock and flow connections"
Real stock "The current stock level (e.g. Potential) of a connected stock or flow data for special stocks";
flow Real rate "Flows that affect the material stock";
input Boolean stopInflow "True indicates that nothing can flow into the stock";
input Boolean stopOutflow "True indicates that nothing can flow out of the stock";
end FlowPort;
connector StockPort "Used to represent stock and flow connections"
Real stock "Current value of stock";
flow Real rate "Flow that affects the stock";
output Boolean stopInflow "True indicates that nothing can flow into the stock";
output Boolean stopOutflow "True indicates that nothing can flow out of the stock";
end StockPort;
model MaterialStock "Stock that cannot be drained below zero"
StockPort outflow;
parameter Real initialValue;
protected
Real x(start = initialValue);
equation
// rate of change for the stock
der(x) = outflow.rate;
// ports shall have level information for stock
outflow.stock = x;
// inflow to stock is unrestricted
outflow.stopInflow = false;
// provide Boolean signal in case of negative stock
outflow.stopOutflow = x <= 0;
end MaterialStock;
model LinearDecline "Decline of stock at a constant rate"
FlowPort massPort;
parameter Real declineRate(min = 0) "Rate of decline (positive rate diminishes stock)";
protected
// a positive rate should drain the stock (which here matches Modelica's rule for flows)
Real rate(min = 0);
equation
rate = declineRate;
// observe stock signals and constraints
assert(rate >= 0, "Rate must be positive and will be set to zero", level = AssertionLevel.warning);
// set the rate according to constraints given by stock
massPort.rate = constrainedRate( max(rate, 0), massPort.stopInflow, massPort.stopOutflow );
end LinearDecline;
// main model
MaterialStock stock( initialValue = 5 );
LinearDecline process( declineRate = 1 );
equation
connect( stock.outflow, process.massPort );
end TestModel;
Simulating the model using DASSL from StartTime = 0
to StopTime = 10
reveals the expected behavior for the variable stock.outflow.stock
:
Unfortunately the value is slightly below zero at t = 5.0
and later.
Somehow the event (stock value <= 0) is detected too late. What can I do?
(So far the imo unelegant cure has been to use a when
event (state-event) to reinit
the stock value to zero. My experiments with using noEvent
wrappers on if
statements and Boolean conditions have also not been successful.)
Upvotes: 4
Views: 524
Reputation: 847
Could you test this solution? Using Modelica.Constants.eps works for me. I also used Dassl and it worked for different stepsizes. I changed the following line from:
// provide Boolean signal in case of negative stock
outflow.stopOutflow = x <= 0;
to
// provide Boolean signal in case of negative stock
outflow.stopOutflow = x <= Modelica.Constants.eps;
Then, the output array(viewed in python for this post):
Output using a zero instead of eps:
[ 5.00000000e+00 4.00000000e+00 3.00000000e+00 2.00000000e+00
1.00000000e+00 0.00000000e+00 -7.37276906e-12 -7.37276906e-12
-7.37276906e-12 -7.37276906e-12 -7.37276906e-12 -7.37276906e-12
-7.37276906e-12 -7.37276906e-12]
Output using eps:
[5. 4. 3. 2. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
Upvotes: 2