From 5437b5c8403ed3fcfbc87245dd12a0d9ad9846e5 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 13 Dec 2023 12:09:54 -0500 Subject: [PATCH] renamed type aliases to use a more standard convention --- balm/SuccessionDiagram.py | 26 ++++----- .../_sd_algorithms/compute_attractor_seeds.py | 4 +- balm/_sd_algorithms/expand_source_SCCs.py | 16 +++--- balm/_sd_algorithms/expand_to_target.py | 4 +- balm/control.py | 49 +++++++++-------- balm/drivers.py | 12 ++--- balm/motif_avoidant.py | 30 +++++------ balm/space_utils.py | 34 ++++++------ balm/state_utils.py | 24 +++++---- balm/terminal_restriction_space.py | 12 ++--- balm/trappist_core.py | 54 +++++++++---------- balm/types.py | 10 ++-- tests/control_test.py | 32 +++++------ tests/motif_avoidant_test.py | 36 ++++++------- tests/succession_diagram_test.py | 6 +-- tests/terminal_restriction_space_test.py | 6 +-- 16 files changed, 183 insertions(+), 172 deletions(-) diff --git a/balm/SuccessionDiagram.py b/balm/SuccessionDiagram.py index 40319084..8e6c793d 100644 --- a/balm/SuccessionDiagram.py +++ b/balm/SuccessionDiagram.py @@ -19,7 +19,7 @@ from balm.petri_net_translation import network_to_petrinet from balm.space_utils import percolate_space, space_unique_key from balm.trappist_core import trappist -from balm.types import space_type +from balm.types import BooleanSpace # Enables helpful "progress" messages. DEBUG = False @@ -137,7 +137,7 @@ def __setstate__( self.petri_net = cast(nx.DiGraph, state["petri net"]) self.nfvs = cast(list[str], state["nfvs"]) self.dag = cast(nx.DiGraph, state["G"]) # type: ignore - self.node_indices = cast(space_type, state["node_indices"]) # type: ignore + self.node_indices = cast(BooleanSpace, state["node_indices"]) # type: ignore def __len__(self) -> int: """ @@ -174,7 +174,7 @@ def from_file(path: str) -> SuccessionDiagram: """ return SuccessionDiagram(BooleanNetwork.from_file(path)) - def expanded_attractor_seeds(self) -> list[list[space_type]]: + def expanded_attractor_seeds(self) -> list[list[BooleanSpace]]: return [self.node_attractor_seeds(id) for id in self.expanded_ids()] def summary(self) -> str: @@ -263,7 +263,7 @@ def minimal_trap_spaces(self) -> list[int]: """ return [i for i in self.expanded_ids() if self.node_is_minimal(i)] - def find_node(self, node_space: space_type) -> int | None: + def find_node(self, node_space: BooleanSpace) -> int | None: """ Return the ID of the node matching the provided `node_space`, or `None` if no such node exists in this succession diagram. @@ -336,14 +336,14 @@ def node_depth(self, node_id: int) -> int: """ return cast(int, self.dag.nodes[node_id]["depth"]) - def node_space(self, node_id: int) -> space_type: + def node_space(self, node_id: int) -> BooleanSpace: """ Get the sub-space associated with the provided `node_id`. Note that this is the space *after* percolation. Hence it can hold that `|node_space(child)| < |node_space(parent)| + |stable_motif(parent, child)|`. """ - return cast(space_type, self.dag.nodes[node_id]["space"]) + return cast(BooleanSpace, self.dag.nodes[node_id]["space"]) def node_is_expanded(self, node_id: int) -> bool: """ @@ -388,7 +388,7 @@ def node_successors(self, node_id: int, compute: bool = False) -> list[int]: def node_attractor_seeds( self, node_id: int, compute: bool = False - ) -> list[space_type]: + ) -> list[BooleanSpace]: """ Return the list of attractor seed states corresponding to the given `node_id`. Similar to `node_successors`, the method either computes the @@ -402,7 +402,7 @@ def node_attractor_seeds( """ node = cast(dict[str, Any], self.dag.nodes[node_id]) - attractors = cast(list[space_type] | None, node["attractors"]) + attractors = cast(list[BooleanSpace] | None, node["attractors"]) if attractors is None and not compute: raise KeyError(f"Attractor data not computed for node {node_id}.") @@ -415,7 +415,7 @@ def node_attractor_seeds( def edge_stable_motif( self, parent_id: int, child_id: int, reduced: bool = False - ) -> space_type: + ) -> BooleanSpace: """ Return the *stable motif* associated with the specified parent-child edge. If `reduced` is set to `False` (default), the unpercolated stable @@ -429,7 +429,7 @@ def edge_stable_motif( if reduced: return cast( - space_type, + BooleanSpace, { k: v for k, v in self.dag.edges[parent_id, child_id]["motif"].items() # type: ignore @@ -437,7 +437,7 @@ def edge_stable_motif( }, ) else: - return cast(space_type, self.dag.edges[parent_id, child_id]["motif"]) + return cast(BooleanSpace, self.dag.edges[parent_id, child_id]["motif"]) def build(self): """ @@ -538,7 +538,7 @@ def expand_attractor_seeds(self, size_limit: int | None = None) -> bool: return expand_attractor_seeds(self, size_limit) def expand_to_target( - self, target: space_type, size_limit: int | None = None + self, target: BooleanSpace, size_limit: int | None = None ) -> bool: """ Expands the succession diagram using BFS in such a way that only nodes @@ -619,7 +619,7 @@ def _expand_one_node(self, node_id: int): if DEBUG: print(f"[{node_id}] Created edge into node {child_id}.") - def _ensure_node(self, parent_id: int | None, stable_motif: space_type) -> int: + def _ensure_node(self, parent_id: int | None, stable_motif: BooleanSpace) -> int: """ Internal method that ensures the provided node is present in this succession diagram as a child of the given `parent_id`. diff --git a/balm/_sd_algorithms/compute_attractor_seeds.py b/balm/_sd_algorithms/compute_attractor_seeds.py index 2cd1ed23..79f061aa 100644 --- a/balm/_sd_algorithms/compute_attractor_seeds.py +++ b/balm/_sd_algorithms/compute_attractor_seeds.py @@ -10,13 +10,13 @@ from balm.motif_avoidant import detect_motif_avoidant_attractors, make_retained_set from balm.terminal_restriction_space import get_terminal_restriction_space from balm.trappist_core import compute_fixed_point_reduced_STG -from balm.types import space_type +from balm.types import BooleanSpace def compute_attractor_seeds( sd: SuccessionDiagram, node_id: int, -) -> list[space_type]: +) -> list[BooleanSpace]: """ Compute the list of vertices such that each attractor within the subspace of the given `node_id` is covered by exactly one vertex. diff --git a/balm/_sd_algorithms/expand_source_SCCs.py b/balm/_sd_algorithms/expand_source_SCCs.py index 91ca5ebc..f30d6d66 100644 --- a/balm/_sd_algorithms/expand_source_SCCs.py +++ b/balm/_sd_algorithms/expand_source_SCCs.py @@ -13,7 +13,7 @@ from balm.interaction_graph_utils import infer_signed_interaction_graph from balm.petri_net_translation import extract_variable_names, network_to_petrinet from balm.space_utils import percolate_network, percolate_space -from balm.types import space_type +from balm.types import BooleanSpace if TYPE_CHECKING: expander_function_type = Callable[ @@ -72,7 +72,7 @@ def expand_source_SCCs( if len(source_nodes) != 0: bin_values_iter = it.product(range(2), repeat=len(source_nodes)) for bin_values in bin_values_iter: - source_comb = cast(space_type, dict(zip(source_nodes, bin_values))) + source_comb = cast(BooleanSpace, dict(zip(source_nodes, bin_values))) sub_space = source_comb sub_space.update(perc_space) @@ -90,7 +90,7 @@ def expand_source_SCCs( # each level consists of one round of fixing all source SCCs for node_id in current_level: - sub_space = cast(space_type, sd.dag.nodes[node_id]["space"]) + sub_space = cast(BooleanSpace, sd.dag.nodes[node_id]["space"]) # find source SCCs clean_bnet, clean_bn = perc_and_remove_constants_from_bn(perc_bn, sub_space) @@ -184,7 +184,7 @@ def find_source_nodes(network: BooleanNetwork | DiGraph) -> list[str]: def perc_and_remove_constants_from_bn( - bn: BooleanNetwork, space: space_type + bn: BooleanNetwork, space: BooleanSpace ) -> tuple[str, BooleanNetwork]: """ Take a BooleanNetwork and percolate given space. @@ -318,11 +318,11 @@ def find_scc_sd( # delete the implicit parameters from the node subspaces and the edge motifs for node_id in scc_sd.node_ids(): for implicit in implicit_parameters: - cast(space_type, scc_sd.dag.nodes[node_id]["space"]).pop(implicit, None) + cast(BooleanSpace, scc_sd.dag.nodes[node_id]["space"]).pop(implicit, None) for x, y in cast(Iterable[tuple[int, int]], scc_sd.dag.edges): for implicit in implicit_parameters: - cast(space_type, scc_sd.dag.edges[x, y]["motif"]).pop(implicit, None) + cast(BooleanSpace, scc_sd.dag.edges[x, y]["motif"]).pop(implicit, None) return scc_sd, exist_maa @@ -364,7 +364,7 @@ def attach_scc_sd( parent_id = size_before_attach + scc_parent_id - 1 motif = scc_sd.edge_stable_motif(scc_parent_id, scc_node_id) - motif.update(cast(space_type, sd.dag.nodes[branch]["space"])) + motif.update(cast(BooleanSpace, sd.dag.nodes[branch]["space"])) child_id = sd._ensure_node(parent_id, motif) # type: ignore if check_maa: @@ -385,7 +385,7 @@ def attach_scc_sd( scc_child_ids = cast(list[int], list(scc_sd.dag.successors(scc_node_id))) # type: ignore for scc_child_id in scc_child_ids: motif = scc_sd.edge_stable_motif(scc_node_id, scc_child_id) - motif.update(cast(space_type, sd.dag.nodes[branch]["space"])) + motif.update(cast(BooleanSpace, sd.dag.nodes[branch]["space"])) child_id = sd._ensure_node(parent_id, motif) # type: ignore assert child_id == size_before_attach + scc_child_id - 1 diff --git a/balm/_sd_algorithms/expand_to_target.py b/balm/_sd_algorithms/expand_to_target.py index 32794162..458f3fdc 100644 --- a/balm/_sd_algorithms/expand_to_target.py +++ b/balm/_sd_algorithms/expand_to_target.py @@ -6,11 +6,11 @@ from balm.SuccessionDiagram import SuccessionDiagram from balm.space_utils import intersect, is_subspace -from balm.types import space_type +from balm.types import BooleanSpace def expand_to_target( - sd: SuccessionDiagram, target: space_type, size_limit: int | None = None + sd: SuccessionDiagram, target: BooleanSpace, size_limit: int | None = None ): """ See `SuccessionDiagram.exapnd_to_target` for documentation. diff --git a/balm/control.py b/balm/control.py index 25856240..6cc41269 100644 --- a/balm/control.py +++ b/balm/control.py @@ -8,16 +8,19 @@ from balm.space_utils import is_subspace, percolate_space from balm.SuccessionDiagram import SuccessionDiagram -from balm.types import ControlType, SuccessionType, space_type +from balm.types import BooleanSpace, ControlOverrides, SubspaceSuccession -def controls_are_equal(a: ControlType, b: ControlType) -> bool: +def controls_are_equal(a: ControlOverrides, b: ControlOverrides) -> bool: return set(frozenset(x.items()) for x in a) == set(frozenset(x.items()) for x in b) class Intervention: def __init__( - self, control: list[ControlType], strategy: str, succession: SuccessionType + self, + control: list[ControlOverrides], + strategy: str, + succession: SubspaceSuccession, ): self._control = control self._strategy = strategy @@ -105,7 +108,7 @@ def __str__(self): def succession_control( bn: BooleanNetwork, - target: space_type, + target: BooleanSpace, strategy: str = "internal", succession_diagram: SuccessionDiagram | None = None, max_drivers_per_succession_node: int | None = None, @@ -118,7 +121,7 @@ def succession_control( ---------- bn : BooleanNetwork The network to analyze, which contains the Boolean update functions. - target : space_type + target : BooleanSpace The target subspace. strategy : str, optional The searching strategy to use to look for driver nodes. Options are @@ -172,9 +175,9 @@ def succession_control( def successions_to_target( succession_diagram: SuccessionDiagram, - target: space_type, + target: BooleanSpace, expand_diagram: bool = True, -) -> list[SuccessionType]: +) -> list[SubspaceSuccession]: """Find lists of nested trap spaces (successions) that lead to the specified target subspace. @@ -182,7 +185,7 @@ def successions_to_target( ---------- succession_diagram : SuccessionDiagram The succession diagram from which successions will be extracted. - target : space_type + target : BooleanSpace The target subspace. expand_diagram: bool Whether to ensure that the succession diagram is expanded enough to @@ -190,11 +193,11 @@ def successions_to_target( Returns ------- - list[SuccessionType] + list[SubspaceSuccession] A list of successions, where each succession is a list of sequentially nested trap spaces that specify the target. """ - successions: list[SuccessionType] = [] + successions: list[SubspaceSuccession] = [] # expand the succession_diagram toward the target if expand_diagram: @@ -226,18 +229,18 @@ def successions_to_target( def drivers_of_succession( bn: BooleanNetwork, - succession: list[space_type], + succession: list[BooleanSpace], strategy: str = "internal", max_drivers_per_succession_node: int | None = None, forbidden_drivers: set[str] | None = None, -) -> list[ControlType]: +) -> list[ControlOverrides]: """Find driver nodes of a list of sequentially nested trap spaces Parameters ---------- bn : BooleanNetwork The network to analyze, which contains the Boolean update functions. - succession : list[space_type] + succession : list[BooleanSpace] A list of sequentially nested trap spaces that specify the target. strategy: str The searching strategy to use to look for driver nodes. Options are @@ -252,13 +255,13 @@ def drivers_of_succession( Returns ------- - list[ControlType] + list[ControlOverrides] A list of controls. Each control is a list of lists of driver sets, represented as state dictionaries. Each list item corresponds to a list of drivers for the corresponding trap space in the succession. """ - control_strategies: list[ControlType] = [] - assume_fixed: space_type = {} + control_strategies: list[ControlOverrides] = [] + assume_fixed: BooleanSpace = {} for ts in succession: control_strategies.append( find_drivers( @@ -278,19 +281,19 @@ def drivers_of_succession( def find_drivers( bn: BooleanNetwork, - target_trap_space: space_type, + target_trap_space: BooleanSpace, strategy: str = "internal", - assume_fixed: space_type | None = None, + assume_fixed: BooleanSpace | None = None, max_drivers_per_succession_node: int | None = None, forbidden_drivers: set[str] | None = None, -) -> ControlType: +) -> ControlOverrides: """Finds drives of a given target trap space Parameters ---------- bn : BooleanNetwork The network to analyze, which contains the Boolean update functions. - target_trap_space : space_type + target_trap_space : BooleanSpace The trap space we want to find drivers for. strategy: str The searching strategy to use to look for driver nodes. Options are @@ -307,7 +310,7 @@ def find_drivers( Returns ------- - ControlType + ControlOverrides A list of internal driver sets, represented as state dictionaries. If empty, then no drivers are found. This can happen if `max_drivers_per_succession_node` is not `None`, or if all controls @@ -334,14 +337,14 @@ def find_drivers( if max_drivers_per_succession_node is None: max_drivers_per_succession_node = len(target_trap_space_inner) - drivers: ControlType = [] + drivers: ControlOverrides = [] for driver_set_size in range(max_drivers_per_succession_node + 1): for driver_set in combinations(driver_pool, driver_set_size): if any(set(d) <= set(driver_set) for d in drivers): continue if strategy == "internal": - driver_dict: space_type = { + driver_dict: BooleanSpace = { k: cast(Literal[0, 1], target_trap_space_inner[k]) for k in driver_set } diff --git a/balm/drivers.py b/balm/drivers.py index 7266f34f..c3739a14 100644 --- a/balm/drivers.py +++ b/balm/drivers.py @@ -5,15 +5,15 @@ from biodivine_aeon import BooleanNetwork from balm.space_utils import percolate_space -from balm.types import space_type +from balm.types import BooleanSpace -def find_single_node_LDOIs(bn: BooleanNetwork) -> dict[tuple[str, int], space_type]: +def find_single_node_LDOIs(bn: BooleanNetwork) -> dict[tuple[str, int], BooleanSpace]: """ finds LDOIs of every single node state TODO: take an initial set of LDOIs (e.g., of the original system) as an argument for speed-up """ - LDOIs: dict[tuple[str, int], space_type] = {} + LDOIs: dict[tuple[str, int], BooleanSpace] = {} for var in bn.variables(): name = bn.get_variable_name(var) function = bn.get_update_function(var) @@ -22,16 +22,16 @@ def find_single_node_LDOIs(bn: BooleanNetwork) -> dict[tuple[str, int], space_ty continue for i in range(2): fix = (name, i) - space: space_type = {name: cast(Literal[0, 1], i)} + space: BooleanSpace = {name: cast(Literal[0, 1], i)} LDOIs[fix] = percolate_space(bn, space) return LDOIs def find_single_drivers( - target_subspace: space_type, + target_subspace: BooleanSpace, bn: BooleanNetwork, - LDOIs: dict[tuple[str, int], space_type] | None = None, + LDOIs: dict[tuple[str, int], BooleanSpace] | None = None, ) -> set[tuple[str, int]]: """ find all the single node drivers for a given target_subspace, diff --git a/balm/motif_avoidant.py b/balm/motif_avoidant.py index eaec5e73..a3348496 100644 --- a/balm/motif_avoidant.py +++ b/balm/motif_avoidant.py @@ -18,7 +18,7 @@ state_list_to_bdd, state_to_bdd, ) -from balm.types import space_type +from balm.types import BooleanSpace if TYPE_CHECKING: from biodivine_aeon import BooleanNetwork @@ -33,9 +33,9 @@ def make_retained_set( network: BooleanNetwork, nfvs: list[str], - space: space_type, - child_spaces: list[space_type] | None = None, -) -> space_type: + space: BooleanSpace, + child_spaces: list[BooleanSpace] | None = None, +) -> BooleanSpace: """ Calculate the retained set. @@ -102,12 +102,12 @@ def make_retained_set( def detect_motif_avoidant_attractors( network: BooleanNetwork, petri_net: DiGraph, - candidates: list[space_type], + candidates: list[BooleanSpace], terminal_restriction_space: BinaryDecisionDiagram, max_iterations: int, - ensure_subspace: space_type | None = None, + ensure_subspace: BooleanSpace | None = None, is_in_an_mts: bool = False, -) -> list[space_type]: +) -> list[BooleanSpace]: """ Compute a sub-list of `candidates` which correspond to motif-avoidant attractors. Other method inputs: @@ -150,12 +150,12 @@ def detect_motif_avoidant_attractors( def _preprocess_candidates( network: BooleanNetwork, - candidates: list[space_type], + candidates: list[BooleanSpace], terminal_restriction_space: BinaryDecisionDiagram, max_iterations: int, - ensure_subspace: space_type | None = None, + ensure_subspace: BooleanSpace | None = None, is_in_an_mts: bool = False, -) -> list[space_type]: +) -> list[BooleanSpace]: """ A fast but incomplete method for eliminating spurious attractor candidates. @@ -201,7 +201,7 @@ def _preprocess_candidates( if not is_in_an_mts: # Copy is sufficient because we won't be modifying the states within the set. candidates_dnf = candidates.copy() - filtered_candidates: list[space_type] = [] + filtered_candidates: list[BooleanSpace] = [] for state in candidates: # Remove the state from the candidates. If we can prove that is # is not an attractor, we will put it back. @@ -269,15 +269,15 @@ def _preprocess_candidates( def _filter_candidates( petri_net: DiGraph, - candidates: list[space_type], + candidates: list[BooleanSpace], terminal_restriction_space: BinaryDecisionDiagram, -) -> list[space_type]: +) -> list[BooleanSpace]: """ Filter candidate states using reachability procedure in Pint. """ avoid_states = ~terminal_restriction_space | state_list_to_bdd(candidates) - filtered_candidates: list[space_type] = [] + filtered_candidates: list[BooleanSpace] = [] for state in candidates: state_bdd = state_to_bdd(state) @@ -295,7 +295,7 @@ def _filter_candidates( def _Pint_reachability( petri_net: DiGraph, - initial_state: space_type, + initial_state: BooleanSpace, target_states: BinaryDecisionDiagram, ) -> bool: """ diff --git a/balm/space_utils.py b/balm/space_utils.py index 60e39348..e0c8fbb7 100644 --- a/balm/space_utils.py +++ b/balm/space_utils.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from pyeda.boolalg.bdd import BinaryDecisionDiagram from pyeda.boolalg.expr import Expression - from balm.types import space_type + from balm.types import BooleanSpace from biodivine_aeon import BooleanNetwork, RegulatoryGraph from pyeda.boolalg.expr import Complement @@ -31,12 +31,12 @@ ) -def intersect(x: space_type, y: space_type) -> space_type | None: +def intersect(x: BooleanSpace, y: BooleanSpace) -> BooleanSpace | None: """ Compute the space which is the intersection of two spaces, or `None` if the spaces don't intersect. """ - result: space_type = {} + result: BooleanSpace = {} for k, v in x.items(): result[k] = v for k, v in y.items(): @@ -46,7 +46,7 @@ def intersect(x: space_type, y: space_type) -> space_type | None: return result -def is_subspace(x: space_type, y: space_type) -> bool: +def is_subspace(x: BooleanSpace, y: BooleanSpace) -> bool: """ Checks if `x` is a subspace of `y`. """ @@ -58,7 +58,7 @@ def is_subspace(x: space_type, y: space_type) -> bool: return True -def is_syntactic_trap_space(bn: BooleanNetwork, space: space_type) -> bool: +def is_syntactic_trap_space(bn: BooleanNetwork, space: BooleanSpace) -> bool: """ Uses percolation to check if the given `space` is a trap space in the given `BooleanNetwork`. @@ -85,9 +85,9 @@ def is_syntactic_trap_space(bn: BooleanNetwork, space: space_type) -> bool: def percolate_space( network: BooleanNetwork, - space: space_type, + space: BooleanSpace, strict_percolation: bool = True, -) -> space_type: +) -> BooleanSpace: """ Takes a Boolean network and a space (partial assignment of `0`/`1` to the network variables). It then percolates the values in the given `space` to @@ -109,11 +109,11 @@ def percolate_space( """ if strict_percolation: - result: space_type = {} + result: BooleanSpace = {} else: result = {var: space[var] for var in space} - fixed: space_type = {var: space[var] for var in space} + fixed: BooleanSpace = {var: space[var] for var in space} fixed_bddvars = {bddvar_cache(k): v for k, v in fixed.items()} bdds: dict[str, BinaryDecisionDiagram] = {} bdd_inputs = {} @@ -169,7 +169,7 @@ def percolate_space( def percolation_conflicts( network: BooleanNetwork, - space: space_type, + space: BooleanSpace, strict_percolation: bool = True, ) -> set[str]: """ @@ -193,7 +193,7 @@ def percolation_conflicts( return conflicts -def percolate_network(bn: BooleanNetwork, space: space_type) -> BooleanNetwork: +def percolate_network(bn: BooleanNetwork, space: BooleanSpace) -> BooleanNetwork: """ Takes an AEON.py Boolean network and a space (partial assignment of network variables to `0`/`1`). It then produces a new network with @@ -243,7 +243,9 @@ def percolate_network(bn: BooleanNetwork, space: space_type) -> BooleanNetwork: return new_bn -def percolate_pyeda_expression(expression: Expression, space: space_type) -> Expression: +def percolate_pyeda_expression( + expression: Expression, space: BooleanSpace +) -> Expression: """ Takes a PyEDA expression and a subspace (dictionary assigning `1`/`0` to a subset of variables). Returns a simplified expression that is valid @@ -256,7 +258,7 @@ def percolate_pyeda_expression(expression: Expression, space: space_type) -> Exp return expression.simplify() -def expression_to_space_list(expression: Expression) -> list[space_type]: +def expression_to_space_list(expression: Expression) -> list[BooleanSpace]: """ Convert a PyEDA expression to a list of subspaces whose union represents an equivalent set of network states. @@ -270,11 +272,11 @@ def expression_to_space_list(expression: Expression) -> list[space_type]: # (or at least in some sense canonical) DNF. In the future, we might # want to either enforce this explicitly or relax this requirement. - sub_spaces: list[space_type] = [] + sub_spaces: list[BooleanSpace] = [] expression_dnf = expression.to_dnf() for clause in expression_dnf.xs: # type: ignore - sub_space: space_type = {} + sub_space: BooleanSpace = {} # Since we know this is a DNF clause, it can only be # a literal, or a conjunction of literals. @@ -296,7 +298,7 @@ def expression_to_space_list(expression: Expression) -> list[space_type]: return sub_spaces -def space_unique_key(space: space_type, network: BooleanNetwork) -> int: +def space_unique_key(space: BooleanSpace, network: BooleanNetwork) -> int: """ Computes an integer which is a unique representation of the provided `space` (with respect to the given `network`). diff --git a/balm/state_utils.py b/balm/state_utils.py index a342bc39..d87bfde4 100644 --- a/balm/state_utils.py +++ b/balm/state_utils.py @@ -14,10 +14,10 @@ if TYPE_CHECKING: from pyeda.boolalg.bdd import BDDVariable, BinaryDecisionDiagram -from balm.types import space_type +from balm.types import BooleanSpace -def _state_dict_to_bdd_valuation(state: space_type) -> dict[BDDVariable, int]: +def _state_dict_to_bdd_valuation(state: BooleanSpace) -> dict[BDDVariable, int]: """ Convert state variables in a dictionary to their BDD counterparts. """ @@ -48,7 +48,7 @@ def state_to_bdd_cacheable(state: frozenset[tuple[str, int]]) -> BinaryDecisionD return state_bdd -def state_to_bdd(state: space_type, usecache: bool = True) -> BinaryDecisionDiagram: +def state_to_bdd(state: BooleanSpace, usecache: bool = True) -> BinaryDecisionDiagram: """ Convert a state variables to a BDD encoding the state singleton. """ @@ -68,7 +68,7 @@ def state_to_bdd(state: space_type, usecache: bool = True) -> BinaryDecisionDiag return state_bdd -def state_list_to_bdd(states: list[space_type]) -> BinaryDecisionDiagram: +def state_list_to_bdd(states: list[BooleanSpace]) -> BinaryDecisionDiagram: """ Convert a list of state dictionaries to a BDD representation. """ @@ -80,7 +80,7 @@ def state_list_to_bdd(states: list[space_type]) -> BinaryDecisionDiagram: def function_restrict( - f: BinaryDecisionDiagram, state: space_type + f: BinaryDecisionDiagram, state: BooleanSpace ) -> BinaryDecisionDiagram: """ Restrict the validity of the given BDD function to valuations which @@ -90,7 +90,9 @@ def function_restrict( return f.restrict(bdd_state) -def function_eval(f: BinaryDecisionDiagram, state: space_type) -> Literal[0, 1] | None: +def function_eval( + f: BinaryDecisionDiagram, state: BooleanSpace +) -> Literal[0, 1] | None: """ Evaluate a BDD function in the given state to an integer value. If the state is incomplete (i.e. it is a space), the function may not evaluate to an exact integer. In such case, @@ -107,7 +109,7 @@ def function_eval(f: BinaryDecisionDiagram, state: space_type) -> Literal[0, 1] return None -def function_is_true(f: BinaryDecisionDiagram, state: space_type) -> bool: +def function_is_true(f: BinaryDecisionDiagram, state: BooleanSpace) -> bool: """ Returns `True` if the given BDD function evaluates to `1` for the given state (or space). @@ -118,7 +120,7 @@ def function_is_true(f: BinaryDecisionDiagram, state: space_type) -> bool: return function_restrict(f, state).is_one() -def dnf_function_is_true(dnf: list[space_type], state: space_type) -> bool: +def dnf_function_is_true(dnf: list[BooleanSpace], state: BooleanSpace) -> bool: """ Returns `True` if the given DNF function evaluates to `1` for the given state (or space). @@ -132,11 +134,13 @@ def dnf_function_is_true(dnf: list[space_type], state: space_type) -> bool: return False -def remove_state_from_dnf(dnf: list[space_type], state: space_type) -> list[space_type]: +def remove_state_from_dnf( + dnf: list[BooleanSpace], state: BooleanSpace +) -> list[BooleanSpace]: """ Removes all conjunctions that are True in the state """ - modified_dnf: list[space_type] = [] + modified_dnf: list[BooleanSpace] = [] for conjunction in dnf: if conjunction.items() <= state.items(): pass diff --git a/balm/terminal_restriction_space.py b/balm/terminal_restriction_space.py index 668622e7..69168440 100644 --- a/balm/terminal_restriction_space.py +++ b/balm/terminal_restriction_space.py @@ -14,17 +14,17 @@ from balm.space_utils import percolate_network, percolation_conflicts from balm.state_utils import state_list_to_bdd, state_to_bdd from balm.trappist_core import trappist -from balm.types import space_type +from balm.types import BooleanSpace -def get_self_neg_tr_trap_spaces(network: BooleanNetwork) -> list[space_type]: +def get_self_neg_tr_trap_spaces(network: BooleanNetwork) -> list[BooleanSpace]: """ Takes a Boolean network and gets its self-negating time-reversal trap spaces. To find time-reversal trap spaces in a specific trap space, percolated network should be given as input. """ tr_trap_spaces = trappist(network, problem="max", reverse_time=True) - self_neg_tr_trap_spaces: list[space_type] = [] + self_neg_tr_trap_spaces: list[BooleanSpace] = [] for tr_trap_space in tr_trap_spaces: conflicts = percolation_conflicts(network, tr_trap_space) if conflicts: @@ -34,9 +34,9 @@ def get_self_neg_tr_trap_spaces(network: BooleanNetwork) -> list[space_type]: def get_terminal_restriction_space( - stable_motifs: list[space_type], + stable_motifs: list[BooleanSpace], network: BooleanNetwork, - ensure_subspace: space_type, + ensure_subspace: BooleanSpace, use_single_node_drivers: bool = True, use_tr_trapspaces: bool = True, ) -> BinaryDecisionDiagram: @@ -71,7 +71,7 @@ def get_terminal_restriction_space( # ~R(X) includes delta result_bdd = result_bdd | state_to_bdd( - cast(space_type, dict(single_node_drivers)) + cast(BooleanSpace, dict(single_node_drivers)) ) for single_node_driver in single_node_drivers: diff --git a/balm/trappist_core.py b/balm/trappist_core.py index 5141e135..fba5718c 100644 --- a/balm/trappist_core.py +++ b/balm/trappist_core.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing import Callable from clingo import Model - from balm.types import space_type + from balm.types import BooleanSpace from biodivine_aeon import BooleanNetwork from clingo import Control, SolveHandle @@ -25,11 +25,11 @@ def trappist_async( network: BooleanNetwork | DiGraph, - on_solution: Callable[[space_type], bool], + on_solution: Callable[[BooleanSpace], bool], problem: str = "min", reverse_time: bool = False, - ensure_subspace: space_type | None = None, - avoid_subspaces: list[space_type] | None = None, + ensure_subspace: BooleanSpace | None = None, + avoid_subspaces: list[BooleanSpace] | None = None, ): """ The same as the `trappist` method, but instead of returning a list of spaces @@ -88,9 +88,9 @@ def trappist( problem: str = "min", reverse_time: bool = False, solution_limit: int | None = None, - ensure_subspace: space_type | None = None, - avoid_subspaces: list[space_type] | None = None, -) -> list[space_type]: + ensure_subspace: BooleanSpace | None = None, + avoid_subspaces: list[BooleanSpace] | None = None, +) -> list[BooleanSpace]: """ Solve the given `problem` for the given `network` using the Trappist algorithm, internally relying on the Python bindings of the `clingo` ASP @@ -118,9 +118,9 @@ def trappist( if avoid_subspaces is None: avoid_subspaces = [] - results: list[space_type] = [] + results: list[BooleanSpace] = [] - def save_result(x: space_type) -> bool: + def save_result(x: BooleanSpace) -> bool: results.append(x) if solution_limit is None: return True @@ -139,8 +139,8 @@ def save_result(x: space_type) -> bool: return results -def _clingo_model_to_space(model: Model) -> space_type: - space: space_type = {} +def _clingo_model_to_space(model: Model) -> BooleanSpace: + space: BooleanSpace = {} for atom in model.symbols(atoms=True): atom_str = str(atom) (variable, is_positive) = place_to_variable(atom_str) @@ -160,8 +160,8 @@ def _create_clingo_constraints( petri_net: DiGraph, problem: str = "min", reverse_time: bool = False, - ensure_subspace: space_type | None = None, - avoid_subspaces: list[space_type] | None = None, + ensure_subspace: BooleanSpace | None = None, + avoid_subspaces: list[BooleanSpace] | None = None, optimize_source_variables: list[str] | None = None, ) -> Control: """ @@ -276,7 +276,7 @@ def _create_clingo_constraints( return ctl -def _clingo_model_to_fixed_point(model: Model) -> space_type: +def _clingo_model_to_fixed_point(model: Model) -> BooleanSpace: """ Convert a clingo `Model` to a subspace representing a single fixed point. That is, the space should have all model variables fixed. @@ -285,7 +285,7 @@ def _clingo_model_to_fixed_point(model: Model) -> space_type: produces "positive" models (i.e. the space is represented by positive atoms present in the model). """ - space: space_type = {} + space: BooleanSpace = {} for atom in model.symbols(atoms=True): atom_str = str(atom) @@ -307,8 +307,8 @@ def _clingo_model_to_fixed_point(model: Model) -> space_type: def _create_clingo_fixed_point_constraints( variables: list[str], petri_net: DiGraph, - ensure_subspace: space_type | None = None, - avoid_subspaces: list[space_type] | None = None, + ensure_subspace: BooleanSpace | None = None, + avoid_subspaces: list[BooleanSpace] | None = None, ) -> Control: """ Generate the ASP characterizing all deadlocks of the Petri net (equivalently all @@ -375,10 +375,10 @@ def _create_clingo_fixed_point_constraints( def compute_fixed_point_reduced_STG_async( petri_net: DiGraph, - retained_set: space_type, - on_solution: Callable[[space_type], bool], - ensure_subspace: space_type | None = None, - avoid_subspaces: list[space_type] | None = None, + retained_set: BooleanSpace, + on_solution: Callable[[BooleanSpace], bool], + ensure_subspace: BooleanSpace | None = None, + avoid_subspaces: list[BooleanSpace] | None = None, ): """ The same as the `compute_fixed_point_reduced_STG`, but instead of returning a @@ -426,11 +426,11 @@ def compute_fixed_point_reduced_STG_async( def compute_fixed_point_reduced_STG( petri_net: DiGraph, - retained_set: space_type, - ensure_subspace: space_type = {}, - avoid_subspaces: list[space_type] = [], + retained_set: BooleanSpace, + ensure_subspace: BooleanSpace = {}, + avoid_subspaces: list[BooleanSpace] = [], solution_limit: int | None = None, -) -> list[space_type]: +) -> list[BooleanSpace]: """ This method computes the fixed points of the given Petri-net-encoded Boolean network. This makes it possible to modify the Petri net instead of @@ -442,9 +442,9 @@ def compute_fixed_point_reduced_STG( the Petri net, forcing given variables to retain the specified values. """ - results: list[space_type] = [] + results: list[BooleanSpace] = [] - def save_result(x: space_type) -> bool: + def save_result(x: BooleanSpace) -> bool: results.append(x) if solution_limit is None: return True diff --git a/balm/types.py b/balm/types.py index b0f9dca3..4847b12d 100644 --- a/balm/types.py +++ b/balm/types.py @@ -1,5 +1,7 @@ -from typing import Literal +from typing import Literal, TypeAlias -space_type = dict[str, Literal[0, 1]] -SuccessionType = list[space_type] # sequence of stable motifs -ControlType = list[space_type] # ways of locking in an individual stable motif +BooleanSpace: TypeAlias = dict[str, Literal[0, 1]] +SubspaceSuccession: TypeAlias = list[BooleanSpace] # sequence of stable motifs +ControlOverrides: TypeAlias = list[ + BooleanSpace +] # ways of locking in an individual stable motif diff --git a/tests/control_test.py b/tests/control_test.py index bfd3552e..a90fa27f 100644 --- a/tests/control_test.py +++ b/tests/control_test.py @@ -8,7 +8,7 @@ successions_to_target, ) from balm.SuccessionDiagram import SuccessionDiagram -from balm.types import space_type +from balm.types import BooleanSpace def test_intervention_equality_and_equivalence(): @@ -42,13 +42,13 @@ def test_basic_succession_control(): E, false """ ) - target_succession: list[space_type] = [ + target_succession: list[BooleanSpace] = [ {"S": 0}, {"A": 0, "B": 0}, {"C": 1, "D": 1}, ] - cs: list[list[space_type]] = [ + cs: list[list[BooleanSpace]] = [ [{"S": 0}], [{"A": 0}, {"B": 0}], [{"C": 1}, {"D": 1}], @@ -104,7 +104,7 @@ def test_basic_succession_finding(): {"A": 0, "B": 0}, ], ] - target: space_type = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} + target: BooleanSpace = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} succession_diagram = SuccessionDiagram(bn) successions = successions_to_target(succession_diagram, target) @@ -131,14 +131,14 @@ def test_internal_succession_control(): E, false """ ) - target: space_type = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} + target: BooleanSpace = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} - true_controls: list[list[list[space_type]]] = [ + true_controls: list[list[list[BooleanSpace]]] = [ [[{"S": 0}], [{"A": 0}, {"B": 0}], [{"C": 1}, {"D": 1}]], [[{"S": 0}], [{"C": 1}, {"D": 1}], [{"A": 0}, {"B": 0}]], ] - true_successions: list[list[space_type]] = [ + true_successions: list[list[BooleanSpace]] = [ [ {"S": 0}, {"A": 0, "B": 0}, @@ -173,14 +173,14 @@ def test_all_succession_control(): E, false """ ) - target: space_type = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} + target: BooleanSpace = {"S": 0, "E": 0, "A": 0, "B": 0, "C": 1, "D": 1} - true_controls: list[list[list[space_type]]] = [ + true_controls: list[list[list[BooleanSpace]]] = [ [[{"S": 0}], [{"A": 0}, {"B": 0}], [{"C": 1}, {"D": 1}]], [[{"S": 0}], [{"A": 1}, {"B": 1}, {"C": 1}, {"D": 1}], [{"A": 0}, {"B": 0}]], ] - true_successions: list[list[space_type]] = [ + true_successions: list[list[BooleanSpace]] = [ [ {"S": 0}, {"A": 0, "B": 0}, @@ -212,13 +212,13 @@ def test_forbidden_drivers(): C, A & B """ ) - target: space_type = {"A": 1, "B": 1, "C": 1} + target: BooleanSpace = {"A": 1, "B": 1, "C": 1} # Test with no forbidden drivers first - true_controls: list[list[list[space_type]]] = [ + true_controls: list[list[list[BooleanSpace]]] = [ [[{"A": 1, "B": 1}, {"A": 1, "C": 1}, {"B": 1, "C": 1}]] ] - true_successions: list[list[space_type]] = [[{"A": 1, "B": 1, "C": 1}]] + true_successions: list[list[BooleanSpace]] = [[{"A": 1, "B": 1, "C": 1}]] true_interventions = [ Intervention(c, "internal", s) for c, s in zip(true_controls, true_successions) @@ -281,13 +281,13 @@ def test_size_restriction(): C, A & B """ ) - target: space_type = {"A": 1, "B": 1, "C": 1} + target: BooleanSpace = {"A": 1, "B": 1, "C": 1} # Test with no restrictions - true_controls: list[list[list[space_type]]] = [ + true_controls: list[list[list[BooleanSpace]]] = [ [[{"A": 1, "B": 1}, {"A": 1, "C": 1}, {"B": 1, "C": 1}]] ] - true_successions: list[list[space_type]] = [[{"A": 1, "B": 1, "C": 1}]] + true_successions: list[list[BooleanSpace]] = [[{"A": 1, "B": 1, "C": 1}]] true_interventions = [ Intervention(c, "internal", s) for c, s in zip(true_controls, true_successions) diff --git a/tests/motif_avoidant_test.py b/tests/motif_avoidant_test.py index 59f643ce..172413c2 100644 --- a/tests/motif_avoidant_test.py +++ b/tests/motif_avoidant_test.py @@ -5,7 +5,7 @@ from balm.motif_avoidant import _preprocess_candidates # type: ignore from balm.petri_net_translation import network_to_petrinet from balm.state_utils import state_list_to_bdd -from balm.types import space_type +from balm.types import BooleanSpace def test_preprocessing_ssf_not_optimal(): @@ -16,9 +16,9 @@ def test_preprocessing_ssf_not_optimal(): """ ) - s0: space_type = {"x1": 0, "x2": 0} - s1: space_type = {"x1": 0, "x2": 1} - s2: space_type = {"x1": 1, "x2": 0} + s0: BooleanSpace = {"x1": 0, "x2": 0} + s1: BooleanSpace = {"x1": 0, "x2": 1} + s2: BooleanSpace = {"x1": 1, "x2": 0} # s3 = {"x1": 1, "x2": 1} """ @@ -60,14 +60,14 @@ def test_preprocessing_ssf_optimal(): """ ) - s0: space_type = {"A": 0, "B": 0, "C": 0} + s0: BooleanSpace = {"A": 0, "B": 0, "C": 0} # s1 = {"A": 0, "B": 0, "C": 1} - s2: space_type = {"A": 0, "B": 1, "C": 0} - # s3: space_type = {"A": 0, "B": 1, "C": 1} - s4: space_type = {"A": 1, "B": 0, "C": 0} - # s5: space_type = {"A": 1, "B": 0, "C": 1} - # s6: space_type = {"A": 1, "B": 1, "C": 0} - s7: space_type = {"A": 1, "B": 1, "C": 1} + s2: BooleanSpace = {"A": 0, "B": 1, "C": 0} + # s3: BooleanSpace = {"A": 0, "B": 1, "C": 1} + s4: BooleanSpace = {"A": 1, "B": 0, "C": 0} + # s5: BooleanSpace = {"A": 1, "B": 0, "C": 1} + # s6: BooleanSpace = {"A": 1, "B": 1, "C": 0} + s7: BooleanSpace = {"A": 1, "B": 1, "C": 1} """ This BN has two minimal trap spaces: 101 + 011. @@ -99,10 +99,10 @@ def test_ABNReach_current_version(): """ ) - s0: space_type = {"x1": 0, "x2": 0, "x3": 1} - s1: space_type = {"x1": 0, "x2": 1, "x3": 1} - # s2: space_type = {"x1": 1, "x2": 0, "x3": 1} - s3: space_type = {"x1": 1, "x2": 1, "x3": 1} + s0: BooleanSpace = {"x1": 0, "x2": 0, "x3": 1} + s1: BooleanSpace = {"x1": 0, "x2": 1, "x3": 1} + # s2: BooleanSpace = {"x1": 1, "x2": 0, "x3": 1} + s3: BooleanSpace = {"x1": 1, "x2": 1, "x3": 1} petri_net = network_to_petrinet(bn) @@ -133,9 +133,9 @@ def test_FilteringProcess(): """ ) - s0: space_type = {"x1": 0, "x2": 0} - s1: space_type = {"x1": 0, "x2": 1} - s2: space_type = {"x1": 1, "x2": 0} + s0: BooleanSpace = {"x1": 0, "x2": 0} + s1: BooleanSpace = {"x1": 0, "x2": 1} + s2: BooleanSpace = {"x1": 1, "x2": 0} # s3 = {"x1": 1, "x2": 1} terminal_res_space = state_list_to_bdd([s0, s1, s2]) diff --git a/tests/succession_diagram_test.py b/tests/succession_diagram_test.py index 4867efff..ca8fd3a4 100644 --- a/tests/succession_diagram_test.py +++ b/tests/succession_diagram_test.py @@ -7,7 +7,7 @@ import balm import balm.SuccessionDiagram from balm.SuccessionDiagram import SuccessionDiagram -from balm.types import space_type +from balm.types import BooleanSpace # This just ensures that the debug outputs are a part of the test output. balm.SuccessionDiagram.DEBUG = True @@ -235,7 +235,7 @@ def test_attractor_detection(network_file: str): # Compute attractors in diagram nodes. # TODO: There will probably be a method that does this in one "go". - nfvs_attractors: list[space_type] = [] + nfvs_attractors: list[BooleanSpace] = [] for i in sd.node_ids(): attr = sd.node_attractor_seeds(i, compute=True) for a in attr: @@ -312,7 +312,7 @@ def test_attractor_expansion(network_file: str): # Compute attractors in diagram nodes. # TODO: There will probably be a method that does this in one "go". - nfvs_attractors: list[space_type] = [] + nfvs_attractors: list[BooleanSpace] = [] # This is an important change compared to the original test: Here, we only # care about expanded nodes, everything else is ignored. for i in sd.expanded_ids(): diff --git a/tests/terminal_restriction_space_test.py b/tests/terminal_restriction_space_test.py index 4a7e8663..ad80af2a 100644 --- a/tests/terminal_restriction_space_test.py +++ b/tests/terminal_restriction_space_test.py @@ -6,7 +6,7 @@ state_list_to_bdd, ) from balm.trappist_core import trappist -from balm.types import space_type +from balm.types import BooleanSpace def test_tr_trap_spaces(): @@ -36,7 +36,7 @@ def test_get_terminal_restriction_space(): C, A & B """ ) - stable_motifs: list[space_type] = [{"A": 1, "B": 1, "C": 1}] + stable_motifs: list[BooleanSpace] = [{"A": 1, "B": 1, "C": 1}] trs = get_terminal_restriction_space( stable_motifs, @@ -62,7 +62,7 @@ def test_get_terminal_restriction_space2(): F, B """ ) - stable_motifs: list[space_type] = [{"A": 1, "B": 0, "C": 1}] + stable_motifs: list[BooleanSpace] = [{"A": 1, "B": 0, "C": 1}] trs = get_terminal_restriction_space( stable_motifs,