Automatic generation of C++ code for process models – 1 of 3

Coding process models in C++ with the help of LIBPF is easy (try one of our tutorials !), but it’s fair to say that it’s somewhat boring and error prone.

For example if you add an integer parameter to your model you need to make 4 distinct changes:

  1. declare the parameter in the model interface:
    Integer n; ///< number of edges of the base

    (notice the optional but useful doxygen-style comment that described the parameter)

  2. initialize it before the constructor body:
    DEFINE(n, "number of edges of the base", 4)

    (notice how the description quotes the doxygen-style comment above)

  3. register it for reflection in the constructor body:
    addVariable(n);
  4. and finally add the command to retrieve it and define the default, max and min values:
    n = retrieveInteger(defaults, id, persistency, 4, 999, 3); // number of edges of the base

    (again, the comment quotes the doxygen-style comment in the header).

We want to make this workflow easier and less boring, so here is a series of three posts.

In this part one we will present a new way to represent process models as JSON files, and how to generate the required C++ code (interface and implementation) automatically from this representation.

The JSON representation

If you haven’t heard about JSON, that’s an open standard format that uses human-readable text to represent objects consisting of attribute-value pairs, other sub-objects and arrays thereof; think of it as a leaner alternative to XML.

Here is an example JSON file to describe a car:

{
  "wheels": 4,
  "max_speed": 165.0,
  "color": "blue"
}

and here is another one to describe an integer parameter of a LIBPF process model:

{
  "name": "n",
  "description": "number of edges of the base",
  "min": 3,
  "max": 999,
  "value": 4
}

Notice how these two JSON files are made of of attribute-value pairs each separated by a colon, arranged in a list where each pair is separated by a comma, and wrapped in curly brackets: this is a JSON object !

In general a process model can have several integer parameters, so we could define an array attribute integerOptions (arrays are enclosed in square brackets and the elements are separated by commas):

integerOptions: [{ ... }, { ... } ... , { ... } ]

Each of the { ... } sections could be a different integer parameter object, complete with name, description, min, max and value attributes.

For a complete model representation we need quite a few attibutes:

  • name: model name (will be the class name);
  • description: a one-liner description of the model;
  • author: for author and copyright;
  • icon: used for visualization;
  • category: one of flowsheet, phase, stream, unit or option; actually will be set to flowsheet in most cases for user defined models !
  • instantiable: normally set to true;
  • integerOptions: array of integer parameter objects, each complete with name, description, min, max and value fields;
  • stringOptions: array of string parameter objects, each complete with name, description, enumerator and value fields;
  • variables: array of variable objects, each complete with name, description and value fields, with optional unit, input and output fields;
  • units: array of unit operations object, each complete with name, description and type, plus optional arrays of integer and string parameters for configuration;
  • streams: array of material stream objects, each complete with name, description, type, from /portFrom and to/portTo fields, plus optional arrays of integer and string parameters for configuration;
  • cuts: array of cut material streams.

Here is an example of a complete JSON file for a dummy model “Simple”:

