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); } } }
std::string generateCodeForModel(CellmlApiObjects* capi, std::map<std::string, unsigned char>& variableTypes, std::map<std::string, std::map<unsigned char, int> >& variableIndices, int numberOfInputs, int numberOfStates) { std::stringstream code; std::string codeString; if (! (capi && capi->codeInformation)) { std::cerr << "CellML Model Definition::generateCodeForModel: missing model implementation?" << std::endl; return ""; } // We need to regenerate the code information to make use of the flagged variables. ObjRef<iface::cellml_services::CodeGeneratorBootstrap> cgb = CreateCodeGeneratorBootstrap(); ObjRef<iface::cellml_services::CodeGenerator> cg = cgb->createCodeGenerator(); // catch any exceptions from the CellML API try { // annotate the source variables in the model with the code-names based on existing annotations for (unsigned int i=0; i < capi->cevas->length(); i++) { ObjRef<iface::cellml_services::ConnectedVariableSet> cvs = capi->cevas->getVariableSet(i); ObjRef<iface::cellml_api::CellMLVariable> sv = cvs->sourceVariable(); std::string currentId = getVariableUniqueId(sv); std::map<std::string, unsigned char>::iterator typeit(variableTypes.find(currentId)); if (typeit != variableTypes.end()) { std::wstringstream ename; // here we assign an array and index based on the "primary" purpose of the variable. Later // we will add in secondary purposes. unsigned char vType = typeit->second; if (vType & csim::StateType) { ename << L"CSIM_STATE[" << variableIndices[currentId][csim::StateType] << L"]"; } else if (vType & csim::IndependentType) { // do nothing, but stop input and output annotations } else if (vType & csim::InputType) { ename << L"CSIM_INPUT[" << variableIndices[currentId][csim::InputType] << L"]"; } else if (vType & csim::OutputType) { ename << L"CSIM_OUTPUT[" << variableIndices[currentId][csim::OutputType] << L"]"; } capi->annotations->setStringAnnotation(sv, L"expression", ename.str()); if (vType & csim::StateType) { ename.str(L""); ename.clear(); ename << L"CSIM_RATE[" << variableIndices[currentId][csim::StateType] << L"]"; capi->annotations->setStringAnnotation(sv, L"expression_d1", ename.str()); } } } cg->useCeVAS(capi->cevas); cg->useAnnoSet(capi->annotations); ObjRef<iface::cellml_services::CodeInformation> cci = cg->generateCode(capi->model); std::wstring m = cci->errorMessage(); if (m != L"") { std::cerr << "CellML Model Definition::generateCodeForModel: error generating code?" << std::endl; return ""; } iface::cellml_services::ModelConstraintLevel mcl = cci->constraintLevel(); if (mcl == iface::cellml_services::UNDERCONSTRAINED) { std::cerr << "Model is underconstrained" << std::endl; return ""; } else if (mcl == iface::cellml_services::OVERCONSTRAINED) { std::cerr << "Model is overconstrained" << std::endl; return ""; } else if (mcl == iface::cellml_services::UNSUITABLY_CONSTRAINED) { std::cerr << "Model is unsuitably constrained" << std::endl; return ""; } std::cout << "Model is correctly constrained" << std::endl; // create the code in the format we know how to handle code << "//#include <math.h>\n" /* required functions */ << "double fabs(double x);\n" << "double acos(double x);\n" << "double acosh(double x);\n" << "double atan(double x);\n" << "double atanh(double x);\n" << "double asin(double x);\n" << "double asinh(double x);\n" << "double acos(double x);\n" << "double acosh(double x);\n" << "double asin(double x);\n" << "double asinh(double x);\n" << "double atan(double x);\n" << "double atanh(double x);\n" << "double ceil(double x);\n" << "double cos(double x);\n" << "double cosh(double x);\n" << "double tan(double x);\n" << "double tanh(double x);\n" << "double sin(double x);\n" << "double sinh(double x);\n" << "double exp(double x);\n" << "double floor(double x);\n" << "double pow(double x, double y);\n" << "double factorial(double x);\n" << "double log(double x);\n" << "double arbitrary_log(double x, double base);\n" << "double gcd_pair(double a, double b);\n" << "double lcm_pair(double a, double b);\n" << "double gcd_multi(unsigned int size, ...);\n" << "double lcm_multi(unsigned int size, ...);\n" << "double multi_min(unsigned int size, ...);\n" << "double multi_max(unsigned int size, ...);\n" << "void NR_MINIMISE(double(*func)" "(double VOI, double *C, double *R, double *S, double *A)," "double VOI, double *C, double *R, double *S, double *A, " "double *V);\n"; std::wstring frag = cci->functionsString(); code << ws2s(frag); int nAlgebraic = cci->algebraicIndexCount(); int nConstants = cci->constantIndexCount(); code << "\n\nvoid csim_rhs_routine(double VOI, double* CSIM_STATE, double* CSIM_RATE, double* CSIM_OUTPUT, " << "double* CSIM_INPUT)\n{\n\n" << "double DUMMY_ASSIGNMENT;\n" << "double CONSTANTS[" << nConstants << "], ALGEBRAIC[" << nAlgebraic << "];\n\n"; /* initConsts - all variables which aren't state variables but have * an initial_value attribute, and any variables & rates * which follow. */ code << ws2s(cci->initConstsString()); /* rates - All rates which are not static. */ code << ws2s(cci->ratesString()); /* variables - All variables not computed by initConsts or rates * (i.e., these are not required for the integration of the model and * thus only need to be called for output or presentation or similar * purposes) */ code << ws2s(cci->variablesString()); // add in the setting of any outputs that are not already defined for (unsigned int i=0; i < capi->cevas->length(); i++) { ObjRef<iface::cellml_services::ConnectedVariableSet> cvs = capi->cevas->getVariableSet(i); ObjRef<iface::cellml_api::CellMLVariable> sv = cvs->sourceVariable(); std::string currentId = getVariableUniqueId(sv); std::map<std::string, unsigned char>::iterator typeit(variableTypes.find(currentId)); if (typeit != variableTypes.end()) { unsigned char vType = typeit->second; if (vType & csim::OutputType) { if (vType & csim::StateType) code << "CSIM_OUTPUT[" << variableIndices[currentId][csim::OutputType] << "] = CSIM_STATE[" << variableIndices[currentId][csim::StateType] << "];\n"; else if (vType & csim::InputType) code << "CSIM_OUTPUT[" << variableIndices[currentId][csim::OutputType] << "] = CSIM_INPUT[" << variableIndices[currentId][csim::InputType] << "];\n"; else if (vType & csim::IndependentType) code << "CSIM_OUTPUT[" << variableIndices[currentId][csim::OutputType] << "] = VOI;\n"; } } } // close the subroutine code << "\n\n}//csim_rhs_routine()\n\n"; // and now clear out initialisation of state variables and known variables from // the RHS routine. codeString = clearCodeAssignments(code.str(), "CSIM_STATE", numberOfStates); codeString = clearCodeAssignments(codeString, "CSIM_INPUT", numberOfInputs); // and finally create the initialisation routine std::stringstream initRoutine; initRoutine << "\nvoid csim_initialise_routine(double* CSIM_STATE, double* CSIM_OUTPUT, double* CSIM_INPUT)\n{\n"; // FIXME: this doesn't need to be in the interface? initRoutine << "double CSIM_RATES[" << numberOfStates << "];\n"; initRoutine << "double CONSTANTS[" << nConstants << "];\n"; initRoutine << ws2s(cci->initConstsString()); initRoutine << "\n}\n"; codeString += initRoutine.str(); } catch (...) { std::cerr << "CellML Model Definition::generateCodeForModel: something went wrong generating code?" << std::endl; return ""; } return codeString; }