HOWTO migrate existing code to adapt to the major persistency refactoring

Introduction

Following the major persistency refactoring, model developer code has to be refactored accordingly. Here are some hints:

Detailed instructions follow.

Model construction

Model construction is simplified, as the number of classes involved has been reduced:


Before: before


After: after


A Model-derived class should inherit from Model or from a class inheriting from Model or from one or more classes inheriting virtual public Model.

For each Model-derived class Qqq:

class SaltPepper : public Model {
  1. declare only one universal constructor:

     Qqq(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);
    
  2. Define it as a minimum with this template:

     Qqq::Qqq(Libpf::User::Defaults defaults, uint32_t id, Persistency *persistency, Persistent *parent, Persistent *root) :
       Model(defaults, id, persistency, parent, root) {
       static const int verbosityLocal = 0;
       diagnostic(2, "Entered");
       diagnostic(3, "Done");
     }  // Qqq::Qqq
    
  3. if the class declares any variables or parameters (Q, S or I):

     Quantity T;
     String option;
     Integer stages;
    

then add the required DEFINE and addVariable statements (in the same order)

    Qqq::Qqq(Libpf::User::Defaults defaults, uint32_t id, Persistency *persistency, Persistent *parent, Persistent *root) :
      Model(defaults, id, persistency, parent, root) {
      DEFINE(T, "Tolerance for sequential modular mode", 298.15, "K"),
      DEFINE(option, "Configuration option", "default"),
      DEFINE(stages, "Number of stages", 0),
      static const int verbosityLocal = 0;
      diagnostic(2, "Entered");
      addVariable(T);
      addVariable(option);
      addVariable(stages);
  1. if the class declares any parameters (S or I) add the call to readParameters if persistency is not equal to nullptr:

       if (persistency) {
         readParameters(persistency);
       }
       diagnostic(3, "Done");
     }  // Qqq::Qqq
    
  2. if any real variable should be input or output, mark them as such at the end of the constructor:

     T.setOutput();
     diagnostic(3, "Done");
    
  3. if the class declares any vector variable:

     std::vector<Integer> options;
    

add the required addVectorVariable statement in the body:

    addVectorVariable(options, "options", "some integer options vector", 10, 1);
  1. if the class contains any sub-model, add them if persistency is equal to nullptr using addChild, addStream or addUnit, passing a copy of the received defaults with the tag and description:

     if (!persistency) {
       addChild("ReactionType1", defaults.relay("reaction1", "first reaction"));
       addChild("ReactionType2", defaults.relay("reaction2", "second reaction"));
       addStream("StreamSimpleLiquid", defaults.relay("S01", "feed stream"));
       addUnit("Compressor", defaults.relay("C01", "offgas compressor"));
     }
    

these statements were previously inside an if (options.id() == -1) { } block

  1. if any sub-object needs any additional default option set, relay them using the currying syntax:

     addUnit("Column", defaults.relay("C01", "rectification")("stages", 10));
    
  2. if there is any integer or string configuration parameter that is required in the construction, for example to determine the sizing of an array or to be passed as option to a sub-object, retrieve its value before the addVariable statement:

