Example #1
0
QStringList CellmlFileRuntime::componentHierarchy(iface::cellml_api::CellMLElement *pElement)
{
    // Make sure that we have a given element

    if (!pElement)
        return QStringList();

    // Try to retrieve the component that owns the given element, unless the
    // given element is a component itself (which will be the case when we come
    // here through recursion)

    ObjRef<iface::cellml_api::CellMLComponent> component = QueryInterface(pElement);

    ObjRef<iface::cellml_api::CellMLElement> parent = pElement->parentElement();
    ObjRef<iface::cellml_api::CellMLComponent> parentComponent = QueryInterface(parent);

    if (!component && !parentComponent) {
        // The element isn't a component and neither is its parent, so it
        // doesn't have a hierarchy

        return QStringList();
    }

    // Check whether this is an imported component and, if so, retrieve its
    // imported name

    QString componentName = QString::fromStdWString(component?component->name():parentComponent->name());

    ObjRef<iface::cellml_api::CellMLElement> componentParent = component?component->parentElement():parentComponent->parentElement();
    ObjRef<iface::cellml_api::CellMLElement> componentParentParent = componentParent->parentElement();

    if (componentParentParent) {
        // The given element comes from or is an imported component, so go
        // through our different imported components and look for the one we are
        // after

        ObjRef<iface::cellml_api::CellMLImport> import = QueryInterface(componentParentParent);
        ObjRef<iface::cellml_api::ImportComponentSet> importComponents = import->components();
        ObjRef<iface::cellml_api::ImportComponentIterator> importComponentsIter = importComponents->iterateImportComponents();

        for (ObjRef<iface::cellml_api::ImportComponent> importComponent = importComponentsIter->nextImportComponent();
             importComponent; importComponent = importComponentsIter->nextImportComponent()) {
            if (!componentName.compare(QString::fromStdWString(importComponent->componentRef()))) {
                // This is the imported component we are after, so retrieve its
                // imported name

                componentName = QString::fromStdWString(importComponent->name());

                break;
            }
        }
    }

    // Recursively retrieve the component hierarchy of the given element's
    // encapsulation parent, if any

    ObjRef<iface::cellml_api::CellMLComponent> componentEncapsulationParent = component?component->encapsulationParent():parentComponent->encapsulationParent();

    return componentHierarchy(componentEncapsulationParent) << componentName;
}
std::wstring CompactorReport::getReport() const
{
    std::wstringstream report;
    report << L"Model Compaction Report\n"
           << L"=======================\n\n";
    if (! mErrorMessage.empty()) report << L"Error message: " << mErrorMessage << L"\n\n";
    std::wstring indent = L"";
    if (mVariableForCompaction.size() > 0)
    {
        report << L"Some variables have not been compacted.\n"
               << L"Uncompacted variables are given below.\n\n";
        for (size_t i=0; i<mVariableForCompaction.size(); ++i)
        {
            ObjRef<iface::cellml_api::CellMLVariable> variable = mVariableForCompaction[i].first;
            ObjRef<iface::cellml_api::CellMLVariable> srcVariable = mVariableForCompaction[i].second;
            std::wstring modelUri = variable->modelElement()->base_uri()->asText();
            std::wstring srcModelUri = srcVariable->modelElement()->base_uri()->asText();
            for (size_t j=0;j<i;++j) indent += L"\t";
            report << indent << L"Compaction requested for variable: " << modelUri << L" # "
                   << variable->componentName() << L" / "
                   << variable->name() << L";\n" << indent << L"with the actual source variable being: "
                   << srcModelUri << L" # " << srcVariable->componentName() << L" / " << srcVariable->name() << L"\n";
        }
    }
    report.flush();
    return report.str();
}
Example #3
0
void Tests::basicTests()
{
    // Some very basic tests to make sure that we have access to the CellML API

    // Get a bootstrap object and its model loader

    ObjRef<iface::cellml_api::CellMLBootstrap> cellmlBootstrap = CreateCellMLBootstrap();
    ObjRef<iface::cellml_api::DOMModelLoader> modelLoader = cellmlBootstrap->modelLoader();

    QVERIFY(cellmlBootstrap);
    QVERIFY(modelLoader);

    // Create a CellML 1.0 model

    ObjRef<iface::cellml_api::Model> cellml10Model = cellmlBootstrap->createModel(L"1.0");

    QVERIFY(cellml10Model);

    // Create a CellML 1.1 model

    ObjRef<iface::cellml_api::Model> cellml11Model = cellmlBootstrap->createModel(L"1.1");

    QVERIFY(cellml11Model);

    // Create an invalid CellML model

    ObjRef<iface::cellml_api::Model> invalidCellmlModel;

    try {
        invalidCellmlModel = cellmlBootstrap->createModel(L"xxx");
    } catch (...) {
        QVERIFY(!invalidCellmlModel);
    }

    // Load an existing model

    ObjRef<iface::cellml_api::Model> existingModel = modelLoader->loadFromURL(QUrl::fromPercentEncoding(QUrl::fromLocalFile(QFileInfo(OpenCOR::fileName("models/noble_model_1962.cellml")).canonicalFilePath()).toEncoded()).toStdWString());

    QVERIFY(existingModel);

    QCOMPARE(QString::fromStdWString(existingModel->name()), QString("noble_model_1962"));
    QCOMPARE(QString::fromStdWString(existingModel->cellmlVersion()), QString("1.0"));

    // Load a non-existing model

    ObjRef<iface::cellml_api::Model> nonExistingModel;

    try {
        nonExistingModel = modelLoader->loadFromURL(L"xxx");
    } catch (...) {
        QVERIFY(!nonExistingModel);
    }
}
void
WriteCode(iface::cellml_services::CodeInformation* cci, uint32_t useida)
{
  iface::cellml_services::ModelConstraintLevel mcl =
    cci->constraintLevel();
  if (mcl == iface::cellml_services::UNDERCONSTRAINED)
  {
    ObjRef<iface::cellml_services::ComputationTarget> ctMissingIV(cci->missingInitial());
    if (ctMissingIV != NULL)
    {
      ObjRef<iface::cellml_api::CellMLVariable> v = ctMissingIV->variable();
      std::wstring n = v->name();
      std::wstring c = v->componentName();
      std::wstring str;
      uint32_t deg = ctMissingIV->degree();
      if (deg != 0)
      {
        str += L"d^";
        wchar_t buf[20];
        any_swprintf(buf, 20, L"%u", deg);
        str += buf;
        str += L"/dt^";
        str += buf;
        str += L" ";
      }
      str += n;
      str += L" (in ";
      str += c;
      str += L")\n";
      printf("/* Model is underconstrained due to missing initial_value on %S\n", str.c_str());
    }
    else
    {
      printf("/* Model is underconstrained.\n"
             " * List of undefined targets follows...\n");
      iface::cellml_services::ComputationTargetIterator* cti = cci->iterateTargets();
      iface::cellml_services::ComputationTarget* ct;
      std::vector<std::wstring> messages;
      while (true)
      {
        ct = cti->nextComputationTarget();
        if (ct == NULL)
          break;
        if (ct->type() != iface::cellml_services::FLOATING)
        {
          ct->release_ref();
          continue;
        }
        iface::cellml_api::CellMLVariable* v = ct->variable();
        std::wstring n = v->name();
        std::wstring c = v->componentName();
        std::wstring str = L" * * ";
        uint32_t deg = ct->degree();
        if (deg != 0)
        {
          str += L"d^";
          wchar_t buf[20];
          any_swprintf(buf, 20, L"%u", deg);
          str += buf;
          str += L"/dt^";
          str += buf;
          str += L" ";
        }
        str += n;
        str += L" (in ";
        str += c;
        str += L")\n";
        messages.push_back(str);
        v->release_ref();
      ct->release_ref();
      }
      cti->release_ref();
      // Sort the messages...
      std::sort(messages.begin(), messages.end());
      std::vector<std::wstring>::iterator msgi;
      for (msgi = messages.begin(); msgi != messages.end(); msgi++)
        printf("%S", (*msgi).c_str());
      printf(" */\n");
    }
    return;
  }
  else if (mcl == iface::cellml_services::OVERCONSTRAINED)
  {
    printf("/* Model is overconstrained.\n"
           " * List variables defined at time of error follows...\n");
    iface::cellml_services::ComputationTargetIterator* cti = cci->iterateTargets();
    iface::cellml_services::ComputationTarget* ct;
    std::vector<std::wstring> messages;
    while (true)
    {
      ct = cti->nextComputationTarget();
      if (ct == NULL)
        break;
      if (ct->type() == iface::cellml_services::FLOATING)
      {
        ct->release_ref();
        continue;
      }
      iface::cellml_api::CellMLVariable* v = ct->variable();
      std::wstring n = v->name();
      std::wstring str = L" * * ";
      uint32_t deg = ct->degree();
      if (deg != 0)
      {
        str += L"d^";
        wchar_t buf[20];
        any_swprintf(buf, 20, L"%u", deg);
        str += buf;
        str += L"/dt^";
        str += buf;
        str += L" ";
      }
      str += n;
      str += L"\n";
      messages.push_back(str);
      v->release_ref();
      ct->release_ref();
    }
    cti->release_ref();

    // Sort the messages...
    std::sort(messages.begin(), messages.end());
    std::vector<std::wstring>::iterator msgi;
    for (msgi = messages.begin(); msgi != messages.end(); msgi++)
      printf("%S", (*msgi).c_str());

    // Get flagged equations...
    iface::mathml_dom::MathMLNodeList* mnl = cci->flaggedEquations();
    printf(" * Extraneous equation was:\n");
    iface::dom::Node* n = mnl->item(0);
    mnl->release_ref();
    iface::dom::Element* el =
      reinterpret_cast<iface::dom::Element*>(n->query_interface("dom::Element"));
    n->release_ref();

    std::wstring cmeta = el->getAttribute(L"id");
    if (cmeta == L"")
      printf(" *   <equation with no cmeta ID>\n");
    else
      printf(" *   %S\n", cmeta.c_str());

    n = el->parentNode();
    el->release_ref();

    if (n != NULL)
    {
      el = reinterpret_cast<iface::dom::Element*>
        (n->query_interface("dom::Element"));
      n->release_ref();

      cmeta = el->getAttribute(L"id");
      if (cmeta == L"")
        printf(" *   in <math with no cmeta ID>\n");
      else
        printf(" *   in math with cmeta:id %S\n", cmeta.c_str());

      el->release_ref();
    }

    printf(" */\n");
    return;
  }
  else if (mcl == iface::cellml_services::UNSUITABLY_CONSTRAINED)
  {
    printf("/* Model is unsuitably constrained (i.e. would need capabilities"
           " beyond those of the CCGS to solve).\n"
           " * The status of variables at time of error follows...\n");
    iface::cellml_services::ComputationTargetIterator* cti = cci->iterateTargets();
    iface::cellml_services::ComputationTarget* ct;
    std::vector<std::wstring> messages;
    while (true)
    {
      ct = cti->nextComputationTarget();
      if (ct == NULL)
        break;
      std::wstring str = L" * * ";
      if (ct->type() == iface::cellml_services::FLOATING)
        str += L" Undefined: ";
      else
        str += L" Defined: ";

      uint32_t deg = ct->degree();
      if (deg != 0)
      {
        str += L"d^";
        wchar_t buf[20];
        any_swprintf(buf, 20, L"%u", deg);
        str += buf;
        str += L"/dt^";
        str += buf;
        str += L" ";
      }
      iface::cellml_api::CellMLVariable* v = ct->variable();
      std::wstring n = v->name();
      str += n;
      str += L"\n";
      messages.push_back(str);
      v->release_ref();
      ct->release_ref();
    }
    cti->release_ref();

    // Sort the messages...
    std::sort(messages.begin(), messages.end());
    std::vector<std::wstring>::iterator msgi;
    for (msgi = messages.begin(); msgi != messages.end(); msgi++)
      printf("%S", (*msgi).c_str());

    printf(" */\n");
    return;
  }

  printf("/* Model is correctly constrained.\n");
  iface::mathml_dom::MathMLNodeList* mnl = cci->flaggedEquations();
  uint32_t i, l = mnl->length();
  if (l == 0)
    printf(" * No equations needed Newton-Raphson evaluation.\n");
  else
    printf(" * The following equations needed Newton-Raphson evaluation:\n");

  std::vector<std::wstring> messages;
  for (i = 0; i < l; i++)
  {
    iface::dom::Node* n = mnl->item(i);
    iface::dom::Element* el =
      reinterpret_cast<iface::dom::Element*>(n->query_interface("dom::Element"));
    n->release_ref();

    std::wstring cmeta = el->getAttribute(L"id");
    std::wstring str;
    if (cmeta == L"")
      str += L" *   <equation with no cmeta ID>\n";
    else
    {
      str += L" *   ";
      str += cmeta;
      str += L"\n";
    }

    n = el->parentNode();
    el->release_ref();

    el = reinterpret_cast<iface::dom::Element*>
      (n->query_interface("dom::Element"));
    n->release_ref();

    cmeta = el->getAttribute(L"id");
    if (cmeta == L"")
      str += L" *   in <math with no cmeta ID>\n";
    else
    {
      str += L" *   in math with cmeta:id ";
      str += cmeta;
      str += L"\n";
    }
    el->release_ref();

    messages.push_back(str);
  }
  mnl->release_ref();

  // Sort the messages...
  std::sort(messages.begin(), messages.end());
  std::vector<std::wstring>::iterator msgi;
  for (msgi = messages.begin(); msgi != messages.end(); msgi++)
    printf("%S", (*msgi).c_str());
  
  printf(" * The rate and state arrays need %u entries.\n", cci->rateIndexCount());
  printf(" * The algebraic variables array needs %u entries.\n", cci->algebraicIndexCount());
  printf(" * The constant array needs %u entries.\n", cci->constantIndexCount());
  printf(" * Variable storage is as follows:\n");
  
  messages.clear();
  iface::cellml_services::ComputationTargetIterator* cti = cci->iterateTargets();
  while (true)
  {
    iface::cellml_services::ComputationTarget* ct = cti->nextComputationTarget();
    if (ct == NULL)
      break;
    iface::cellml_api::CellMLVariable* v = ct->variable();
    iface::cellml_api::CellMLElement* el = v->parentElement();
    iface::cellml_api::CellMLComponent* c =
      reinterpret_cast<iface::cellml_api::CellMLComponent*>
      (el->query_interface("cellml_api::CellMLComponent"));
    el->release_ref();

    std::wstring str;
    std::wstring vn = v->name(), cn = c->name();
    str += L" * * Target ";
    uint32_t deg = ct->degree();
    if (deg != 0)
    {
      str += L"d^";
      wchar_t buf[20];
      any_swprintf(buf, 20, L"%u", deg);
      str += buf;
      str += L"/dt^";
      str += buf;
      str += L" ";
    }
    str += vn;
    str += L" in component ";
    str += cn;
    str += L"\n";

    c->release_ref();
    v->release_ref();

    str += L" * * * Variable type: ";
    str += TypeToString(ct->type());
    str += L"\n * * * Variable index: ";
    wchar_t buf[40];
    any_swprintf(buf, 40, L"%u\n", ct->assignedIndex());
    str += buf;

    str += L" * * * Variable storage: ";
    std::wstring vsn = ct->name();
    str += vsn;
    str += '\n';

    ct->release_ref();

    messages.push_back(str);
  }
  cti->release_ref();

  // Sort the messages...
  std::sort(messages.begin(), messages.end());
  for (msgi = messages.begin(); msgi != messages.end(); msgi++)
    printf("%S", (*msgi).c_str());

  printf(" */\n");

  std::wstring frag = cci->functionsString();
  printf("%S", frag.c_str());

  // Now start the code...
  frag = cci->initConstsString();
  printf("void SetupFixedConstants(double* CONSTANTS, double* RATES, double* STATES)\n{\n%S}\n", frag.c_str());

  frag = cci->variablesString();
  printf("void EvaluateVariables(double VOI, double* CONSTANTS, double* RATES, double* STATES, double* ALGEBRAIC)\n"
         "{\n%S}\n", frag.c_str());

  if (useida)
  {
    iface::cellml_services::IDACodeInformation* icci
      = reinterpret_cast<iface::cellml_services::IDACodeInformation*>(cci->query_interface("cellml_services::IDACodeInformation"));

    frag = icci->essentialVariablesString();
    printf("void EvaluateEssentialVariables(double VOI, double* CONSTANTS, double* RATES, double* STATES, double* ALGEBRAIC)\n"
           "{\n%S}\n", frag.c_str());

    frag = cci->ratesString();
    printf("void ComputeResiduals(double VOI, double* STATES, double* RATES, double* CONSTANTS, "
           "double* ALGEBRAIC)\n"
           "{\n%S}\n", frag.c_str());

    frag = icci->stateInformationString();
    printf("void SetupStateInfo(double * SI)\n{\n%S}\n", frag.c_str());

    frag = icci->rootInformationString();
    printf("void RootInformation()\n{\n%S}\n", frag.c_str());

    icci->release_ref();
  }
  else
  {
    frag = cci->ratesString();
    printf("void ComputeRates(double VOI, double* STATES, double* RATES, double* CONSTANTS, "
           "double* ALGEBRAIC)\n"
           "{\n%S}\n", frag.c_str());
  }
}
Example #5
0
QStringList CellmlFileRuntime::componentHierarchy(iface::cellml_api::CellMLElement *pElement)
{
    // Make sure that we have a given element

    if (!pElement)
        return QStringList();

    // Try to retrieve the component that owns the given element, unless the
    // given element is a component itself (which will be the case when we come
    // here through recursion)

    ObjRef<iface::cellml_api::CellMLComponent> component = QueryInterface(pElement);
    ObjRef<iface::cellml_api::CellMLElement> parent = pElement->parentElement();
    ObjRef<iface::cellml_api::CellMLComponent> parentComponent = QueryInterface(parent);

    if (!component && !parentComponent) {
        // The element isn't a component and neither is its parent, so it
        // doesn't have a hierarchy

        return QStringList();
    }

    // Recursively retrieve the component hierarchy of the given element's
    // encapsulation parent, if any

    ObjRef<iface::cellml_api::CellMLComponent> componentEncapsulationParent = component?component->encapsulationParent():parentComponent->encapsulationParent();

    return componentHierarchy(componentEncapsulationParent) << QString::fromStdWString(component?component->name():parentComponent->name());
}
Example #6
0
void CellmlFileRuntime::update()
{
    // Reset the runtime's properties

    reset(true, true);

    // Check that the model is either a 'simple' ODE model or a DAE model
    // Note #1: we don't check whether a model is valid, since all we want is to
    //          update its runtime (which has nothing to do with editing or even
    //          validating a model), so if it can be done then great otherwise
    //          tough luck (so to speak)...
    // Note #2: in order to do so, we need to get a 'normal' code generator (as
    //          opposed to an IDA, i.e. DAE, code generator) since if the model
    //          is correctly constrained, then we can check whether some of its
    //          equations were flagged as needing a Newton-Raphson evaluation,
    //          in which case we would be dealing with a DAE model...
    // Note #3: ideally, there would be a more convenient way to determine the
    //          type of a model, but there isn't...

    iface::cellml_api::Model *model = mCellmlFile->model();

    if (!model)
        return;

    // Retrieve the model's type
    // Note: this can be done by checking whether some equations were flagged
    //       as needing a Newton-Raphson evaluation...

    retrieveOdeCodeInformation(model);

    if (!mOdeCodeInformation)
        return;

    ObjRef<iface::mathml_dom::MathMLNodeList> flaggedEquations = mOdeCodeInformation->flaggedEquations();

    mModelType = flaggedEquations->length()?CellmlFileRuntime::Dae:CellmlFileRuntime::Ode;

    // If the model is of DAE type, then we don't want the ODE-specific code
    // information, but the DAE-specific code one

    ObjRef<iface::cellml_services::CodeInformation> genericCodeInformation;

    if (mModelType == CellmlFileRuntime::Ode) {
        genericCodeInformation = mOdeCodeInformation;
    } else {
        retrieveDaeCodeInformation(model);

        if (!mDaeCodeInformation)
            return;

        genericCodeInformation = mDaeCodeInformation;
    }

    // Retrieve the number of constants, states/rates, algebraic and conditional
    // variables in the model
    // Note: this is to avoid having to go through the ODE/DAE code information
    //       an unnecessary number of times when we want to retrieve either of
    //       those numbers (e.g. see
    //       SingleCellViewSimulationResults::addPoint())...

    if (mModelType == CellmlFileRuntime::Ode) {
        mConstantsCount   = mOdeCodeInformation->constantIndexCount();
        mStatesRatesCount = mOdeCodeInformation->rateIndexCount();
        mAlgebraicCount   = mOdeCodeInformation->algebraicIndexCount();
        mCondVarCount     = 0;
    } else {
        mConstantsCount   = mDaeCodeInformation->constantIndexCount();
        mStatesRatesCount = mDaeCodeInformation->rateIndexCount();
        mAlgebraicCount   = mDaeCodeInformation->algebraicIndexCount();
        mCondVarCount     = mDaeCodeInformation->conditionVariableCount();
    }

    // Retrieve all the parameters and sort them by component/variable name

    ObjRef<iface::cellml_services::ComputationTargetIterator> computationTargetIter = genericCodeInformation->iterateTargets();

    for (ObjRef<iface::cellml_services::ComputationTarget> computationTarget = computationTargetIter->nextComputationTarget();
         computationTarget; computationTarget = computationTargetIter->nextComputationTarget()) {
        // Determine the type of the parameter

        ObjRef<iface::cellml_api::CellMLVariable> variable = computationTarget->variable();
        CellmlFileRuntimeParameter::ParameterType parameterType;

        switch (computationTarget->type()) {
        case iface::cellml_services::VARIABLE_OF_INTEGRATION:
            parameterType = CellmlFileRuntimeParameter::Voi;

            break;
        case iface::cellml_services::CONSTANT:
            // We are dealing with a constant, but the question is whether that
            // constant is a 'proper' constant, a 'computed' constant or even a
            // rate, and this can be determined by checking whether the computed
            // target has an initial value or even a degree
            // Note: a state variable that is initialised using the initial
            //       value of another variable will have its rate considered as
            //       a constant. However, when it comes to the GUI, we really
            //       want it to be seen as a rate hence we check for the degree
            //       of the computed target...

            if (QString::fromStdWString(variable->initialValue()).isEmpty()) {
                // The computed target doesn't have an initial value, so it must
                // be a 'computed' constant

                parameterType = CellmlFileRuntimeParameter::ComputedConstant;
            } else if (computationTarget->degree()) {
                // The computed target has a degree, so it is effectively a rate

                parameterType = CellmlFileRuntimeParameter::Rate;
            } else {
                // The computed target has an initial value, so it must be a
                // 'proper' constant

                parameterType = CellmlFileRuntimeParameter::Constant;
            }

            break;
        case iface::cellml_services::STATE_VARIABLE:
        case iface::cellml_services::PSEUDOSTATE_VARIABLE:
            parameterType = CellmlFileRuntimeParameter::State;

            break;
        case iface::cellml_services::ALGEBRAIC:
            // We are dealing with either a 'proper' algebraic variable or a
            // rate variable
            // Note: if the variable's degree is equal to zero, then we are
            //       dealing with a 'proper' algebraic variable otherwise we
            //       are dealing with a rate variable...

            if (computationTarget->degree())
                parameterType = CellmlFileRuntimeParameter::Rate;
            else
                parameterType = CellmlFileRuntimeParameter::Algebraic;

            break;
        case iface::cellml_services::FLOATING:
            parameterType = CellmlFileRuntimeParameter::Floating;

            break;
        case iface::cellml_services::LOCALLY_BOUND:
            parameterType = CellmlFileRuntimeParameter::LocallyBound;

            break;
        }

        // Keep track of the parameter, should its type be of interest

        if (   (parameterType != CellmlFileRuntimeParameter::Floating)
            && (parameterType != CellmlFileRuntimeParameter::LocallyBound)) {
            CellmlFileRuntimeParameter *parameter = new CellmlFileRuntimeParameter(QString::fromStdWString(variable->name()),
                                                                                   computationTarget->degree(),
                                                                                   QString::fromStdWString(variable->unitsName()),
                                                                                   componentHierarchy(variable),
                                                                                   parameterType,
                                                                                   computationTarget->assignedIndex());

            if (parameterType == CellmlFileRuntimeParameter::Voi)
                mVariableOfIntegration = parameter;

            mParameters.append(parameter);
        }
    }

    std::sort(mParameters.begin(), mParameters.end(), sortParameters);

    // Generate the model code, after having prepended to it all the external
    // functions that may, or not, be needed
    // Note: indeed, we cannot include header files since we don't (and don't
    //       want in order to avoid complications) deploy them with OpenCOR. So,
    //       instead, we must declare as external functions all the functions
    //       that we would normally use through header files...

    QString modelCode = "extern double fabs(double);\n"
                        "\n"
                        "extern double exp(double);\n"
                        "extern double log(double);\n"
                        "\n"
                        "extern double ceil(double);\n"
                        "extern double floor(double);\n"
                        "\n"
                        "extern double factorial(double);\n"
                        "\n"
                        "extern double sin(double);\n"
                        "extern double cos(double);\n"
                        "extern double tan(double);\n"
                        "extern double sinh(double);\n"
                        "extern double cosh(double);\n"
                        "extern double tanh(double);\n"
                        "extern double asin(double);\n"
                        "extern double acos(double);\n"
                        "extern double atan(double);\n"
                        "extern double asinh(double);\n"
                        "extern double acosh(double);\n"
                        "extern double atanh(double);\n"
                        "\n"
                        "extern double arbitrary_log(double, double);\n"
                        "\n"
                        "extern double pow(double, double);\n"
                        "\n"
                        "extern double gcd_multi(int, ...);\n"
                        "extern double lcm_multi(int, ...);\n"
                        "extern double multi_max(int, ...);\n"
                        "extern double multi_min(int, ...);\n"
                        "\n";

    QString functionsString = QString::fromStdWString(genericCodeInformation->functionsString());

    if (!functionsString.isEmpty()) {
        // We will need to solve at least one NLA system

        mAtLeastOneNlaSystem = true;

        modelCode +=  "struct rootfind_info\n"
                      "{\n"
                      "    double aVOI;\n"
                      "\n"
                      "    double *aCONSTANTS;\n"
                      "    double *aRATES;\n"
                      "    double *aSTATES;\n"
                      "    double *aALGEBRAIC;\n"
                      "\n"
                      "    int *aPRET;\n"
                      "};\n"
                      "\n"
                      "extern void doNonLinearSolve(char *, void (*)(double *, double *, void*), double *, int *, int, void *);\n"
                      "\n"
                     +functionsString.replace("do_nonlinearsolve(", QString("doNonLinearSolve(\"%1\", ").arg(address()))
                     +"\n";

        // Note: we rename do_nonlinearsolve() to doNonLinearSolve() because
        //       CellML's CIS service already defines do_nonlinearsolve(), yet
        //       we want to use our own non-linear solve routine defined in our
        //       Compiler plugin. Also, we add a new parameter to all our calls
        //       to doNonLinearSolve() so that doNonLinearSolve() can retrieve
        //       the correct instance of our NLA solver...
    }

    // Retrieve the body of the function that initialises constants and extract
    // the statements that are related to computed variables (since we want to
    // be able to recompute those whenever the user modifies a parameter)
    // Note: ideally, we wouldn't have to do that, but the CellML API doesn't
    //       distinguish between 'proper' and 'computed' constants...
    //       (See https://tracker.physiomeproject.org/show_bug.cgi?id=3499)

    static const QRegularExpression InitializationStatementRegEx = QRegularExpression("^(CONSTANTS|RATES|STATES)\\[\\d*\\] = [+-]?\\d*\\.?\\d+([eE][+-]?\\d+)?;$");

    QStringList initConstsList = QString::fromStdWString(genericCodeInformation->initConstsString()).split("\r\n");
    QString initConsts = QString();
    QString compCompConsts = QString();

    foreach (const QString &initConst, initConstsList) {
        // Add the statement either to our list of 'proper' constants or
        // 'computed' constants

        if (InitializationStatementRegEx.match(initConst).hasMatch()) {
            // We are dealing with a 'proper' constant (or a rate or a state)

            if (!initConsts.isEmpty())
                initConsts += "\n";

            initConsts += initConst;
        } else {
            // We are dealing with a 'computed' constant

            if (!compCompConsts.isEmpty())
                compCompConsts += "\n";

            compCompConsts += initConst;
        }
    }

    modelCode += functionCode("int initializeConstants(double *CONSTANTS, double *RATES, double *STATES)",
                              initConsts, true);
    modelCode += "\n";
    modelCode += functionCode("int computeComputedConstants(double *CONSTANTS, double *RATES, double *STATES)",
                              compCompConsts, true);
    modelCode += "\n";

    // Retrieve the body of the remaining functions

    if (mModelType == CellmlFileRuntime::Ode) {
        modelCode += functionCode("int computeOdeRates(double VOI, double *CONSTANTS, double *RATES, double *STATES, double *ALGEBRAIC)",
                                  QString::fromStdWString(mOdeCodeInformation->ratesString()));
        modelCode += "\n";
        modelCode += functionCode("int computeOdeVariables(double VOI, double *CONSTANTS, double *RATES, double *STATES, double *ALGEBRAIC)",
                                  QString::fromStdWString(genericCodeInformation->variablesString()));
    } else {
        modelCode += functionCode("int computeDaeEssentialVariables(double VOI, double *CONSTANTS, double *RATES, double *OLDRATES, double *STATES, double *OLDSTATES, double *ALGEBRAIC, double *CONDVAR)",
                                  QString::fromStdWString(mDaeCodeInformation->essentialVariablesString()));
        modelCode += "\n";
        modelCode += functionCode("int computeDaeResiduals(double VOI, double *CONSTANTS, double *RATES, double *OLDRATES, double *STATES, double *OLDSTATES, double *ALGEBRAIC, double *CONDVAR, double *resid)",
                                  QString::fromStdWString(mDaeCodeInformation->ratesString()));
        modelCode += "\n";
        modelCode += functionCode("int computeDaeRootInformation(double VOI, double *CONSTANTS, double *RATES, double *OLDRATES, double *STATES, double *OLDSTATES, double *ALGEBRAIC, double *CONDVAR)",
                                  QString::fromStdWString(mDaeCodeInformation->rootInformationString()));
        modelCode += functionCode("int computeDaeStateInformation(double *SI)",
                                  QString::fromStdWString(mDaeCodeInformation->stateInformationString()));
        modelCode += "\n";
        modelCode += functionCode("int computeDaeVariables(double VOI, double *CONSTANTS, double *RATES, double *STATES, double *ALGEBRAIC, double *CONDVAR)",
                                  QString::fromStdWString(genericCodeInformation->variablesString()));
    }

    // Check whether the model code contains a definite integral, otherwise
    // compute it and check that everything went fine

    if (modelCode.contains("defint(func")) {
        mIssues << CellmlFileIssue(CellmlFileIssue::Error,
                                   QObject::tr("definite integrals are not yet supported"));
    } else if (!mCompilerEngine->compileCode(modelCode)) {
        mIssues << CellmlFileIssue(CellmlFileIssue::Error,
                                   QString("%1").arg(mCompilerEngine->error()));
    }

    // Keep track of the ODE/DAE functions, but only if no issues were reported

    if (mIssues.count()) {
        reset(true, false);
    } else {
        // Add the symbol of any required external function, if any

        if (mAtLeastOneNlaSystem)
            llvm::sys::DynamicLibrary::AddSymbol("doNonLinearSolve",
                                                 (void *) (intptr_t) doNonLinearSolve);

        // Retrieve the ODE/DAE functions

        mInitializeConstants = (InitializeConstantsFunction) (intptr_t) mCompilerEngine->getFunction("initializeConstants");

        mComputeComputedConstants = (ComputeComputedConstantsFunction) (intptr_t) mCompilerEngine->getFunction("computeComputedConstants");

        if (mModelType == CellmlFileRuntime::Ode) {
            mComputeOdeRates     = (ComputeOdeRatesFunction) (intptr_t) mCompilerEngine->getFunction("computeOdeRates");
            mComputeOdeVariables = (ComputeOdeVariablesFunction) (intptr_t) mCompilerEngine->getFunction("computeOdeVariables");
        } else {
            mComputeDaeEssentialVariables = (ComputeDaeEssentialVariablesFunction) (intptr_t) mCompilerEngine->getFunction("computeDaeEssentialVariables");
            mComputeDaeResiduals          = (ComputeDaeResidualsFunction) (intptr_t) mCompilerEngine->getFunction("computeDaeResiduals");
            mComputeDaeRootInformation    = (ComputeDaeRootInformationFunction) (intptr_t) mCompilerEngine->getFunction("computeDaeRootInformation");
            mComputeDaeStateInformation   = (ComputeDaeStateInformationFunction) (intptr_t) mCompilerEngine->getFunction("computeDaeStateInformation");
            mComputeDaeVariables          = (ComputeDaeVariablesFunction) (intptr_t) mCompilerEngine->getFunction("computeDaeVariables");
        }

        // Make sure that we managed to retrieve all the ODE/DAE functions

        bool functionsOk =    mInitializeConstants
                           && mComputeComputedConstants;

        if (mModelType == CellmlFileRuntime::Ode) {
            functionsOk =    functionsOk
                          && mComputeOdeRates
                          && mComputeOdeVariables;
        } else {
            functionsOk =    functionsOk
                          && mComputeDaeEssentialVariables
                          && mComputeDaeResiduals
                          && mComputeDaeRootInformation
                          && mComputeDaeStateInformation
                          && mComputeDaeVariables;
        }

        if (!functionsOk) {
            mIssues << CellmlFileIssue(CellmlFileIssue::Error,
                                       QObject::tr("an unexpected problem occurred while trying to retrieve the model functions"));

            reset(true, false);
        }
    }
}