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; }
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); } } }