Skip to content

Commit

Permalink
added parameter explanations
Browse files Browse the repository at this point in the history
  • Loading branch information
jcrozum committed Mar 7, 2024
1 parent f279197 commit c9ffc84
Showing 1 changed file with 141 additions and 45 deletions.
186 changes: 141 additions & 45 deletions balm/petri_net_translation.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
"""
Implements the translation from a `BooleanNetwork` object into a Petri net that can be processed
by Trappist. The Petri net is represented as a `DiGraph`, with nodes having either a `kind=place`
or a `kind=transition` attribute.
The variable names in the network have to be "sanitized" before translation. In particular, this
means they can't use any special characters beyond "_". In the resulting Petri net, we then use
a "b0_*" and "b1_*" prefix to distinguish the "zero" and "one" places created for each variable.
This is also important for `clingo`, as we have to guarantee that in our logic program, symbols
start with a lowercase letter.
Utilities for translating Boolean networks into Petri nets.
Implements the translation from a `BooleanNetwork` object into a Petri net that
can be processed by Trappist (for finding trap spaces). The Petri net is
represented as a `networkx.DiGraph`, with nodes having either a `kind=place` or
a `kind=transition` attribute.
The variable names in the network have to be "sanitized" before translation. In
particular, this means they can't use any special characters beyond "_". In the
resulting Petri net, we then use a "b0_*" and "b1_*" prefix to distinguish the
"zero" and "one" places created for each variable. This is also important for
`clingo`, as we have to guarantee that in our logic program, symbols start with
a lowercase letter.
"""

from __future__ import annotations
Expand Down Expand Up @@ -35,20 +39,37 @@

# Enables statistics logging.
DEBUG = False
"""Enables debug logging to stdout."""


def sanitize_network_names(network: BooleanNetwork, check_only: bool = False):
"""
Verifies that all network variable names contain only alphanumeric characters and "_".
If this is not the case, attempts to rename the variables to make them compliant.
Returns a *copy* of the original network that only uses sanitized names.
Note that AEON should already prune away most of the special characters when parsing models,
but it still allows names like `Gene_{Subscript}` (i.e. curly brackets) which we have
to sanitize here.
If `check_only=True` is specified, no renaming takes place and the function fails with
a `RuntimeError` instead.
Rename variables in a network so that they can be safely used in Trappist.
Verifies that all network variable names contain only alphanumeric
characters and underscores. If this is not the case, attempts to rename the
variables to make them compliant. Returns a *copy* of the original network
that only uses sanitized names.
Note that AEON should already prune away most of the special characters when
parsing models, but it still allows names like `Gene_{Subscript}` (i.e.
curly brackets) which we have to sanitize here.
If `check_only=True` is specified, no renaming takes place and the function
fails with a `RuntimeError` instead.
Parameters
----------
network : BooleanNetwork
The network to sanitize.
check_only : bool
If `True`, no renaming takes place and the function fails with a
`RuntimeError` if the network contains invalid variable names.
Returns
-------
BooleanNetwork
A copy of the original network with sanitized variable names.
"""
network = copy.copy(network)
for var in network.variables():
Expand All @@ -72,8 +93,20 @@ def sanitize_network_names(network: BooleanNetwork, check_only: bool = False):

def variable_to_place(variable: str, positive: bool) -> str:
"""
Convert the name of a network variable to the name of the corresponding positive
or negative Petri net place.
Generate a Petri net place name from a network variable name.
Parameters
----------
variable : str
The name of the network variable.
positive : bool
`True` if the place corresponding to the variable should be positive,
`False` if it shoudl be negative.
Returns
-------
str
The name of the corresponding Petri net place.
"""
if positive:
return f"b1_{variable}"
Expand All @@ -83,8 +116,17 @@ def variable_to_place(variable: str, positive: bool) -> str:

def place_to_variable(place: str) -> tuple[str, bool]:
"""
Convert the name of a Petri net place to the name of the network variable, plus
a Boolean indicating whether the original place was positive or negative.
Extract the variable name and state from a Peteri net place name.
Parameters
----------
place : str
The name of the Petri net place.
Returns
-------
tuple[str, bool]
The name of the variable, and whether the variable is positive or negative.
"""
if place.startswith("b1_"):
return (place[3:], True)
Expand All @@ -98,10 +140,21 @@ def extract_variable_names(encoded_network: DiGraph) -> list[str]:
"""
Extract the variable names from a Petri net encoded Boolean network.
The variables are sorted lexicographically, since the original BN ordering is not
preserved by the Petri net. However, BNs order variables lexicographically by default,
so unless the Petri net was created from a custom BN (i.e. not from a normal model file),
the ordering should be the same.
The variables are sorted lexicographically, since the original BN ordering
is not preserved by the Petri net. However, BNs order variables
lexicographically by default, so unless the Petri net was created from a
custom BN (i.e. not from a normal model file), the ordering should be the
same.
Parameters
----------
encoded_network : DiGraph
The Petri net encoded Boolean network.
Returns
-------
list[str]
The list of variable names.
"""
variables: list[str] = []
for node in encoded_network.nodes(): # type: ignore
Expand All @@ -114,9 +167,19 @@ def extract_variable_names(encoded_network: DiGraph) -> list[str]:

