Ejemplo n.º 1
0
ObjRef<iface::cellml_api::CellMLVariable> findLocalVariable(CellmlApiObjects* capi, const std::string& variableId)
{
    if (!(capi->cevas))
    {
        std::cerr << "CellMLModelDefinition::findLocalVariable -- missing CeVAS object?" << std::endl;
        return NULL;
    }
    // find named variable - in local components only!
    CVpair cv = splitName(variableId);
    if ((cv.first.length() == 0) || (cv.second.length() == 0)) return NULL;
    //std::cout << "Component name: " << cv.first << "; variable name: " << cv.second << std::endl;
    ObjRef<iface::cellml_api::CellMLComponentSet> components = capi->model->localComponents();
    ObjRef<iface::cellml_api::CellMLComponent> component = components->getComponent(s2ws(cv.first));
    if (!component)
    {
        std::cerr << "CellMLModelDefinition::findLocalVariable -- unable to find local component: " << cv.first << std::endl;
        return NULL;
    }
    ObjRef<iface::cellml_api::CellMLVariableSet> variables = component->variables();
    ObjRef<iface::cellml_api::CellMLVariable> variable = variables->getVariable(s2ws(cv.second));
    if (!variable)
    {
        std::cerr << "CellMLModelDefinition::findLocalVariable -- unable to find variable: " << cv.first << " / "
                  << cv.second << std::endl;
        return NULL;
    }
    // get source variable
    ObjRef<iface::cellml_services::ConnectedVariableSet> cvs = capi->cevas->findVariableSet(variable);
    ObjRef<iface::cellml_api::CellMLVariable> v = cvs->sourceVariable();
    if (!v)
    {
        std::cerr << "CellMLModelDefinition::findLocalVariable -- unable get source variable for variable: "
                  << cv.first << " / " << cv.second << std::endl;
        return NULL;
    }
    return v;
}
Ejemplo n.º 2
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);
        }
    }
}
Ejemplo n.º 3
0
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;
}