Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate sites support #410

Merged
merged 10 commits into from May 10, 2019

Rename MultiSite to MultiState

  • Loading branch information...
alubbock committed Apr 25, 2019
commit 41d5305cb7840e65e2046e6f42408e20e6814739
@@ -3,7 +3,7 @@

__all__ = ['Observable', 'Initial', 'MatchOnce', 'Model', 'Monomer',
'Parameter', 'Compartment', 'Rule', 'Expression', 'ANY', 'WILD',
'Annotation', 'MultiSite']
'Annotation', 'MultiState']

try:
import reinteract # fails if reinteract not installed
@@ -364,7 +364,7 @@ def _check_state_bond_tuple(monomer, site, state):
return is_state_bond_tuple(state) and _check_state(monomer, site, state[0])


def validate_site_value(state, monomer=None, site=None, _in_multisite=False):
def validate_site_value(state, monomer=None, site=None, _in_multistate=False):
if state is None:
This conversation was marked as resolved by alubbock

This comment has been minimized.

Copy link
@jmuhlich

jmuhlich Apr 24, 2019

Member

Would this series of if statements better reflect the overall intent as a big if-elif-elif-elif...-else? I think they all explicitly return or raise, except the last one does seem to have a fallthrough case when isinstance(state, MultiSite) and not (monomer and site) -- was that intentional? If that fallthrough is resolved then I do think a series of elifs would read better. The extra blank lines should go, regardless.

return True
elif isinstance(state, basestring):
@@ -378,15 +378,15 @@ def validate_site_value(state, monomer=None, site=None, _in_multisite=False):
if monomer and site:
_check_state(monomer, site, state[0])
return True
elif isinstance(state, MultiSite):
if _in_multisite:
raise ValueError('Cannot nest MultiSite within each other')
elif isinstance(state, MultiState):
if _in_multistate:
raise ValueError('Cannot nest MultiState within each other')

if monomer and site:
site_counts = collections.Counter(monomer.sites)
if len(state) > site_counts[site]:
raise ValueError(
'MultiSite for site "{}" on monomer "{}" has maximum '
'MultiState for site "{}" on monomer "{}" has maximum '
'length {}'.format(site, monomer.name, site_counts[site])
)

@@ -398,23 +398,23 @@ def validate_site_value(state, monomer=None, site=None, _in_multisite=False):
return False


