5. Development

This section describes the development of the Buildings library. The development of the library is conducted at https://github.com/lbl-srg/modelica-buildings

5.1. Contributing

Contributions of new models and suggestions for how to improve the library are welcome. Contributions are ideally made by first opening an issue at https://github.com/lbl-srg/modelica-buildings and by providing a pull request with the new code.

5.2. Guidelines for contributions

Models, blocks and functions that are contributed need to adhere to the following guidelines, as this is needed to integrate them in the library, make them accessible to users and further maintain them:

  • They should be of general interest to other users and well documented and tested.

  • They need to follow the coding conventions described in

  • They need to be made available under the Modelica Buildings Library license.

  • For models of thermofluid flow components, they need to be based on the base classes in Buildings.Fluid.Interfaces, which are described in the user guide of this package. Otherwise, it becomes difficult to ensure that the implementation is numerically robust.

5.3. Style guide

5.3.1. General

  1. Classes declared as partial and base classes that are not of interest to the user should be stored in a subdirectory called BaseClasses. Each other class, except for constants, must have an icon.

  2. Examples and validation models should be in directories such as Valves.Examples and Valves.Validations. A script for the regression tests must be added as described below.

  3. Do not copy sections of code. Use object inheritance.

  4. Implement components of fluid flow systems by extending the classes in Buildings.Fluid.Interfaces.

  5. Use the full package names when instantiating a class.

  6. Models, functions and blocks must be implemented as one model, function or block per file. An exception are the Buildings.Media packages.

5.3.2. Type declarations

  1. Declare all public parameters before protected ones.

  2. Declare variables and final parameters that are not of interest to users as protected.

  3. Set default parameter values as follows:

    1. If a parameter value can range over a large region, do not provide a default value. Examples are nominal mass flow rates.

    2. If a parameter value does not vary significantly but need to be verified by the user, provide a default value by using its start attribute. For example, for a heat exchanger, use

      parameter Real eps(start=0.8, min=0, max=1, unit="1")
        "Heat exchanger effectiveness";
      

      Do not use parameter Real eps=0.8 as this can lead to errors that are difficult to detect if a modeler forgets to overwrite the default value of 0.8 with the actual value. The model will simulate, but gives wrong results due to unsuited parameter values and there will be no warning. On the other hand, using parameter Real eps(start=0.8) will give a warning and hence users can assign better values.

    3. If a parameter value can be precomputed based on other parameters, set its value to this equation. For example,

      parameter Medium.MassFlowRate m_flow_small(min=0) = 1E-4*m_flow_nominal
      ...
      
    4. If a parameter assignment should not be changed by a user, use the final keyword.

  4. For parameters and variables, provide values for the min and max attribute where applicable. Be aware, that these bounds are not enforced by the simulator. If the min and max attribute are set, each violation of these bounds during the simulation may raise a warning.

    Simulators may allow to suppress these warnings. In Dymola, violation of bounds can be checked using

    Advanced.AssertAllInsideMinMax=true;
    
  5. For any variable or parameter that may need to be solved numerically, provide a value for the start and nominal attribute.

  6. Use types from Modelica.Units.SI where possible, except in the package Buildings.Controls.OBC where the units should be declared as shown in Listing 5.1.