{
  "author": "(C) Copyright 2015 Paolo Greppi simevo s.r.l.",
  "name": "Simple",
  "icon": "Simple.svg",
  "description": "Simple process model in JSON format",
  "category": "flowsheet",
  "instantiable": true,
  "integerOptions": [{
    "name": "n",
    "description": "number of stages",
    "min": 3,
    "max": 999,
    "value": 4
  }],
  "stringOptions": [ {
    "enumerator": "processType",
    "name": "processType",
    "description": "adjusts temperature and holding time",
    "value": "HTST15"
  }, {
    "enumerator": "feedType",
    "name": "feedType",
    "description": "sets a predefined composition for the fluid to be processed",
    "value": "chocolateIceCream"
  } ],
  "variables": [
    {
      "input": true,
      "name": "Qin",
      "description": "Thermal power input",
      "value": 0.0,
      "unit": "W"
    },
    {
      "output": true,
      "name": "Qout",
      "description": "Thermal power output",
      "value": 0.0,
      "unit": "W"
    }
  ],
  "streams": [
    { "description": "Feed1", "from": "source", "name": "S01", "portFrom": "out", "portTo": "in", "to": "MIX", "type": "StreamVapor" },
    { "description": "Feed2", "from": "source", "name": "S02", "portFrom": "out", "portTo": "in", "to": "MIX", "type": "StreamLiquid" },
    { "description": "Product", "from": "MIX", "name": "S03", "portFrom": "out", "portTo": "in", "to": "SPL", "type": "StreamIdealLiquidVapor" },
    { "description": "Recycle", "from": "SPL", "name": "S04", "portFrom": "out", "portTo": "in", "to": "MIX", "type": "StreamIdealLiquidVapor" },
    { "description": "Discharge", "from": "SPL", "name": "S05", "portFrom": "out", "portTo": "in", "to": "sink", "type": "StreamIdealLiquidVapor" }
  ],
  "units": [
    { "description": "Mixer", "name": "MIX", "type": "Mixer" },
    { "description": "Three-way valve", "name": "SPL", "type": "Divider",
      "integerOptions": [{"name": "nOutlets", "value": 2}]}
  ],
  "cuts": ["S04"]
}

Formalization of the JSON schema

We can formally define a JSON format to represent a process model using a JSON schema, another JSON file which is kind of “meta” in that it describes how the other JSON files should look like. If you come from the XML world, the JSON schema is similar to the XSD (XML Schema Definition).

A schema is useful for validation but also has other advantages as we’ll se in part 2.

