Skip to content

Commit

Permalink
disallow one rate for bidirectional rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Marketa Opichalova committed Mar 4, 2024
1 parent ee00206 commit a9cc4b9
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 49 deletions.
13 changes: 8 additions & 5 deletions Testing/parsing/test_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ def test_bidirectional():
assert objects.rule_no_rate in parsed.data["rules"]
assert objects.reversed_no_rate in parsed.data["rules"]

rule_expr = (
"#! rules\nK(S{u}).B()::cyt <=> K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1"
)
rule_expr = "#! rules\nK(S{u}).B()::cyt <=> K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1 | 3*[K()::cyt]/2*v_1"
parsed = objects.rules_parser.parse(rule_expr)
assert parsed.success
assert objects.r4 in parsed.data["rules"]
Expand All @@ -41,13 +39,13 @@ def test_bidirectional():
assert objects.r4 in parsed.data["rules"]
assert objects.reversed_r4b in parsed.data["rules"]

rule_expr = "#! rules\n <=> K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1"
rule_expr = "#! rules\n <=> K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1 | 3*[K()::cyt]/2*v_1"
parsed = objects.rules_parser.parse(rule_expr)
assert parsed.success
assert objects.one_side_bidirectional_a in parsed.data["rules"]
assert objects.one_side_bidirectional_b in parsed.data["rules"]

rule_expr = "#! rules\n K(S{p})::cyt + B()::cyt <=> @ 3*[K()::cyt]/2*v_1"
rule_expr = "#! rules\n K(S{p})::cyt + B()::cyt <=> @ 3*[K()::cyt]/2*v_1 | 3*[K()::cyt]/2*v_1"
parsed = objects.rules_parser.parse(rule_expr)
assert parsed.success
assert objects.one_side_bidirectional_a in parsed.data["rules"]
Expand All @@ -65,5 +63,10 @@ def test_bidirectional():
assert objects.one_side_bidirectional_a_no_rate in parsed.data["rules"]
assert objects.one_side_bidirectional_b_no_rate in parsed.data["rules"]

rule_expr = (
"#! rules\nK(S{u}).B()::cyt <=> K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1"
)
assert not objects.rules_parser.parse(rule_expr).success

rule_expr = "#! rules\nK(S{u}).B()::cyt => K(S{p})::cyt + B()::cyt @ 3*[K()::cyt]/2*v_1 | 2*[K()::cyt]/3*v_1"
assert not objects.rules_parser.parse(rule_expr).success
158 changes: 115 additions & 43 deletions eBCSgen/Core/Rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ def column(lst, index):


class Rule:
def __init__(self, agents: tuple, mid: int, compartments: list, complexes: list, pairs: list, rate: Rate, label=None):
def __init__(
self,
agents: tuple,
mid: int,
compartments: list,
complexes: list,
pairs: list,
rate: Rate,
label=None,
):
"""
Class to represent BCSL rule
Expand All @@ -35,9 +44,15 @@ def __init__(self, agents: tuple, mid: int, compartments: list, complexes: list,
self.label = label
self.comment = (False, [])

def __eq__(self, other: 'Rule'):
return self.agents == other.agents and self.mid == other.mid and self.compartments == other.compartments and \
self.complexes == other.complexes and self.pairs == other.pairs and str(self.rate) == str(other.rate)
def __eq__(self, other: "Rule"):
return (
self.agents == other.agents
and self.mid == other.mid
and self.compartments == other.compartments
and self.complexes == other.complexes
and self.pairs == other.pairs
and str(self.rate) == str(other.rate)
)

def __repr__(self):
return str(self)
Expand All @@ -47,14 +62,23 @@ def __str__(self):
rate = " @ " + str(self.rate) if self.rate else ""
pre_comment, post_comment = "", ""
if self.comment[1]:
comment = "// redundant #{" + ", ".join(list(map(str, self.comment[1]))) + "} "
comment = (
"// redundant #{" + ", ".join(list(map(str, self.comment[1]))) + "} "
)
pre_comment = comment + "// " if self.comment[0] else ""
post_comment = " " + comment if not self.comment[0] else ""

label = str(self.label) + " ~ " if self.label else ""

return label + pre_comment + " + ".join(lhs.to_list_of_strings()) + \
" => " + " + ".join(rhs.to_list_of_strings()) + rate + post_comment
return (
label
+ pre_comment
+ " + ".join(lhs.to_list_of_strings())
+ " => "
+ " + ".join(rhs.to_list_of_strings())
+ rate
+ post_comment
)

def __lt__(self, other):
return str(self) < str(other)
Expand All @@ -70,10 +94,12 @@ def get_unique_complexes_from_rule(self) -> dict:
:return: dict of {Complexes:{SBML codes of all isomorphisms in set}}
"""
unique_complexes_from_rule = dict()
for (f, t) in self.complexes:
c = Complex(self.agents[f:t + 1], self.compartments[f])
double = (c, c.to_SBML_species_code())
unique_complexes_from_rule[c] = unique_complexes_from_rule.get(c, set()) | {double}
for f, t in self.complexes:
c = Complex(self.agents[f : t + 1], self.compartments[f])
double = (c, c.to_SBML_species_code())
unique_complexes_from_rule[c] = unique_complexes_from_rule.get(c, set()) | {
double
}
return unique_complexes_from_rule