def extract_source_variables(encoded_network: DiGraph) -> list[str]:
"""
Extract the list of variable names that represent source nodes of the encoded network.
List variable names that represent source nodes of the encoded network.
That is, nodes with an identity update function.
Source nodes are those nodes with an identity update function.
Parameters
----------
encoded_network : DiGraph
The Petri net encoded Boolean network.
Returns
-------
list[str]
The list of source variable names.
"""
variables = extract_variable_names(encoded_network)
source_set = set(variables)
Expand All @@ -132,12 +195,27 @@ def restrict_petrinet_to_subspace(
sub_space: BooleanSpace,
) -> DiGraph:
"""
Create a copy of the given Petri net, but with the variables given in `sub_space` fixed to their
respective values.
Note that this completely eliminates the constant variables from the {etri net, but it does not
perform any further constant propagation or percolation. Variables that are fixed in the sub_space
but do not exist in the Petri net are ignored.
Create a copy of a Petri net restricted to a sub-space.
Creates a copy of the given Petri net, but with the variables given in
`sub_space` fixed to their respective values.
Note that this completely eliminates the constant variables from the {etri
net, but it does not perform any further constant propagation or
percolation. Variables that are fixed in the sub_space but do not exist in
the Petri net are ignored.
Parameters
----------
petri_net : DiGraph
The Petri net to restrict.
sub_space : BooleanSpace
The sub-space to restrict the Petri net to.
Returns
-------
DiGraph
The restricted Petri net.
"""
result = copy.deepcopy(petri_net)
for var, value in sub_space.items():
Expand Down Expand Up @@ -181,15 +259,33 @@ def network_to_petrinet(
network: BooleanNetwork, ctx: SymbolicContext | None = None
) -> DiGraph:
"""
Convert a Boolean network to a Petri net.
Converts a `BooleanNetwork` to a `DiGraph` representing a Petri net encoding
of the original network. For details about the encoding, see module description.
of the original network. For details about the encoding, see module
description.
Note that the given network needs to have "sanitized" names, otherwise the
method will fail (see `sanitize_network_names` in this module).
The operation uses translation through `biodivine_aeon.Bdd` to generate a disjunctive normal form
of the network's update functions. This is facilitated by `biodivine_aeon.SymbolicContext`. If such
context already exists, it can be provided as the second argument. Otherwise it will be created.
The operation uses translation through `biodivine_aeon.Bdd` to generate a
disjunctive normal form of the network's update functions. This is
facilitated by `biodivine_aeon.SymbolicContext`. If such context already
exists, it can be provided as the second argument. Otherwise it will be
created.
Parameters
----------
network : BooleanNetwork
The network to convert.
ctx : SymbolicContext | None
The context used for the symbolic conversion. If not given, a new one
will be created from the network.
Returns
-------
DiGraph
The Petri net encoding of the given network.
"""
# Assert that all network names are already sanitized.
sanitize_network_names(network, check_only=True)
Expand Down Expand Up @@ -257,13 +353,13 @@ def _optimized_recursive_dnf_generator(
bdd: Bdd,
) -> Generator[BddPartialValuation, None, None]:
"""
Yields a generator of `BddPartialValuation` objects, similar to `bdd.clause_iterator`,
but uses a recursive optimization strategy to return a smaller result than the default
method `Bdd` clause sequence. Note that this is still not the "optimal" DNF, but is often
close enough.
Yields a generator of `BddPartialValuation` objects, similar to
`bdd.clause_iterator`, but uses a recursive optimization strategy to return
a smaller result than the default method `Bdd` clause sequence. Note that
this is still not the "optimal" DNF, but is often close enough.
This is technically slower for BDDs that already have a small clause count, but can be
much better in the long-term when the clause count is high.
This is technically slower for BDDs that already have a small clause count,
but can be much better in the long-term when the clause count is high.
"""

# Some performance notes:
Expand Down

1 comment on commit c9ffc84

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
balm
   control.py1141488%102, 114, 120, 124, 129, 138–154, 472, 475, 488
   interaction_graph_utils.py30487%6–8, 145–146
   motif_avoidant.py148299%25, 171
   petri_net_translation.py1491193%22–26, 79, 136, 303–304, 328–329, 338, 442
   space_utils.py129497%25–27, 252, 278
   succession_diagram.py2571694%6, 184–189, 197, 257–258, 268, 274, 390, 580, 772, 810, 847
   symbolic_utils.py26388%10–12, 44
   trappist_core.py1833084%11–15, 52, 54, 89, 165, 212, 214, 216, 244–247, 251–253, 273–279, 337, 339, 369, 417, 419, 450, 502
balm/_sd_algorithms
   compute_attractor_seeds.py30197%8
   expand_attractor_seeds.py51590%6, 42, 97–102
   expand_bfs.py28196%6
   expand_dfs.py30197%6
   expand_minimal_spaces.py37295%6, 31
   expand_source_SCCs.py164696%19–21, 91, 101, 143, 287
   expand_to_target.py31390%6, 38, 43
TOTAL146810393% 

Tests Skipped Failures Errors Time
361 0 💤 0 ❌ 0 🔥 52.951s ⏱️

Please sign in to comment.