Here is the basic JSON schema for our process models:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 0,
      "maxLength": 50
    },
    "description": {
      "type": "string",
      "minLength": 0,
      "maxLength": 255
    },
    "author": {
      "type": "string"
    },
    "icon": {
      "type": "string",
      "minLength": 0,
      "maxLength": 50
    },
    "category": {
      "type": "string",
      "enum": ["flowsheet", "phase", "stream", "unit", "option"]
    },
    "instantiable": {
      "type": "boolean"
    },
    "time_out": {
      "type": "number"
    },
    "integerOptions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 0,
            "maxLength": 50
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          },
          "min": {
            "type": "integer"
          },
          "max": {
            "type": "integer"
          },
          "value": {
            "type": "integer"
          }
        },
        "additionalProperties": false,
        "required": ["name", "description", "min", "max", "value"]
      }
    },
    "stringOptions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 0,
            "maxLength": 50
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          },
          "enumerator": {
            "type": "string"
          },
          "value": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          }
        },
        "additionalProperties": false,
        "required": ["name", "description", "enumerator", "value"]
      }
    },
    "variables": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 0,
            "maxLength": 50
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          },
          "input": {
            "type": "boolean"
          },
          "output": {
            "type": "boolean"
          },
          "value": {
            "type": "number"
          },
          "unit": {
            "type": "string",
            "enum": ["",
                      "°C",
                      "°F",
                      "$",
                      "$/J",
                      "$/mmBTU",
                      "€",
                      "€/J",
                      "€/kcal",
                      "€/kWh",
                      "€/mmBTU",
                      "€/MWh",
                      "1/K",
                      "1/m",
                      "A",
                      "A/cm2",
                      "A/dm2",
                      "A/ft2",
                      "A/m2",
                      "A/mm2",
                      "atm",
                      "bar",
                      "barg",
                      "bbl",
                      "BTU",
                      "BTU/(hft2F)",
                      "BTU/ft3",
                      "BTU/h",
                      "BTU/h/°F",
                      "BTU/lb",
                      "BTU/lb/°F",
                      "BTU/lbmol",
                      "C",
                      "C/kmol",
                      "cd",
                      "cm",
                      "cm2",
                      "cm3",
                      "cP",
                      "d",
                      "dm",
                      "dm2",
                      "dm3",
                      "F",
                      "ft",
                      "ft2",
                      "ft3",
                      "ft3/h",
                      "g",
                      "g/d",
                      "g/m3",
                      "g/Nm3",
                      "GPU",
                      "GW",
                      "H",
                      "h",
                      "hPa",
                      "Hz",
                      "in",
                      "J",
                      "J/(kg*K)",
                      "J/(kg*K2)",
                      "J/(kg*K3)",
                      "J/(kmol*K)",
                      "J/K",
                      "J/kg",
                      "J/kg/K",
                      "J/kmol",
                      "J/kmol/K",
                      "J/l",
                      "J/m3",
                      "K",
                      "K/Pa",
                      "K2/Pa",
                      "kcal",
                      "kcal/(hm2K)",
                      "kcal/(kg*K)",
                      "kcal/h",
                      "kcal/kg",
                      "kcal/kg/K",
                      "kcal/kmol",
                      "kcal/l",
                      "kcal/m3",
                      "kcal/s",
                      "kg",
                      "kg/(m3*K)",
                      "kg/(m3*K2)",
                      "kg/cm2",
                      "kg/d",
                      "kg/h",
                      "kg/J",
                      "kg/kmol",
                      "kg/m3",
                      "kg/m7",
                      "kg/s",
                      "kJ",
                      "kJ/g",
                      "kJ/kg",
                      "kJ/kg/K",
                      "kJ/kmol",
                      "kJ/kmol/K",
                      "kJ/m3",
                      "km",
                      "kmol/h",
                      "kmol/kg",
                      "kmol/kg/m",
                      "kmol/m3",
                      "kmol/s",
                      "kPa",
                      "kW",
                      "kWh",
                      "kWh/m3",
                      "l",
                      "l/h",
                      "lb",
                      "lb/ft3",
                      "lb/h",
                      "lb/lbmol",
                      "lbmol/ft3",
                      "lbmol/h",
                      "m",
                      "m*K/W",
                      "m*s",
                      "m/s",
                      "m/s2",
                      "m2",
                      "m2*K/W",
                      "m2*K2/W2",
                      "m2/s",
                      "m2/s2",
                      "m3",
                      "m3/h",
                      "m3/kg",
                      "m3/kmol",
                      "m3/s",
                      "mA/cm2",
                      "mbar",
                      "mg",
                      "mg/m3",
                      "mg/Nm3",
                      "min",
                      "MJ/kg",
                      "ml",
                      "mm",
                      "mm2",
                      "mm3",
                      "mmBTU",
                      "mmH2O",
                      "mmHg",
                      "mmol/dm3",
                      "mol",
                      "mol/dm3",
                      "mol/h",
                      "mol/kg",
                      "mol/m3",
                      "mol/s",
                      "MPa",
                      "mPa*s",
                      "mV",
                      "mW",
                      "MW",
                      "MWh",
                      "N",
                      "N*m4",
                      "N/m",
                      "Nm3/d",
                      "Nm3/h",
                      "Nm4kmol-2",
                      "ohm",
                      "ohm*cm",
                      "ohm*cm2",
                      "ohm*m",
                      "ohm*m2",
                      "Pa",
                      "Pa*m6/kmol2",
                      "Pa*m6/kmol2/K",
                      "Pa*m6/kmol2/K2",
                      "Pa*s",
                      "Pa/K",
                      "Pa/K2",
                      "psi",
                      "rad",
                      "rpm",
                      "S",
                      "s",
                      "T",
                      "t",
                      "t/d",
                      "t/h",
                      "t/yr",
                      "Torr",
                      "TW",
                      "ul",
                      "um",
                      "uPa*s",
                      "V",
                      "W",
                      "W/(m*K)",
                      "W/(m2*K)",
                      "W/K",
                      "W/m/K",
                      "W/m2",
                      "W/m2/K",
                      "Wb",
                      "yr"]
          }
        },
        "additionalProperties": false,
        "required": ["name", "description", "value"]
      }
    },
    "units": {
      "type": "array",
      "items": {
        "type": "object",
        "headerTemplate": "{{ i1 }} - {{ self.name }} (type: {{ self.type }})",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 0,
            "maxLength": 50
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          },
          "type": {
            "type": "string",
            "enum": ["Column",
                      "Compressor",
                      "Decanter",
                      "Degasser",
                      "Divider",
                      "Exchanger",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDegasser",
                      "FlashDrum",
                      "FlashSplitter",
                      "FlashSplitter",
                      "FlashSplitter",
                      "FlashSplitter",
                      "FlashSplitter",
                      "FlashSplitter",
                      "HtuNtu",
                      "LiquidRingVacuumPump",
                      "Mixer",
                      "MultiCompressorIntercooled1",
                      "MultiCompressorIntercooled2",
                      "MultiCompressorIntercooled3",
                      "MultiExchanger",
                      "Multiplier",
                      "Other",
                      "Pipe",
                      "PressureSwingAbsorption",
                      "Pump",
                      "Selector",
                      "Separator",
                      "Separator",
                      "Splitter",
                      "Terminator"]
          },
          "integerOptions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 50
                },
                "value": {
                  "type": "integer"
                }
              },
              "additionalProperties": false,
              "required": ["name", "value"]
            }
          },
          "stringOptions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 50
                },
                "value": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 255
                }
              },
              "additionalProperties": false,
              "required": ["name", "value"]
            }
          }
        },
        "additionalProperties": false,
        "required": ["name", "description", "type"]
      }
    },
    "streams": {
      "type": "array",
      "items": {
        "type": "object",
        "headerTemplate": "{{ i1 }} - {{ self.name }} (type: {{ self.type }})",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 0,
            "maxLength": 50
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 255
          },
          "type": {
            "type": "string",
            "enum": ["Other",
                      "StreamEosLiquid",
                      "StreamEosLiquidSolidVapor",
                      "StreamEosLiquidVapor",
                      "StreamEosSolidVapor",
                      "StreamEosVapor",
                      "StreamGerg2004Liquid",
                      "StreamGerg2004LiquidVapor",
                      "StreamGerg2004Vapor",
                      "StreamIapwsLiquid",
                      "StreamIapwsLiquidVapor",
                      "StreamIapwsVapor",
                      "StreamIdealLiquidSolid",
                      "StreamIdealLiquidSolidVapor",
                      "StreamIdealLiquidVapor",
                      "StreamIdealSolidVapor",
                      "StreamLiquid",
                      "StreamNrtl1Liquid",
                      "StreamNrtl1LiquidSolid",
                      "StreamNrtl1LiquidSolidVapor",
                      "StreamNrtl1LiquidVapor",
                      "StreamNrtl2Liquid",
                      "StreamNrtl2LiquidSolid",
                      "StreamNrtl2LiquidSolidVapor",
                      "StreamNrtl2LiquidVapor",
                      "StreamPcsaftLiquid",
                      "StreamPcsaftLiquidVapor",
                      "StreamPcsaftVapor",
                      "StreamSimpleLiquid",
                      "StreamSimpleLiquidSolid",
                      "StreamSimpleLiquidSolidVapor",
                      "StreamSimpleSolid",
                      "StreamSimpleVapor",
                      "StreamSolid",
                      "StreamTabularLiquidLiquid",
                      "StreamTabularLiquidVapor",
                      "StreamVapor"]
          },
          "from": {
            "type": "string"
          },
          "portFrom": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "portTo": {
            "type": "string"
          },
          "integerOptions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 50
                },
                "value": {
                  "type": "integer"
                }
              },
              "additionalProperties": false,
              "required": ["name", "value"]
            }
          },
          "stringOptions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 50
                },
                "value": {
                  "type": "string",
                  "minLength": 0,
                  "maxLength": 255
                }
              },
              "additionalProperties": false,
              "required": ["name", "value"]
            }
          }
        },
        "additionalProperties": false,
        "required": ["name", "description", "type", "from", "portFrom", "to", "portTo"]
      }
    },
    "cuts": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false,
  "required": ["name", "description", "author", "category", "instantiable", "integerOptions", "stringOptions", "variables", "units", "streams", "cuts"]
}

