Skip to content

Commit

Permalink
Merge 72b85e1 into 91fefad
Browse files Browse the repository at this point in the history
  • Loading branch information
alubbock committed Mar 21, 2019
2 parents 91fefad + 72b85e1 commit c1ce377
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 123 deletions.
12 changes: 9 additions & 3 deletions pysb/bng.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def __init__(self, model=None, verbose=False, cleanup=False,
self._logger = get_logger(__name__,
model=model,
log_level=verbose)
self._base_file_stem = 'pysb'
if model:
self._base_file_stem = re.sub('[^0-9a-zA-Z_]+', '_', model.name)
else:
self._base_file_stem = 'pysb'
self.cleanup = cleanup
self.output_prefix = 'tmpBNG' if output_prefix is None else \
output_prefix
Expand Down Expand Up @@ -781,7 +784,7 @@ def _parse_species(model, line):
for ms in monomer_strings:
monomer_name, site_strings, monomer_compartment_name = \
re.match(r'\$?(\w+)\(([^)]*)\)(?:@(\w+))?', ms).groups()
site_conditions = {}
site_conditions = collections.defaultdict(list)
if len(site_strings):
for ss in site_strings.split(','):
# FIXME this should probably be done with regular expressions
Expand All @@ -805,7 +808,10 @@ def _parse_species(model, line):
site_name, condition = ss.split('~')
else:
site_name, condition = ss, None
site_conditions[site_name] = condition
site_conditions[site_name].append(condition)

site_conditions = {k: v[0] if len(v) == 1 else tuple(v)
for k, v in site_conditions.items()}
monomer = model.monomers[monomer_name]
monomer_compartment = model.compartments.get(monomer_compartment_name)
# Compartment prefix notation in BNGL means "assign this compartment to
Expand Down
179 changes: 114 additions & 65 deletions pysb/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,6 @@ def __init__(self, name, sites=None, site_states=None, _export=True):
raise ValueError('Invalid site name: ' + str(site))
sites_seen.setdefault(site, 0)
sites_seen[site] += 1
sites_dup = [site for site, count in sites_seen.items() if count > 1]
if sites_dup:
raise ValueError("Duplicate sites specified: " + str(sites_dup))

# ensure site_states keys are all known sites
unknown_sites = [site for site in site_states if not site in sites_seen]
Expand Down Expand Up @@ -339,6 +336,32 @@ def _check_state(monomer, site, state):
template = "Invalid state choice '{}' in Monomer {}, site {}. Valid " \
"state choices: {}"
raise ValueError(template.format(*args))
return True


def _check_bond(bond):
""" A bond can either by a single int, WILD, ANY, or a list of ints """
return (
isinstance(bond, int) or
bond is WILD or
bond is ANY or
isinstance(bond, list) and all(isinstance(b, int) for b in bond)
)


def is_state_bond_tuple(state):
""" Check the argument is a (state, bond) tuple for a Mononer site """
return (
isinstance(state, tuple) and
len(state) == 2 and
isinstance(state[0], basestring) and
_check_bond(state[1])
)


def _check_state_bond_tuple(monomer, site, state):
""" Check that 'state' is a (state, bond) tuple, and validate the state """
return is_state_bond_tuple(state) and _check_state(monomer, site, state[0])


class MonomerPattern(object):
Expand Down Expand Up @@ -389,34 +412,33 @@ def __init__(self, monomer, site_conditions, compartment):

# ensure each value is one of: None, integer, list of integers, string,
# (string,integer), (string,WILD), ANY, WILD
site_counts = collections.Counter(monomer.sites)
invalid_sites = []
for (site, state) in site_conditions.items():
# pass through to next iteration if state type is ok
if state is None:
continue
elif isinstance(state, int):
continue
elif isinstance(state, list) and \
all(isinstance(s, int) for s in state):
continue
elif isinstance(state, basestring):
_check_state(monomer, site, state)
continue
elif isinstance(state, tuple) and \
isinstance(state[0], basestring) and \
(isinstance(state[1], int) or state[1] is WILD or \
state[1] is ANY):
_check_state(monomer, site, state[0])
elif _check_bond(state):
continue
elif state is ANY:
elif _check_state_bond_tuple(monomer, site, state):
continue
elif state is WILD:
elif (isinstance(state, tuple) and
len(state) <= site_counts[site] and
all(s is None or
_check_bond(s) or
_check_state_bond_tuple(monomer, site, s) or
_check_state(monomer, site, s)
for s in state)):
continue
invalid_sites.append(site)
if invalid_sites:
raise ValueError("Invalid state value for sites: " +
'; '.join(['%s=%s' % (s, str(site_conditions[s]))
for s in invalid_sites]))
for s in invalid_sites]) +
' in {}'.format(monomer))

