From 193ecf4a4a76a29164496fc1a8c0a345b051f7bd Mon Sep 17 00:00:00 2001 From: = Date: Tue, 12 Dec 2023 16:04:01 -0500 Subject: [PATCH] summary function, builders, source scc integration --- balm/SuccessionDiagram.py | 86 ++++++++++++++++++++++- balm/_sd_algorithms/expand_source_SCCs.py | 31 ++++---- scratch.py | 14 ++++ 3 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 scratch.py diff --git a/balm/SuccessionDiagram.py b/balm/SuccessionDiagram.py index ef68a8b9..edfe6029 100644 --- a/balm/SuccessionDiagram.py +++ b/balm/SuccessionDiagram.py @@ -4,15 +4,16 @@ if TYPE_CHECKING: from typing import Iterator - from biodivine_aeon import BooleanNetwork import networkx as nx # type: ignore +from biodivine_aeon import BooleanNetwork from balm._sd_algorithms.compute_attractor_seeds import compute_attractor_seeds from balm._sd_algorithms.expand_attractor_seeds import expand_attractor_seeds from balm._sd_algorithms.expand_bfs import expand_bfs from balm._sd_algorithms.expand_dfs import expand_dfs from balm._sd_algorithms.expand_minimal_spaces import expand_minimal_spaces +from balm._sd_algorithms.expand_source_SCCs import expand_source_SCCs from balm._sd_algorithms.expand_to_target import expand_to_target from balm.interaction_graph_utils import feedback_vertex_set from balm.petri_net_translation import network_to_petrinet @@ -116,6 +117,75 @@ def __len__(self) -> int: """ return self.G.number_of_nodes() + @staticmethod + def from_aeon(model: str) -> SuccessionDiagram: + """ + Read a `BooleanNetwork` from the string contents of an `.aeon` model. + """ + return SuccessionDiagram(BooleanNetwork.from_aeon(model)) + + @staticmethod + def from_bnet(model: str) -> SuccessionDiagram: + """ + Read a `BooleanNetwork` from the string contents of a `.bnet` model. + """ + return SuccessionDiagram(BooleanNetwork.from_bnet(model)) + + @staticmethod + def from_sbml(model: str) -> SuccessionDiagram: + """ + Read a `BooleanNetwork` from the string contents of an `.sbml` model. + """ + return SuccessionDiagram(BooleanNetwork.from_sbml(model)) + + @staticmethod + def from_file(path: str) -> SuccessionDiagram: + """ + Read a `BooleanNetwork` from the given file path. The format is automatically inferred from + the file extension. + """ + return SuccessionDiagram(BooleanNetwork.from_file(path)) + + def expanded_attractor_seeds(self) -> list[list[dict[str, int]]]: + return [self.node_attractor_seeds(id) for id in self.expanded_ids()] + + def summary(self) -> str: + """ + Return a summary of the succession diagram. + """ + var_ordering = sorted( + [self.network.get_variable_name(v) for v in self.network.variables()] + ) + report_string = ( + f"Succession Diagram with {len(self)} nodes.\n" + f"State order: {', '.join(var_ordering)}\n\n" + "Attractors in diagram:\n\n" + ) + for node in self.node_ids(): + try: + attrs = self.node_attractor_seeds(node, compute=False) + except KeyError: + continue + + space = self.node_space(node) + + if self.node_is_minimal(node): + space_str_prefix = "minimal trap space " + else: + space_str_prefix = "motif avoidance in " + space_str = "" + for var in var_ordering: + if var in space: + space_str += str(space[var]) + else: + space_str += "*" + report_string += f"{space_str_prefix}{space_str}\n" + for attr in attrs: + attr_str = "".join(str(v) for _, v in sorted(attr.items())) + report_string += "." * len(space_str_prefix) + f"{attr_str}\n" + report_string += "\n" + return report_string + def root(self) -> int: """ Return the ID of the root node. @@ -341,6 +411,20 @@ def edge_stable_motif( else: return cast(dict[str, int], self.G.edges[parent_id, child_id]["motif"]) + def build(self): + """ + Expand the succession diagram and search for attractors using default methods. + """ + self.expand_scc() + for node_id in self.node_ids(): + self.node_attractor_seeds(node_id, compute=True) + + def expand_scc(self, find_motif_avoidant_attractors: bool = True) -> bool: + """ + Expand the succession diagram using the source SCC method. + """ + return expand_source_SCCs(self, check_maa=find_motif_avoidant_attractors) + def expand_bfs( self, node_id: int | None = None, diff --git a/balm/_sd_algorithms/expand_source_SCCs.py b/balm/_sd_algorithms/expand_source_SCCs.py index f1b8f741..648b7a6c 100644 --- a/balm/_sd_algorithms/expand_source_SCCs.py +++ b/balm/_sd_algorithms/expand_source_SCCs.py @@ -1,29 +1,33 @@ +from __future__ import annotations + import itertools as it import sys -from typing import Callable, Iterable, cast +from typing import TYPE_CHECKING, Callable, Iterable, cast import networkx as nx # type: ignore from biodivine_aeon import BooleanNetwork from networkx import DiGraph +import balm.SuccessionDiagram +from balm._sd_algorithms.expand_bfs import expand_bfs 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.SuccessionDiagram import SuccessionDiagram + +if TYPE_CHECKING: + expander_function_type = Callable[ + [balm.SuccessionDiagram.SuccessionDiagram, int | None, int | None, int | None], + bool, + ] sys.path.append(".") DEBUG = False -expander_function_type = Callable[ - [SuccessionDiagram, int | None, int | None, int | None], - bool, -] - def expand_source_SCCs( - sd: SuccessionDiagram, - expander: expander_function_type = SuccessionDiagram.expand_bfs, + sd: balm.SuccessionDiagram.SuccessionDiagram, + expander: expander_function_type = expand_bfs, check_maa: bool = True, ) -> bool: """ @@ -252,7 +256,7 @@ def find_source_SCCs(bn: BooleanNetwork) -> list[list[str]]: def find_scc_sd( bnet: str, source_scc: list[str], expander: expander_function_type, check_maa: bool -) -> tuple[SuccessionDiagram, bool]: +) -> tuple[balm.SuccessionDiagram.SuccessionDiagram, bool]: """ TODO: better way that does not use bnet but rather bn directly or petri_net directly to find the scc_sd would be useful. @@ -291,7 +295,7 @@ def find_scc_sd( scc_bn = scc_bn.infer_regulatory_graph() # Compute the succession diagram. - scc_sd = SuccessionDiagram(scc_bn) + scc_sd = balm.SuccessionDiagram.SuccessionDiagram(scc_bn) fully_expanded = expander(scc_sd) # type: ignore assert fully_expanded @@ -323,7 +327,10 @@ def find_scc_sd( def attach_scc_sd( - sd: SuccessionDiagram, scc_sd: SuccessionDiagram, branch: int, check_maa: bool + sd: balm.SuccessionDiagram.SuccessionDiagram, + scc_sd: balm.SuccessionDiagram.SuccessionDiagram, + branch: int, + check_maa: bool, ) -> list[int]: """ Attach scc_sd to the given branch point of the sd. diff --git a/scratch.py b/scratch.py new file mode 100644 index 00000000..cb3431a5 --- /dev/null +++ b/scratch.py @@ -0,0 +1,14 @@ +# type: ignore + +# import pickle + +from balm.SuccessionDiagram import SuccessionDiagram + +rules = """ +A, !A & !B | C +B, !A & !B | C +C, A & B""" + +sd = SuccessionDiagram.from_bnet(rules) +sd.build() +print(sd.summary())