Code generation

Now we can use the JSON representation of the process model to generate the required C++ code. We’ll use Python and jinja2, a full-featured template engine available as a Python extension.

Jinja2 templates are plain text files which closely resemble the files we want to generate. They contain magic commands enclosed in double curly brackets {{ ... }} (variables and/or expressions) which get replaced with values retrieved from a JSON file, and tags enclosed in curly-percent brackets {% ... %} which control the logic of the template.

Here is an example template:

Car{% if female %}a{% else %}o{% endif %} {{ name }}, ciao !

When rendered against this JSON:

{ "name": "Cristina", "female": true }

the template will give you:

Cara Cristina, ciao !

Here it is in a graphical depiction of the process:
jinjaBut let’s try something closer to our application; suppose we are bored of repeating the class name in the constructor interface and implementation. This template:

class {{ name }} {
  public:
  {{ name }}(void);
}; // {{ name }}

{{ name }}::{{ name }}(void) {
  // init stuff here
}

will render like this with the same JSON as above:

class Cristina {
public:
  Cristina(void);
}; // Cristina

Cristina::Cristina(void) {
  // init stuff here
}

Go have a look at the full jinja2 Template Designer Documentation for going deeper in the syntax.

To generate the C++ code we will need two separate templates:

  • interface file (the header file, .h):
    /** @file {{ name }}.h
     *  @brief Interface for the {{ name }} class
     *  @author {{ author }}
     */
    
    #ifndef {{ name | upper }}_H
    #define {{ name | upper }}_H
    
    /* SYSTEM INCLUDES */
    
    /* PROJECT INCLUDES */
    #include <libpf/Model.h>
    #include <libpf/FlowSheet.h>
    
    /* LOCAL INCLUDES */
    
    /* FORWARD REFERENCES */
    
    /** @class {{ name }}
     * @brief {{ description }}
     * 
    {% if stringOptions | length > 0 %} * string options:
    {% for option in stringOptions %} * - {{ option.name }}: {{ option.description }}
    {% endfor %}{% endif %}{% if integerOptions | length > 0 %} * integer options:
    {% for option in integerOptions %} * - {{ option.name }}: {{ option.description }}
    {% endfor %}{% endif %} */
    class {{ name }} : public FlowSheet {
    private:
      const static std::string type_;
    public:
      // LIFECYCLE
      {{ name }}(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);
    
      // CUSTOM variables
    {% for variable in variables %}  Quantity {{ variable.name }}; ///< {{ variable.description }}
    {% endfor %}
    {% for option in stringOptions %}  String {{ option.name }}; ///< {{ option.description }}
    {% endfor %}
    {% for option in integerOptions %}  Integer {{ option.name }}; ///< {{ option.description }}
    {% endfor %}
      // CUSTOM function
    
      // MANDATORY
      const std::string &type(void) const { return type_; }
      void makeUserEquations(std::list::iterator &p) { }
      void setup(void);
      void pre(SolutionMode solutionMode, int level) { }
      void post(SolutionMode solutionMode, int level) { }
    
      // NON-MANDATORY
    }; // {{ name }}
    
    #endif // {{ name | upper }}_H
  • implementation file (the actual C++ file, .cc):
    /** @file {{ name }}.cc
     *  @brief Implementation of {{ name }} class
     *  @author {{ author }}
     */
    
    /* SYSTEM INCLUDES */
    //
    
    /* PROJECT INCLUDES */
    //
    #include <libpf/utility/diagnostic.h>
    
    /* LOCAL INCLUDES */
    //
    #include "{{ name }}.h"
    
    /* LOCAL VARIABLES */
    //
    static const int verbositySubSystem = 0;
    
    /* FUNCTIONS */
    //
    
    const std::string {{ name }}::type_("{{ name }}");
    
    {{ name }}::{{ name }}(Libpf::User::Defaults defaults, uint32_t id, Persistency *persistency, Persistent *parent, Persistent *root) :
      Model(defaults, id, persistency, parent, root),
      VertexBase(defaults, id, persistency, parent, root),
      FlowSheet(defaults, id, persistency, parent, root),
    {% for variable in variables %}{% if variable.unit %}  DEFINE({{ variable.name }}, "{{ variable.description }}", {{ variable.value }}, "{{ variable.unit }}"),{% else %}  DEFINE({{ variable.name }}, "{{ variable.description }}", {{ variable.value }}),{% endif %}
    {% endfor %}
    {% for option in stringOptions %}  DEFINE({{ option.name }}, "{{ option.description }}", "{{ option.value }}"),
    {% endfor %}
    {% for option in integerOptions %}  DEFINE({{ option.name }}, "{{ option.description }}", {{ option.value }}),
    {% endfor %}{
      static const int verbosityLocal = 0;
    
      diagnostic(2, "Entered");
    {% for variable in variables %}  addVariable({{ variable.name }});
    {% endfor %}
    {% for option in stringOptions %}  addVariable({{ option.name }});
    {% endfor %}
    {% for option in integerOptions %}  addVariable({{ option.name }});
    {% endfor %}
      diagnostic(3, "Retrieve string options");
    {% for option in stringOptions %}  {{ option.name }} = retrieveString(defaults, id, persistency, "{{ option.enumerator }}", "{{ option.value }}"); // {{ option.description }}
    {% endfor %}
      diagnostic(3, "Retrieve integer options");
    {% for option in integerOptions %}  {{ option.name }} = retrieveInteger(defaults, id, persistency, {{ option.value }}, {{ option.max }}, {{ option.min }}); // {{ option.description }}
    {% endfor %}
      if (!persistency) {
        diagnostic(2, "Define unit operations");
    {% for unit in units %}    addUnit("{{ unit.type }}", defaults.relay("{{ unit.name }}", "{{ unit.description }}"){% for option in unit.integerOptions %}, ("{{ option.name }}", "{{ option.value }}"){% endfor %}{% for option in unit.stringOptions %}, ("{{ option.name }}", "{{ option.value }}"){% endfor %});
    {% endfor %}
        diagnostic(2, "Define stream objects and connect");
    {% for stream in streams %}    addStream("{{ stream.type }}", defaults.relay("{{ stream.name }}", "{{ stream.description }}"){% for option in stream.integerOptions %}, ("{{ option.name }}", "{{ option.value }}"){% endfor %}{% for option in stream.stringOptions %}, ("{{ option.name }}", "{{ option.value }}"){% endfor %}, "{{ stream.from }}", "{{ stream.portFrom }}", "{{ stream.to }}", "{{ stream.portTo }}");
    {% endfor %}  }
    } // {{ name }}::{{ name }}
    
    void {{ name }}::setup(void) {
      static const int verbosityLocal = 0;
      try {
        diagnostic(2, "Entered for " << tag());     diagnostic(3, "Calling flowsheet::setup to initialize any sub-flowsheet");     FlowSheet::setup();     diagnostic(3, "Setting input variables: feed streams");     /// @TODO     diagnostic(3, "Setting input variables: operating conditions for the unit operations");     /// @TODO     diagnostic(3, "Initializing cut streams");     /// @TODO {% if cuts | length > 0 %}    diagnostic(3, "Defining cut streams");
    {% for cut in cuts %}    addCut("{{ cut }}");
    {% endfor %}{% endif %}
        diagnostic(3, "Making selected inputs/outputs visible in GUI");
    {% for variable in variables %}{% if variable.input %}    {{ variable.name }}.setInput();
    {% endif %}{% if variable.output %}    {{ variable.name }}.setOutput();{% endif %}
    {% endfor %}  } // try
      catch(Error &e) {
        e.append(CURRENT_FUNCTION);
        throw;
      } // catch
    } // {{ name }}::setup

