Skip to content

Commit

Permalink
- fix an issue with setting expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
fbergmann committed Mar 14, 2024
1 parent 671b2a4 commit d8a8fba
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 13 deletions.
73 changes: 60 additions & 13 deletions basico/model_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,25 @@ def wrap_copasi_string(text):
"""
return 'String=' + COPASI.CCommonName.escape(text)

def _is_known_reference_start(text):
if text.startswith('['):
return True
if text.startswith('Values['):
return True
if text.startswith('('):
return True
return False

def _is_known_reference_end(text):
for end in [']',
'].InitialValue',
'].Value',
'].Rate',
'].InitialConcentration',
]:
if text.endswith(end):
return True
return False

def _replace_names_with_cns(expression, **kwargs):
""" replaces all names in the given expression with cns
Expand All @@ -1246,9 +1265,32 @@ def _replace_names_with_cns(expression, **kwargs):
resulting_expression = ''
expression = expression.replace('{', ' {')
expression = expression.replace('}', '} ')
current_word = None

for word in expression.split():
if word.startswith('{') and word.endswith('}'):
if current_word is not None:
resulting_expression += ' ' + current_word
current_word = None
word = word[1:-1]
else:
is_known = dm.findObjectByDisplayName(word)
if _is_known_reference_start(word) and not is_known:
if current_word is not None:
resulting_expression += ' ' + current_word
current_word = word
continue

if current_word is not None:
current_word += ' ' + word
if _is_known_reference_end(word):
word = current_word
current_word = None
elif dm.findObjectByDisplayName(word) is not None:
word = current_word
current_word = None
else:
continue

obj = dm.findObjectByDisplayName(word)
if obj is not None:
Expand Down Expand Up @@ -1886,6 +1928,8 @@ def add_species(name, compartment_name='', initial_concentration=1.0, **kwargs):
if model.getNumCompartments() == 0:
model.createCompartment('compartment', 1)
compartment_name = model.getCompartment(0).getObjectName()
elif not model.getCompartment(compartment_name):
model.createCompartment(compartment_name, 1)

species = model.createMetabolite(name, compartment_name, initial_concentration)
if species is None:
Expand Down Expand Up @@ -2703,14 +2747,14 @@ def _set_compartment(compartment, c_model, **kwargs):
if transient in kwargs:
compartment.setValue(float(kwargs[transient]))
if 'initial_expression' in kwargs:
_set_initial_expression(compartment, kwargs['initial_expression'])
_set_initial_expression(compartment, kwargs['initial_expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)
if 'status' in kwargs:
compartment.setStatus(__status_to_int(kwargs['status']))
if 'type' in kwargs:
compartment.setStatus(__status_to_int(kwargs['type']))
if 'expression' in kwargs:
_set_expression(compartment, kwargs['expression'])
_set_expression(compartment, kwargs['expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)
if 'dimensionality' in kwargs:
compartment.setDimensionality(kwargs['dimensionality'])
Expand All @@ -2720,43 +2764,46 @@ def _set_compartment(compartment, c_model, **kwargs):
compartment.setSBMLId(kwargs['sbml_id'])


def _set_initial_expression(element, expression):
def _set_initial_expression(element, expression, model=None):
"""Utility function to safely set an initial expression
:param element: model element
:type element: COPASI.CModelEntity
:type element: COPASI.CModelEntity
:param expression: infix expression to set
:param model: the data model to use
:return: None
"""
if element is None:
return

_set_safe(element.setInitialExpression, expression)
_set_safe(element.setInitialExpression, expression, model)


def _set_expression(element, expression):
def _set_expression(element, expression, model=None):
"""Utility function to safely set an ODE / assignment expression
:param element: model element
:type element: COPASI.CModelEntity
:param expression: infix expression to set
:param model: the data model to use
:return: None
"""
if element is None:
return
_set_safe(element.setExpression, expression)
_set_safe(element.setExpression, expression, model=model)


def _set_safe(fun, expression):
def _set_safe(fun, expression, model=None):
"""Calls the given function that is supposed to return a COPASI.CIssue
:param fun: function to call
:param expression: infic expression
:param model: the data model to use
:return:
"""
result = fun(_replace_names_with_cns(expression))
result = fun(_replace_names_with_cns(expression, model=model))
if result.isError():
logger.error('Invalid expression: {0}'.format(expression))
# set to empty string avoid crash
Expand Down Expand Up @@ -2846,7 +2893,7 @@ def _set_parameter(param, c_model, **kwargs):
param.setValue(float(kwargs['value']))

if 'initial_expression' in kwargs:
_set_initial_expression(param, kwargs['initial_expression'])
_set_initial_expression(param, kwargs['initial_expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)

if 'status' in kwargs:
Expand All @@ -2858,7 +2905,7 @@ def _set_parameter(param, c_model, **kwargs):
c_model.setCompileFlag(True)

if 'expression' in kwargs:
_set_expression(param, kwargs['expression'])
_set_expression(param, kwargs['expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)

if 'notes' in kwargs:
Expand Down Expand Up @@ -3681,14 +3728,14 @@ def _set_species(metab, c_model, **kwargs):
if 'particle_number' in kwargs:
metab.setValue(float(kwargs['particle_number']))
if 'initial_expression' in kwargs:
_set_initial_expression(metab, kwargs['initial_expression'])
_set_initial_expression(metab, kwargs['initial_expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)
if 'status' in kwargs:
metab.setStatus(__status_to_int(kwargs['status']))
if 'type' in kwargs:
metab.setStatus(__status_to_int(kwargs['type']))
if 'expression' in kwargs:
_set_expression(metab, kwargs['expression'])
_set_expression(metab, kwargs['expression'], model=c_model.getObjectDataModel())
c_model.setCompileFlag(True)
if 'notes' in kwargs:
metab.setNotes(kwargs['notes'])
Expand Down
35 changes: 35 additions & 0 deletions tests/test_basico_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ def test_load_biomodel(self):
self.assertEqual(params.shape[0], 176)
basico.remove_datamodel(dm)

def test_load_and_reconstruct(self):
dm = basico.load_biomodel(64)
species = basico.as_dict(basico.get_species())
params = basico.as_dict(basico.get_parameters())
basico.remove_datamodel(dm)
dm = basico.new_model()

# ensure we have all species
for s in species:
basico.add_species(s['name'], s['compartment'])

for p in params:
basico.add_parameter(p['name'], p['value'])

# now set all values as we had them
for s in species:
basico.set_species(exact=True, **s)

for p in params:
basico.set_parameters(exact=True, **p)

# now we should have the same expressions in both
species2 = basico.as_dict(basico.get_species())
params2 = basico.as_dict(basico.get_parameters())

self.assertListEqual(
[ s['expression'] for s in species if s['expression'] != ''],
[ s['expression'] for s in species2 if s['expression'] != ''])

self.assertListEqual(
[ p['expression'] for s in params if s['expression'] != ''],
[ s['expression'] for s in params2 if s['expression'] != ''])

basico.remove_datamodel(dm)

def test_search_biomodels(self):
models = basico.biomodels.search_for_model('Hodgkin')
model_ids = [model['id'] for model in models]
Expand Down

0 comments on commit d8a8fba

Please sign in to comment.