# ensure compartment is a Compartment
if compartment and not isinstance(compartment, Compartment):
Expand Down Expand Up @@ -448,29 +470,44 @@ def is_site_concrete(self):
Return a bool indicating whether the pattern is 'site-concrete'.
'Site-concrete' means all sites have specified conditions."""
if len(self.site_conditions) != len(self.monomer.sites):
dup_sites = {k: v for k, v in
collections.Counter(self.monomer.sites).items() if v > 1}
if len(self.site_conditions) != len(self.monomer.sites) and \
not dup_sites:
return False
for site_name, site_val in self.site_conditions.items():
if isinstance(site_val, basestring):
site_state = site_val
site_bond = None
elif isinstance(site_val, collections.Iterable):
site_state, site_bond = site_val
elif isinstance(site_val, int):
site_bond = site_val
site_state = None
else:
site_bond = site_val
site_state = None

if site_bond is ANY or site_bond is WILD:
return False
if site_state is None and site_name in \
self.monomer.site_states.keys():
if site_name in dup_sites:
if not isinstance(site_val, tuple) or \
len(site_val) < dup_sites[site_name] or \
is_state_bond_tuple(site_val):
return False

if not all(self._site_instance_concrete(site_name, s)
for s in site_val):
return False
elif not self._site_instance_concrete(site_name, site_val):
return False

return True

def _site_instance_concrete(self, site_name, site_val):
if isinstance(site_val, basestring):
site_state = site_val
site_bond = None
elif isinstance(site_val, tuple):
site_state, site_bond = site_val
else:
site_bond = site_val
site_state = None

if site_bond is ANY or site_bond is WILD:
return False
if site_state is None and site_name in \
self.monomer.site_states.keys():
return False

return True

def _as_graph(self):
"""
Convert MonomerPattern to networkx graph, caching the result
Expand Down Expand Up @@ -546,9 +583,11 @@ def __pow__(self, other):

def __repr__(self):
value = '%s(' % self.monomer.name
sites_unique = list(collections.OrderedDict.fromkeys(
self.monomer.sites))
value += ', '.join([
k + '=' + repr(self.site_conditions[k])
for k in self.monomer.sites
for k in sites_unique
if k in self.site_conditions
])
value += ')'
Expand Down Expand Up @@ -692,6 +731,41 @@ def add_or_get_compartment_node(cpt):
if self.compartment:
species_cpt_node_id = add_or_get_compartment_node(self.compartment)

def _handle_site_instance(state_or_bond):
mon_site_id = next(node_count)
g.add_node(mon_site_id, id=site)
g.add_edge(mon_node_id, mon_site_id)
state = None
bond_num = None
if state_or_bond is WILD:
return
elif isinstance(state_or_bond, basestring):
state = state_or_bond
elif is_state_bond_tuple(state_or_bond):
state = state_or_bond[0]
bond_num = state_or_bond[1]
elif isinstance(state_or_bond, int):
bond_num = state_or_bond
elif state_or_bond is not ANY and state_or_bond is not None:
raise ValueError('Unrecognized state: {}'.format(
state_or_bond))

if state_or_bond is ANY or bond_num is ANY:
bond_num = any_bond_tester
any_bond_tester_id = next(node_count)
g.add_node(any_bond_tester_id, id=any_bond_tester)
g.add_edge(mon_site_id, any_bond_tester_id)

if state is not None:
mon_site_state_id = next(node_count)
g.add_node(mon_site_state_id, id=state)
g.add_edge(mon_site_id, mon_site_state_id)

if bond_num is None:
bond_edges[NO_BOND].append(mon_site_id)
elif isinstance(bond_num, int):
bond_edges[bond_num].append(mon_site_id)

for mp in self.monomer_patterns:
mon_node_id = next(node_count)
g.add_node(mon_node_id, id=mp.monomer)
Expand All @@ -701,37 +775,12 @@ def add_or_get_compartment_node(cpt):
g.add_edge(mon_node_id, cpt_node_id)

for site, state_or_bond in mp.site_conditions.items():
mon_site_id = next(node_count)
g.add_node(mon_site_id, id=site)
g.add_edge(mon_node_id, mon_site_id)
state = None
bond_num = None
if state_or_bond is WILD:
continue
elif isinstance(state_or_bond, basestring):
state = state_or_bond
elif isinstance(state_or_bond, collections.Iterable) and len(
state_or_bond) == 2:
state = state_or_bond[0]
bond_num = state_or_bond[1]
elif isinstance(state_or_bond, int):
bond_num = state_or_bond

if state_or_bond is ANY or bond_num is ANY:
bond_num = any_bond_tester
any_bond_tester_id = next(node_count)
g.add_node(any_bond_tester_id, id=any_bond_tester)
g.add_edge(mon_site_id, any_bond_tester_id)

if state is not None:
mon_site_state_id = next(node_count)
g.add_node(mon_site_state_id, id=state)
g.add_edge(mon_site_id, mon_site_state_id)

if bond_num is None:
bond_edges[NO_BOND].append(mon_site_id)
elif isinstance(bond_num, int):
bond_edges[bond_num].append(mon_site_id)
if isinstance(state_or_bond, tuple) and not \
is_state_bond_tuple(state_or_bond):
# Duplicate sites
[_handle_site_instance(s) for s in state_or_bond]
else:
_handle_site_instance(state_or_bond)

# Unbound edges
unbound_sites = bond_edges.pop(NO_BOND, None)
Expand Down
21 changes: 13 additions & 8 deletions pysb/generator/bng.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
import warnings
import pysb
from pysb.core import is_state_bond_tuple
import sympy
from sympy.printing import StrPrinter

Expand Down Expand Up @@ -45,7 +46,7 @@ def generate_parameters(self):
max_length = max(len(p.name) for p in
self.model.parameters | self.model.expressions)
for p in self.model.parameters:
self.__content += ((" %-" + str(max_length) + "s %e\n") %
self.__content += ((" %-" + str(max_length) + "s %.16g\n") %
(p.name, p.value))
for e in exprs:
self.__content += ((" %-" + str(max_length) + "s %s\n") %
Expand Down Expand Up @@ -226,12 +227,15 @@ def format_site_condition(site, state):
state_code = '~' + state
# state AND single bond
elif isinstance(state, tuple):
# bond is wildcard (zero or more unspecified bonds)
if state[1] == pysb.WILD:
state = (state[0], '?')
elif state[1] == pysb.ANY:
state = (state[0], '+')
state_code = '~%s!%s' % state
if is_state_bond_tuple(state):
# bond is wildcard (zero or more unspecified bonds)
if state[1] == pysb.WILD:
state = (state[0], '?')
elif state[1] == pysb.ANY:
state = (state[0], '+')
state_code = '~%s!%s' % state
else:
return ','.join(format_site_condition(site, s) for s in state)
# one or more unspecified bonds
elif state is pysb.ANY:
state_code = '!+'
Expand All @@ -241,7 +245,8 @@ def format_site_condition(site, state):
elif state is pysb.WILD:
state_code = '!?'
else:
raise Exception("BNG generator has encountered an unknown element in a rule pattern site condition.")
raise ValueError("BNG generator has encountered an unknown element in "
"a rule pattern site condition.")
return '%s%s' % (site, state_code)

def warn_caller(message):
Expand Down

0 comments on commit c1ce377

Please sign in to comment.