def create_complexes(self):
Expand All @@ -83,8 +109,8 @@ def create_complexes(self):
:return: two multisets of Complexes represented as object Side
"""
lhs, rhs = [], []
for (f, t) in self.complexes:
c = Complex(self.agents[f:t + 1], self.compartments[f])
for f, t in self.complexes:
c = Complex(self.agents[f : t + 1], self.compartments[f])
lhs.append(c) if t < self.mid else rhs.append(c)
return Side(lhs), Side(rhs)

Expand All @@ -108,7 +134,9 @@ def rate_to_vector(self, ordering, definitions: dict):
if self.rate:
self.rate.vectorize(ordering, definitions)

def create_reactions(self, atomic_signature: dict, structure_signature: dict) -> set:
def create_reactions(
self, atomic_signature: dict, structure_signature: dict
) -> set:
"""
Create all possible reactions.
Decide if rule is of replication type and call corresponding lower level method.
Expand All @@ -118,13 +146,21 @@ def create_reactions(self, atomic_signature: dict, structure_signature: dict) ->
:return: set of created reactions
"""
unique_lhs_indices = set(column(self.pairs, 0))
if len(self.pairs) > 1 and len(unique_lhs_indices) == 1 and None not in unique_lhs_indices:
if (
len(self.pairs) > 1
and len(unique_lhs_indices) == 1
and None not in unique_lhs_indices
):
# should be the replication rule
return self._create_replication_reactions(atomic_signature, structure_signature)
return self._create_replication_reactions(
atomic_signature, structure_signature
)
else:
return self._create_normal_reactions(atomic_signature, structure_signature)

def _create_replication_reactions(self, atomic_signature: dict, structure_signature: dict) -> set:
def _create_replication_reactions(
self, atomic_signature: dict, structure_signature: dict
) -> set:
"""
Create reaction from rule of special form for replication (A -> 2 A)
Expand All @@ -144,13 +180,22 @@ def _create_replication_reactions(self, atomic_signature: dict, structure_signat
# replicate RHS agent n times
for _ in range(len(self.pairs)):
new_agents.append(deepcopy(new_agents[-1]))
new_rule = Rule(tuple(new_agents), self.mid, self.compartments,
self.complexes, self.pairs, self.rate, self.label)
new_rule = Rule(
tuple(new_agents),
self.mid,
self.compartments,
self.complexes,
self.pairs,
self.rate,
self.label,
)
reactions.add(new_rule.to_reaction())

return reactions

def _create_normal_reactions(self, atomic_signature: dict, structure_signature: dict) -> set:
def _create_normal_reactions(
self, atomic_signature: dict, structure_signature: dict
) -> set:
"""
Adds context to all agents and generated all possible combinations.
Then, new rules with these enhances agents are generated and converted to Reactions.
Expand All @@ -160,7 +205,7 @@ def _create_normal_reactions(self, atomic_signature: dict, structure_signature:
:return: set of created reactions
"""
results = []
for (l, r) in self.pairs:
for l, r in self.pairs:
if l is None:
right = -1
left = self.agents[r]
Expand All @@ -170,17 +215,27 @@ def _create_normal_reactions(self, atomic_signature: dict, structure_signature:
else:
left = self.agents[l]
right = self.agents[r]
results.append(left.add_context(right, atomic_signature, structure_signature))
results.append(
left.add_context(right, atomic_signature, structure_signature)
)

reactions = set()
for result in itertools.product(*results):
new_agents = tuple(filter(None, column(result, 0) + column(result, 1)))
new_rule = Rule(new_agents, self.mid, self.compartments, self.complexes, self.pairs, self.rate, self.label)
new_rule = Rule(
new_agents,
self.mid,
self.compartments,
self.complexes,
self.pairs,
self.rate,
self.label,
)
reactions.add(new_rule.to_reaction())

return reactions

def compatible(self, other: 'Rule') -> bool:
def compatible(self, other: "Rule") -> bool:
"""
Checks whether Rule is compatible (position-wise) with the other Rule.
Is done by formaly translating to Reactions (just a better object handling).
Expand All @@ -201,7 +256,14 @@ def reduce_context(self):
"""
new_agents = tuple([agent.reduce_context() for agent in self.agents])
new_rate = self.rate.reduce_context() if self.rate else None
return Rule(new_agents, self.mid, self.compartments, self.complexes, self.pairs, new_rate)
return Rule(
new_agents,
self.mid,
self.compartments,
self.complexes,
self.pairs,
new_rate,
)

def is_meaningful(self) -> bool:
"""
Expand Down Expand Up @@ -231,7 +293,9 @@ def create_all_compatible(self, atomic_signature: dict, structure_signature: dic
:param structure_signature: given structure signature
:return: set of all created Complexes
"""
return self.to_reaction().create_all_compatible(atomic_signature, structure_signature)
return self.to_reaction().create_all_compatible(
atomic_signature, structure_signature
)

def evaluate_rate(self, state, params):
"""
Expand All @@ -242,7 +306,7 @@ def evaluate_rate(self, state, params):
:return: a real number of the rate
"""
values = dict()
for (state_complex, count) in state.content.value.items():
for state_complex, count in state.content.value.items():
for agent in self.rate_agents:
if agent.compatible(state_complex):
values[agent] = values.get(agent, 0) + count
Expand Down Expand Up @@ -277,16 +341,24 @@ def replace(self, aligned_match):
# replace respective agents

unique_lhs_indices = set(column(self.pairs, 0))
if len(self.pairs) > 1 and len(unique_lhs_indices) == 1 and \
None not in unique_lhs_indices and len(aligned_match) == 1:
if (
len(self.pairs) > 1
and len(unique_lhs_indices) == 1
and None not in unique_lhs_indices
and len(aligned_match) == 1
):
resulting_rhs = self._replace_replicated_rhs(aligned_match[0])
else:
resulting_rhs = self._replace_normal_rhs(aligned_match)

# construct resulting complexes
output_complexes = []
for (f, t) in list(filter(lambda item: item[0] >= self.mid, self.complexes)):
output_complexes.append(Complex(resulting_rhs[f - self.mid:t - self.mid + 1], self.compartments[f]))
for f, t in list(filter(lambda item: item[0] >= self.mid, self.complexes)):
output_complexes.append(
Complex(
resulting_rhs[f - self.mid : t - self.mid + 1], self.compartments[f]
)
)

return Multiset(collections.Counter(output_complexes))

Expand All @@ -298,7 +370,7 @@ def _replace_normal_rhs(self, aligned_match):
:return: RHS with replaced agents
"""
resulting_rhs = []
for i, rhs_agent in enumerate(self.agents[self.mid:]):
for i, rhs_agent in enumerate(self.agents[self.mid :]):
if len(aligned_match) <= i:
resulting_rhs.append(rhs_agent)
else:
Expand Down Expand Up @@ -329,8 +401,8 @@ def reconstruct_complexes_from_match(self, match):
:return: multiset of constructed agents
"""
output_complexes = []
for (f, t) in list(filter(lambda item: item[1] < self.mid, self.complexes)):
output_complexes.append(Complex(match[f:t + 1], self.compartments[f]))
for f, t in list(filter(lambda item: item[1] < self.mid, self.complexes)):
output_complexes.append(Complex(match[f : t + 1], self.compartments[f]))
return Multiset(collections.Counter(output_complexes))

def create_reversible(self, rate: Rate = None):
Expand All @@ -343,22 +415,22 @@ def create_reversible(self, rate: Rate = None):
:return: reversed Rule
"""
agents = self.agents[self.mid:] + self.agents[:self.mid]
agents = self.agents[self.mid :] + self.agents[: self.mid]
mid = len(self.agents) - self.mid
compartments = self.compartments[self.mid:] + self.compartments[:self.mid]
complexes = sorted([((f - self.mid) % len(self.agents),
(t - self.mid) % len(self.agents)) for (f, t) in self.complexes])
compartments = self.compartments[self.mid :] + self.compartments[: self.mid]
complexes = sorted(
[
((f - self.mid) % len(self.agents), (t - self.mid) % len(self.agents))
for (f, t) in self.complexes
]
)
pairs = []
for (l, r) in self.pairs:
for l, r in self.pairs:
if l is None or r is None:
pairs.append((r, l))
else:
pairs.append((l, r))

if rate is None:
rate = self.rate
else:
rate = deepcopy(rate)
label = None
if self.label:
label = self.label + "_bw"
Expand Down
2 changes: 1 addition & 1 deletion eBCSgen/Parsing/ParseBCSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def to_side(self):
init: const? rate_complex (COMMENT)?
definition: def_param "=" number (COMMENT)?
rule: ((label)? side ARROW side ("@" rate)? (";" variable)? (COMMENT)?) | ((label)? side BI_ARROW side ("@" (rate | (rate "|" rate)))? (";" variable)? (COMMENT)?)
rule: ((label)? side ARROW side ("@" rate)? (";" variable)? (COMMENT)?) | ((label)? side BI_ARROW side ("@" rate "|" rate)? (";" variable)? (COMMENT)?)
cmplx_dfn: cmplx_name "=" value (COMMENT)?
side: (const? complex "+")* (const? complex)?
Expand Down

0 comments on commit a9cc4b9

Please sign in to comment.