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); } } }
int CellmlModelDefinition::instantiateCellmlApiObjects() { try { // create an annotation set to manage our variable usages ObjRef<iface::cellml_services::AnnotationToolService> ats = CreateAnnotationToolService(); ObjRef<iface::cellml_services::AnnotationSet> as = ats->createAnnotationSet(); mCapi->annotations = as; // mapping the connections between variables is a very expensive operation, so we want to // only do it once and keep hold of the mapping (tracker item 3294) ObjRef<iface::cellml_services::CeVASBootstrap> cvbs = CreateCeVASBootstrap(); ObjRef<iface::cellml_services::CeVAS> cevas = cvbs->createCeVASForModel(mCapi->model); std::wstring msg = cevas->modelError(); if (msg != L"") { std::cerr << "loadModel: Error creating CellML Variable Association Service: " << ws2s(msg) << std::endl; return -2; } mCapi->cevas = cevas; // now check we can generate code and grab hold of the initial code information ObjRef<iface::cellml_services::CodeGeneratorBootstrap> cgb = CreateCodeGeneratorBootstrap(); ObjRef<iface::cellml_services::CodeGenerator> cg = cgb->createCodeGenerator(); try { cg->useCeVAS(cevas); ObjRef<iface::cellml_services::CodeInformation> cci = cg->generateCode(mCapi->model); msg = cci->errorMessage(); if (msg != L"") { std::cerr << "CellmlModelDefintion::loadModel: Error generating code: " << ws2s(msg) << std::endl; return -4; } // TODO: we are only interested in models we can work with? if (cci->constraintLevel() != iface::cellml_services::CORRECTLY_CONSTRAINED) { std::cerr << "CellmlModelDefintion::loadModel: Model is not correctly constrained: " << std::endl; return -5; } mCapi->codeInformation = cci; // always flag all state variables and the variable of integration ObjRef<iface::cellml_services::ComputationTargetIterator> cti = cci->iterateTargets(); while (true) { ObjRef<iface::cellml_services::ComputationTarget> ct = cti->nextComputationTarget(); if (ct == NULL) break; if (ct->degree() > 0) break; // only want to initialise the base variables not the differential ObjRef<iface::cellml_api::CellMLVariable> v(ct->variable()); if (ct->type() == iface::cellml_services::STATE_VARIABLE) { mVariableTypes[getVariableUniqueId(v)] = csim::StateType; mVariableIndices[getVariableUniqueId(v)][csim::StateType] = mStateCounter; mStateCounter++; } else if (ct->type() == iface::cellml_services::VARIABLE_OF_INTEGRATION) { mVariableTypes[getVariableUniqueId(v)] = csim::IndependentType; mNumberOfIndependentVariables++; } else { // need to initialise the variable type mVariableTypes[getVariableUniqueId(v)] = csim::UndefinedType; } } } catch (...) { std::cerr << "loadModel: Error generating the code information for the model" << std::endl; return -3; } // if we get to here, everything worked. mModelLoaded = true; } catch (...) { std::wcerr << L"Error instantiating CellML API objects." << std::endl; return -1; } return csim::CSIM_OK; }