Compiling of OpenModelica-Model in Twinbuilder - modelica

I have a model which is written in Openmodelica. I want to implement this model in Twinbuilder with Modelica. The code of the model is as follows:
model SelfActingValve
extends Modelica.Fluid.Interfaces.PartialTwoPortTransport;
// Import Elements
import Modelica.Constants.pi;
import Modelica.Constants.g_n;
// Thermodynamic states
//Medium.ThermodynamicState port_a_state_inflow "State at port_a if inflowing";
//Medium.ThermodynamicState port_b_state_inflow "State at port_b if inflowing";
// Parameter
parameter Modelica.SIunits.Length dGap=0.0062 "Mean value of outer diameter of the valve and the diameter of the pipe in front of the valve
";
parameter Modelica.SIunits.Angle alpha=pi/2"Angle between the vertical and the bevel edge of the cone valve";
parameter Modelica.SIunits.Length dchamber=0.008"diameter before the valve";
parameter Real gravityAccelerat = g_n;
parameter Real cValve = 100 "spring constant/(N/m)";
parameter Modelica.SIunits.Length spPre = 0.0029 "length of the spring preload";
parameter Modelica.SIunits.Length scaleS = 0.0089 "maximum of possible movement of the sprin";
parameter Modelica.SIunits.Mass mValve = 0.000486 "Mass of the valve body";
parameter Modelica.SIunits.Area surfacePorta=1"Surface of the valve at port_a";
parameter Modelica.SIunits.Area surfacePortb=1"Surface of the valve at port_b";
parameter Modelica.SIunits.Volume volValve= 436e-9 "Volume of the valvebody";
parameter Modelica.SIunits.Length dhydro=1.52e-3"hydraulic eyuivalent diameter of the gap ";
parameter Boolean outletValve = false "= true --> outletValve, false-->inlet valve" annotation(
Dialog(tab = "General"),
Evaluate = true);
parameter Real kgeo=10"geometric constant";
parameter Real zetaOwn=0.7"loss coeffizient of the valve";
// Variables in the equation
//protected
// Real zetaOwn_dummy(start=0.7)"dummy for loss coefficient";
// Real zetaOwn(start=0.7) "loss coefficient";
// Real P_lose(start=0);
Real x(start = 0, min=0, max=scaleS) "Position of the valve";
Real xdot(start = 0) "velocity of the valve";
Real force0(start = 0) " preload of spring";
Real forceSpr(start = 0) "spring force";
Real dummydot(start = 0) "first derrivative dummy";
Real forcePress(start=0);
Real forceGrav(start = 0) "Gravity force of valve";
Real forceDamp(start=0)"Damping force from friction";
Real densiWat(start=998.2)"density of the medium";
Real eta(start=1)"dynamic viscosity";
Real thetaStar(start=0)"damping factor";
Real volumstream(start=0);
// Real reynold(start=10000)"Reynold number of the fluid before the gap";
// Real psi(start=0.4)"coeffizeint to describe the not equal pressure distribution on the valve";
Real reynoldGap(start=1)"Reynold number of the fluid in the gap";
equation
volumstream=m_flow*densiWat;
// outflow equation
m_flow=x*dGap*pi*densiWat*sin(alpha)*(abs((2*dp)/(zetaOwn*densiWat)))^(1/2);
//Ansatz für Lösen der Singularität, aber keine Ergebnisse.
// Isenthalpic state transformation (no storage and no loss of energy)
port_a.h_outflow = inStream(port_b.h_outflow);
port_b.h_outflow = inStream(port_a.h_outflow);
// Fence modelation
if dummydot < 0 and x<0 then
der(xdot) = 0;
der(x) = 0;
elseif dummydot > 0 and x>scaleS then
der(xdot) = 0;
der(x) = 0;
else
if outletValve == true then
der(xdot) = 1 / mValve * ((-force0) - forceGrav - forceSpr + forcePress-forceDamp*xdot);
// der(xdot) = 1 / mValve * ((-force0) - forceGrav - forceSpr + forcePress);
xdot = der(x);
else
der(xdot) = 1 / mValve * ((-force0) + forceGrav - forceSpr+ forcePress-forceDamp*xdot);
// der(xdot) = 1 / mValve * ((-force0) + forceGrav - forceSpr+ forcePress);
xdot = der(x);
end if;
// This If loop is for the direction of the Forces, e.g. Depending if we have a outlet or a inlet valve the pressure force appeals in different directions.
end if;
// This If loop is for modelling of the fences of the valve
when dummydot < 0 and x<0 then
// xdotnew=if edge(impact) then 0 else xdot;
reinit(xdot, 0);
elsewhen dummydot > 0 and x> scaleS then
// xdotnew=if edge(impact) then 0 else xdot;
reinit(xdot, 0);
end when;
// This when loop reinitializes the speed and the accleration of the valve, when it hit the fences.
// equation of motion and force definition
if outletValve == true then
dummydot = 1 / mValve * ((-force0) - forceGrav - forceSpr+ forcePress-forceDamp*xdot );
// dummydot = 1 / mValve * ((-force0) - forceGrav - forceSpr+ forcePress );
else
dummydot = 1 / mValve * ((-force0) + forceGrav - forceSpr + forcePress-forceDamp*xdot);
// dummydot = 1 / mValve * ((-force0) + forceGrav - forceSpr + forcePress);
end if;
// This If loop is for the direction of the Forces, e.g. Depending if we have a outlet or a inlet valve the pressure force appeals in different directions. Dummydot is to calculate the actual acceleration to see, if we get away from the fences.
force0 = spPre * cValve "preload of the spring";
forcePress= port_a.p*surfacePorta-port_b.p*surfacePortb"Force of the pressures acting on the valve";
forceSpr = cValve * x"force of the spring due to movements of the valve";
forceGrav = gravityAccelerat*( mValve -densiWat*volValve) "Gravity of the valve";
forceDamp=thetaStar*eta*dGap/kgeo"damping force";
thetaStar=10*reynoldGap+505"help variable for caculation forceDamp";
// psi=0.82-1.6*x/dGap-0.075*log(abs(reynold));
//zetaOwn=280/reynoldGap+1;
eta = if m_flow > 0 then
Medium.dynamicViscosity(state_a)
else
Medium.dynamicViscosity(state_b);
densiWat=if m_flow>0 then
Medium.density(state_a)
else
Medium.density(state_b);
if x< scaleS/1000 then
reynoldGap=0;
// reynold=1;
else
reynoldGap=4*m_flow/(pi*2*x*sin(alpha)*eta);
// reynold=4*m_flow/(pi*dchamber*eta);
end if;
// if loop: First fix for to big Reynold number in case of nearly closed valve positions
annotation(Icon(coordinateSystem(
preserveAspectRatio=true,
extent={{-100,-100},{100,100}}), graphics={
Line(points={{0,50},{0,0}}),
Rectangle(
extent={{-20,60},{20,50}},
fillPattern=FillPattern.Solid),
Polygon(
points={{-100,50},{100,-50},{100,50},{0,0},{-100,-50},{-100,50}},
fillColor={255,255,255},
fillPattern=FillPattern.Solid),
Polygon(
points=DynamicSelect({{-100,0},{100,-0},{100,0},{0,0},{-100,-0},{-100,
0}}, {{-100,50*opening},{-100,50*opening},{100,-50*opening},{
100,50*opening},{0,0},{-100,-50*opening},{-100,50*opening}}),
fillColor={0,255,0},
lineColor={255,255,255},
fillPattern=FillPattern.Solid),
Polygon(points={{-100,50},{100,-50},{100,50},{0,0},{-100,-50},{-100,
50}})}),Documentation(info="<html>
<p>
This model is build up witht the equations of the doctoral degree thesis ofErwin Thiel at the University Erlangen-Nürnberg with the title 'Kinematik und Druckverlust selbsttätiger Ventile oszillierender Verdrängerpumpen'. GENAUERE BESCHREIBUNG FOLGT
<p>
</html>"),Icon(coordinateSystem(initialScale = 0.1), graphics = {Line(points = {{0, 50}, {0, 0}}), Rectangle(fillPattern = FillPattern.Solid, extent = {{-20, 60}, {20, 50}}), Polygon(fillColor = {255, 255, 255}, fillPattern = FillPattern.Solid, points = {{-100, 50}, {100, -50}, {100, 50}, {0, 0}, {-100, -50}, {-100, 50}}), Polygon(lineColor = {255, 255, 255}, fillColor = {0, 255, 0}, fillPattern = FillPattern.Solid, points = {{-100, 0}, {100, 0}, {100, 0}, {0, 0}, {-100, 0}, {-100, 0}}), Polygon(points = {{-100, 50}, {100, -50}, {100, 50}, {0, 0}, {-100, -50}, {-100, 50}})}),
uses(Modelica(version = "3.2.3")));
end SelfActingValve;
I get the error when compiling this code in TwinBuilder. The error is :
Command GetStaticIconSVG failed with following error:
User error:
ERROR: The annotation for the icon for SelfActingValve is malformed, cannot generate an icon
java exception in java.lang.NullPointerException
Do you know how to handle this error?
I appreciate your help and time you invest to give the advice.

You have two Icon-annotations, at the end of the model, remove one of them.
You can remove the entire annotation if you just want to test it.

Related

OpenModelica Building Libary, using PlugFlowPipe component

This question is a continuation to the question: OpenModelica: How to model plug flow for multi substance fluid medium?
The modelica does not include by default a medium model supporting multiple substances and trace substances. Therefore, I was putting together a medium model that works fine with basic Modelica fluid components (sources, open tanks, static pipe). However, Buildings library's component Buildings.Fluid.FixedResistances.PlugFlowPipe seems to be problematic and I could not figure out why? I have a very limited experience on OpenModelica and would appreciate if someone could help and point out the reasoning.
I have ended up to the Building library since I haven't found any other component in other libraries that would model the plug flow and transfer delays in pipe lines.
Below are the medium model and Simulation model that has been used for testing.
Medium model used:
// file: SimpleMachineStockMedium_400_v1.mo
package SimpleMachineStockMedium_400_v1
import Modelica = Modelica;
// EXTENDING FROM A CLASS
// **************************
extends Modelica.Media.Interfaces.PartialMedium(
final ThermoStates = Modelica.Media.Interfaces.Choices.IndependentVariables.pTX,
final singleState = true,
final reducedX = true,
final fixedX = false,
reference_X=fill(1/nX,nX),
mediumName="SimpleMachineStockMedium_400_v1",
substanceNames={"water","fiber","filler"},
extraPropertiesNames=fill("", 0)
//extraPropertiesNames={"reta"}
);
// SPECIFY CONSTANTS
// *********************************
constant SpecificHeatCapacity cp_const=4184 "Constant specific heat capacity at constant pressure";
constant SpecificHeatCapacity cv_const=4184 "Constant specific heat capacity at constant volume";
constant Density d_const=995.586 "Constant density";
constant DynamicViscosity eta_const=1.e-3 "Constant dynamic viscosity";
constant ThermalConductivity lambda_const=0.598 "Constant thermal conductivity";
constant VelocityOfSound a_const=1484 "Constant velocity of sound";
constant Temperature T_min=273 "Minimum temperature valid for medium model";
constant Temperature T_max=373 "Maximum temperature valid for medium model";
constant Temperature T0=273.15 "Zero enthalpy temperature";
// defining fluid constants for substances
import Modelica.Media.Water.ConstantPropertyLiquidWater.simpleWaterConstants;
constant Modelica.Media.Interfaces.Types.Basic.FluidConstants[3]
simpleWaterConstants(
each chemicalFormula="H2O",
each structureFormula="H2O",
each casRegistryNumber="7732-18-5",
each iupacName="oxidane",
each molarMass=0.018015268);
//constant MolarMass MM_const "Molar mass";
// Molarmasses are defined for substances, just giving same values for all
constant Real MM_const_fiber = 0.018015268;
constant Real MM_const_filler = 0.018015268;
constant Real MM_const_water = 0.018015268;
constant MolarMass[nX] MMX ={MM_const_fiber, MM_const_filler, MM_const_water} "Molar mass";
// THERMODYNAMIC STATE
// **********************
redeclare record extends ThermodynamicState "Thermodynamic state"
AbsolutePressure p "Absolute pressure of medium";
Temperature T "Temperature of medium";
// bring in the substances
MassFraction[nX] X(start=reference_X) "Mass fractions (= (component mass)/total mass m_i/m)";
end ThermodynamicState;
// MODEL BaseProperties
// ********************
redeclare replaceable model extends BaseProperties(
T(stateSelect=if preferredMediumStates then StateSelect.prefer else StateSelect.default),
p(stateSelect=if preferredMediumStates then StateSelect.prefer else StateSelect.default),
Xi(each stateSelect = if preferredMediumStates then StateSelect.prefer else StateSelect.default),
final standardOrderComponents = true) "Base properties"
equation
assert(T >= T_min and T <= T_max, "
Temperature T (= " + String(T) + " K) is not
in the allowed range (" + String(T_min) + " K <= T <= " + String(T_max) + " K)
required from medium model \"" + mediumName + "\".
");
// h = cp_const*(T-T0);
h = specificEnthalpy_pTX(
p,
T,
X);
u = cv_const*(T - T0);
d = d_const;
R_s = 0;
//MM = MM_const;
MM = molarMass(state);
state.T = T;
state.p = p;
state.X = if fixedX then reference_X else X;
annotation (Documentation(info="<html>
<p>
This is the most simple incompressible medium model, where
specific enthalpy h and specific internal energy u are only
a function of temperature T and all other provided medium
quantities are assumed to be constant.
Note that the (small) influence of the pressure term p/d is neglected.
</p>
</html>"));
end BaseProperties;
// DECLARE FUNCTIONS
// *******************
//-------------------
redeclare function setState_pTX
"Return thermodynamic state from p, T, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input Temperature T "Temperature";
input MassFraction X[:]=reference_X "Mass fractions";
output ThermodynamicState state "Thermodynamic state record";
algorithm
//state := ThermodynamicState(p=p, T=T);
// take into account substances
state := if size(X,1) == 0 then ThermodynamicState(p=p,T=T,X=reference_X)
else if size(X,1) == nX then ThermodynamicState(p=p,T=T, X=X)
else ThermodynamicState(p=p,T=T, X=cat(1,X,{1-sum(X)})); // when reduceX = true
end setState_pTX;
//-------------------
redeclare function setState_phX
"Return thermodynamic state from p, h, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input SpecificEnthalpy h "Specific enthalpy";
input MassFraction X[:]=reference_X "Mass fractions";
output ThermodynamicState state "Thermodynamic state record";
algorithm
state := if size(X,1) == 0 then ThermodynamicState(p = p, T = T0 + h / cp_const, X=X)
else if size(X,1) == nX then ThermodynamicState(p = p, T = T0 + h / cp_const, X=X)
else ThermodynamicState(p = p, T = T0 + h / cp_const, X=cat(1,X,{1-sum(X)}));
end setState_phX;
//-------------------
redeclare replaceable function setState_psX
"Return thermodynamic state from p, s, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input SpecificEntropy s "Specific entropy";
input MassFraction X[:]=reference_X "Mass fractions";
output ThermodynamicState state "Thermodynamic state record";
algorithm
//state := ThermodynamicState(p=p, T=Modelica.Math.exp(s/cp_const +
// Modelica.Math.log(reference_T)))
// "Here the incompressible limit is used, with cp as heat capacity";
// take into account substances
state := if size(X,1) == 0 then ThermodynamicState(p = p, T = Modelica.Math.exp(s / cp_const + Modelica.Math.log(reference_T)), X=X)
else if size(X,1) == nX then ThermodynamicState(p = p, T = Modelica.Math.exp(s / cp_const + Modelica.Math.log(reference_T)), X=X)
else ThermodynamicState(p = p, T = Modelica.Math.exp(s / cp_const + Modelica.Math.log(reference_T)), X=cat(1,X,{1-sum(X)}));
end setState_psX;
//-------------------
redeclare function setState_dTX
"Return thermodynamic state from d, T, and X or Xi"
extends Modelica.Icons.Function;
input Density d "Density";
input Temperature T "Temperature";
input MassFraction X[:]=reference_X "Mass fractions";
output ThermodynamicState state "Thermodynamic state record";
algorithm
assert(false,
"Pressure can not be computed from temperature and density for an incompressible fluid!");
end setState_dTX;
//-------------------
redeclare function extends setSmoothState
"Return thermodynamic state so that it smoothly approximates: if x > 0 then state_a else state_b"
algorithm
state := ThermodynamicState(p=Media.Common.smoothStep(
x,
state_a.p,
state_b.p,
x_small), T=Media.Common.smoothStep(
x,
state_a.T,
state_b.T,
x_small));
end setSmoothState;
//-------------------
redeclare function extends dynamicViscosity "Return dynamic viscosity"
algorithm
eta := eta_const;
end dynamicViscosity;
//-------------------
redeclare function extends thermalConductivity
"Return thermal conductivity"
algorithm
lambda := lambda_const;
end thermalConductivity;
//-------------------
redeclare function extends pressure "Return pressure"
algorithm
p := state.p;
end pressure;
//-------------------
redeclare function extends temperature "Return temperature"
algorithm
T := state.T;
end temperature;
//-------------------
redeclare function extends density "Return density"
algorithm
d := d_const;
end density;
//-------------------
redeclare function extends specificEnthalpy "Return specific enthalpy"
algorithm
h := cp_const*(state.T - T0);
end specificEnthalpy;
//-------------------
redeclare function extends specificHeatCapacityCp
"Return specific heat capacity at constant pressure"
algorithm
cp := cp_const;
end specificHeatCapacityCp;
//-------------------
redeclare function extends specificHeatCapacityCv
"Return specific heat capacity at constant volume"
algorithm
cv := cv_const;
end specificHeatCapacityCv;
//-------------------
redeclare function extends isentropicExponent "Return isentropic exponent"
algorithm
gamma := cp_const/cv_const;
end isentropicExponent;
//-------------------
redeclare function extends velocityOfSound "Return velocity of sound"
algorithm
a := a_const;
end velocityOfSound;
//-------------------
redeclare function specificEnthalpy_pTX
"Return specific enthalpy from p, T, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input Temperature T "Temperature";
input MassFraction X[nX] "Mass fractions";
output SpecificEnthalpy h "Specific enthalpy";
algorithm
h := cp_const*(T - T0);
annotation (Documentation(info="<html>
<p>
This function computes the specific enthalpy of the fluid, but neglects the (small) influence of the pressure term p/d.
</p>
</html>"));
end specificEnthalpy_pTX;
//-------------------
redeclare function temperature_phX
"Return temperature from p, h, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input SpecificEnthalpy h "Specific enthalpy";
input MassFraction X[nX] "Mass fractions";
output Temperature T "Temperature";
algorithm
T := T0 + h/cp_const;
end temperature_phX;
//-------------------
redeclare function density_phX "Return density from p, h, and X or Xi"
extends Modelica.Icons.Function;
input AbsolutePressure p "Pressure";
input SpecificEnthalpy h "Specific enthalpy";
input MassFraction X[nX] "Mass fractions";
output Density d "Density";
algorithm
d := density(setState_phX(
p,
h,
X));
end density_phX;
//-------------------
redeclare function extends specificInternalEnergy
"Return specific internal energy"
extends Modelica.Icons.Function;
algorithm
// u := cv_const*(state.T - T0) - reference_p/d_const;
u := cv_const*(state.T - T0);
annotation (Documentation(info="<html>
<p>
This function computes the specific internal energy of the fluid, but neglects the (small) influence of the pressure term p/d.
</p>
</html>"));
end specificInternalEnergy;
//-------------------
redeclare function extends specificEntropy "Return specific entropy"
extends Modelica.Icons.Function;
algorithm
s := cv_const*Modelica.Math.log(state.T/T0);
end specificEntropy;
//-------------------
redeclare function extends specificGibbsEnergy
"Return specific Gibbs energy"
extends Modelica.Icons.Function;
algorithm
g := specificEnthalpy(state) - state.T*specificEntropy(state);
end specificGibbsEnergy;
//-------------------
redeclare function extends specificHelmholtzEnergy
"Return specific Helmholtz energy"
extends Modelica.Icons.Function;
algorithm
f := specificInternalEnergy(state) - state.T*specificEntropy(state);
end specificHelmholtzEnergy;
//-------------------
redeclare function extends isentropicEnthalpy "Return isentropic enthalpy"
algorithm
h_is := cp_const*(temperature(refState) - T0);
end isentropicEnthalpy;
//-------------------
redeclare function extends isobaricExpansionCoefficient
"Returns overall the isobaric expansion coefficient beta"
algorithm
beta := 0.0;
end isobaricExpansionCoefficient;
//-------------------
redeclare function extends isothermalCompressibility
"Returns overall the isothermal compressibility factor"
algorithm
kappa := 0;
end isothermalCompressibility;
//-------------------
redeclare function extends density_derp_T
"Returns the partial derivative of density with respect to pressure at constant temperature"
algorithm
ddpT := 0;
end density_derp_T;
//-------------------
redeclare function extends density_derT_p
"Returns the partial derivative of density with respect to temperature at constant pressure"
algorithm
ddTp := 0;
end density_derT_p;
//-------------------
redeclare function extends density_derX
"Returns the partial derivative of density with respect to mass fractions at constant pressure and temperature"
algorithm
dddX := fill(0, nX);
end density_derX;
//-------------------
redeclare function extends molarMass "Return the molar mass of the medium"
algorithm
//MM := MM_const;
MM := 1/sum(state.X[j]/MMX[j] for j in 1:size(state.X,1));
end molarMass;
// functions that have been adopted from class PARTIALMIXTUREMEDIUM
// -----------------
replaceable function gasConstant
"Return the gas constant of the mixture (also for liquids)"
extends Modelica.Icons.Function;
input ThermodynamicState state "Thermodynamic state";
output SI.SpecificHeatCapacity R_s "Mixture gas constant";
algorithm
R_s := 0;
end gasConstant;
// -----------------
function moleToMassFractions "Return mass fractions X from mole fractions"
extends Modelica.Icons.Function;
input SI.MoleFraction moleFractions[:] "Mole fractions of mixture";
input MolarMass[:] MMX "Molar masses of components";
output SI.MassFraction X[size(moleFractions, 1)]
"Mass fractions of gas mixture";
protected
MolarMass Mmix=moleFractions*MMX "Molar mass of mixture";
algorithm
for i in 1:size(moleFractions, 1) loop
X[i] := moleFractions[i]*MMX[i]/Mmix;
end for;
annotation (smoothOrder=5);
end moleToMassFractions;
// -----------------
function massToMoleFractions "Return mole fractions from mass fractions X"
extends Modelica.Icons.Function;
input SI.MassFraction X[:] "Mass fractions of mixture";
input SI.MolarMass[:] MMX "Molar masses of components";
output SI.MoleFraction moleFractions[size(X, 1)]
"Mole fractions of gas mixture";
protected
Real invMMX[size(X, 1)] "Inverses of molar weights";
SI.MolarMass Mmix "Molar mass of mixture";
algorithm
for i in 1:size(X, 1) loop
invMMX[i] := 1/MMX[i];
end for;
Mmix := 1/(X*invMMX);
for i in 1:size(X, 1) loop
moleFractions[i] := Mmix*X[i]/MMX[i];
end for;
annotation (smoothOrder=5);
end massToMoleFractions;
end SimpleMachineStockMedium_400_v1;
PlugFlowPipe model derived from the example :
model testing_PlugFlowPipe_example_ver01 "Simple example of plug flow pipe"
// Modifications to the example
import Buildings = Buildings;
import Sources = Buildings.Fluid.Sources;
import Sensors = Buildings.Fluid.Sensors;
//replaceable package Medium = Buildings.Media.Water;
replaceable package Medium = SimpleMachineStockMedium_400_v1;
// End of modifications
final parameter Modelica.Units.SI.MassFlowRate m_flow_nominal = 3 "Mass flow rate";
Modelica.Blocks.Sources.Ramp Tin(height = 20, duration = 0, offset = 273.15 + 50, startTime = 100) "Ramp pressure signal" annotation(
Placement(transformation(extent = {{-100, -10}, {-80, 10}})));
Sources.Boundary_pT sin(redeclare package Medium = Medium, T = 273.15 + 10, nPorts = 2, p(displayUnit = "Pa") = 101325) "Pressure boundary condition" annotation(
Placement(transformation(extent = {{100, -10}, {80, 10}})));
Buildings.Fluid.FixedResistances.PlugFlowPipe pip(redeclare package Medium = Medium, dh = 0.1, length = 100, dIns = 0.05, kIns = 0.028, m_flow_nominal = m_flow_nominal, cPip = 500, thickness = 0.0032, initDelay = true, m_flow_start = m_flow_nominal, rhoPip = 8000, T_start_in = 323.15, T_start_out = 323.15) "Pipe" annotation(
Placement(transformation(extent = {{0, 10}, {20, 30}})));
Buildings.HeatTransfer.Sources.FixedTemperature bou[2](each T = 283.15) "Boundary temperature" annotation(
Placement(transformation(extent = {{-40, 60}, {-20, 80}})));
Buildings.Fluid.Sources.MassFlowSource_T sou(redeclare package Medium = Medium, use_T_in = true, m_flow = m_flow_nominal, nPorts = 1) "Flow source" annotation(
Placement(transformation(extent = {{-60, 10}, {-40, 30}})));
Buildings.Fluid.Sensors.TemperatureTwoPort senTemOut(redeclare package Medium = Medium, m_flow_nominal = m_flow_nominal, tau = 0, T_start = 323.15) "Temperature sensor" annotation(
Placement(transformation(extent = {{40, 10}, {60, 30}})));
Buildings.Fluid.Sensors.TemperatureTwoPort senTemIn(redeclare package Medium = Medium, m_flow_nominal = m_flow_nominal, tau = 0, T_start = 323.15) "Temperature sensor" annotation(
Placement(transformation(extent = {{-30, 10}, {-10, 30}})));
Sensors.TemperatureTwoPort senTemInNoMix(redeclare package Medium = Medium, m_flow_nominal = m_flow_nominal, tau = 0, T_start = 323.15) "Temperature sensor" annotation(
Placement(transformation(extent = {{-30, -30}, {-10, -10}})));
Buildings.Fluid.FixedResistances.PlugFlowPipe pipNoMix(have_pipCap = false, redeclare package Medium = Medium, dh = 0.1, length = 100, dIns = 0.05, kIns = 0.028, m_flow_nominal = m_flow_nominal, cPip = 500, thickness = 0.0032, initDelay = true, m_flow_start = m_flow_nominal, rhoPip = 8000, T_start_in = 323.15, T_start_out = 323.15) "Pipe" annotation(
Placement(transformation(extent = {{0, -30}, {20, -10}})));
Sensors.TemperatureTwoPort senTemOutNoMix(redeclare package Medium = Medium, m_flow_nominal = m_flow_nominal, tau = 0, T_start = 323.15) "Temperature sensor" annotation(
Placement(transformation(extent = {{40, -30}, {60, -10}})));
Sources.MassFlowSource_T souNoMix(redeclare package Medium = Medium, use_T_in = true, m_flow = m_flow_nominal, nPorts = 1) "Flow source" annotation(
Placement(transformation(extent = {{-60, -30}, {-40, -10}})));
equation
connect(Tin.y, sou.T_in) annotation(
Line(points = {{-79, 0}, {-68, 0}, {-68, 24}, {-62, 24}}, color = {0, 0, 127}));
connect(pip.port_b, senTemOut.port_a) annotation(
Line(points = {{20, 20}, {40, 20}}, color = {0, 127, 255}));
connect(senTemOut.port_b, sin.ports[1]) annotation(
Line(points = {{60, 20}, {76, 20}, {76, -1}, {80, -1}}, color = {0, 127, 255}));
connect(senTemIn.port_b, pip.port_a) annotation(
Line(points = {{-10, 20}, {0, 20}}, color = {0, 127, 255}));
connect(senTemInNoMix.port_b, pipNoMix.port_a) annotation(
Line(points = {{-10, -20}, {0, -20}}, color = {0, 127, 255}));
connect(pipNoMix.port_b, senTemOutNoMix.port_a) annotation(
Line(points = {{20, -20}, {40, -20}}, color = {0, 127, 255}));
connect(senTemOutNoMix.port_b, sin.ports[2]) annotation(
Line(points = {{60, -20}, {76, -20}, {76, 1}, {80, 1}}, color = {0, 127, 255}));
connect(bou[1].port, pip.heatPort) annotation(
Line(points = {{-20, 70}, {-4, 70}, {-4, 40}, {10, 40}, {10, 30}}, color = {191, 0, 0}));
connect(bou[2].port, pipNoMix.heatPort) annotation(
Line(points = {{-20, 70}, {-4, 70}, {-4, 0}, {10, 0}, {10, -10}}, color = {191, 0, 0}));
connect(sou.ports[1], senTemIn.port_a) annotation(
Line(points = {{-40, 20}, {-30, 20}}, color = {0, 127, 255}));
connect(souNoMix.ports[1], senTemInNoMix.port_a) annotation(
Line(points = {{-40, -20}, {-30, -20}}, color = {0, 127, 255}));
connect(Tin.y, souNoMix.T_in) annotation(
Line(points = {{-79, 0}, {-68, 0}, {-68, -16}, {-62, -16}}, color = {0, 0, 127}));
annotation(
__Dymola_Commands(file = "modelica://Buildings/Resources/Scripts/Dymola/Fluid/FixedResistances/Examples/PlugFlowPipe.mos" "Simulate and Plot"),
experiment(StopTime = 1000, Tolerance = 1e-006),
Documentation(info = "<html>
<p>Basic test of model
<a href=\"modelica://Buildings.Fluid.FixedResistances.PlugFlowPipe\">
Buildings.Fluid.FixedResistances.PlugFlowPipe</a> with and without outlet mixing volume.
This test includes an inlet temperature step under a constant mass flow rate.
</p>
</html>", revisions = "<html>
<ul>
<li>July 27, 2021 by Baptiste Ravache<br/>Add case without mixing volume</li>
</ul>
<ul>
<li>September 8, 2017 by Bram van der Heijde<br/>First implementation</li>
</ul>
</html>"),
Diagram(coordinateSystem(extent = {{-120, -100}, {120, 100}})),
Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}})));
end testing_PlugFlowPipe_example_ver01;
EDIT:
Version information:
MSL 4.0.0
Buildings library:
I was told to use version 9.0.0. The repository https://github.com/lbl-srg/modelica-buildings did not have any specific label for that. I was using the master branch of the repository and the file "modelica-buildings/Buildings/package.mo" gives the version information 9.0.0 (cloned 27.1.2022).
Information obtained from the Buildings library through OMEdit
Error Message (Intergrator method dassl):
Latest Debug message from Transformational debugger
Error Message when using rungekutta integrator:
You need the latest development version of Buildings (the master branch; not any released version) because have_pipCap was only introduced in https://github.com/lbl-srg/modelica-buildings/commit/89bf74035bdea926701edef2c3dbf35f48b32680
Without this Buildings version, you will get the message:
Error: Modified element have_pipCap not found in class PlugFlowPipe.
It is recommended to let the Modelica tool put the annotation uses on the code so the same version can be used for testing. I am assuming this is the problem because the questions did not list the version of Buildings used or the error-message you got.

How to fix incorrect energy conservation problem in mass-spring-system simulation using RK4 method

I am making a simulation where you create different balls of certain mass, connected by springs which you can define (in the program below all springs have natural length L and spring constant k). How I do it is I created a function accel(b,BALLS), (note b is the specific ball and BALLS are all of the ball objects in various stages of update) which gets me acceleration on this one ball from calculating all the forces acting on it (tensions from ball the springs connected to it and gravity) and I would think this function is definitely correct and problems lie elsewhere in the while loop. I then use the RK4 method described on this website: http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf in the while loop to update velocity and position of each ball. To test my understanding of the method I first made a simulation where only two balls and one spring is involved on Desmos: https://www.desmos.com/calculator/4ag5gkerag
I allowed for energy display and saw that indeed RK4 is much better than Euler method. Now I made it in python in the hope that it should work with arbitrary config of balls and springs, but energy isn't even conserved when I have two balls and one spring! I couldn't see what I did differently, at least when two balls on involved. And when I introduce a third ball and a second spring to the system, energy increases by the hundreds every second. This is my first time coding a simulation with RK4, and I expect you guys can find mistakes in it. I have an idea that maybe the problem is caused by because there are multiple bodies and difficulties arises when I update their kas or kvs at the same time but then again I can't spot any difference between what this code is doing when simulating two balls and my method used in the Desmos file. Here is my code in python:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 20
k=10
L=200
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class ball:
def __init__(self, x, y, vx, vy, mass,spr,index,ka,kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr=spr
self.index = index
self.ka=ka
self.kv=kv
def detectbounce(self,width,height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self,v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self,v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
#(self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [ball(1, 19, 0, 0,5,[1],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,1,[0],1,[0,0,0,0],[0,0,0,0])]
# springs = [[0, 1]]
balls = [ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
springs = [[0, 1],[1,2],[0,3],[1,3],[2,3]]
def accel(b,BALLS):
A=[0,g]
for i in range(0,len(b.spr)):
ball1=b
ball2=BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0]+=force/ball1.mass*r1[0]
A[1]+=force/ball1.mass*r1[1]
return A
initE=0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#compute k1a and k1v for all balls
for ball in balls:
ball.ka[0]=accel(ball,balls)
ball.kv[0]=ball.v
#create newb1 based on 'updated' position of all balls with their own k1v
newb=[]
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[0], dt/2)
#compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1]=accel(ball,newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1]=[ball.v[0]+0.5*dt*ball.ka[0][0],ball.v[1]+0.5*dt*ball.ka[0][1]]
#create newb2 based on 'updated' position of all balls with their own k2v
newb=[]
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[1], dt/2)
#compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2]=accel(ball,newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2]=[ball.v[0]+0.5*dt*ball.ka[1][0],ball.v[1]+0.5*dt*ball.ka[1][1]]
newb=[]
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball)
ball.un_update_r(ball.kv[2], dt)
#compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3]=accel(ball,newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3]=[ball.v[0]+dt*ball.ka[2][0],ball.v[1]+dt*ball.ka[2][1]]
#final stage of update
for ball in balls:
if ball.detectbounce(width,height)==True:
ball.bounce_walls(width, height)
else:
ball.v=[ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6, ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r=[ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6, ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0,len(ball.spr)):
ball1=ball
ball2=balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
#check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text = font.render('init Energy: ' + str(round(initE,1))+' '+'KE: ' + str(round(KE, 1)) + ' '+'EPE: ' + str(round(EPE, 1))+' ' + 'GPE: ' + str(round(GPE, 1)) + ' ' + 'Total: ' + str(round(KE+EPE+GPE, 1)) + ' ' + 'Diff: ' + str(round((KE+EPE+GPE-initE), 1)),
True, (255, 255, 255))
textRect = text.get_rect()
textRect.center = (370, 70)
SCREEN.blit(text, textRect)
pygame.display.flip()
This is the edited, corrected by Lutz Lehmann and with some extra improvements:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 5
k = 10
L = 200
digits = 6
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class Ball:
def __init__(self, x, y, vx, vy, mass, spr, index, ka, kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr = spr
self.index = index
self.ka = ka
self.kv = kv
def copy(self):
return Ball(self.r[0], self.r[1], self.v[0], self.v[1], self.mass, self.spr, self.index, self.ka, self.kv)
def detectbounce(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self, v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self, v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
# (self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [Ball(1, 19, 0, 0, 1, [1], 0, [0, 0, 0, 0], [0, 0, 0, 0]),
# Ball(250, 20, 0, 0, 1, [0], 1, [0, 0, 0, 0], [0, 0, 0, 0])]
# springs = [[0, 1]]
balls = [Ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), Ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),Ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),Ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
# n=5
# resprings=[]
# for i in range(0,n):
# for j in range(0,n):
# if i==0 and j==0:
# resprings.append([1,2,n,n+1,2*n])
# if i==n and j==0:
# resprings.apend([n*(n-1)+1,n*(n-1)+2,n*(n-2),n*(n-3),n*(n-2)+1])
# if j==0 and i!=0 or i!=n:
# resprings.append([(i-1)*n+1,(i-1)*n+2,(i-2)*n,(i-2)*n+1,(i)*n,(i)*n+1])
def getsprings(B):
S=[]
for i in range(0,len(B)):
theball=B[i]
for j in range(len(theball.spr)):
spring=sorted([i,theball.spr[j]])
if spring not in S:
S.append(spring)
return S
springs = getsprings(balls)
def accel(b, BALLS):
A = [0, g]
for i in range(0, len(b.spr)):
ball1 = b
ball2 = BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0] += force/ball1.mass*r1[0]
A[1] += force/ball1.mass*r1[1]
return A
initE = 0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for ball in balls:
ball.bounce_walls(width, height)
# compute k1a and k1v for all balls
for ball in balls:
ball.ka[0] = accel(ball, balls)
ball.kv[0] = ball.v
# create newb1 based on 'updated' position of all balls with their own k1v
newb = []
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[0], dt/2)
# compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1] = accel(ball, newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1] = [ball.v[0]+0.5*dt*ball.ka[0]
[0], ball.v[1]+0.5*dt*ball.ka[0][1]]
# create newb2 based on 'updated' position of all balls with their own k2v
newb = []
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[1], dt/2)
# compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2] = accel(ball, newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2] = [ball.v[0]+0.5*dt*ball.ka[1]
[0], ball.v[1]+0.5*dt*ball.ka[1][1]]
newb = []
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball.copy())
ball.un_update_r(ball.kv[2], dt)
# compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3] = accel(ball, newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3] = [ball.v[0]+dt*ball.ka[2][0], ball.v[1]+dt*ball.ka[2][1]]
# final stage of update
for ball in balls:
ball.v = [ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6,
ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r = [ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6,
ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0, len(ball.spr)):
ball1 = ball
ball2 = balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
# check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text1 = font.render(f"initial energy: {str(round(initE, digits))}", True, (255, 255, 255))
text2 = font.render(f"kinetic energy: {str(round(KE, digits))}", True, (255, 255, 255))
text3 = font.render(f"elastic potential energy: {str(round(EPE, digits))}", True, (255, 255, 255))
text4 = font.render(f"gravitational energy: {str(round(GPE, digits))}", True, (255, 255, 255))
text5 = font.render(f"total energy: {str(round(KE + EPE + GPE, digits))}", True, (255, 255, 255))
text6 = font.render(f"change in energy: {str(round(KE + EPE + GPE - initE, digits))}", True, (255, 255, 255))
SCREEN.blit(text1, (10, 10))
SCREEN.blit(text2, (10, 60))
SCREEN.blit(text3, (10, 110))
SCREEN.blit(text4, (10, 160))
SCREEN.blit(text5, (10, 210))
SCREEN.blit(text6, (10, 260))
pygame.display.flip()
The immediate error seems to be this
for ball in balls:
...
newb1.append(ball)
...
as ball is just a reference to the class ball instance, thus newb1 is a list of references to the objects in balls, it makes no difference if you manipulate the one or the other, it is always the same data records that get changed.
You need to apply a copy mechanism, as you have lists of lists, you need a deep copy, or a dedicated copy member method, else you just copy the array references in the ball instances, so you get different instances, but pointing to the same arrays.
It is probably not an error but still a bad idea to have the class name also as variable name in the same scope.

Avoid chattering in a block that finds the index of the highest value in a vector

Modelica Newbie trying to build a block that identifies the highest value of a set of real values (vector).
I tried two versions - one which would just give me the index of the highest value and one which would switch the value of a corresponding boolean vector.
Both failed. For certain high input values the model worked (almost), but most of the time I get an error saying: "Chattering detected around time 0.0277777777839..0.0277777778544 (100 state events in a row with a total time delta less than the step size 0.02). This can be a performance bottleneck. Use -lv LOG_EVENTS for more information. The zero-crossing was: indexMaxBool1.temp > indexMaxBool1.tempMax"
I googled error messages involving chattering and found answers telling to replace the IF-statement by WHEN. I only am affraid, that the latter won't raise the calculation as often as needed (only once??? Does it need a reset?).
I would appreciate getting some hints on how to avoid that chattering.
Here are my models so far:
Block to get the index of the highest value:
block IndexMax
extends Modelica.Blocks.Icons.Block;
parameter Integer nin=1 "Number of inputs";
Modelica.Blocks.Interfaces.RealInput v[nin];
Modelica.Blocks.Interfaces.IntegerOutput index;
protected
Real tempMax;
Real temp;
algorithm
if size(v,1)>1 then
tempMax := v[1];
index := 1;
for i in 2:size(v,1) loop
temp := v[i];
if temp > tempMax then
tempMax := temp;
index := i;
end if;
end for;
else
index :=0;
end if;
end IndexMax;
Block to get a vector of boolean values, with one switched on at the index of the highest input:
block IndexMaxBool
extends Modelica.Blocks.Icons.Block;
parameter Integer nin = 1 "Number of inputs";
Modelica.Blocks.Interfaces.RealInput v[nin];
Modelica.Blocks.Interfaces.BooleanOutput bool[nin];
protected
Real tempMax;
Real temp;
Integer index;
algorithm
if size(v, 1) > 1 then
for i in 1:size(v,1) loop
bool[i] := false;
end for;
tempMax := v[1];
index := 1;
for i in 2:size(v, 1) loop
temp := v[i];
if temp > tempMax then
tempMax := temp;
index := i;
end if;
end for;
else
index := 0;
end if;
if index > 0 then
bool[index]:=true;
end if;
end IndexMaxBool;
The test Models:
model testMax2
Modelica.Blocks.Sources.RealExpression realExpression1(y = 1);
FlowsAndStreams.BDSCalc.IndexMaxBool indexMaxBool1(nin = 3);
Modelica.Blocks.Interfaces.BooleanOutput y;
Modelica.Blocks.Interfaces.BooleanOutput y1;
Modelica.Blocks.Interfaces.BooleanOutput y2;
Modelica.Blocks.Sources.Sine sine1(amplitude = 2, freqHz = 1, phase = 0.785398);
Modelica.Blocks.Sources.Sine sine2(amplitude = 2, freqHz = 3);
equation
connect(sine1.y, indexMaxBool1.v[2]);
connect(sine2.y, indexMaxBool1.v[3]);
connect(indexMaxBool1.bool[3], y2);
connect(indexMaxBool1.bool[1], y);
connect(indexMaxBool1.bool[2], y1);
connect(realExpression1.y, indexMaxBool1.v[1]);
end testMax2;
and:
model testMax
FlowsAndStreams.BDSCalc.IndexMax indexMax1(nin = 3);
Modelica.Blocks.Sources.RealExpression realExpression1(y = 1);
Modelica.Blocks.Sources.RealExpression realExpression2(y = 10);
Modelica.Blocks.Sources.RealExpression realExpression3(y = 3);
Modelica.Blocks.Interfaces.IntegerOutput y;
equation
connect(realExpression3.y, indexMax1.v[3]) annotation(
Line(points = {{-69, -40}, {-8, -40}, {-8, 0}}, color = {0, 0, 127}));
connect(realExpression2.y, indexMax1.v[2]) annotation(
Line(points = {{-69, 0}, {-8, 0}}, color = {0, 0, 127}));
connect(realExpression1.y, indexMax1.v[1]) annotation(
Line(points = {{-69, 30}, {-10, 30}, {-10, 0}, {-8, 0}}, color = {0, 0, 127}));
connect(indexMax1.index, y) annotation(
Line(points = {{10, 0}, {50, 0}}, color = {255, 127, 0}));
end testMax;
I found a solution - but I don't know why it works.
I removed the intermediate variable temp and work instead directly with the v values. Voila!
If v[i]>maxTemp then...
I leave this question here, may it help someone.

Model flow from a pipe

I have been trying to model flow through a pipe that can be partially full, or totally full in modelica and running it in OpenModelica. I have finally reduced the example to essentially just use the area of a circle, and to have no outflow until it is full, then complete outflow. But, I am still getting errors. I have tried it a few different ways. The first way gives an error about solving a nonlinear system once the pipe becomes "filled up". Instead I want it to switch over:
model SimplePipe1
Modelica.SIunits.Area A;
Modelica.SIunits.Mass mass;
Modelica.SIunits.Height level(start = 0.5, fixed = true, min = 0.0, max = 2 * R) "Liquid level (m)";
parameter Modelica.SIunits.Radius R = 1.0 "pipe inner radius (m)";
parameter Real flow_in = 1.0;
Real flow_out;
Modelica.SIunits.Angle phi(start = 3.1, min = 0.0, max = 7.2832) "Angle from center to surface level";
Modelica.SIunits.Area A;
Modelica.SIunits.Mass mass;
Modelica.SIunits.Height level(start = 0.5, fixed = true, min = 0.0, max = 2 * R) "Liquid level (m)";
parameter Modelica.SIunits.Radius R = 1.0 "pipe inner radius (m)";
parameter Real flow_in = 1.0;
Real flow_out;
Modelica.SIunits.Angle phi(start = 3.1, min = 0.0, max = 7.2832) "Angle from center to surface level";
equation
mass = A;
//Assume unit length pipe and unit density
flow_in + flow_out = der(mass);
A = 0.5 * R ^ 2 * (phi - sin(phi));
// phi = if noEvent(level <= 0) then 0 elseif noEvent(level >= 2 * R) then 2 * Modelica.Constants.pi else 2 * acos((R - level) / R);
if noEvent(level <= 0) then
phi = 0;
flow_out = 0;
elseif noEvent(level >= 2 * R) then
phi = 2 * Modelica.Constants.pi;
flow_out = -flow_in;
else
flow_out = 0;
//Partially full pipe has no out outflow
phi = 2 * acos((R - level) / R);
end if;
annotation(Icon, Diagram, experiment(StartTime = 0, StopTime = 10, Tolerance = 1e-06, Interval = 0.02));
end SimplePipe1;
This version seems to give results that are closer to what I want, but it still doesn't work. In this case, the problem is that phi is supposed to be limited to 2*pi. Instead it keeps increasing. Meanwhile, I don't actually see flowmode change. I do see the outflow go negative for a single cycle, then it jumps back to zero. I don't understand what is changing the flowmode back from channel to full, since there is no corresponding "when" to change it back.
model SimplePipe2
type modetype = enumeration(empty, full, channel);
modetype flowmode(start = modetype.channel);
Modelica.SIunits.Area A;
Modelica.SIunits.Mass mass;
Modelica.SIunits.Height level(start = 0.5, fixed = true, min = 0.0, max = 2 * R) "Liquid level (m)";
Modelica.SIunits.Height level_limit;
parameter Modelica.SIunits.Radius R = 1.0 "pipe inner radius (m)";
parameter Real flow_in = 1.0;
Real flow_out;
Modelica.SIunits.Angle phi(start = 3.1, min = 0.0, max = 7.2832) "Angle from center to surface level";
Real flow_out;
initial equation
flowmode = modetype.channel;
equation
mass = A;
//Assume unit length pipe and unit density
flow_in + flow_out = der(mass);
A = 0.5 * R ^ 2 * (phi - sin(phi));
cos(phi / 2) = (R - level) / R;
if flowmode == modetype.empty then
flow_out = 0;
elseif flowmode == modetype.full then
flow_out = -flow_in;
else
flow_out = 0;
//Partially full pipe has no out outflow
end if;
when noEvent(phi >= 2 * Modelica.Constants.pi) then
reinit(flow_out, -flow_in);
reinit(level, 2 * R);
flowmode = modetype.full;
end when;
annotation(Icon, Diagram, experiment(StartTime = 0, StopTime = 10, Tolerance = 1e-06, Interval = 0.02));
end SimplePipe2;
This question that I asked is related to solving the same problem, but doesn't have the issue of a circle/cylinder. And, my second example above is based somewhat on this question.
I am using the newest beta of OpenModelica. My complete model will have other feature that are not included in either of these examples. But, hopefully if I can get this simple version working, I can expand from there.
Your code ends up with a non-linear equation for phi (after level_limit and one flow_out was removed from the model).
0 = 0.5 * R ^ 2.0 * (phi - sin(phi)) - mass
OM solves this without adding constraints for the variable phi. The assertion is instead checked after the solution is found. If you use non-linear solver=kinsol in OpenModelica, the constraints are added to the non-linear equation, but it does not help in this case. I am also a bit unsure if when noEvent() would ever trigger.

Modelica Flow Simulation says divide by zero

I am trying to simulate the flow in a pipe that starts out partially full, and gradually fills up. Once it is full, the flow equation needs to change. I have tried the following code, but I get a divide by zero error once the pipe is full. TestTrap1 is a demo that shows the problem. It seems that some equation is not correct once the pipe is full.
I am using openmodelica to run this.
package FlowPackage
package Interfaces
extends Modelica.Icons.InterfacesPackage;
//Use this for a inlet outlet device
connector Fitting "Inlet port"
flow Modelica.SIunits.MassFlowRate q "Flow rate [Kg/s]";
Modelica.SIunits.Pressure P "Pressure";
annotation(defaultComponentName = "fitting", Icon(coordinateSystem(preserveAspectRatio = true, extent = {{-100, -100}, {100, 100}}), graphics = {Rectangle(extent = {{-100, 100}, {100, -100}}, lineColor = {0, 0, 255}, fillColor = {0, 0, 255}, fillPattern = FillPattern.Solid)}), Diagram(coordinateSystem(preserveAspectRatio = true, extent = {{-100, -100}, {100, 100}}), graphics = {Rectangle(extent = {{-40, 40}, {40, -40}}, lineColor = {0, 0, 255}, fillColor = {0, 0, 255}, fillPattern = FillPattern.Solid), Text(extent = {{-160, 110}, {40, 50}}, lineColor = {0, 0, 255}, textString = "%name")}));
end Fitting;
end Interfaces;
model SourceConstant
parameter Modelica.SIunits.MassFlowRate q = 0.015 "Flow rate [Kg/s]";
Interfaces.Fitting fitting annotation(Placement(visible = true, transformation(origin = {100, -6}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {100, -6}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
equation
-fitting.q = q;
annotation(Icon(graphics = {Rectangle(origin = {-3, 30}, extent = {{-93, 60}, {93, -60}}), Rectangle(origin = {0, 15}, fillColor = {0, 0, 255}, fillPattern = FillPattern.Solid, extent = {{-90, 45}, {90, -45}})}), Diagram);
end SourceConstant;
model ZeroP
parameter Modelica.SIunits.Pressure p = 0.00 "Pressure";
Interfaces.Fitting fitting annotation(Placement(visible = true, transformation(origin = {100, -6}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {100, -6}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
equation
fitting.P = p;
annotation(Icon(graphics = {Rectangle(origin = {-1, 2}, extent = {{-95, 94}, {95, -94}})}), Diagram);
end ZeroP;
model TestTrap1
TrapTemp trap1 ( level(start = 0.0509)) annotation(Placement(visible = true, transformation(origin = {10, 8}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
SourceConstant sourceConstant1(q = 0.03) annotation(Placement(visible = true, transformation(origin = {-50, 26}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
ZeroP zeroP1 (p = 101000) annotation(Placement(visible = true, transformation(origin = {64, 8}, extent = {{10, -10}, {-10, 10}}, rotation = 0)));
equation
connect(sourceConstant1.fitting, trap1.fitting_in) annotation(Line(points = {{-40, 26}, {-18, 26}, {-18, 8}, {0, 8}, {0, 8}}, color = {0, 0, 255}));
connect(trap1.fitting_out, zeroP1.fitting) annotation(Line(points = {{20, 8}, {54, 8}, {54, 8}, {54, 8}}, color = {0, 0, 255}));
annotation(Icon, Diagram, experiment(StartTime = 0, StopTime = 1450, Tolerance = 1e-6, Interval = 2.9));
end TestTrap1;
model TrapTemp
//Calculate the velocity for pipe segments
Interfaces.Fitting fitting_in annotation(Placement(visible = true, transformation(origin = {-100, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {-100, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
Interfaces.Fitting fitting_out annotation(Placement(visible = true, transformation(origin = {98, -2}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {98, -2}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
import m = Modelica.Math;
parameter Modelica.SIunits.Length L = 25000 "pipe length (m)";
parameter Modelica.SIunits.Radius R = 0.10194 / 2.0 "pipe inner radius (m)";
parameter Modelica.SIunits.Height roughness = 6.0e-5 "Average height of surface asperities (default: smooth steel pipe)";
/*Values below are liquid at 300K*/
parameter Modelica.SIunits.DynamicViscosity mu = 0.092977 "Dynamic Viscocity (cp)";
parameter Modelica.SIunits.Density row = 501.22 "Density (kg/m^3)";
Modelica.SIunits.Area A;
Modelica.SIunits.Length s(start = 0.158) "wetted perimeter";
Modelica.SIunits.Length rh "hydraulic radius";
Modelica.SIunits.Diameter De(start = 0.1) "eqivalent diameter [m]";
Modelica.SIunits.ReynoldsNumber Re(start = 3000);
Modelica.SIunits.Velocity v(start = 0.0001);
Modelica.SIunits.Length hf "Friction head loss";
Modelica.SIunits.Acceleration g = Modelica.Constants.g_n;
Modelica.SIunits.Acceleration a "Acceleration of flow";
Modelica.SIunits.CoefficientOfFriction f;
Real relative_roughness;
Real percent_full "Fraction of pipe is full [%]";
Modelica.SIunits.Mass mass;
Real percent_full "Fraction of pipe is full [%]";
Modelica.SIunits.Height level "Liquid level (m)";
equation
percent_full = level / R * 100.0;
relative_roughness = roughness / De;
a = der(v);
s = 2 * level + R;
rh = A / s;
De = 4 * rh;
//Protect against negative Re:
Re = De * abs(v) * row / mu;
//Head loss due to friction. Corrected for direction
hf = sign(v) * f * L * v ^ 2 / (2 * De * g);
f = 64 / Re ;
mass = A * row * L;
fitting_in.q + fitting_out.q = der(mass);
fitting_out.q = -v * A * row;
mass * a = A * (fitting_in.P - fitting_out.P - hf * row * g ) ;
A = R * level;
if noEvent(level >= R) then
//full pipe
fitting_in.q = -fitting_out.q;
else
//partially full pipe
fitting_in.P = fitting_out.P;
end if;
annotation(Icon, Diagram);
end TrapTemp;
annotation(Icon, Diagram);
end FlowPackage;
Any suggestions about why this doesn't work, or how to fix it would be greatly appreciated.
firstly, you need to define what is what you want, I mean, if you want to start storing fluid within the pipe before transferring it, you need to define the set of mass and energy equations to store liquid, i.e: if you want to store liquid you cannot calculate Re, because v=0, and once is filled, swap the model as you did with one conditional and define the navier-stokes equations to define the momentum of the fluid.
Anyway, I think you should try connecting one mass flow rate source, one tank, one valve (define the opening law for the valve in function of the level of the tank), one pipe and one sink with the Modelica Standard library components.