def faithfulness(primes: dict, update: str, trap_space: Union[dict, str]) -> bool: """ The model checking approach for deciding whether *trap_space* is faithful, i.e., whether all free variables oscillate in all of the attractors contained in it, in the state transition graph defined by *primes* and *update*. The approach is described and discussed in :ref:`Klarner2015(a) <klarner2015trap>`. It is decided by a single CTL query of the pattern :ref:`EF_all_unsteady <EF_all_unsteady>` and the random-walk-approach of the function :ref:`random_walk <random_walk>`. .. note:: In the (very unlikely) case that the random walk does not end in an attractor state an exception will be raised. .. note:: Faithfulness depends on the update strategy, i.e., a trapspace may be faithful in the synchronous STG but not faithful in the asynchronous STG or vice versa. .. note:: A typical use case is to decide whether a minimal trap space is faithful. .. note:: *trap_space* is in fact not required to be a trap set, i.e., it may be an arbitrary subspace. If it is an arbitrary subspace then the involved variables are artificially fixed to be constant. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *trap_space*: a subspace **returns**: * *answer*: whether *trap_space* is faithful in the STG defined by *primes* and *update* **example**:: >>> mintspaces = compute_trap_spaces(primes, "min") >>> x = mintspaces[0] >>> faithfulness(primes, x) True """ if len(trap_space) == len(primes): return True primes = copy_primes(primes=primes) create_constants(primes=primes, constants=trap_space) percolate(primes=primes, remove_constants=True) constants = find_constants(primes=primes) if len(constants) > len(trap_space): return False formula = exists_finally_unsteady_components(names=list(primes)) spec = f"CTLSPEC AG({formula})" init = "INIT TRUE" answer = model_checking(primes=primes, update=update, initial_states=init, specification=spec) return answer
def faithfulness_with_counterexample(primes: dict, update: str, trap_space: dict) -> (bool, List[dict]): """ Performs the same steps as :ref:`faithfulness` but also returns a counterexample which is *None* if it does not exist. A counterexample of a faithful test is a state that belongs to an attractor which has more fixed variables than there are in *trap_space*. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *trap_space*: a subspace **returns**: * *answer*: whether *trap_space* is faithful in the STG defined by *primes* and *update* * *counter_example*: a state that belongs to an attractor that does not oscillate in all free variables or *None* if no counterexample exists **example**:: >>> mintspaces = compute_trap_spaces(primes, "min") >>> x = mintspaces[0] >>> faithfulness(primes, x) True """ if len(trap_space) == len(primes): return True, None primes = percolate(primes=primes, add_constants=trap_space, copy=True) constants = find_constants(primes=primes) remove_all_constants(primes=primes) if len(constants) > len(trap_space): counterexample = find_attractor_state_by_randomwalk_and_ctl( primes=primes, update=update) attractor_state = merge_dicts(dicts=[counterexample, constants]) return False, attractor_state spec = f"CTLSPEC AG({exists_finally_unsteady_components(names=list(primes))})" answer, counterexample = model_checking(primes=primes, update=update, initial_states="INIT TRUE", specification=spec, enable_counterexample=True) if answer: return True, None else: attractor_state = find_attractor_state_by_randomwalk_and_ctl( primes=primes, update=update, initial_state=counterexample[-1]) attractor_state = merge_dicts(dicts=[attractor_state, constants]) return False, attractor_state
def completeness_naive_with_counterexample(primes: dict, update: str, trap_spaces: List[Union[dict, str]]): """ The naive approach to deciding whether *Trapspaces* is complete, i.e., whether there is no attractor outside of *Trapspaces*. The approach is described and discussed in :ref:`Klarner2015(a) <klarner2015trap>`. It is decided by a single CTL query of the :ref:`EF_oneof_subspaces <EF_oneof_subspaces>`. The state explosion problem limits this function to networks with around 40 variables. For networks with more variables (or a faster answer) use :ref:`completeness_iterative <completeness_iterative>`. .. note:: Completeness depends on the update strategy, i.e., a set of subspaces may be complete in the synchronous STG but not complete in the asynchronous STG or vice versa. .. note:: A typical use case is to decide whether the minimal trap spaces of a network are complete. .. note:: The subspaces of *Trapspaces* are in in fact not required to be a trap sets, i.e., it may contain arbitrary subspaces. If there are arbitrary subspaces then the semantics of the query is such that it checks whether each attractor *intersects* one of the subspaces. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *Trapspaces*: list of subspaces in string or dict representation **returns**: * *answer*: whether *subspaces* is complete in the STG defined by *primes* and *update*, * *counter_example*: a state from which none of the *subspaces* is reachable (if *answer* is *False*) **example**:: >>> mintspaces = trap_spaces(primes, "min") >>> answer, counterexample = completeness_naive(primes, "asynchronous", mintspaces) >>> answer True """ spec = f"CTLSPEC {exists_finally_one_of_subspaces(primes=primes, subspaces=trap_spaces)}" init = "INIT TRUE" answer, counterexample = model_checking(primes=primes, update=update, initial_states=init, specification=spec, enable_counterexample=True) if counterexample: counterexample = counterexample[-1] return answer, counterexample
def univocality_with_counterexample( primes: dict, update: str, trap_space: Union[dict, str]) -> (bool, List[dict]): """ Performs the same steps as :ref:`univocality` but also returns a counterexample which is *None* if it does not exist. A counterexample of a univocality test are two states that belong to different attractors. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *trap_space*: a subspace **returns**: * *answer*: whether *trap_space* is univocal in the STG defined by *primes* and *update* * *counter_example*: two states that belong to different attractors or *None* if no counterexample exists **example**:: >>> mintspaces = compute_trap_spaces(primes, "min") >>> trapspace = mintrapspaces[0] >>> answer, counterex = univocality_with_counterexample(primes, trapspace, "asynchronous") """ primes = percolate(primes=primes, add_constants=trap_space, copy=True) constants = find_constants(primes=primes) remove_all_constants(primes=primes) if primes == {}: return True, None attractor_state = find_attractor_state_by_randomwalk_and_ctl(primes=primes, update=update) spec = f"CTLSPEC {exists_finally_one_of_subspaces(primes=primes, subspaces=[attractor_state])}" init = "INIT TRUE" answer, counterexample = model_checking(primes=primes, update=update, initial_states=init, specification=spec, enable_counterexample=True) if answer: return True, None else: attractor_state2 = find_attractor_state_by_randomwalk_and_ctl( primes=primes, update=update, initial_state=counterexample[-1]) attractor_state2 = merge_dicts(dicts=[attractor_state2, constants]) attractor_state = merge_dicts(dicts=[attractor_state, constants]) counterexample = attractor_state, attractor_state2 return False, counterexample
def _basin_handle(primes: dict, update: str, subspace: Union[dict, str], minimize: bool, ctl_pattern: str) -> dict: """ The handle for :ref:`weak_basin`, :ref:`strong_basin` and :ref:`cycle_free_basin`. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *minimize*: minimize the Boolean expressions * *subspace*: a subspace * *CTLpattern*: **returns**: * *basin*: with keys "size"=number of states, "formula"=state formula and "perc"=percentage of state space **example**:: >>> _basin_handle(primes, update, True, "0---1", "CTLSPEC EF({x})") {"size":134, "formula": "Erk & !Raf | Mek", "perc": 12.89338} """ prop = subspace2proposition(primes, subspace) init = "INIT TRUE" spec = ctl_pattern.format(x=prop) answer, accepting_states = model_checking(primes, update, init, spec, enable_accepting_states=True) size = accepting_states["INITACCEPTING_SIZE"] formula = accepting_states["INITACCEPTING"] if minimize and formula not in ["TRUE", "FALSE"]: formula = minimize_espresso(formula) size_total = size_state_space(primes) return {"size": size, "formula": formula, "perc": 100. * size / size_total}
def iterative_completeness_algorithm( primes: dict, update: str, compute_counterexample: bool, max_output: int = 1000) -> Union[Tuple[bool, Optional[dict]], bool]: """ The iterative algorithm for deciding whether the minimal trap spaces are complete. The function is implemented by line-by-line following of the pseudo code algorithm given in "Approximating attractors of Boolean networks by iterative CTL model checking", Klarner and Siebert 2015. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *compute_counterexample*: whether to compute a counterexample **returns**: * *answer*: whether *subspaces* is complete in the STG defined by *primes* and *update*, * *counterexample*: a state that can not reach one of the minimal trap spaces of *primes* or *None* if no counterexample exists **example**:: >>> answer, counterexample = completeness_with_counterexample(primes, "asynchronous") >>> answer False >>> state2str(counterexample) 10010111101010100001100001011011111111 """ primes = percolate(primes=primes, copy=True) constants_global = find_constants(primes=primes) remove_all_constants(primes=primes) min_trap_spaces = compute_trap_spaces(primes=primes, type_="min", max_output=max_output) if min_trap_spaces == [{}]: if compute_counterexample: return True, None else: return True current_set = [({}, set([]))] while current_set: p, w = current_set.pop() primes_reduced = copy_primes(primes=primes) create_constants(primes=primes_reduced, constants=p) igraph = primes2igraph(primes=primes_reduced) cgraph = digraph2condensationgraph(digraph=igraph) cgraph_dash = cgraph.copy() for U in cgraph.nodes(): if set(U).issubset(set(w)): cgraph_dash.remove_node(U) w_dash = w.copy() refinement = [] top_layer = [ U for U in cgraph_dash.nodes() if cgraph_dash.in_degree(U) == 0 ] for U in top_layer: u_dash = find_ancestors(igraph, U) primes_restricted = copy_primes(primes_reduced) remove_all_variables_except(primes=primes_restricted, names=u_dash) q = compute_trap_spaces(primes=primes_restricted, type_="min", max_output=max_output) phi = exists_finally_one_of_subspaces(primes=primes_restricted, subspaces=q) init = "INIT TRUE" spec = f"CTLSPEC {phi}" if compute_counterexample: answer, counterexample = model_checking( primes=primes_restricted, update=update, initial_states=init, specification=spec, enable_counterexample=True) if not answer: downstream = [x for x in igraph if x not in U] arbitrary_state = random_state(downstream) top_layer_state = counterexample[-1] counterexample = merge_dicts([ constants_global, p, top_layer_state, arbitrary_state ]) return False, counterexample else: answer = model_checking(primes=primes_restricted, update=update, initial_states=init, specification=spec) if not answer: return False refinement += intersection([p], q) w_dash.update(u_dash) for q in intersection(refinement): q_tilde = find_constants( primes=percolate(primes=primes, add_constants=q, copy=True)) if q_tilde not in min_trap_spaces: current_set.append((q_tilde, w_dash)) if compute_counterexample: return True, None else: return True
def univocality(primes: dict, update: str, trap_space: Union[dict, str]) -> bool: """ The model checking and random-walk-based method for deciding whether *trap_space* is univocal, i.e., whether there is a unique attractor contained in it, in the state transition graph defined by *primes* and *update*. The approach is described and discussed in :ref:`Klarner2015(a) <klarner2015trap>`. The function performs two steps: first it searches for a state that belongs to an attractor inside of *trap_space* using the random-walk-approach and the function :ref:`random_walk <random_walk>`, then it uses CTL model checking, specifically the pattern :ref:`AGEF_oneof_subspaces <AGEF_oneof_subspaces>`, to decide if the attractor is unique inside *trap_space*. .. note:: In the (very unlikely) case that the random walk does not end in an attractor state an exception will be raised. .. note:: Univocality depends on the update strategy, i.e., a trapspace may be univocal in the synchronous STG but not univocal in the asynchronous STG or vice versa. .. note:: A typical use case is to decide whether a minimal trap space is univocal. .. note:: *trap_space* is in fact not required to be a trap set, i.e., it may be an arbitrary subspace. If it is an arbitrary subspace then the involved variables are artificially fixed to be constant. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *trap_space*: a subspace **returns**: * *answer*: whether *trap_space* is univocal in the STG defined by *primes* and *update* **example**:: >>> mintspaces = compute_trap_spaces(primes, "min") >>> x = mintrapspaces[0] >>> univocality(primes, "asynchronous", x) True """ primes = copy_primes(primes=primes) create_constants(primes=primes, constants=trap_space) percolate(primes=primes, remove_constants=True) if primes == {}: return True attractor_state = find_attractor_state_by_randomwalk_and_ctl(primes=primes, update=update) formula = exists_finally_one_of_subspaces(primes=primes, subspaces=[attractor_state]) spec = f"CTLSPEC {formula}" init = "INIT TRUE" answer = model_checking(primes=primes, update=update, initial_states=init, specification=spec) return answer
def find_attractor_state_by_randomwalk_and_ctl(primes: dict, update: str, initial_state: Union[dict, str] = {}, length: int = 0, attempts: int = 10) -> dict: """ Attempts to find a state inside an attractor by the "long random walk" method, see :ref:`Klarner2015(b) <klarner2015approx>` Sec. 3.2 for a formal definition. The method generates a random walk of *length* states, starting from a state defined by *initial_state*. If *initial_state* is a subspace then :ref:`random_state` will be used to draw a random state from inside it. The function then uses CTL model checking, i.e., :ref:`check_primes <check_primes>`, to decide whether the last state of the random walk is inside an attractor. If so it is returned, otherwise the process is repeated. If no attractor state has been found after *Attempts* many trials an exception is raised. .. note:: The default value for length, namely *length=0*, will be replaced by:: length = 10*len(primes) which proved sufficiently large in our computer experiments. **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *initial_state*: an initial state or subspace * *length*: length of random walk * *attempts*: number of attempts before exception is raised **returns**: * *attractor_state*: a state that belongs to some attractor * raises *Exception* if no attractor state is found **example**:: >>> find_attractor_state_by_randomwalk_and_ctl(primes, "asynchronous") {"v1": 1, "v2": 1, "v3": 1} """ if type(initial_state) == str: initial_state = state2dict(primes=primes, state=initial_state) assert update in UPDATE_STRATEGIES assert set(initial_state).issubset(set(primes)) if length == 0: length = 10 * len(primes) if update == "asynchronous": transition = partial(random_successor_asynchronous, primes) elif update == "synchronous": transition = partial(successor_synchronous, primes) elif update == "mixed": transition = partial(random_successor_mixed, primes) else: log.error(f"unknown update strategy: update={update}") raise Exception log.info("find_attractor_state_by_randomwalk_and_ctl(..)") log.info( f"len(primes)={len(primes)}, update={update}, length={length}, attempts={attempts}" ) trials = 0 while trials < attempts: log.info(f"trial {trials}") current_state = random_state(primes, initial_state) log.info(f"start: {state2str(current_state)}") transitions = 0 while transitions < length: current_state = transition(current_state) transitions += 1 log.info(f"end: {state2str(current_state)}") formula = all_globally_exists_finally_one_of_sub_spaces( primes=primes, sub_spaces=[current_state]) spec = f"CTLSPEC {formula}" init = f"INIT {subspace2proposition(primes=primes, subspace=current_state)}" if model_checking(primes, update, init, spec): log.info("is attractor state") return current_state trials += 1 log.error( "could not find attractor state: increase Length or Attempts parameter." ) raise Exception
def compute_phenotype_diagram(phenotypes: dict, fname_json: Optional[str] = None, fname_image: Optional[str] = None): """ Computes the phenotype diagram from the phenotypes object obtained from :ref:`phenotypes_compute_json`. save the diagram as json data with *fname_json*. useful for e.g. manually renaming nodes. **arguments**: * *phenotypes*: result of compute_json(..) * *fname_json*: save diagram as json * *fname_image*: generate image for diagram **returns**:: * *diagram*: the phenotype diagram **example**:: >>> phenos = compute_phenotypes(attrobj, markers) >>> compute_phenotype_diagram(phenos, fname_image="phenos.pdf") created phenos.pdf """ primes = phenotypes["primes"] update = phenotypes["update"] assert update in UPDATE_STRATEGIES assert primes diagram = networkx.DiGraph() for key in phenotypes: diagram.graph[key] = copy_json_data(phenotypes[key]) node_id = 0 flags = [[0, 1]] * len(phenotypes["phenotypes"]) for i, flags in enumerate(itertools.product(*flags)): state_formulas, names = [], [] for j, flag in enumerate(flags): if flag: state_formulas.append( phenotypes["phenotypes"][j]["stateformula"]) names.append(phenotypes["phenotypes"][j]["name"]) state_formulas.sort() names = tuple(sorted(names)) if not state_formulas: unreachable = " & ".join(f"!EF({x['stateformula']})" for x in phenotypes["phenotypes"]) spec = f"CTLSPEC {unreachable}" else: reach = [f"EF({x})" for x in state_formulas] reach_all = " & ".join(reach) reach_some = " | ".join(reach) spec = f"CTLSPEC {reach_all} & AG({reach_some})" init = "INIT TRUE" answer, accepting = model_checking(primes, update, init, spec, enable_accepting_states=True) data = { "names": names, "init": init, "spec": spec, "initaccepting_size": accepting["INITACCEPTING_SIZE"], "initaccepting": accepting["INITACCEPTING"] } if data["initaccepting_size"] > 0: log.info(f"[{', '.join(names)}] = {data['initaccepting_size']}") diagram.add_node(node_id) for key, value in data.items(): diagram.nodes[node_id][key] = value node_id += 1 for source in diagram: for target in diagram: if source == target: continue source_set = set(diagram.nodes[source]["names"]) target_set = set(diagram.nodes[target]["names"]) if target_set.issubset(source_set): init = f"INIT {diagram.nodes[source]['initaccepting']}" spec = f"CTLSPEC EX({diagram.nodes[target]['initaccepting']})" answer, accepting = model_checking( primes, update, init, spec, enable_accepting_states=True) if accepting["INITACCEPTING_SIZE"] > 0: data = { "init": init, "spec": spec, "initaccepting_size": accepting["INITACCEPTING_SIZE"], "initaccepting": accepting["INITACCEPTING"] } diagram.add_edge(source, target) for key, value in data.items(): diagram.edges[source, target][key] = value log.info( f"[{', '.join(diagram.nodes[source]['names'])}] --{data['initaccepting_size']}--> [{', '.join(diagram.nodes[target]['names'])}]" ) if fname_image: phenotype_diagram2image(diagram, fname_image) if fname_json: save_phenotype_diagram(diagram=diagram, fname_json=fname_json) log.info(f"created {fname_json}") return diagram
def _compute_diagram_component(primes: dict, update: str, subspaces, edge_data: bool): """ Computes the commitment diagram but without removing out-DAGs or considering connected components separately. """ assert update in UPDATE_STRATEGIES assert primes counter_mc = 0 node_id = 0 worst_case_nodes = 0 inputs = find_inputs(primes) states_per_case = pyboolnet.state_space.size_state_space(primes, fixed_inputs=True) diagram = networkx.DiGraph() log.info("_compute_diagram_component(..)") log.info(f"inputs: {len(inputs)} ({', '.join(inputs)})") log.info(f"combinations: {2**len(inputs)}") for i, combination in enumerate(list_input_combinations(primes)): attr = [ x for x in subspaces if pyboolnet.state_space.is_subspace( primes, this=x, other=combination) ] worst_case_nodes += 2**len(attr) - 1 states_covered = 0 specs = [subspace2proposition(primes, x) for x in attr] vectors = len(attr) * [[0, 1]] vectors = list(itertools.product(*vectors)) random.shuffle(vectors) combination_formula = subspace2proposition(primes, combination) log.info( f"input combination {i}, worst case #nodes: {2 ** len(attr) - 1}") for vector in vectors: if sum(vector) == 0: continue if states_covered == states_per_case: log.info("avoided executions of NuSMV due to state counting") break if len(vector) == 1: data = { "attractors": attr, "size": states_per_case, "formula": combination_formula } else: init = f"INIT {combination_formula}" reach = [f"EF({x})" for flag, x in zip(vector, specs) if flag] reach_all = " & ".join(reach) reach_some = " | ".join(reach) spec = f"CTLSPEC {reach_all} & AG({reach_some})" answer, accepting_states = model_checking( primes, update, init, spec, enable_accepting_states=True) counter_mc += 1 data = { "attractors": [x for flag, x in zip(vector, attr) if flag], "size": accepting_states["INITACCEPTING_SIZE"], "formula": accepting_states["INITACCEPTING"] } if data["size"] > 0: diagram.add_node(node_id) for key, value in data.items(): diagram.nodes[node_id][key] = value node_id += 1 states_covered += data["size"] perc = f"= {(100 * diagram.order() / worst_case_nodes)}" if worst_case_nodes else "" log.info(f"worst case #nodes: {worst_case_nodes}") log.info(f"actual nodes: {diagram.order()} {perc}") potential_targets = {} for source, source_data in diagram.nodes(data=True): successors = [] for target, target_data in diagram.nodes(data=True): if source == target: continue if all(x in source_data["attractors"] for x in target_data["attractors"]): successors.append((target, target_data)) potential_targets[source] = successors worst_case_edges = sum(len(x) for x in potential_targets.values()) log.info(f"worst case #edges: {worst_case_edges}") for source, source_data in diagram.nodes(data=True): for target, target_data in potential_targets[source]: init = f"INIT {source_data['formula']}" spec = f"CTLSPEC EX({target_data['formula']})" answer, accepting_states = model_checking( primes, update, init, spec, enable_accepting_states=True) counter_mc += 1 data = { "EX_size": accepting_states["INITACCEPTING_SIZE"], "EX_formula": accepting_states["INITACCEPTING"] } if data["EX_size"] > 0: if edge_data: if len(potential_targets[source]) == 1: data["EF_size"] = source_data["size"] data["EF_formula"] = source_data["formula"] else: spec = f"CTLSPEC E[{source_data['formula']} U {target_data['formula']}]" answer, accepting_states = model_checking( primes, update, init, spec, enable_accepting_states=True) counter_mc += 1 data["EF_size"] = accepting_states[ "INITACCEPTING_SIZE"] data["EF_formula"] = accepting_states["INITACCEPTING"] diagram.add_edge(source, target) for key, value in data.items(): diagram.edges[source, target][key] = value perc = f"= {100 * diagram.size() / worst_case_edges:.2f}%" if worst_case_edges else "" log.info(f"actual edges: {diagram.size()} {perc}") log.info(f"total executions of NuSMV: {counter_mc}") return diagram, counter_mc
from pyboolnet.model_checking import model_checking from pyboolnet.repository import get_primes from pyboolnet.state_transition_graphs import best_first_reachability if __name__ == "__main__": # basic model checking primes = get_primes("remy_tumorigenesis") init = "INIT TRUE" spec = "CTLSPEC DNA_damage -> AG(EF(Apoptosis_medium))" #tournier_apoptosis answer = model_checking(primes, "asynchronous", init, spec) print(answer) # model checking with accepting states answer, accepting_states = model_checking(primes, "asynchronous", init, spec, enable_accepting_states=True) for key, value in accepting_states.items(): print(f"{key} = {value}") # model checking with counter examples spec = "CTLSPEC DNA_damage -> AG(EF(Proliferation))" answer, counterexample = model_checking(primes,