KarrLab / wc_lang

Language for describing whole-cell models as reaction networks
MIT License
9 stars 3 forks source link

Problem converting to SBML #130

Closed artgoldberg closed 4 years ago

artgoldberg commented 4 years ago

This wc_lang model https://github.com/KarrLab/wc_sim/blob/ode/tests/fixtures/dynamic_tests/static_sbml/static.xlsx generates a libSBML returned None error when attempting to write it as SBML. wc_lang passes all tests and the model validates:

The code:

from wc_lang.sbml import io as sbml_io
from wc_lang.io import Reader, Writer

lang_filename = 'tests/fixtures/dynamic_tests/static_sbml/static.xlsx'
model_from_file = Reader().run(lang_filename)[Model][0]
assert model_from_file.validate() is None

# write SBML file
sbml_dirname = ... # new dir
sbml_io.SbmlWriter().run(model_from_file, sbml_dirname)

The error:

...

            # write SBML file
            sbml_io.SbmlWriter().run(model_from_file, sbml_dirname)

tests/test_multialgorithm_simulation.py:280: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.6/site-packages/wc_lang/sbml/io.py:108: in run
    sbml_doc = SbmlExporter.run(model)
/usr/local/lib/python3.6/site-packages/wc_lang/sbml/io.py:201: in run
    sbml_model = LibSbmlInterface.init_model(model, sbml_doc, packages=packages)
/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:306: in init_model
    cls.create_units(model, sbml_model)
/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:327: in create_units
    sbml_unit = cls.create_unit(unit, sbml_model)
/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:361: in create_unit
    unit_def = cls.call_libsbml(sbml_model.createUnitDefinition)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.util.LibSbmlInterface'>
method = <bound method Model.createUnitDefinition of <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7f2d2fc5bea0> >>
returns_int = False, debug = False, args = (), new_args = []

    @classmethod
    def call_libsbml(cls, method, *args, returns_int=False, debug=False):
        """ Call a libSBML method and handle any errors.

        Unfortunately, libSBML methods that do not return data usually report errors via return codes,
        instead of exceptions, and the generic return codes contain virtually no information.
        This function wraps these methods and raises useful exceptions when errors occur.

        Set `returns_int` `True` to avoid raising false exceptions or warnings from methods that return
        integer values.

        Args:
            method (:obj:`type`, :obj:`types.FunctionType`, or :obj:`types.MethodType`): `libsbml` method to execute
            args (:obj:`list`): a `list` of arguments to the `libsbml` method
            returns_int (:obj:`bool`, optional): whether the method returns an integer; if `returns_int`
                is `True`, then an exception will not be raised if the method call returns an integer
            debug (:obj:`bool`, optional): whether to print debug output

        Returns:
            :obj:`obj` or `int`: if the call does not return an error, return the `libsbml`
                method's return value, either an object that has been created or retrieved, or an integer
                value, or the `libsbml` success return code, :obj:`libsbml.LIBSBML_OPERATION_SUCCESS`

        Raises:
            :obj:`LibSbmlError`: if the `libsbml` call raises an exception, or returns None, or
                returns a known integer error code != :obj:`libsbml.LIBSBML_OPERATION_SUCCESS`
        """
        new_args = []
        for arg in args:
            new_args.append(arg)
        if new_args:
            new_args_str = ', '.join([str(a) for a in new_args])
            call_str = "method: {}; args: {}".format(method, new_args_str)
        else:
            call_str = "method: {}".format(method)
        if debug:
            print('libSBML call:', call_str)
        try:
            rc = method(*tuple(new_args))
        except BaseException as error:
            raise LibSbmlError("Error '{}' in libSBML method call '{}'.".format(error, call_str))
        if rc == None:
>           raise LibSbmlError("libSBML returned None when executing '{}'.".format(call_str))
E           wc_lang.sbml.util.LibSbmlError: libSBML returned None when executing 'method: <bound method Model.createUnitDefinition of <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7f2d2fc5bea0> >>'.

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:957: LibSbmlError
artgoldberg commented 4 years ago

More complete trace:

_________________________ TestMultialgorithmSimulationDynamically.test_convert_to_sbml _________________________

