コード例 #1
0
def _get_stimulus(model, skip_chaste_stimulus_conversion):
    """ Store the stimulus currents in the model"""
    stim_params_orig, stim_params, return_stim_eqs = set(), set(), set()
    if not skip_chaste_stimulus_conversion:
        try:  # Get required stimulus parameters
            for tag, unit, required in STIM_PARAM_TAGS:
                param = model.get_variable_by_ontology_term((OXMETA, tag))
                stim_params_orig.add(param)  # originals ones
                stim_params.add(
                    model.convert_variable(
                        param, model.conversion_units.get_unit(unit),
                        DataDirectionFlow.INPUT))
        except KeyError:
            if required:  # Optional params are allowed to be missing
                LOGGER.info('The model has no default stimulus params tagged.')
                return set(), []
        except TypeError as e:
            if str(
                    e
            ) == "unsupported operand type(s) for /: 'HeartConfig::Instance()->GetCapacitance' and 'NoneType'":
                e = CodegenError(
                    "Membrane capacitance is required to be able to apply conversion to stimulus current!"
                )
            raise (e)

        return_stim_eqs = get_equations_for(
            model, stim_params, filter_modifiable_parameters_lhs=False)
    return stim_params | stim_params_orig, return_stim_eqs
コード例 #2
0
def load_model_with_conversions(model_file,
                                use_modifiers=False,
                                quiet=False,
                                skip_singularity_fixes=False,
                                skip_conversions=False):
    if quiet:
        LOGGER.setLevel(logging.ERROR)
    try:
        model = cellmlmanip.load_model(model_file)
    except Exception as e:
        raise CodegenError('Could not load cellml model: \n    ' + str(e))
    if not skip_singularity_fixes:
        V = model.get_variable_by_ontology_term((OXMETA, 'membrane_voltage'))
        tagged = set(
            model.get_variables_by_rdf((PYCMLMETA, 'modifiable-parameter'),
                                       'yes',
                                       sort=False))
        annotated = set(
            filter(lambda q: model.has_ontology_annotation(q, OXMETA),
                   model.variables()))
        excluded = (tagged | annotated) - set(
            model.get_derived_quantities(sort=False))

        model.remove_fixable_singularities(V, exclude=excluded)
    if not skip_conversions:
        add_conversions(model, use_modifiers=use_modifiers)
    return model
コード例 #3
0
def _get_time_variable(model):
    """ Get the variable representing time (the free variable) and convert to milliseconds"""
    try:
        time_variable = model.get_free_variable()
    except ValueError:
        raise CodegenError('The model has no free variable (time)!')
    # Annotate as quantity and not modifiable parameter
    set_is_metadata(model, time_variable, 'derived-quantity')
    try:
        # If the variable is in units that can be converted to millisecond, perform conversion
        return model.convert_variable(
            model.get_free_variable(),
            model.conversion_units.get_unit('millisecond'),
            DataDirectionFlow.INPUT)
    except DimensionalityError:
        raise CodegenError(
            ("Incorrect definition of free variable (time): "
             "time needs to be dimensionally equivalent to second!"))
コード例 #4
0
def _get_membrane_voltage_var(model, convert=True):
    """ Find the membrane_voltage variable"""
    try:
        # Get and convert V
        voltage = model.get_variable_by_ontology_term(
            (OXMETA, 'membrane_voltage'))
        if not convert:
            return voltage
        voltage = model.convert_variable(
            voltage, model.conversion_units.get_unit('millivolt'),
            DataDirectionFlow.INPUT)
    except KeyError:
        raise CodegenError('Voltage not tagged in the model!')
    except DimensionalityError:
        raise CodegenError(
            'Incorrect definition of membrane_voltage variable: '
            'units of membrane_voltage need to be dimensionally equivalent to Volt'
        )
    annotate_if_not_statevar(
        model, voltage)  # If V is not state var annotate as appropriate.
    return voltage
