Ejemplo n.º 1
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();
    }

    // Determine whether the model imports some components
    // Note #1: a model that only imports units will clearly not affect our
    //          model's variables, hence such a model won't be relevant for our
    //          mapping below...
    // Note #2: a model cannot be fully instantiated if it imports an empty
    //          component or a component with unmapped variables, so we don't
    //          need to worry about this type of model...

    ObjRef<iface::cellml_api::CellMLImportSet> imports = model->imports();
    ObjRef<iface::cellml_api::CellMLImportIterator> importsIter = imports->iterateImports();
    bool hasComponentImports = false;

    for (ObjRef<iface::cellml_api::CellMLImport> import = importsIter->nextImport();
         import; import = importsIter->nextImport()) {
        ObjRef<iface::cellml_api::ImportComponentSet> importComponents = import->components();

        if (importComponents->length()) {
            hasComponentImports = true;

            break;
        }
    }

    // If the model contains some imports then we want to to go through the
    // variables defined or referenced in our main CellML file and do a mapping
    // between the source of a variable and a variable in the main CellML file
    // Note: indeed, when it comes to CellML 1.1 files, we only want to list the
    //       parameters that are either defined or referenced in our main CellML
    //       file. Not only does it make sense, but also only the parameters
    //       listed in a main CellML file can be referenced in SED-ML...

    QMap<iface::cellml_api::CellMLVariable *, iface::cellml_api::CellMLVariable *> mainVariables = QMap<iface::cellml_api::CellMLVariable *, iface::cellml_api::CellMLVariable *>();
    ObjRef<iface::cellml_api::CellMLComponentSet> localComponents = model->localComponents();
    ObjRef<iface::cellml_api::CellMLComponentIterator> localComponentsIter = localComponents->iterateComponents();

    if (hasComponentImports) {
        for (ObjRef<iface::cellml_api::CellMLComponent> component = localComponentsIter->nextComponent();
             component; component = localComponentsIter->nextComponent()) {
            ObjRef<iface::cellml_api::CellMLVariableSet> variables = component->variables();
            ObjRef<iface::cellml_api::CellMLVariableIterator> variablesIter = variables->iterateVariables();

            for (ObjRef<iface::cellml_api::CellMLVariable> variable = variablesIter->nextVariable();
                 variable; variable = variablesIter->nextVariable()) {
                ObjRef<iface::cellml_api::CellMLVariable> sourceVariable = variable->sourceVariable();

                mainVariables.insert(sourceVariable, variable);
            }
        }
    }

    // Go through all our computation targets and determine which ones are
    // referenced in our main CellML file, and sort them by component and
    // variable name

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

    for (ObjRef<iface::cellml_services::ComputationTarget> computationTarget = computationTargetIter->nextComputationTarget();
         computationTarget; computationTarget = computationTargetIter->nextComputationTarget()) {
        // Make sure that our computation target is defined or referenced in our
        // main CellML file, if it has imports

        ObjRef<iface::cellml_api::CellMLVariable> variable = computationTarget->variable();
        iface::cellml_api::CellMLVariable *mainVariable = mainVariables.value(variable);
        iface::cellml_api::CellMLVariable *realVariable = mainVariable?mainVariable:variable.getPointer();

        if (   hasComponentImports && !mainVariable
            &&  (computationTarget->type() != iface::cellml_services::VARIABLE_OF_INTEGRATION)) {
            continue;
        }

        // Determine the type of our computation target

        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 our computation target, should its type be of interest

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

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

            if (!hasComponentImports || (realVariable == mainVariable))
                mParameters << parameter;
        }
    }

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

    // Generate the model code

    QString modelCode = QString();
    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 = cleanCode(genericCodeInformation->initConstsString()).split("\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())
            initConsts += (initConsts.isEmpty()?QString():"\n")+initConst;
        else
            compCompConsts += (compCompConsts.isEmpty()?QString():"\n")+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)",
                                  cleanCode(mOdeCodeInformation->ratesString()));
        modelCode += "\n";
        modelCode += functionCode("int computeOdeVariables(double VOI, double *CONSTANTS, double *RATES, double *STATES, double *ALGEBRAIC)",
                                  cleanCode(genericCodeInformation->variablesString()));
    } else {
        modelCode += functionCode("int computeDaeEssentialVariables(double VOI, double *CONSTANTS, double *RATES, double *OLDRATES, double *STATES, double *OLDSTATES, double *ALGEBRAIC, double *CONDVAR)",
                                  cleanCode(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)",
                                  cleanCode(mDaeCodeInformation->ratesString()));
        modelCode += "\n";
        modelCode += functionCode("int computeDaeRootInformation(double VOI, double *CONSTANTS, double *RATES, double *OLDRATES, double *STATES, double *OLDSTATES, double *ALGEBRAIC, double *CONDVAR)",
                                  cleanCode(mDaeCodeInformation->rootInformationString()));
        modelCode += functionCode("int computeDaeStateInformation(double *SI)",
                                  cleanCode(mDaeCodeInformation->stateInformationString()));
        modelCode += "\n";
        modelCode += functionCode("int computeDaeVariables(double VOI, double *CONSTANTS, double *RATES, double *STATES, double *ALGEBRAIC, double *CONDVAR)",
                                  cleanCode(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,
                                   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);
        }
    }
}