5.3.3. Equations and algorithms

  1. Avoid events where possible.

  2. Only divide by quantities that cannot take on zero. For example, if x may take on zero, use y=x, not 1=y/x, as the second version indicates to a simulator that it is safe to divide by x.

  3. Use the assert function together with "In " + getInstanceName() + ":... to check for invalid values of parameters or variables. For example, use

    assert(phi>=0, "In " + getInstanceName() + ": Relative humidity must not be negative.");
    

    Note the use of getInstanceName(), which will write the instance name as part of the error message. Otherwise, OPTIMICA will not write the instance name.

  4. Use either graphical modeling or textual code. When using graphical schematic modeling, do not add textual equations. For example, avoid the following, as on the graphical editor, the model looks appears to be singular:

    model Avoid
      Modelica.Blocks.Continuous.Integrator integrator "Integrator"
        annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
      equation
      integrator.u = 1;
    end Avoid;
    
  5. For computational efficiency, equations shall were possible be differentiable and have a continuous first derivative.

  6. Avoid equations where the first derivative with respect to another variable is zero. For example, avoid

    \[\begin{split}f(x) = \begin{cases} 0, & \text{for } x < 0 \\ x^2, & \text{otherwise.} \end{cases}\end{split}\]

    because any \(x \le 0\) is a solution, which can cause instability in the solver. Note that this problem do not exist for functions that assign a value to a constant as these will be evaluated during the model translation.

  7. Do not replace an equation by a constant for a single value, unless the derivative of the original equation is zero for this value. For example, if computing a pressure drop dp may involve computing a long equation, but one knows that the result is always zero if the volume flow rate V_flow is zero, one may be inclined to use a construct of the form

    dp = smooth(1, if V_flow == 0 then 0 else f(V_flow));
    

    The problem with this formulation is that for V_flow=0, the derivative is dp/dV_flow = 0. However, the limit dp/dV_flow, as |V_flow| tends to zero, may be non-zero. Hence, the first derivative has a discontinuity at V_flow=0, which can cause a solver to fail to solve the equation because the smooth statement declared that the first derivative exists and is continuous.

  8. Make sure that the derivatives of equations are bounded on compact sets. For example, instead of using y=sign(x) * sqrt(abs(x)), approximate the equation with a differentiable function that has a finite derivative near zero. Use functions form Buildings.Utilities.Math for this approximation.

  9. Whenever possible, a Modelica tool should not have to do numerical differentiation. For example, in Dymola, if your model translation log shows

    Number of numerical Jacobians: 1
    

    (or any number other than zero), then enter on the command line

    Advanced.PrintFailureToDifferentiate = true;
    

    Next, translate the model again to see what functions cannot be differentiated symbolically. Then, implement symbolic derivatives for this function. See implementation of function derivatives.

5.3.4. Functions

  1. Use the smoothOrder annotation if a function is differentiable.

  2. If a function is invertible, also implement its inverse function and use the inverse annotation. See Buildings.Fluid.BaseClasses.FlowModels for an example.

  3. If a model allows a linearized implementation of an equation, then implement the linearized equation in an equation section and not in the algorithm section of a function. Otherwise, a symbolic processor cannot invert the linear equation, which can lead to coupled systems of equations. See Buildings.Fluid.BaseClasses.FlowModels for an example.

5.3.5. Package order

  1. Packages are first sorted alphabetically by the function _sort_package_order. That function is part of BuildingsPy and can be invoked as

    import buildingspy.development.refactor as r
    r.write_package_order(".", True)
    
  2. After alphabetical sorting, the following packages, if they exist, are moved to the front:

    Tutorial
    UsersGuide
    

    and the following packages, if they exist, are moved to the end:

    Data
    Types
    Examples
    Validation
    Benchmarks
    Experimental
    Interfaces
    BaseClasses
    Internal
    Obsolete
    

    The remaining classes are ordered as follows and inserted between the above list: First, models, blocks and records are listed, then functions, and then packages.

5.3.6. Documentation

  1. Add a description string to all parameters and variables, including protected ones.

  2. Group similar variables using the group and tab annotation. For example, use

    parameter Modelica.Units.SI.Time tau = 60
      "Time constant at nominal flow"
      annotation (Dialog(group="Nominal condition"));
    

    or use

    parameter Types.Dynamics substanceDynamics=energyDynamics
      "Formulation of substance balance"
      annotation(Evaluate=true, Dialog(tab = "Assumptions", group="Dynamics"));
    
  3. Add model documentation to the info section. This applies to validation tests as well. To document equations, use the format

    <p>
    The polynomial has the form
    </p>
    <p align="center" style="font-style:italic;">
    y = a<sub>1</sub> + a<sub>2</sub> x + a<sub>3</sub> x<sup>2</sup> + ...,
    </p>
    <p>
    where <i>a<sub>1</sub></i> is ...
    

    To denote time derivatives, such as for mass flow rate, use <code>m&#775;</code>.

    To refer to parameters of the model, use the <code>...</code> section as in

    To linearize the equation, set <code>linearize=true</code>.
    

    To format tables, use

    <p>
    <table summary="summary" border="1" cellspacing="0" cellpadding="2" style="border-collapse:collapse;">
    <tr><th>Header 1</th>       <th>Header 2</th>     </tr>
    <tr><td>Data 1</td>         <td>Data 2</td>       </tr>
    </table>
    </p>
    

    To include figures, place the figure into a directory in Buildings/Resources/Images/ that has the same name as the full package. For example, use

    </p>
    <p align="center">
    <img alt="Image of ..."
    src="modelica://Buildings/Resources/Images/Fluid/FixedResistances/FixedResistanceDpM.png"/>
    </p>
    <p>
    

    To create new figures, put the source file for the figure, preferably in svg format, in the same directory as the png file. svg files can be created with https://inkscape.org/, which works on any operating system. See for example the file in Resources/Images/Examples/Tutorial/SpaceCooling/schematics.svg.

  4. Add author information to the revision section.

  5. Run a spell check.

  6. Start headings with <h4>.

  7. Add hyperlinks to other models using their full name. For example, use

    See
    <a href="modelica://Buildings.Fluid.Sensors.Density">
    Buildings.Fluid.Sensors.Density</a>.
    
  8. Add a default component name, such as

    annotation(defaultComponentName="senDen", ...
    

    to objects that will be used as drag and drop elements, as this automatically assigns them this name.

  9. Keep the line length to no more than around 80 characters.

  10. For complex packages, provide a User’s Guide, and reference the User’s Guide in Buildings.UsersGuide.

  11. Use the string fixme within development branches to mark passages that still need to be revised (e.g., to improve code or to fix bugs). Before merging a branch into the master, all fixme strings must be removed. Within the master branch, no fixme are allowed.

  12. A suggested template for the documentation of classes is below. Except for the short introduction, the sections are optional.

    <p>
    A short introduction.
    </p>
    <h4>Main equations</h4>
    <p>
    xxx
    </p>
    <h4>Assumption and limitations</h4>
    <p>
    xxx
    </p>
    <h4>Typical use and important parameters</h4>
    <p>
    xxx
    </p>
    <h4>Options</h4>
    <p>
    xxx
    </p>
    <h4>Dynamics</h4>
    <p>
    Describe which states and dynamics are present in the model
    and which parameters may be used to influence them.
    This need not be added in partial classes.
    </p>
    <h4>Validation</h4>
    <p>
    Describe whether the validation was done using
    analytical validation, comparative model validation
    or empirical validation.
    </p>
    <h4>Implementation</h4>
    <p>
    xxx
    </p>
    <h4>References</h4>
    <p>
    Add references, if applicable.
    </p>
    
  13. Always use lower case html tags.

5.4. Adding a new class

Adding a new class, such as a model or a function, is usually easiest by extending, or copying and modifying, an existing class. In many cases, the similar component already exists. In this situation, it is recommended to copy and modify a similar component. If both components share a significant amount of similar code, then a base class should be introduced that implements the common code. See for example Buildings.Fluid.Sensors.BaseClasses.PartialAbsoluteSensor which is shared by all sensors with one fluid port in the package Buildings.Fluid.Sensors.

The next sections give guidance that is specific to the implementation of thermofluid flow devices, pressure drop models and control sequences.

5.4.1. Thermofluid flow device

To add a component of a thermofluid flow device, the package Buildings.Fluid.Interface contains basic classes that can be extended. See Buildings.Fluid.Interface.UsersGuide for a description of these classes. Alternatively, simple models such as the models below may be used as a starting point for implementing new models for thermofluid flow devices:

Buildings.Fluid.HeatExchangers.HeaterCooler_u

For a device that adds heat to a fluid stream.

Buildings.Fluid.Humidifiers.Humidifier_u

For a device that adds humidity to a fluid stream.

Buildings.Fluid.Chillers.Carnot_y

For a device that exchanges heat between two fluid streams.

Buildings.Fluid.MassExchangers.ConstantEffectiveness

For a device that exchanges heat and humidity between two fluid streams.

_images/Merkel.svg

Fig. 5.1 Schematic diagram of the cooling tower model based on the Merkel theory.

If models involve complex calculations, then these models are generally easiest to understand for users if these calculations are in a separate block that then interfaces to the fluid flow model using the above basic class. An example is the model Buildings.Fluid.HeatExchangers.CoolingTowers.Merkel that will be released with Buildings 6.0.0. Fig. 5.1 shows the schematic diagram of the model. The block per in the figure implements the thermodynamic calculations. The model shows that the cooling tower performance only depends on the control signal y, the air inlet temperature TAir, the water inlet temperature TWatIn and the water mass flow rate mWat_flow.

5.4.2. Pressure drop

When implementing equations for pressure drop, it is recommended to expand the base class Buildings.Fluid.BaseClasses.PartialResistance. Models should allow computing the flow resistance as a quadratic function with regularization near zero as implemented in Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp and in Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow. The governing equation is

\[k = \frac{\dot m}{\sqrt{\Delta p}}\]

with regularization near zero to avoid that the limit \({d \dot m}/{d \Delta p}\) tends to infinity as \(\dot m \to 0\), as this can cause Newton-based solvers to stall. For fixed flow resistances, \(k\) is typically computed based on nominal conditions such as \(k = \dot m_0/\sqrt{\Delta p_0}\), where \(\dot m_0\) is equal to the parameter m_flow_nominal and \(\Delta p_0\) is equal to the parameter dp_nominal.

All pressure drop models should also provide a parameter that allows replacing the equation by a linear model of the form

\[\dot m \, \dot m_0 = \bar k^2 \, \Delta p\]

Note

Equations for pressure drop are implemented as a function of mass flow rate and not volume flow rate. For some models, this allows decoupling the mass flow balance from the energy balance. Otherwise, computing the mass flow distribution would require knowledge of the density, which may depend on temperature, and temperature is only known after solving the energy balance.

When implementing the pressure drop model, also provide means to

  1. use homotopy, which should be used by default, and

  2. disable the pressure-drop model.

Disabling the pressure-drop model allows, for example, a user to set in a series connection of a heating coil and a cooling coil the pressure drop of the heating coil to zero, and to lump the pressure drop of the heating coil into the pressure drop model of the cooling coil. This often reduces the size of the system of nonlinear equations.

5.4.3. Control sequences using the Control Description Language

To implement reusable control sequences, such as done within the OpenBuildingControl project, the sequences need to comply with the specification of the Control Description Language.

The following rules need to be followed, in addition to the guidelines described in Section 5.2.

  1. The naming of parameters, inputs, outputs and instances must follow the naming conventions in Buildings.UsersGuide.Conventions. Avoid providing duplicate information in the instance name, for example if the block is within the Boilers package, the instance name must not contain boi. Ensure that the instance name is unambiguous when viewed in a top level controller block. Consider whether the block can be used to control other equipment as well, and if so, make sure the instance name is also applicable for these applications.

  2. Parameters that can be grouped together, such as parameters relating to temperature setpoints or to the configuration of the trim and respond logic, should be grouped together with the Dialog(group=STRING)) annotation. See for example G36_PR1.TerminalUnits.Controller. Do not use Dialog(tab=STRING)), unless the parameter is declared with a default value and is of no interest to typical users.

  3. In the source code, the instances must be ordered as follows:

    • First, list Boolean parameters,, then Integer parameters and then Real parameters.

    • Next, list inputs, then outputs, followed by blocks.

    • Protected instances are below all the public instances and follow the same instance ordering rules.

    • Within the above order, list scalar values before arrays, but prioritize groupings based on model specific similarities.

  4. Each block must have a defaultComponentName annotation and a %name label placed above the icon. See for example line 13 in the CDL.Logical.Sources.Constant block.

  5. To aid readability, the formatting of the Modelica source code file must be consistent with other implemented blocks, e.g., use two spaces for indentation (no tabulators), assign each parameter value on a new line. It is recommended to add an empty line between instances. See for example G36_PR1.AHUs.SingleZone.VAV.SetPoints.ExhaustDamper.

  6. For parameters, where generally valid values can be provided, provide them as default values.

  7. Add comments to all instances. The comments should be concise. The comments should not contain redundant information and must not contain hard coded parameters as those can change. If the functionality of an instance is obvious the developer may use comments that closely resemble the class names, such as “Logical And”.

  8. Each block must have an info section that explains its functionality. In this info section, names of parameters, inputs and outputs need to be referenced using the html <code>...</code> element. In the info section, units need to be provided in SI units, or in dual units. For SI units, use Kelvin for temperature differences and degree Celsius for actual temperatures.

  9. For PI controllers, normalize the inputs for setpoint and measured value so that the control error is of the order of one. As control errors for temperature tracking are usually in the order of one, these need not be normalized. But for pressure differentials, which can be thousands of Pascal, normalization aids in providing reasonable control gains and it aids in tuning.

  10. Never use an inequality comparison without a hysteresis or a time delay if the variable that is used in the inequality test is computed using an iterative solver, or is obtained from a measurement and hence can contain measurement noise. An exception is a sampled value because the output of a sampler remains constant until the next sampling instant. See Section 2.5.

  11. CDL uses the following units, which also need to be used in controllers, including their parameters:

    Physical Quantity

    Unit

    Note

    Temperature

    K

    Use displayUnit=degC

    Temperature difference

    K

    Volume flow rate

    m3/s

    Mass flow rate

    kg/s

    Pressure

    Pa

    Use displayUnit=bar

    Pressure differential

    Pa

    Relative humidity

    1

    Range of control signal

    1

    Hence, for example, a controller that takes as an input a temperature and a temperature difference and produces as an output a damper position signal, use a declaration such as shown in the code snippet below in which graphical annotations are omitted.

    Listing 5.1 Unit declaration for CDL.
    Buildings.Controls.OBC.CDL.Interfaces.RealInput TZon(
      final unit="K",
      displayUnit="degC") "Measured zone air temperature";
    
    Buildings.Controls.OBC.CDL.Interfaces.RealInput dTSup(
      final unit="K") "Temperature difference supply air minus exhaust air";
    
    Buildings.Controls.OBC.CDL.Interfaces.RealOutput yDam(
      final min=0,
      final max=1,
      final unit="1") "Exhaust damper position";
    

    Conversion of these units to non-SI units can be done programmatically by tools that process CDL.

  12. Units, quantities and value limits must be declared as final to avoid users to be able to change them, as a change in unit may cause the control logic to be incorrect.

  13. If the block diagram does not fit into the drawing pane, enlarge the drawing pane rather than making the blocks smaller.

  14. The size of the icon should be such that it provides a good fit for all the input and output interfaces. The minimum recommended icon size is 100 by a 100. If there are many interfaces the icon size should be extended in vertical direction. Icons should be symmetrical with reference to the grid origin. E.g, the default specification is

    Icon(
      coordinateSystem(
        preserveAspectRatio=true,
        extent={{-100,-100},{100,100}}))
    
  15. For simple, small controllers, provide a unit test in a Validation or Examples package that is in the hierarchy one level below the implemented controller. See Section 5.5 for unit test implementation. Because some control logic errors may only be noticed when used in a closed loop test, for equipment and system controllers, provide also closed loop examples that test the sequence for all modes of operation. If the closed loop examples include HVAC models, put them outside of the Buildings.Controls.OBC package. Make sure sequences are tested for all modes of operation, and as applicable, for winter, shoulder and summer days.

  16. For general rules on validation models see Section 5.5. If there are multiple instances of the validated block, preferably list them together as opposed to far apart in the Modelica file.

  17. Run the following command to detect various warnings, such as missing comments:

    $ node app.js -f Buildings/Controls/OBC/ASHRAE/PrimarySystem/{path to package} -o json -m cdl
    