コード例 #5
0
def _get_extended_ionic_vars(model):
    """ Get the equations defining the ionic derivatives and all dependant equations"""
    # Update equations to include i_ionic_eq & defining eqs, set stimulus current to 0
    extended_eqs = [
        eq if eq.lhs != model.membrane_stimulus_current_orig else Eq(
            eq.lhs, 0.0) for eq in get_equations_for(model, model.ionic_vars)
        if eq.lhs != model.membrane_stimulus_current_converted
    ]
    if model.time_variable in model.find_variables_and_derivatives(
            extended_eqs):
        raise CodegenError(
            'Ionic variables should not be a function of time. '
            'This is often caused by missing membrane_stimulus_current tag.')
    return extended_eqs
コード例 #6
0
def get_variables_transitively(model, term):
    """Return a list of variables annotated (directly or otherwise) with the given ontology term.

    Direct annotations are those variables annotated with the term via the bqbiol:is or
    bqbiol:isVersionOf predicates.

    However we also look transitively through the 'oxmeta' ontology for terms belonging to the RDF
    class given by ``term``, i.e. are connected to it by a path of ``rdf:type`` predicates, and
    return variables annotated with those terms.

    For example, the oxmeta ontology has a term ``oxmeta:ExtracellularConcentration``, and triples:
    - ``oxmeta:extracellular_calcium_concentration rdf:type oxmeta:ExtracellularConcentration``
    - ``oxmeta:extracellular_sodium_concentration rdf:type oxmeta:ExtracellularConcentration``
    So if you have variables ``Ca_o`` annotated with ``oxmeta:extracellular_calcium_concentration``
    and ``Na_o`` annotated with ``oxmeta:extracellular_sodium_concentration``, then calling
    ``get_variables_transitively(model, oxmeta:ExtracellularConcentration)`` would give you the list
    ``[Ca_o, Na_o]``.

    :param term: the ontology term to search for. Can be anything suitable as input to
        :meth:`create_rdf_node`, typically a :class:`rdflib.term.Node` or ``(ns_uri, local_name)`` pair.
    :return: a list of :class:`cellmlmanip.model.Variable` objects, sorted by order added to the model.
    """

    assert isinstance(term, tuple), "Expecting term to be a namespace tuple"
    assert isinstance(model, Model), "Expecting model to be a cellmlmanip Model"

    global _ONTOLOGY

    if _ONTOLOGY is None:
        # Load oxmeta ontology
        _ONTOLOGY = rdflib.Graph()
        ttl_file = os.path.join(MODULE_DIR, 'ontologies', 'oxford-metadata.ttl')
        _ONTOLOGY.parse(ttl_file, format='turtle')

    term = create_rdf_node(term)

    cmeta_ids = set()
    for annotation in _ONTOLOGY.transitive_subjects(rdflib.RDF.type, term):
        cmeta_ids.update(model.rdf.subjects(PRED_IS, annotation))
        cmeta_ids.update(model.rdf.subjects(PRED_IS_VERSION_OF, annotation))
    variables = []
    for cmeta_id in cmeta_ids:
        try:
            variables.append(model.get_variable_by_cmeta_id(cmeta_id))
        except KeyError as e:
            assert 'cmeta id' in str(e) and str(cmeta_id).replace('#', '', 1) in str(e), str(e)
            raise CodegenError('Rdf subject %s does not refer to any existing variable in the model.' % cmeta_id)
    variables.sort(key=lambda sym: sym.order_added)
    return variables