     nStages = retrieveInteger(defaults, id, persistency, "nStages", -1, INT_MAX, 0);
     addVariable(nStages);
    

You need to convert this:

explicit ColumnSection(Options options);

and this:

StreamTabularLiquidLiquid(Options options);

into this:

ColumnSection(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);
StreamTabularLiquidLiquid(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);

search and replace rules:

sed -i 's/class \(.*\) *: *virtual public Model, /class \1: /g' include/*h
sed -i 's/class \(.*\) *: *virtual public Model, /class \1: /g' src/*cc
sed -i 's/explicit \(.*\)(Options options);/\1(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);/g' include/*h
sed -i 's/explicit \(.*\)(Options options);/\1(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);/g' src/*cc
sed -i 's/if (options.id() == -1) {/if (!persistency) {/g' *cc
sed -i 's/\(.*\)(Options options);/\1(Libpf::User::Defaults defaults, uint32_t id=0, Persistency *persistency=nullptr, Persistent *parent=nullptr, Persistent *root=nullptr);/g' ../include/libpf/*.h

Bulk-convert MAKEINTEGER, MAKESTRING and MAKEQUANTITY to DEFINE/addVariable

This statement originally found in the body:

MAKEQUANTITY(conv, 0.0, "kmol/s", "Rate of Reaction");

has to be converted two statements, this one before the body:

DEFINE(conv, "Rate of Reaction", 0.0, "kmol/s"),

and this one in the body:

  addVariable(conv);

Note: The DEFINE syntax is quite similar to the old MAKEQUANTITY, but the argument order reversal was necessary because the DEFINE macro has to be flexible enough to support Integer, String and Quantity variables (as opposed to separate MAKEINTEGER, MAKESTRING and MAKEQUANTITY).

These search and replace rules can be handy:

sed -i 's/^\( *\)MAKESTRING(\(.*\), *"\(.*\)", *"\(.*\)");/  DEFINE(\2, "\4", "\3"),\n\1addVariable(\2);/g' *cc
sed -i 's/^\( *\)MAKEINTEGER(\(.*\), *\(.*\), *"\(.*\)");/  DEFINE(\2, "\4", \3),\n\1addVariable(\2);/g' *cc
sed -i 's/^\( *\)MAKEQUANTITY(\(.*\), *\(.*\), *"\(.*\)", *"\(.*\)");/  DEFINE(\2, "\5", \3, "\4"),\n\1addVariable(\2);/g' *cc

Note: One still has to manually move the converted DEFINEs before the body of the constructor (while the addVariable statements must stay in the body).

modelFactory

modelFactory used to be a pointer

ATM the preferred way to interact with the object factory is instantiating a NodeFactory object and using its registerType and create methods (there is a negligible overhead since the actual data is in a static data structure of the class):

#include <libpf/persistency/NodeFactory.h>
NodeFactory nodeFactory;
nodeFactory.registerType<StreamSimpleLiquid>("StreamSimpleLiquid");
nodeFactory.create(type, defaults, getId(), nullptr, this, root_)

search:

grep -l modelFactory *cc

add the #include to each file ! instantiate the nodeFactory object before accessing it !

search&replace rule:

sed -i 's/#include <libpf\/Factory.h>/#include <libpf\/persistency\/NodeFactory.h>/g' *cc
sed -i 's/modelFactory->Create/nodeFactory.create/g' *cc
sed -i 's/modelFactory->Register/nodeFactory.registerType/g' *cc

Often the object factory is used to create an object with default settings:

nodeFactory.create(out_->type(), Options(-1))

the preferred way to do so is:

nodeFactory.create(out_->type())

search&replace rule:

sed -i 's/nodeFactory.create(\(.*\), Options(-1))/nodeFactory.create(\1)/g' *cc

Another use case is:

nodeFactory.create(type, Options(id))

the preferred way to do so is:

nodeFactory.create(out_->type(), Libpf::User::Defaults(), id, persistency, this);

search&replace rule:

sed -i 's/nodeFactory.create(\(.*\), Options(\(.*\)))/nodeFactory.create(\1, Libpf::User::Defaults(), \2, persistency)/g' *cc

makeVertex / makeEdge

After the refactoring makeVertex is renamed as addUnit and makeEdge is renamed as addStream; options is renamed as default.

Furthermore the way the defaults are passed to the sub-objects and the way the tag and description are set is changed. The syntax is different depending whether options are set and how.

makeVertex without options

search:

grep 'makeVertex(\(.*\), \(.*\),\(.*\), options)' *cc

patterns matched:

makeVertex("Mixer", "M1", "Vapor feed Mixer", options);

transformation:

addUnit("Mixer", defaults.relay("M1", "Vapor feed Mixer"));

test rule:

sed 's/makeVertex(\(.*\), \(.*\),\(.*\), options)/addUnit(\1, defaults.relay(\2, \3));/g' *cc | grep relay

makeVertex with options

search:

grep 'makeVertex(\(.*\), \(.*\),\(.*\), options.copy()\(.*\))' *cc

patterns matched:

makeVertex("Divider", "SPLIT", "Steam splitter", options.copy() ("nStreams", zones_));

transformation:

addUnit("Divider", defaults.relay("SPLIT", "Steam splitter")("nStreams", zones_));

test rule:

sed 's/makeVertex(\(.*\), \(.*\),\(.*\), options.copy()\(.*\))/addUnit(\1, defaults.relay(\2, \3)\4)/g' *cc | grep relay

makeEdge without options

search:

grep 'makeEdge(\(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), options)' *cc

patterns matched:

makeEdge("StreamVapor", "S26", "Vapori", "PK0803", "vapor", "sink", "in", options)

transformation:

addStream("StreamVapor", defaults.relay("S26", "Vapori"), "PK0803", "vapor", "sink", "in")
addStream(type,          defaults,                        from,     port_from, to, port_to);

test rule:

sed 's/makeEdge(\(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), options)/addStream(\1, defaults.relay(\2, \3), \4, \5, \6, \7);/g' *cc | grep relay | grep addStream

with options

search:

grep 'makeEdge(\(.*\), \(.*\),\(.*\), options.copy()\(.*\))' *cc

patterns matched:

makeEdge("Divider", "SPLIT", "Steam splitter", options.copy() ("nStreams", zones_));

transformation:

addStream("Divider", defaults.relay("SPLIT", "Steam splitter")("nStreams", zones_));

test rule:

sed 's/makeEdge(\(.*\), \(.*\),\(.*\), options.copy()\(.*\))/addStream(\1, defaults.relay(\2, \3)\4)/g' *cc | grep relay

cumulative search&replace rules:

sed -i 's/makeVertex(\(.*\), \(.*\),\(.*\), options)/addUnit(\1, defaults.relay(\2, \3));/g' *cc
sed -i 's/makeVertex(\(.*\), \(.*\),\(.*\), options.copy()\(.*\))/addUnit(\1, defaults.relay(\2, \3)\4)/g' *cc
sed -i 's/makeEdge(\(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), options)/addStream(\1, defaults.relay(\2, \3), \4, \5, \6, \7)/g' *cc
sed -i 's/makeEdge<\(.*\)> *(\(.*\), \(.*\), \(.*\), \(.*\), \(.*\), \(.*\), options)/addStream("\1", defaults.relay(\2, \3), \4, \5, \6, \7)/g' *cc

Reading and writing to variables

Typical constructs are:

if (Q("aaaaaa")->value() == 11.0) {
Q("aaaaaa")->set(11.0, "K");
if (I("aaaaaa")->value() == 11) {
I("aaaaaa")->set(11);
if (S("aaaaaa")->value() == "11") {
S("aaaaaa")->set("11");

The Object::Q(), I() and S() accessors now return a const or non-const reference directly to the Value/integral/string value of the element corresponding to the supplied tag in the respective collection of variables, and not anymore a pointer to the Quantity/Integer/String element. And assignment is performed directly from the Value/integral/string value using the matching assignment operator:

/// assign from Value
Quantity &Quantity::operator=(const Value &);
/// assign from int
Integer &Integer::operator=(const int &rhs);
/// assign from string
String &String::operator=(const std::string &rhs);

Also parameters (I, S) now completely lack a set method, while for Q it has been retained as Value::set() because it is also used to mark the Quantity as input (setInput). Another peculiarity of Quantity is that in the previous API there was no Quantity::val(); rather Q(….)->val() invoked Active::val() which returned a double; therefore Q(….)->val() should be converted to Q(….)->toDouble(). For these reasons the above constructs should become:

if (Q("aaaaaa").tDouble() == 11.0) {
Q("aaaaaa").set(11.0, "K");
if (I("aaaaaa") == 11) {
I("aaaaaa") == 11;
if (S("aaaaaa") == "11") {
S("aaaaaa") = "11";

search:

grep 'S(".*")->value()' *cc
grep 'I(".*")->value()' *cc
grep 'S(".*")->set(".*")' *cc
grep 'I(".*")->set(.*)' *cc

grep 'Q(".*", .*)->val()' *cc
grep 'Q(".*")->val()' *cc
grep 'Q(".*", .*)->set(.*)' *.cc
grep 'Q(".*")->set(.*)' *cc

search&replace rules:

sed -i 's/S(\(.*\))->value()/S(\1)/g' *cc
sed -i 's/S(\(.*\))->value()/S(\1)/g' *cc
sed -i 's/I(\(.*\))->value()/I(\1)/g' *cc
sed -i 's/I(\(.*\))->value()/I(\1)/g' *cc
sed -i 's/S("\(.*\)")->set("\(.*\)")/S("\1") = "\2"/g' *cc
sed -i 's/I("\(.*\)")->set(\(.*\))/I("\1") = \2/g' *cc

sed -i 's/Q("\(.*\)", \(.*\))->val()/Q("\1", \2).toDouble()/g' *cc
sed -i 's/Q("\(.*\)")->val()/Q("\1").toDouble()/g' *cc

sed -i 's/Q("\(.*\)", \(.*\))->set(\(.*\))/Q("\1", \2).set(\3)/g' *cc
sed -i 's/Q("\(.*\)")->set(\(.*\))/Q("\1").set(\2)/g' *cc
sed -i 's/Q("\(.*\)", \(.*\))->setOutput(\(.*\))/Q("\1", \2).setOutput(\3)/g' *cc
sed -i 's/Q("\(.*\)")->setOutput(\(.*\))/Q("\1").setOutput(\2)/g' *cc
sed -i 's/Q("\(.*\)", \(.*\))->setInput(\(.*\))/Q("\1", \2).setInput(\3)/g' *cc
sed -i 's/Q("\(.*\)")->setInput(\(.*\))/Q("\1").setInput(\2)/g' *cc

completeTag

completeTag was renamed fullTag

sed -i 's/completeTag/fullTag/g' *cc

error: O was not declared in this scope

The ModelInterface::O accessor which used to return a non-const pointer is replaced by Object::at (returns a const reference or a non-const reference).

Search:

grep 'O(".*", .*)->' *cc
grep 'O(".*")->' *cc

Search and replace:

sed -i 's/O("\(.*\)", \(.*\))->/at("\1", \2)./g' *cc
sed -i 's/O(\(.*\))->/at(\1)./g' *cc

The s&r rule misses some combinations:

*O("SEZ130:ATTACCO:reactions", 1)->Q("coeff", "H2O") * *O("SEZ130:ATTACCO:reactions", 1)->Q("actconv") +

is converted to:

*at("SEZ130:ATTACCO:reactions", 1)->Q("coeff", "H2O") * *O("SEZ130:ATTACCO:reactions", 1).Q("actconv") +

This catch-all can be used at the end for these outliers:

sed -i 's/O(\(.*\))\./at(\1)./g' *cc

my_cast and at

after the ModelInterface::O is bulk replaced with at, with this search:

grep 'my_cast<Stream \*>(operator\[\]' *cc

you may find many errors caused by these constructs:

my_cast<Stream *>(at("S01"), CURRENT_FUNCTION)->copyscale(...)

here my_cast does not work because at returns a reference whereas my_cast expects a pointer

whereas if you search:

grep 'CURRENT_FUNCTION)\.' *cc

you may find many errors caused by these constructs:

my_cast<Stream *>(at("S24"), CURRENT_FUNCTION).clearcomposition();

here the S&R above incorrectly removed the dereference (-> could be required here)

these lines should be turned into:

my_cast<Stream *>(&at("S01"), CURRENT_FUNCTION)->copyscale(...)
my_cast<Stream *>(&at("S24"), CURRENT_FUNCTION)->clearcomposition();

S&R rules:

sed -i 's/CURRENT_FUNCTION)\./CURRENT_FUNCTION)->/g' *cc
sed -i 's/my_cast<\(.*\) \*>(operator/my_cast<\1 *>(\&operator/' *cc
sed -i 's/my_cast<Stream \*>(operator/my_cast<Stream *>(&operator/' *cc

Stream::calculateFeed

there is no calculateFeed anymore; replace with calls to calculate()

RegistrarMymodels

Registrar is declared in a new header file that has to be included by all drivers:

#include &lt;libpf/utility/Registrar.h&gt;

Terminology

We try to avoid the terminology “embedded”. Use “submodel” and “subflowsheet”. Am exception to this rule are string options with label embeddedType*, used to relay type information for subobjects (thise may be converted soon).

sed -i 's/embedded flowsheet/subflowsheet/g' src/*cc
sed -i 's/embedded flowsheet/subflowsheet/g' src/*cc

ideally the word ’embedded’ should never occur:

grep -i embedded src/*cc | grep -v embeddedType

MAXITS

MAXITS was used somewhat inconsistently to pass the number of iterations (if stricly greater than 0) or to signal that the model would be solved inside a simultaneous calculation. We now use setMaximumIterations for the former, and the solutionMode parameter to calculate for the latter.

These constructs:

if (MAXITS == 0) {
::calculate(MAXITS, level)

should be converted to:

if (solutionMode >= Calculatable::simultaneous) {
::calculate(solutionMode, level)

search and replace rule:

sed -i 's/(MAXITS == 0)/(solutionMode >= Calculatable::simultaneous)/g' *.cc
sed -i 's/::calculate(MAXITS, level)/::calculate(solutionMode, level)/g' *.cc

Options

remove all references to the #include <libpf/persistency/Options.h> includes

references to the * collection types

search:

grep 'std::map<std::string, std::unique_ptr<Node> >' *cc
grep 'std::map<std::string, *Integer *\*>' *cc
grep 'std::map<std::string, *Quantity *\*>' *cc
grep 'std::map<std::string, *Quantity *\*>' *cc
grep 'std::map<std::string, *String *\*>' *cc
grep 'std::map<std::string, ' *cc

transformation:

sed -i 's/std::map<std::string, std::unique_ptr<Node> >/Node::CollectionDescendantsType/g' *cc
sed -i 's/objects_/children_/g' *cc
sed -i 's/std::map<std::string, *Integer *\*>/Object::CollectionIntegersType/g' *cc
sed -i 's/std::map<std::string, *Quantity *\*>/Object::CollectionQuantitiesType/g' *cc
sed -i 's/std::map<std::string, *String *\*>/Object::CollectionStringsType/g' *cc
sed -i 's/std::map<std::string, *ModelInterface *\*>/Node::CollectionDescendantsType/g' *cc

resizing vectors of variables

resizing a container of type std::vector<Integer> or std::vector<String> or std::vector<Quantity> is not allowed because there is no default constructor for Integer, String or Quantity. The g++ 4.9 compiler issues the error: no matching function for call to ‘Integer::Integer()’

The solution is to use a combination of reserve and emplace_back as in:

vector.reserve(newsize);
for (uint32_t i = vector.size(); i < newsize; ++i) {
  vector.emplace_back(tag, description, value, this);
}

Iterating on the descendants

There is a lot of constructs similar to:

for (std::map<std::string, ModelInterface *>::const_iterator p = objects_.begin(); p != objects_.end(); ++p) {

this gets transformed into:

for (Node::CollectionDescendantsType::const_iterator p = children_.begin(); p != children_.end(); ++p) {

Note: a better C++11 construct would be:

for (const auto &child : children_) {

But there are further changes required to the code, as p.second now is a std::unique_ptr<Node> and not anymore a ModelInterface *, therefore to access the pointer p.second should be replaced by p.second.get() using std::unique_ptr::get

These constructs are often associated to a dynamic_cast, for example if a Model * is required as in:

Model *f = dynamic_cast<Model *>(p->second);

search:

grep 'dynamic_cast<.* *\*>(p->second)' *cc

test rule:

sed 's/dynamic_cast<\(.*\) *\*>(p->second)/dynamic_cast<\1*>(p->second.get())/g' *cc | grep dynamic_cast | grep get

search&replace rule:

sed -i 's/dynamic_cast<\(.*\) *\*>(p->second)/dynamic_cast<\1*>(p->second.get())/g' *cc

class Node has no member named I/Q/S

This error is caused by an attempt to access the collections of variables on a std::unique_ptr<Node> obtained by iterating on the descendants as in:

for (Node::CollectionDescendantsType::const_iterator p = children_.begin(); p != children_.end(); ++p) {
  p->second->I("toID")

but the I() Q() S() accessors are only available on Objects, not on Nodes

the simple way to work around this is to use the Object::at accessor; the code becomes:

at(p->first).I("toID")

there is (small) cost because the search on the descendants is repeated, assuming p->first could be the full relative label to a recursive descendants (whereas we know it’s a direct descendant); also there is a const-cast involved

error: class Node has no member named setup, initializeNonPersistents,

This error is typically caused by an attempt to access former ModelInterface methods on a std::unique_ptr<Node> obtained by iterating on the descendants as in:

for (Node::CollectionDescendantsType::const_iterator p = children_.begin(); p != children_.end(); ++p) {
  p->second->setup();

The workaround is to cast it dynamically to a Model first:

Model *model = dynamic_cast<Model *>(p->second.get());
if (model != NULL) {
  model->setup();
} // is a Model

error: class GenericValue<GenericActive<double>> has no member named fullTag

This errors is caused by an attempt to access the Quantity::fullTag method (most often for diagnostic) from a Value :.

diagnostic(3, "Set " << j << "-th variable to " << xp_[j]->fullTag() << " with scaling " << scalers_[j]->tostring());

The workaround is to cast it dynamically to a Quantity first:

const Quantity *u = dynamic_cast<const Quantity *>(&(unknown(j)));
if (u != NULL) {
  diagnostic(0, j << " - " << u->fullTag() << " = " << unknown(j).value().printFull());
} else {
  diagnostic(0, j << " = " << unknown(j).value().printFull());
}

catalogRAM

catalogRAM was a dirty hack, a global data structure holding the correspondence between ids and ModelInterface *

this information is not anymore available globally; each node can queried using the exists and search methods for the ids in its own scope (its own + that of all its descendants) the root node of each tree knows about all the ids to the tree

ModelInterface

Previously Model and ModelInterface were different types. Now we only have Model.

search&replace rule:

sed -i 's/static_cast<ModelInterface *\*>/static_cast<Model *>/g' *cc

Cumulative search&replace rule:

Fire this one across all your source files & good luck !

...

error: use of undeclared identifier O

this likely comes from constructs like:

my_cast<FlowSheetBase *>(O(tag), CURRENT_FUNCTION)

or:

O(tag)->aaaaaa...

search:

grep ' O(.*)' *cc
grep '\*O(' *cc
grep '(O(.*)' *cc
grep '\->O(.*)' *cc

use the Object::at accessor

search&replace rule:

sed -i 's/ O(\(.*\))/ at(\1)/g' *cc
sed -i 's/\*O(\(.*\))/*at(\1)/g' *cc
sed -i 's/(O(\(.*\))/(at(\1)/g' *cc
sed -i 's/->O(\(.*\))/->at(\1)/g' *cc

but these accessors now return a const or non-const reference to the element, not a pointer as before.

These changes are best done manually !

newCalculate

the method all Models should implement is Model::calculate()

FlowSheets have an overload calculate(SolutionMode, int)

all occurrences of newCalculate should be transformed into calculate

search&replace rule:

sed -i 's/newCalculate/calculate/g' *cc
sed -i 's/newCalculate/calculate/g' ../include/libpf/*h

calculateResiduals

The previous signature of calculateResiduals was:

void calculateResiduals(Quantity *y);

the residuals were stored in a std::vector<Quantity> ObjectiveNleAuto::y_ and the calculateResiduals would be called by different objectve functions inside a larger object with different offsets:

calculateResiduals(&y_[0]);
calculateResiduals(&y_[100]);

The new signature of calculateResiduals is:

int calculateResiduals(std::vector<Value> &y, uint32_t offset);

the previous calls should be changed to:

calculateResiduals(y_, 0);
calculateResiduals(y_, 100);

Implementations should be changed to access the passed vector from the offset and to return the number of evaluated residuals.

Bulk-convert MAKEINTEGERVECTOR, MAKESTRINGVECTOR and MAKEQUANTITYVECTOR to DEFINE/addVariable

Turn these: 2 3 4 5 6 MAKEINTEGERVECTOR( nReactions.val(), reactionSides, -1, “heat exchange side the reactions are taking place”); MAKESTRINGVECTOR( nRatings.val(), embeddedTypeRatings, “”, “Type of RatingColumn embedded class”); MAKEQUANTITYVECTOR(stages_ + 2, T, t0, “K”, “Temperature profile”);

into these:

                  3              "3"              "5S"                                                  2                 4
addVectorVariable(reactionSides, "reactionSides", "heat exchange side the reactions are taking place", nReactions.val(), -1);
                  3                    "3"                    "5"                                      2               "4"
addVectorVariable(embeddedTypeRatings, "embeddedTypeRatings", "Type of RatingColumn embedded class", nRatings.val(), "");
                  3  "3"   "6"                     2            4   "5"
addVectorVariable(T, "T", "Temperature profile", stages_ + 2, t0, "K");

search:

grep MAKEINTEGERVECTOR *cc
grep MAKESTRINGVECTOR *cc
grep MAKEQUANTITYVECTOR *cc

search and replace:

sed -i 's/^\( *\)MAKEINTEGERVECTOR(\(.*\), *\(.*\), *\(.*\), *"\(.*\)");/\1addVectorVariable(\3, "\3", "\5", \2, \4);/g' *cc
sed -i 's/^\( *\)MAKESTRINGVECTOR(\(.*\), *\(.*\), *"\(.*\)", *"\(.*\)");/\1addVectorVariable(\3, "\3", "\5", \2, "\4");/g' *cc
sed -i 's/^\( *\)MAKEQUANTITYVECTOR(\(.*\), *\(.*\), *\(.*\), *"\(.*\)", *"\(.*\)");/\1addVectorVariable(\3, "\3", "\6", \2, \4, "\5");/g' *cc

Bulk-convert MAKEQUANTITYCOMPONENTVECTOR

Turn these:

                            2      3  4   5
MAKEQUANTITYCOMPONENTVECTOR(coeff, 0, "", "Molar stoichiometric coefficient");

into these:

                           2  "2"   "5"                   3   "4"
addComponentVectorVariable(T, "T", "Temperature profile", t0, "K");

search:

grep MAKEQUANTITYCOMPONENTVECTOR *cc

search and replace:

sed -i 's/^\( *\)MAKEQUANTITYCOMPONENTVECTOR(*\(.*\), *\(.*\), *"\(.*\)", *"\(.*\)");/\1addComponentVectorVariable(\2, "\2", "\5", Value(\3, "\4"));/g' *cc

Bulk-converting reactions

These search & replace rules were useful for reactions.cc:

sed -i 's/\(Reaction.*\)::.*(Options options) :/\1::\1(Libpf::User::Defaults defaults, uint32_t id, Persistency *persistency, Persistent *parent, Persistent *root) :/g' reactions.cc
grep -v '^ *PersistentInterface(options.id()),' reactions.cc > qq
cp qq reactions.cc 
grep -v '^ *Persistent(options.id()),' reactions.cc > qq
cp qq reactions.cc 
grep -v '^ *Model(options.id()),' reactions.cc > qq
cp qq reactions.cc 
sed -i 's/Model(options.id()), *//g' reactions.cc 
sed -i 's/Reaction\(.*\)(options)/Reaction\1(defaults, id, persistency, parent, root)/g' reactions.cc 
sed -i 's/PersistentInterface(options.id()), *//g' reactions.cc 
sed -i 's/Persistent(options.id()), *//g' reactions.cc 

ltoa

ltoa and boost::lexical_cast<std::string> have been phased out in favor of C++11’s std::to_string.

Bulk-convert ltoa with:

sed -i 's/ltoa(/std::to_string(/g' src/*cc

Manually search and replace boost::lexical_cast instances and remove if possible the dependency on the boost/lexical_cast.hpp header file.

HeatRating -> RatingHeat and RatingTray -> RatingColumnTray

Due to the way addSubObjects works, it is required that reaction types start with Reaction, heat rating types start with RatingHeat and column rating start with RatingColumn.

Bulk-replace with:

sed -i 's/HeatRating/RatingHeat/g' *cc
sed -i 's/HeatRating/RatingHeat/g' ../include/libpf/*h
sed -i 's/RatingTray/RatingColumnTray/g' ../include/libpf/*h
sed -i 's/RatingTray/RatingColumnTray/g' *cc

This also applies to terminal, concrete classes derived from those, so:

sed -i 's/ShellAndTubeFallingFilmReboiler/RatingHeatShellAndTubeFallingFilmReboiler/g' *cc ../include/libpf/*h
sed -i 's/ShellAndTubeHeater/RatingHeatShellAndTubeHeater/g' *cc ../include/libpf/*h
sed -i 's/ShellAndTubeThermosiphon/RatingHeatShellAndTubeThermosiphon/g' *cc ../include/libpf/*h

Note: these identifiers are 44, 28 and 34 characters long, and share the first 22. The maximum identifier length in C according to this is 63 significant initial characters in an internal identifier or a macro name and 31 significant initial characters in an external identifier. According to this, the C++ standard mandates at least 1024.