Now we need to feed these templates through Jinja2, and save the results as the cc and h files. There are CLI interfaces to jinja2 around, but we can reach out goal with a simple script j2.py:

#!/usr/bin/env python
# coding=utf-8

# ***** BEGIN LICENSE BLOCK *****
# This file is part of LIBPF
# (C) Copyright 2015 Paolo Greppi simevo s.r.l.
# ***** END LICENSE BLOCK ***** */

""" j2.py: generate cc / h file from JSON DSL """

import sys
import jinja2
import json
import jsonschema

if len(sys.argv) != 4:
    print "usage: %s template.tmpl schema.json variables.json"
    sys.exit(-1)

templateLoader = jinja2.FileSystemLoader(searchpath='.')
templateEnv = jinja2.Environment(loader=templateLoader)
template = templateEnv.get_template(sys.argv[1])

model_file = open(sys.argv[3], 'r')
model_json = model_file.read()
model_file.close()
model = json.loads(model_json)

model_schema_file = open(sys.argv[2], 'r')
model_schema_json = model_schema_file.read()
model_schema_file.close()
model_schema = json.loads(model_schema_json)
jsonschema.validate(model, model_schema)

print template.render(model)

Here is a sample invocation for the j2 script:

./j2.py views/model_h.tmpl model_schema_basic.json Simple.json > Simple.h
./j2.py views/model_cc.tmpl model_schema_basic.json Simple.json > Simple.cc

