def test_completeness(): bnet = "\n".join([ "v0, v0", "v1, v2", "v2, v1", "v3, v1&v0", "v4, v2", "v5, v3&!v6", "v6, v4&v5" ]) primes = bnet2primes(bnet) assert completeness(primes, "asynchronous") assert not completeness(primes, "synchronous") answer, example = completeness_with_counterexample(primes, "synchronous") example = state2str(example) stg = primes2stg(primes, "synchronous") for x in compute_trap_spaces(primes, "min"): x = subspace2str(primes, x) states = list_states_in_subspace(primes, x) states = [state2str(x) for x in states] assert not has_path(stg, example, states) bnet = "\n".join([ "v1, !v1&v2&v3 | v1&!v2&!v3", "v2, !v1&!v2 | v1&v3", "v3, !v1&v3 | v1&v2", "v4, 1", "v5, v4" ]) primes = bnet2primes(bnet) assert not completeness(primes, "asynchronous") answer, example = completeness_with_counterexample(primes, "asynchronous") assert len(example) == len(primes) assert completeness(primes, "synchronous") bnet = "\n".join( ["v1, !v1&v2&v3 | v1&!v2&!v3", "v2, !v1&!v2 | v1&v3", "v3, v2 | v3"]) primes = bnet2primes(bnet) assert completeness(primes, "asynchronous") assert completeness(primes, "synchronous") bnet = "\n".join([ "v1, !v2", "v2, v1", "v3, v1", "v4, v2", "v5, v6", "v6, v4&v5", "v7, v2", "v8, v5", "v9, v6&v10", "v10, v9&v7" ]) primes = bnet2primes(bnet) assert completeness(primes, "synchronous")
def add_style_path(stg: networkx.DiGraph, path: Union[List[str], List[dict]], color: str, pen_width: int = 3): """ Sets the color of all nodes and edges involved in the given *path* to *color*. **arguments**: * *stg*: state transition graph * *path*: state dictionaries or state strings * *color*: color of the path * *pen_width*: width of nodes and edges involved in *path* in pt **example**:: >>> path = ["001", "011", "101"] >>> add_style_path(stg, path, "red") """ assert path is not None path = [state2str(x) for x in path] for x in path: stg.nodes[x]["color"] = color stg.nodes[x]["penwidth"] = pen_width if len(path) > 1: for x, y in zip(path[:-1], path[1:]): stg.adj[x][y]["color"] = color stg.adj[x][y]["penwidth"] = pen_width
def test_find_attractor_state_by_randomwalk_and_ctl(): fname_in = get_tests_path_in(fname="randomnet.bnet") fname_out = get_tests_path_out(fname="randomnet.primes") primes = bnet2primes(bnet=fname_in, fname_primes=fname_out) subspace = {"Gene1": 0, "Gene3": 0, "Gene5": 0, "Gene7": 0, "Gene9": 0} length = 200 attempts = 10 min_trap_spaces = { "Gene1": 1, "Gene11": 0, "Gene12": 1, "Gene13": 0, "Gene14": 1, "Gene15": 0, "Gene16": 1, "Gene17": 1, "Gene18": 1, "Gene19": 0, "Gene2": 1, "Gene20": 1, "Gene3": 0, "Gene4": 1, "Gene5": 0, "Gene6": 0, "Gene8": 0, "Gene9": 0 } x = find_attractor_state_by_randomwalk_and_ctl(primes, "asynchronous", subspace, length, attempts) assert state_is_in_subspace(primes, x, min_trap_spaces) y = find_attractor_state_by_randomwalk_and_ctl(primes, "synchronous", subspace, length, attempts) reachable = list_reachable_states(primes, "synchronous", y, 100) assert state2str(y) in reachable z = find_attractor_state_by_randomwalk_and_ctl(primes, "mixed", subspace, length, attempts) assert state_is_in_subspace(primes, z, min_trap_spaces)
def compute_attractors(primes: dict, update: str, fname_json: Optional[str] = None, check_completeness: bool = True, check_faithfulness: bool = True, check_univocality: bool = True, max_output: int = 1000) -> dict: """ computes all attractors of *primes* including information about completeness, univocality, faithfulness **arguments**: * *primes*: prime implicants * *update*: the update strategy, one of *"asynchronous"*, *"synchronous"*, *"mixed"* * *fname_json*: json file name to save result * *check_completeness*: enable completeness check * *check_faithfulness*: enable faithfulness check * *check_univocality*: enable univocality check **returns**: * *attractors*: attractor data **example**:: >>> attractors = compute_attractors(primes, update, "attractors.json") """ assert update in UPDATE_STRATEGIES assert primes attractors = dict() attractors["primes"] = copy_primes(primes) attractors["update"] = update min_tspaces = compute_trap_spaces(primes=primes, type_="min", max_output=max_output) if check_completeness: log.info("attractors.completeness(..)") if completeness(primes, update, max_output=max_output): attractors["is_complete"] = "yes" else: attractors["is_complete"] = "no" log.info(f"{attractors['is_complete']}") else: attractors["is_complete"] = "unknown" attractors["attractors"] = [] for i, mints in enumerate(min_tspaces): mints_obj = dict() mints_obj["str"] = subspace2str(primes=primes, subspace=mints) mints_obj["dict"] = mints mints_obj["prop"] = subspace2proposition(primes=primes, subspace=mints) log.info( f" working on minimal trapspace {i+1}/{len(min_tspaces)}: {mints_obj['str']}" ) if check_univocality: log.info("attractors.univocality(..)") if univocality(primes=primes, update=update, trap_space=mints): mints_obj["is_univocal"] = "yes" else: mints_obj["is_univocal"] = "no" log.info(f" {mints_obj['is_univocal']}") else: mints_obj["is_univocal"] = "unknown" if check_faithfulness: log.info("attractors.faithfulness(..)") if faithfulness(primes=primes, update=update, trap_space=mints): mints_obj["is_faithful"] = "yes" else: mints_obj["is_faithful"] = "no" log.info(f"{mints_obj['is_faithful']}") else: mints_obj["is_faithful"] = "unknown" log.info("attractors.find_attractor_state_by_randomwalk_and_ctl(..)") state = find_attractor_state_by_randomwalk_and_ctl(primes=primes, update=update, initial_state=mints) state_obj = dict() state_obj["str"] = state2str(state) state_obj["dict"] = state state_obj["prop"] = subspace2proposition(primes, state) attractor_obj = dict() attractor_obj["min_trap_space"] = mints_obj attractor_obj["state"] = state_obj attractor_obj["is_steady"] = len(mints) == len(primes) attractor_obj["is_cyclic"] = len(mints) != len(primes) attractors["attractors"].append(attractor_obj) attractors["attractors"] = tuple( sorted(attractors["attractors"], key=lambda x: x["state"]["str"])) if fname_json: write_attractors_json(attractors, fname_json) return attractors
def best_first_reachability(primes: dict, initial_space: Union[str, dict], goal_space: Union[str, dict], memory: int = 1000): """ Performs a best-first search in the asynchronous transition system defined by *primes* to answer the question whether there is a path from a random state in *InitalSpace* to a state in *GoalSpace*. *Memory* specifies the maximal number of states that can be kept in memory as "already explored" before the algorithm terminates. The search is guided by minimizing the Hamming distance between the current state of an incomplete path and the *GoalSpace* where variables that are free in *GoalSpace* are ignored. .. note:: If the number of variables is less than 40 you should use LTL or CTL model checking to answer questions of reachability. :ref:`best_first_reachability` is meant for systems with more than 40 variables. If :ref:`best_first_reachability` returns *None* then that does not prove that there is no path between *InitialSpace* and *GoalSpace*. **arguments**: * *primes*: prime implicants * *initial_space*: initial subspace * *goal_space*: goal subspace * *memory*: maximal number of states memorized before search is stopped **returns**: * *path*: a path from *InitalSpace* to *GoalSpace* if it was found, or *None* otherwise. **example**:: >>> initspace = "1--0" >>> goalspace = "0--1" >>> path = best_first_reachability(primes, initialstate, goalspace) >>> if path: print(len(path)) 4 """ if type(initial_space) is str: initial_space = subspace2dict(primes, initial_space) if type(goal_space) is str: goal_space = subspace2dict(primes, goal_space) xdict = random_state(primes, subspace=initial_space) x = state2str(xdict) fringe = [] seen = set([]) heapq.heappush(fringe, (hamming_distance(xdict, goal_space), [x])) seen.add(x) while fringe: dist, path = heapq.heappop(fringe) if dist == 0: return path x = path[-1] for ydict in successors_asynchronous(primes, state2dict(primes, x)): y = state2str(ydict) if y not in seen: seen.add(y) heapq.heappush( fringe, (hamming_distance(ydict, goal_space), path + [y])) if len(seen) > memory: break log.info(f"explored {len(seen)} transitions, no path found.")
def list_reachable_states(primes: dict, update: str, initial_states: List[str], memory: int): """ Performs a depth-first search in the transition system defined by *primes* and *update* to list all states that are reachable from the *inital states*. *Memory* specifies the maximum number of states that can be kept in memory as "already explored" before the algorithm terminates. **arguments**: * *primes*: prime implicants * *update*: update strategy (either asynchronous or snchronous) * *initial_states*: a list of initial states * *memory*: maximal number of states memorized before search is stopped **returns**: * *reachable_states*: a list of all states explored **example**:: >>> initial_states = ["1000", "1001"] >>> update = "asynchronous" >>> memory = 1000 >>> states = list_reachable_states(primes, update, initial_states, memory) >>> print(len(states)) 287 """ if not initial_states: return [] if type(initial_states) in [dict, str]: initial_states = [initial_states] initial_states = [subspace2str(primes, x) for x in initial_states] assert update in ["asynchronous", "synchronous"] if update == "asynchronous": transition_func = lambda state: successors_asynchronous(primes, state) else: transition_func = lambda state: [successor_synchronous(primes, state)] explored = set([]) stack = set(initial_states) memory_reached = False counter = 0 while stack: state = stack.pop() new_states = set([state2str(x) for x in transition_func(state)]) not_explored = new_states.difference(explored) stack.update(not_explored) explored.add(state2str(state)) counter += 1 if len(explored) > memory: memory_reached = True break log.info(f"states explored: {counter}") if memory_reached: log.info( f"result incomplete. stack size at termination: {len(stack)} increase memory parameter" ) return explored
def primes2stg(primes: dict, update: str, initial_states=lambda x: True) -> networkx.DiGraph: """ Creates the state transition graph (STG) of a network defined by *primes* and *update*. The *initial_states* are either a list of states (in *dict* or *str* representation), a function that flags states that belong to the initial states, or a subspace (in *dict* or *str* representation). If *initial_states* is a function then it must take a single parameter *state* in dict representation and return a Boolean value that indicates whether it belongs to the initial states or not. The STG is constructed by a depth first search (DFS) starting from the given initial states. The default for *initial_states* is ``lambda x: True``, i.e., every state is initial. For a single initial state, say *"100"* use *initial_states="100"*, for a set of initial states use *initial_states=["100", "101"]* and for a initial subspace use *initial_states="1--"* or the *dict* representation of subspaces. **arguments**: * *primes*: prime implicants * *update*: either *"asynchronous"* or *"synchronous"* * *initial_states*: a function, a subspace, a state or a list of states **returns**: * *stg*: state transition graph **example**:: >>> primes = FEX.read_primes("mapk.primes") >>> update = "asynchronous" >>> init = lambda x: x["ERK"]+x["RAF"]+x["RAS"]>=2 >>> stg = primes2stg(primes, update, init) >>> stg.order() 32 >>> stg.edges()[0] ('01000','11000') >>> init = ["00100", "00001"] >>> stg = primes2stg(primes, update, init) >>> init = {"ERK":0, "RAF":0, "RAS":0, "MEK":0, "p38":1} >>> stg = primes2stg(primes, update, init) """ if update not in ['asynchronous', 'synchronous']: log.warning("The chosen update might lead to a very big STG") if len(primes) > 15: log.warning( f"The state transition graph will consist of up to 2**{len(primes)}={2 ** len(primes)} states, depending on the initial states." ) stg = networkx.DiGraph() if len(primes) == 0: log.warning("No Primes were given. Hence, the stg is empty.") return stg if update == "asynchronous": successors = lambda x: successors_asynchronous(primes, x) if update == "synchronous": successors = lambda x: [successor_synchronous(primes, x)] if update == "mixed": successors = lambda x: successors_mixed(primes, x) names = sorted(primes) space = len(names) * [[0, 1]] if hasattr(initial_states, "__call__"): fringe = [ dict(zip(names, values)) for values in itertools.product(*space) ] fringe = [state2str(x) for x in fringe if initial_states(x)] elif type(initial_states) in [str, dict]: fringe = list_states_in_subspace(primes=primes, subspace=initial_states) else: fringe = [state2str(x) for x in initial_states] seen = set([]) while fringe: source = fringe.pop() if source in seen: continue for target in successors(source): target = state2str(target) stg.add_edge(source, target) if target not in seen: fringe.append(target) seen.add(source) stg.graph["node"] = { "shape": "rect", "color": "none", "style": "filled", "fillcolor": "none" } stg.graph["edge"] = {} stg.graph["subgraphs"] = [] if update == "synchronous": stg.graph["overlap"] = "compress" else: stg.graph["overlap"] = "scale" return stg
def test_state2str(): state = {"v2": 0, "v1": 1, "v3": 1} answer = state2str(state) assert answer == "101"