self = <tests.test_multialgorithm_simulation.TestMultialgorithmSimulationDynamically testMethod=test_convert_to_sbml>

    def test_convert_to_sbml(self):
        from wc_lang.sbml import io as sbml_io
        from wc_lang.io import Reader, Writer
        from wc_sim.testing.utils import read_model_for_test
        all_models = ['static', 'one_reaction_linear', 'one_rxn_exponential', 'one_exchange_rxn_compt_growth',
                  'stop_conditions']
        for model_name in all_models:
            print(f'converting {model_name}')
            dirname = os.path.join(os.path.dirname(__file__), 'fixtures', 'dynamic_tests')
            model_filename = os.path.join(dirname, f'{model_name}.xlsx')
            integration_framework = 'ordinary_differential_equations'
            model = read_model_for_test(model_filename, integration_framework=f'WC:{integration_framework}')

            # write wc_lang file
            sbml_dirname = os.path.join(dirname, f'{model_name}_sbml')
            if not os.path.isdir(sbml_dirname):
                os.makedirs(sbml_dirname)

            lang_filename = os.path.join(sbml_dirname, f'{model_name}.xlsx')
            print(f'writing {lang_filename}')
            Writer().run(lang_filename, model)
            model_from_file = Reader().run(lang_filename)[Model][0]
            assert model_from_file.validate() is None

            # write SBML file
            try:
>               sbml_io.SbmlWriter().run(model_from_file, sbml_dirname)

tests/test_multialgorithm_simulation.py:281: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <wc_lang.sbml.io.SbmlWriter object at 0x7fef69cea6a0>
model = <wc_lang.core.Model object at 0x7fef69ea4358>
dirname = '/root/host/Documents/wc_sim/tests/fixtures/dynamic_tests/static_sbml'

    def run(self, model, dirname):
        """ Write the submodels of a `wc_lang` model to separate SBML-encoded XML files.

        Args:
            model (:obj:`wc_lang.core.Model`): `wc_lang` model
            dirname (:obj:`str`): path to directory to save SBML-encoded XML files for each submodel

        Raises:
            :obj:`ValueError`: if the model could not be written to a SBML-encoded file
        """
        # validate model
        model = PrepForSbmlTransform().run(model.copy())
        error = wc_lang.core.Validator().run(model)
        if error:
            warnings.warn('Model is invalid: ' + str(error), wc_lang.core.WcLangWarning)

        # split submodels into separate models
        core, submodels = model.submodels.gen_models()
        all_models = [core] + submodels
        all_models_ids = ['core'] + [m.submodels[0].id for m in submodels]

        # create a directory to save SBML-encoded XML documents for model
        if not os.path.isdir(dirname):
            os.makedirs(dirname)

        # encode models in SBML and save to XML file
        for model, model_id in zip(all_models, all_models_ids):
            # encode models in SBML
>           sbml_doc = SbmlExporter.run(model)

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/io.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.io.SbmlExporter'>, model = <wc_lang.core.Model object at 0x7fef69ea4358>

    @classmethod
    def run(cls, model):
        """ Encode a `wc_lang` model with at most 1 submodel into SBML

        * Validate model
        * Create SBML document
        * Create SBML model
        * Encode model objects in SBML and add to SBML model in dependent order

        Args:
            model (:obj:`wc_lang.core.Model`): `wc_lang` model with at most 1 submodel

        Returns:
            :obj:`libsbml.SBMLDocument`: SBML document with SBML-encoded model

        Raises:
            :obj:`ValueError`: if the model cannot be exported to SBML because it contains multiple submodels
        """
        # verify model has at most 1 submodel
        if len(model.submodels) > 1:
            raise ValueError('Only 1 submodel can be encoded to SBML at a time')

        # validate model
        error = wc_lang.core.Validator().run(model)
        if error:
            warnings.warn('Model is invalid: ' + str(error), wc_lang.core.WcLangWarning)

        # determine SBML packages needed to export model
        packages = {}
        for submodel in model.submodels:
            if submodel.framework == onto['WC:dynamic_flux_balance_analysis']:
                packages['fbc'] = 2

        # create an SBML document
        sbml_doc = LibSbmlInterface.create_doc(packages=packages)

        # create a SBML model
>       sbml_model = LibSbmlInterface.init_model(model, sbml_doc, packages=packages)

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/io.py:201: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.util.LibSbmlInterface'>, model = <wc_lang.core.Model object at 0x7fef69ea4358>
sbml_doc = <SBMLDocument>, packages = {}

    @classmethod
    def init_model(cls, model, sbml_doc, packages=None):
        """ Create and initialize an SMBL model.

        Args:
            model (:obj:`wc_lang.core.Model`): model
            sbml_doc (:obj:`libsbml.SBMLDocument`): a `libsbml` SBMLDocument
            packages (:obj:`dict` that maps :obj:`str` to :obj:`int`, optional): dictionary of required packages
                that maps package identifiers to package numbers

        Returns:
            :obj:`libsbml.Model`: the SBML model
        """

        # create model
        sbml_model = cls.create_model(sbml_doc)

        # enable plugins for packages
        packages = packages or {}
        for package_id in packages.keys():
            plugin = cls.call_libsbml(sbml_model.getPlugin, package_id)
            cls.call_libsbml(plugin.setStrict, True)

        # Set units
>       cls.create_units(model, sbml_model)

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:306: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.util.LibSbmlInterface'>, model = <wc_lang.core.Model object at 0x7fef69ea4358>
sbml_model = <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7fef68846ea0> >

    @classmethod
    def create_units(cls, model, sbml_model):
        """ Set time, extent, and substance units of SBML model

        Args:
            model (:obj:`wc_lang.core.Model`): model
            sbml_model (:obj:`libsbml.Model`): SBML model that encodes the model

        Returns:
            :obj:`dict`: dictionary that maps units to ids of SBML unit definitions
        """
        # Define units
        units = obj_tables.units.get_obj_units(model)

        units_to_sbml = {}
        for unit in units:
>           sbml_unit = cls.create_unit(unit, sbml_model)

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.util.LibSbmlInterface'>, unit = <Unit('1 / second')>
sbml_model = <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7fef68846ea0> >

    @classmethod
    def create_unit(cls, unit, sbml_model):
        """ Add a unit definition to a SBML model

        Args:
            unit (:obj:`unit_registry.Unit`): unit
            sbml_model (:obj:`libsbml.Model`): SBML model that encodes the model

        Returns:
            :obj:`libsbml.UnitDefinition`: unit definition
        """
        id = cls.gen_unit_id(unit)
        if not id.startswith('unit_'):
            return None

>       unit_def = cls.call_libsbml(sbml_model.createUnitDefinition)

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:361: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'wc_lang.sbml.util.LibSbmlInterface'>
method = <bound method Model.createUnitDefinition of <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7fef68846ea0> >>
returns_int = False, debug = False, args = (), new_args = []

    @classmethod
    def call_libsbml(cls, method, *args, returns_int=False, debug=False):
        """ Call a libSBML method and handle any errors.

        Unfortunately, libSBML methods that do not return data usually report errors via return codes,
        instead of exceptions, and the generic return codes contain virtually no information.
        This function wraps these methods and raises useful exceptions when errors occur.

        Set `returns_int` `True` to avoid raising false exceptions or warnings from methods that return
        integer values.

        Args:
            method (:obj:`type`, :obj:`types.FunctionType`, or :obj:`types.MethodType`): `libsbml` method to execute
            args (:obj:`list`): a `list` of arguments to the `libsbml` method
            returns_int (:obj:`bool`, optional): whether the method returns an integer; if `returns_int`
                is `True`, then an exception will not be raised if the method call returns an integer
            debug (:obj:`bool`, optional): whether to print debug output

        Returns:
            :obj:`obj` or `int`: if the call does not return an error, return the `libsbml`
                method's return value, either an object that has been created or retrieved, or an integer
                value, or the `libsbml` success return code, :obj:`libsbml.LIBSBML_OPERATION_SUCCESS`

        Raises:
            :obj:`LibSbmlError`: if the `libsbml` call raises an exception, or returns None, or
                returns a known integer error code != :obj:`libsbml.LIBSBML_OPERATION_SUCCESS`
        """
        new_args = []
        for arg in args:
            new_args.append(arg)
        if new_args:
            new_args_str = ', '.join([str(a) for a in new_args])
            call_str = "method: {}; args: {}".format(method, new_args_str)
        else:
            call_str = "method: {}".format(method)
        if debug:
            print('libSBML call:', call_str)
        try:
            rc = method(*tuple(new_args))
        except BaseException as error:
            raise LibSbmlError("Error '{}' in libSBML method call '{}'.".format(error, call_str))
        if rc == None:
>           raise LibSbmlError("libSBML returned None when executing '{}'.".format(call_str))
E           wc_lang.sbml.util.LibSbmlError: libSBML returned None when executing 'method: <bound method Model.createUnitDefinition of <libsedml.Model; proxy of <Swig Object of type 'Model_t *' at 0x7fef68846ea0> >>'.

/usr/local/lib/python3.6/site-packages/wc_lang/sbml/util.py:957: LibSbmlError
jonrkarr commented 4 years ago

What file are you reffering to? The branch wc-sim-ode and https://github.com/KarrLab/wc_sim/blob/ode/tests/fixtures/dynamic_tests/static_sbml/static.xlsx don't exist.

I copied https://github.com/KarrLab/wc_sim/blob/master/tests/fixtures/dynamic_tests/static.xlsx to a test in wc-lang. tests/sbml/test_sbml_io.py:test_export_another_model. This illustrates that the model converts to SBML fine.

artgoldberg commented 4 years ago

I cannot reproduce.