And here is the generated code, interface:

/** @file Simple.h
 *  @brief Interface for the Simple class
 *  @author (C) Copyright 2015 Paolo Greppi simevo s.r.l.
 */

#ifndef SIMPLE_H
#define SIMPLE_H

/* SYSTEM INCLUDES */

/* PROJECT INCLUDES */
#include <libpf/Model.h>
#include <libpf/FlowSheet.h>

/* LOCAL INCLUDES */

/* FORWARD REFERENCES */

/** @class Simple
 * @brief Simple process model in JSON format
 * 
 * string options:
 * - processType: adjusts temperature and holding time
 * - feedType: sets a predefined composition for the fluid to be processed
 * integer options:
 * - n: number of stages
 */
class Simple : public FlowSheet {
private:
  const static std::string type_;
public:
  // LIFECYCLE
  Simple(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);

  // CUSTOM variables
  Quantity Qin; ///< Thermal power input
  Quantity Qout; ///< Thermal power output

  String processType; ///< adjusts temperature and holding time
  String feedType; ///< sets a predefined composition for the fluid to be processed

  Integer n; ///< number of stages

  // CUSTOM function

  // MANDATORY
  const std::string &type(void) const { return type_; }
  void makeUserEquations(std::list::iterator &p) { }
  void setup(void);
  void pre(SolutionMode solutionMode, int level) { }
  void post(SolutionMode solutionMode, int level) { }