class MultiSite(object):
class MultiState(object):
"""
MultiSite for a Monomer (also known as duplicate sites)
MultiState for a Monomer (also known as duplicate sites)
MultiSites are duplicate copies of a site which each have the same name and
semantics. In BioNetGen, these are known as duplicate sites. MultiSites
MultiStates are duplicate copies of a site which each have the same name and
semantics. In BioNetGen, these are known as duplicate sites. MultiStates
are not supported by Kappa.
This conversation was marked as resolved by alubbock

This comment has been minimized.

Copy link
@jmuhlich

jmuhlich Apr 24, 2019

Member

It should probably mention here that a MultiSite instance is not connected to any particular Monomer or site, and that it's only a syntactical construct to support a particular pattern grammar (I'm not sure how to say that cleanly). This helps explain why the constructor can only perform minimal validation. Possibly add a comment in the constructor too.

When declared, a MultiSite instance is not connected to any Monomer or site,
so full validation is deferred until it is used as part of a
When declared, a MultiState instance is not connected to any Monomer or
site, so full validation is deferred until it is used as part of a
:py:class:`MonomerPattern` or :py:class:`ComplexPattern`.
Examples
--------
Define a Monomer "A" with MultiSite "a", which has two copies, and
Monomer "B" with MultiSite "b", which also has two copies but can take
Define a Monomer "A" with MultiState "a", which has two copies, and
Monomer "B" with MultiState "b", which also has two copies but can take
state values "u" and "p":
>>> Model() # doctest:+ELLIPSIS
@@ -424,23 +424,23 @@ class MultiSite(object):
>>> Monomer('B', ['b', 'b'], {'b': ['u', 'p']}) # BNG: B(b~u~p, b~u~p)
Monomer('B', ['b', 'b'], {'b': ['u', 'p']})
To specify MultiSites, use the MultiSite class. Here are some valid
examples of MultiSite patterns, with their BioNetGen equivalents:
To specify MultiStates, use the MultiState class. Here are some valid
examples of MultiState patterns, with their BioNetGen equivalents:
>>> A(a=MultiSite(1, 2)) # BNG: A(a!1,a!2)
A(a=MultiSite(1, 2))
>>> B(b=MultiSite('u', 'p')) # BNG: A(A~u,A~p)
B(b=MultiSite('u', 'p'))
>>> A(a=MultiSite(1, 2)) % B(b=MultiSite(('u', 1), 2)) # BNG: A(a!1, a!2).B(b~u!1, b~2)
A(a=MultiSite(1, 2)) % B(b=MultiSite(('u', 1), 2))
>>> A(a=MultiState(1, 2)) # BNG: A(a!1,a!2)
A(a=MultiState(1, 2))
>>> B(b=MultiState('u', 'p')) # BNG: A(A~u,A~p)
B(b=MultiState('u', 'p'))
>>> A(a=MultiState(1, 2)) % B(b=MultiState(('u', 1), 2)) # BNG: A(a!1, a!2).B(b~u!1, b~2)
A(a=MultiState(1, 2)) % B(b=MultiState(('u', 1), 2))
"""
def __init__(self, *args):
if len(args) == 1:
raise ValueError('MultiSite should not be used when only a single '
raise ValueError('MultiState should not be used when only a single '
'site is specified')
self.sites = args
for s in self.sites:
validate_site_value(s, _in_multisite=True)
validate_site_value(s, _in_multistate=True)

def __len__(self):
return len(self.sites)
@@ -486,7 +486,7 @@ class MonomerPattern(object):
* *tuple of (str, int)* : state with specified bond
* *tuple of (str, WILD)* : state with wildcard bond
* *tuple of (str, ANY)* : state with any bond
* MultiSite : duplicate sites
* MultiState : duplicate sites
If a site is not listed in site_conditions then the pattern will match any
state for that site, i.e. \"don't write, don't care\".
@@ -547,7 +547,7 @@ def is_site_concrete(self):
return False
for site_name, site_val in self.site_conditions.items():
if site_name in dup_sites:
if not isinstance(site_val, MultiSite) or \
if not isinstance(site_val, MultiState) or \
len(site_val) < dup_sites[site_name]:
return False

@@ -846,7 +846,7 @@ def _handle_site_instance(state_or_bond):
g.add_edge(mon_node_id, cpt_node_id)

for site, state_or_bond in mp.site_conditions.items():
if isinstance(state_or_bond, MultiSite):
if isinstance(state_or_bond, MultiState):
# Duplicate sites
[_handle_site_instance(s) for s in state_or_bond]
else:
@@ -1,7 +1,7 @@
import inspect
import warnings
import pysb
from pysb.core import MultiSite
from pysb.core import MultiState
import sympy
from sympy.printing import StrPrinter

@@ -233,7 +233,7 @@ def format_site_condition(site, state):
elif state[1] == pysb.ANY:
state = (state[0], '+')
state_code = '~%s!%s' % state
elif isinstance(state, MultiSite):
elif isinstance(state, MultiState):
return ','.join(format_site_condition(site, s) for s in state)
# one or more unspecified bonds
elif state is pysb.ANY:
@@ -199,9 +199,9 @@ def format_site_condition(self, site, state, mon_name):
# Multi-bond (list of bonds)
elif isinstance(state, list):
raise KappaException("Kappa generator does not support multi-bonds")
# If there is a MultiBond, raise an Exception (not supported by Kappa)
elif isinstance(state, pysb.MultiSite):
raise KappaException("Kappa generator does not support MultiSites.")
# If there is a MultiState, raise an Exception (not supported by Kappa)
elif isinstance(state, pysb.MultiState):
raise KappaException("Kappa generator does not support MultiStates")
# Site with state
elif isinstance(state, basestring):
state_code = '{%s}[.]' % state
@@ -1,5 +1,5 @@
from pysb.core import MonomerPattern, ComplexPattern, RuleExpression, \
ReactionPattern, ANY, WILD, MultiSite
ReactionPattern, ANY, WILD, MultiState
from pysb.builder import Builder
from pysb.bng import BngFileInterface
import xml.etree.ElementTree
@@ -126,7 +126,7 @@ def _parse_species(self, species_xml):
else:
mon_states[state_nm].append(last_bond)

mon_states = {k: MultiSite(*v) if len(v) > 1 else v[0]
mon_states = {k: MultiState(*v) if len(v) > 1 else v[0]
for k, v in mon_states.items()}

mon_cpt = self.model.compartments.get(mon.get('compartment'))
@@ -396,30 +396,30 @@ def test_duplicate_sites():
Monomer('B', ['b', 'b'], {'b': ['u', 'p']})

assert not A(a=1).is_concrete()
assert A(a=MultiSite(1, 2)).is_concrete()
assert A(a=MultiSite(1, None)).is_concrete()
assert A(a=MultiState(1, 2)).is_concrete()
assert A(a=MultiState(1, None)).is_concrete()

assert not B(b=('u', 1)).is_concrete()

assert B(b=MultiSite('u', 'p')).is_concrete()
assert B(b=MultiSite(('u', 1), ('u', 2))).is_concrete()
assert B(b=MultiState('u', 'p')).is_concrete()
assert B(b=MultiState(('u', 1), ('u', 2))).is_concrete()

# Check _as_graph() works for duplicate sites
B(b=MultiSite(('u', 1), ('u', 2)))._as_graph()
B(b=MultiState(('u', 1), ('u', 2)))._as_graph()

assert B(b=MultiSite('u', ('u', 1))).is_concrete()
assert B(b=MultiState('u', ('u', 1))).is_concrete()

# Syntax errors (should use MultiSite)
# Syntax errors (should use MultiState)
assert_raises(ValueError, B, b=('u', 'p'))
assert_raises(ValueError, B, b=['u', 'p'])

# Syntax error (can't nest MultiSite)
assert_raises(ValueError, MultiSite, MultiSite(1, 2), 'p')
# Syntax error (can't nest MultiState)
assert_raises(ValueError, MultiState, MultiState(1, 2), 'p')

# Duplicate sites with multi-bond
A(a=MultiSite([1, 2], [1, 2]))
A(a=MultiState([1, 2], [1, 2]))


@raises(ValueError)
def test_duplicate_site_single_site():
MultiSite('a')
MultiState('a')
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.