5.5. Validation and unit tests

The developer that introduces a new model, block or a function must:

  1. Implement at least one example or validation model that serves as a unit test for each model, block and function, and run the unit tests. Unit tests should cover all branches of if-then constructs and all realistic operating modes of the system represented by the model.

  2. In the info section of the validation model, describe to others the intent of the unit test. For example, an air handler unit controller test could describe “This model verifies that as the cooling load of the room increases, the controller first increases the mass flow rate setpoint and then reduces the supply temperature setpoint.”

The validation models are part of automated unit tests as described at the unit tests wiki page.

For simple models, the validation can be against analytic solutions. This is for example done in Buildings.Fluid.FixedResistances.PressureDrop which uses a regression tests that checks the correct relation between mass flow rate and pressure drop.

For complex thermofluid flow devices, a comparative model validation needs to be done, for example by comparing the result of the Modelica model against the results from EnergyPlus. An example is Buildings.Fluid.HeatExchangers.CoolingTowers.Validation.MerkelEnergyPlus. For such validations, the following files also need to be added to the repository:

  • The EnergyPlus input data file. Please make sure it only requires a weather data file that already exists in the Buildings library.

  • A bash script called run.sh that

    1. runs the EnergyPlus model on Linux, and

    2. invokes a Python script that converts the EnergyPlus output file (see next item).

    This file will automatically be executed as part of the continuous integration testing.

  • A Python script that converts the EnergyPlus output file to the data file that can be read by the Modelica data reader.

See for example Buildings/Resources/Data/Fluid/HeatExchangers/CoolingTowers/Validation for an implementation.