  // NON-MANDATORY
}; // Simple

#endif // SIMPLE_H

and implementation:

/** @file Simple.cc
 *  @brief Implementation of Simple class
 *  @author (C) Copyright 2015 Paolo Greppi simevo s.r.l.
 */

/* SYSTEM INCLUDES */
//

/* PROJECT INCLUDES */
//
#include <libpf/utility/diagnostic.h>

/* LOCAL INCLUDES */
//
#include "Simple.h"

/* LOCAL VARIABLES */
//
static const int verbositySubSystem = 0;

/* FUNCTIONS */
//

const std::string Simple::type_("Simple");

Simple::Simple(Libpf::User::Defaults defaults, uint32_t id, Persistency *persistency, Persistent *parent, Persistent *root) :
  Model(defaults, id, persistency, parent, root),
  VertexBase(defaults, id, persistency, parent, root),
  FlowSheet(defaults, id, persistency, parent, root),
  DEFINE(Qin, "Thermal power input", 0.0, "W"),
  DEFINE(Qout, "Thermal power output", 0.0, "W"),

  DEFINE(processType, "adjusts temperature and holding time", "HTST15"),
  DEFINE(feedType, "sets a predefined composition for the fluid to be processed", "chocolateIceCream"),

  DEFINE(n, "number of stages", 4),
{
  static const int verbosityLocal = 0;

  diagnostic(2, "Entered");
  addVariable(Qin);
  addVariable(Qout);

  addVariable(processType);
  addVariable(feedType);

  addVariable(n);

  diagnostic(3, "Retrieve string options");
  processType = retrieveString(defaults, id, persistency, "processType", "HTST15"); // adjusts temperature and holding time
  feedType = retrieveString(defaults, id, persistency, "feedType", "chocolateIceCream"); // sets a predefined composition for the fluid to be processed

  diagnostic(3, "Retrieve integer options");
  n = retrieveInteger(defaults, id, persistency, 4, 999, 3); // number of stages

  if (!persistency) {
    diagnostic(2, "Define unit operations");
    addUnit("Mixer", defaults.relay("MIX", "Mixer"));
    addUnit("Divider", defaults.relay("SPL", "Three-way valve"), ("nOutlets", "2"));

    diagnostic(2, "Define stream objects and connect");
    addStream("StreamVapor", defaults.relay("S01", "Feed1"), "source", "out", "MIX", "in");
    addStream("StreamLiquid", defaults.relay("S02", "Feed2"), "source", "out", "MIX", "in");
    addStream("StreamIdealLiquidVapor", defaults.relay("S03", "Product"), "MIX", "out", "SPL", "in");
    addStream("StreamIdealLiquidVapor", defaults.relay("S04", "Recycle"), "SPL", "out", "MIX", "in");
    addStream("StreamIdealLiquidVapor", defaults.relay("S05", "Discharge"), "SPL", "out", "sink", "in");
  }
} // Simple::Simple