コード例 #7
0
def process_command_line():
    # add options for command line interface
    parser = argparse.ArgumentParser(
        description='Chaste code generation for cellml.')
    # Options for added pycml backwards compatibility, these are now always on
    parser.add_argument('--Wu',
                        '--warn-on-units-errors',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_argument('-A',
                        '--fully-automatic',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_argument('--expose-annotated-variables',
                        action='store_true',
                        help=argparse.SUPPRESS)

    parser.add_argument(
        '--version',
        action='version',
        version='%(prog)s {version}'.format(version=cg.__version__))
    parser.add_argument(
        'cellml_file',
        metavar='cellml_file',
        help='The cellml file or URI to convert to chaste code')

    group = parser.add_argument_group(
        'ModelTypes',
        'The different types of solver approach for which code can be '
        'generated; if no model type is set, "normal" models are generated')

    for k in TRANSLATORS:
        group.add_argument('--' + k,
                           help='Generate ' + k + ' model type' +
                           TRANSLATORS[k][4],
                           action='store_true')

    group = parser.add_argument_group(
        'Transformations', 'These options control which transformations '
        '(typically optimisations) are applied in the generated code')
    group.add_argument(
        '-j',
        '--use-analytic-jacobian',
        dest='use_analytic_jacobian',
        default=False,
        action='store_true',
        help='use a symbolic Jacobian calculated by SymPy '
        '(--use-analytic-jacobian only works in combination with --cvode'
        ' and is ignored for other model types)')

    group = parser.add_argument_group('Generated code options')
    group.add_argument(
        '-o',
        dest='outfile',
        metavar='OUTFILE',
        default=None,
        help='write program code to OUTFILE '
        '[default action is to use the input filename with a different extension] '
        'NOTE: expects provided OUTFILE to have an extension relevant to code being generated '
        '(e.g. for CHASTE/C++ code: .cpp, .c, .hpp, or .h)')
    group.add_argument('--output-dir',
                       action='store',
                       help="directory to place output files in",
                       default=None)
    group.add_argument(
        '--show-outputs',
        action='store_true',
        default=False,
        help=
        "don't actually generate code, just show what files would be generated, one per line"
    )
    group.add_argument('-c',
                       default=None,
                       dest='cls_name',
                       help='explicitly set the name of the generated class')
    group.add_argument(
        '-q',
        '--quiet',
        action='store_true',
        default=False,
        help="quiet operation, don't print informational messages to screen")
    group.add_argument(
        '--skip-singularity-fixes',
        action='store_true',
        default=False,
        help="skip singularity fixes in Goldman-Hodgkin-Katz (GHK) equations.")

    group = parser.add_argument_group(
        'Chaste options', description='Options specific to Chaste code output')
    group.add_argument(
        '-y',
        '--dll',
        '--dynamically-loadable',
        dest='dynamically_loadable',
        action='store_true',
        default=False,
        help=
        'add code to allow the model to be compiled to a shared library and dynamically loaded '
    )
    group.add_argument(
        '--opt',
        action='store_true',
        default=False,
        help="apply default optimisations to all generated code")
    group.add_argument(
        '-m',
        '--use-modifiers',
        dest='modifiers',
        action='store_true',
        default=False,
        help=
        'add modifier functions for metadata-annotated variables (except time & stimulus) '
        'for use in sensitivity analysis. Only works with one of the following model types and is '
        'ignored for others: ' + str(TRANSLATORS_WITH_MODIFIERS))
    lut_metavar = ("<metadata tag>", "min", "max", "step")
    group.add_argument(
        '--lookup-table',
        nargs=4,
        default=None,
        action='append',
        metavar=lut_metavar,
        help=
        'Specify variable (using a metadata tag) and ranges for which to generate lookup tables '
        '(optional). --lookup-table can be added multiple times to indicate multiple lookup tables'
        '. Please note: Can only be used in combination with --opt. If the arguments are omitted, '
        'following defaults will be used: %s.' % print_default_lookup_params())
    group.add_argument(
        '--use-model-factory',
        action='store_true',
        default=False,
        help='Make use of ModelFactoy method to allow creating models by name. '
        'Requires ModelFactory.hpp/cpp found in the ApPredict project.')

    # process options
    args = parser.parse_args()

    if not os.path.isfile(args.cellml_file):
        raise CodegenError("Could not find cellml file %s " % args.cellml_file)
    if args.outfile is not None and args.output_dir is not None:
        raise CodegenError("-o and --output-dir cannot be used together!")
    if args.lookup_table and not args.opt:
        raise CodegenError(
            "Can only use lookup tables in combination with --opt")

    # make sure --lookup-table entries are 1 string and 3 floats
    if args.lookup_table is None:
        args.lookup_table = DEFAULT_LOOKUP_PARAMETERS
    else:
        for _, lut in enumerate(args.lookup_table):
            lut_params_mgs = \
                'Lookup tables are expecting the following %s values: %s' % (len(lut_metavar), " ".join(lut_metavar))
            assert len(lut) == len(lut_metavar), lut_params_mgs
            try:  # first argument is a string
                float(lut[0])
                is_float = True
            except ValueError:
                is_float = False  # We are expecting this to be a string

            if is_float:
                raise CodegenError(lut_params_mgs)

            for i in range(1, len(lut)):  # next 3 arguments are floats
                try:
                    lut[i] = float(lut[i])
                except ValueError:
                    raise CodegenError(lut_params_mgs)

    # if no model type is set assume normal
    args.normal = args.normal or not any([
        getattr(args, model_type.replace('-', '_'))
        for model_type in TRANSLATORS
    ])

    # create list of translators to apply
    translators = []
    for model_type in TRANSLATORS:
        use_translator_class = getattr(args, model_type.replace('-', '_'))
        if use_translator_class:
            # if -o or dynamically_loadable is selected with opt, only convert opt model type
            if not args.opt or (not args.outfile
                                and not args.dynamically_loadable):
                translators.append(TRANSLATORS[model_type])
            if args.opt and model_type in TRANSLATORS_OPT:
                translators.append(TRANSLATORS_OPT[model_type])

    # An outfile cannot be set with multiple translations
    if args.outfile and len(translators) > 1:
        raise CodegenError(
            "-o cannot be used when multiple model types have been selected!")
    # Dynamically loadable models can only be built one at a time

    if args.dynamically_loadable and len(translators) > 1:
        raise CodegenError(
            "Only one model type may be specified if creating a dynamic library!"
        )

    if not args.show_outputs:
        # Load model once, not once per translator, but only if we're actually generating code
        model = load_model_with_conversions(
            args.cellml_file,
            use_modifiers=args.modifiers,
            quiet=args.quiet,
            skip_singularity_fixes=args.skip_singularity_fixes,
            skip_conversions=skip_conversion(args))

    if len(translators) > 1 and skip_conversion(args):
        raise CodegenError((
            '--rush-larsen-labview and --rush-larsen-c '
            'cannot be used in combination with other model convertion types'))

    for translator in translators:
        # Make sure modifiers are only passed to models which can generate them
        args.use_modifiers = args.modifiers and translator[3]

        translator_class = translator[0]
        outfile_path, model_name_from_file, outfile_base, ext = \
            get_outfile_parts(args.outfile, args.output_dir, args.cellml_file, translator_class)

        ext = ext if ext else translator_class.DEFAULT_EXTENSIONS

        if args.cls_name is not None:
            args.class_name = args.cls_name
        else:
            args.class_name = ('Dynamic' if args.dynamically_loadable else 'Cell') + model_name_from_file +\
                translator[1]

        args.cellml_base = outfile_base
        if not args.outfile:
            outfile_base += translator[2]

        # generate code Based on parameters a different class of translator may be used
        get_files = []
        for ex in ext:
            if ex is not None:
                get_files.append(os.path.join(outfile_path, outfile_base + ex))

        if args.show_outputs:
            for file in get_files:
                print(file)
        else:
            with translator_class(model,
                                  outfile_base,
                                  header_ext=ext[0],
                                  **vars(args)) as chaste_model:
                chaste_model.generate_chaste_code()

                for file, code in zip(get_files, chaste_model.generated_code):
                    write_file(file, code)