void Simple::setup(void) {
  static const int verbosityLocal = 0;
  try {
    diagnostic(2, "Entered for " << tag());

    diagnostic(3, "Calling flowsheet::setup to initialize any sub-flowsheet");
    FlowSheet::setup();

    diagnostic(3, "Setting input variables: feed streams");
    /// @TODO

    diagnostic(3, "Setting input variables: operating conditions for the unit operations");
    /// @TODO

    diagnostic(3, "Initializing cut streams");
    /// @TODO

    diagnostic(3, "Defining cut streams");
    addCut("S04");

    diagnostic(3, "Making selected inputs/outputs visible in GUI");
    Qin.setInput();

    Qout.setOutput();
  } // try
  catch(Error &e) {
    e.append(CURRENT_FUNCTION);
    throw;
  } // catch
} // Simple::setup

Limitations

The C++ code generated with this automatic technique will not work out of the box: consider it a starting point !

The technique has a few limitations, mainly caused by the shortcomings of the JSON format; for example JSON does not support multiline strings and requires manual escaping of reserved characters such as quote signs (xml’s CDATA is much better !).

So here are the limitations in detail:

  • it does not fill these sections of the implementation (you’ll find TODO reminders there):
    • Setting input variables: feed streams;
    • Setting input variables: operating conditions for the unit operations;
    • Initializing cut streams;
  • it generates just the basic mandatory member functions (constructor, type and setup); there is no way to generate the advanced mandatory member functions (makeUserEquations, pre and post – for these a default empty implementation is provided) and any optional member function;
  • it is rather rigid w.r.t. unit operations and stream types; if you need a unit and stream type outside the list, use the Other type and then choose the correct type by manually overriding the generated Implementation (.cc file).

In part one of this series of posts we have generated C++ code from a compact representation of process models, using a command-line tool. Watch out for part two, where we’ll show how to make the code generation interactive !

avatar

About paolog

homo technologicus cynicus
This entry was posted in C++, Chemeng, Howtos and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *