예제 #1
0
def test_full_continuity():
    model_text = """
    A=B
    B=A"""

    knockouts: Dict[str, int] = {}  # no knockout
    continuous: Dict[str, bool] = {
        "A": True,
        "B": True
    }  # all variables continuous

    variables, update_fn, equation_system = process_model_text(
        model_text, knockouts, continuous)

    assert variables == ("A", "B")

    # small changes unchanged
    for a, b in itertools.product(range(3), repeat=2):
        if abs(a - b) <= 1:
            assert np.all(update_fn([a, b]) == [b, a])

    # all (esp. larger) changes `muted`
    for a, b in itertools.product(range(3), repeat=2):
        assert np.all(
            update_fn([a, b]) == np.mod(
                [
                    b**2 + a + 2 * a**2 + a**2 * b + 2 * a * b**2,
                    a**2 + b + 2 * b**2 + a * b**2 + 2 * a**2 * b,
                ],
                3,
            ))
def test_parse():
    model_text = """
    A=B
    B=A"""

    knockouts = {}  # no knockout
    continuous: Dict[str, bool] = {}  # no continuous variables

    variables, update_fn, equation_system = process_model_text(
        model_text, knockouts, continuous
    )

    assert variables == ("A", "B")

    for a, b in itertools.product(range(3), repeat=2):
        assert np.all(update_fn([a, b]) == [b, a])
예제 #3
0
def test_knockouts():
    model_text = """
    A=B
    B=A"""

    knockouts: Dict[str, int] = {"A": 1}  # knockout A
    continuous: Dict[str, bool] = {}  # no variables continuous

    variables, update_fn, equation_system = process_model_text(
        model_text, knockouts, continuous
    )

    assert variables == ("A", "B")

    # function becomes constant on 2nd step
    for a, b in itertools.product(range(3), repeat=2):
        assert np.all(update_fn(update_fn([a, b])) == [1, 1])
예제 #4
0
def test_update_fn1():
    model_text = """
    A=B
    B=A"""

    knockouts: Dict[str, int] = {}  # no knockout
    continuous: Dict[str, bool] = {}  # all variables continuous

    variables, update_fn, equation_system = process_model_text(
        model_text, knockouts, continuous
    )

    assert variables == ("A", "B")

    # function is order 2
    for a, b in itertools.product(range(3), repeat=2):
        assert np.all(update_fn(update_fn([a, b])) == [a, b])
예제 #5
0
    def download_model(modeltype: str) -> Response:
        use_sbml_qual_format = modeltype[-5:] == ".sbml"
        use_text_format = modeltype[-4:] == ".txt"

        # get the variable list and right hand sides
        model_text: str = request.form["model_text"].strip()

        variables, update_fn, submitted_equation_system = process_model_text(
            model_text, dict(), dict())

        if use_text_format:
            response = make_response(str(submitted_equation_system))
            response.headers["Content-Type"] = "text/plain"
            return response

        if use_sbml_qual_format:
            sbml_qual: BeautifulSoup = submitted_equation_system.as_sbml_qual()
            response = make_response(str(sbml_qual.prettify()))
            response.headers["Content-Type"] = "application/xml"
            return response

        return make_response(error_report("Unknown Error"))
예제 #6
0
def compute_trace(
    *,
    model_text: str,
    knockouts: Dict[str, int],
    continuous: Dict[str, bool],
    init_state: Dict[str, str],
    visualize_variables: Dict[str, bool],
    check_nearby: bool,
):
    """Run the cycle finding simulation for an initial state"""

    # create an update function and equation system
    variables, update_fn, equation_system = process_model_text(
        model_text, knockouts, continuous)

    # construction of initial values can fail with an exception that contains a response
    try:
        edge_lists, init_states, limit_points = run_model_variable_initial_values(
            init_state_prototype=init_state,
            variables=variables,
            update_fn=update_fn,
            equation_system=equation_system,
            check_nearby=check_nearby,
        )
    except ComputeTraceException as e:
        payload = e.args[0]
        if type(payload) in {str, Response}:
            return payload
        else:
            # should not occur
            return make_response(error_report("Unknown error"))

    ################################
    # give numeric labels to vertices and record their type i.e. initial node, limit cycle, other

    def to_key(edges):
        return frozenset(edges.items())

    class NodeType(IntEnum):
        Other = 0
        InitialNode = 1
        LimitNode = 2

    labels: Dict[frozenset, int] = dict()
    # keys: frozenset of (var, val) pairs defining a point in config space
    # values: integer 'id's for the points of config space

    node_type: Dict[int, NodeType] = dict()
    # keys: integer 'id's for the points of config space
    # values: type of node

    init_states_as_keys = set(
        to_key(dict(zip(variables, state))) for state in init_states)
    limit_points_as_keys = set(
        to_key(dict(zip(variables, state.array))) for state in limit_points)

    def get_node_type(key) -> NodeType:
        if key in limit_points_as_keys:
            return NodeType.LimitNode
        elif key in init_states_as_keys:
            return NodeType.InitialNode
        else:
            return NodeType.Other

    count = 0
    for edge_list in edge_lists:
        for edge in edge_list:
            source = to_key(edge["source"])
            if source not in labels:
                labels[source] = count
                node_type[count] = get_node_type(source)
                count += 1
            target = to_key(edge["target"])
            if target not in labels:
                labels[target] = count
                node_type[count] = get_node_type(target)
                count += 1

    return_states = []
    for edge_list in edge_lists:
        return_state = to_key(edge_list[-1]["target"])
        return_states.append(labels[return_state])

    source_labels = [[labels[to_key(edge["source"])] for edge in edge_list]
                     for edge_list in edge_lists]

    variable_level_plots = plot_variable_levels_for_trajectories(
        edge_lists=edge_lists,
        return_states=return_states,
        source_labels=source_labels,
        variables=variables,
        visualize_variables=visualize_variables,
    )

    # create data for the javascript
    edge_lists_by_labels = [[{
        "source": labels[to_key(edge["source"])],
        "target": labels[to_key(edge["target"])],
    } for edge in edge_list] for edge_list in edge_lists]

    num_nodes = len(labels)
    height_percent = min(95,
                         math.ceil(50 + 50 / (1 - math.exp(-num_nodes / 100))))
    width_px = 640
    height_px = math.ceil(320 + 320 / (1 - math.exp(-num_nodes / 100)))

    initial_node_positions = graph_layout(edge_lists_by_labels, height_px,
                                          width_px)
    nodes_json = json.dumps([{
        "id": str(label),
        "group": node_type[label],
        "x": float(initial_node_positions[label][0]),
        "y": float(initial_node_positions[label][1]),
    } for label in labels.values()])

    edge_tuples = set()
    for edge_list in edge_lists:
        edge_tuples.update({
            (labels[to_key(edge["source"])], labels[to_key(edge["target"])])
            for edge in edge_list
            if to_key(edge["source"]) != to_key(edge["target"])
        })
        # `if` blocks self loops, which the force-directed graph freaks out about

    edge_json = json.dumps([{
        "source": str(source),
        "target": str(target)
    } for (source, target) in edge_tuples])

    # respond with the results-of-computation page
    return make_response(
        render_template(
            "compute-trace.html",
            variables=equation_system.target_variables(),
            num_edge_lists=len(edge_lists),
            trajectories=list(
                zip(
                    edge_lists,
                    return_states,
                    source_labels,
                    map(len, edge_lists),
                    variable_level_plots,
                )),
            nodes=nodes_json,
            height_percent=height_percent,
            width_px=width_px,
            height_px=height_px,
            links=edge_json,
        ))
예제 #7
0
def compute_cycles(
    *,
    model_text: str,
    knockouts: Dict[str, int],
    continuous: Dict[str, bool],
    num_iterations: int,
    visualize_variables: Dict[str, bool],
):
    """

    Parameters
    ----------
    model_text
    knockouts
    continuous
    num_iterations
    visualize_variables

    Returns
    -------

    """
    # turn boolean-valued dictionary to list of variables to visualize
    visualized_variables: Tuple[str] = tuple(var for var in visualize_variables
                                             if visualize_variables[var])

    # create an update function and equation system
    try:
        variables, update_fn, equation_system = process_model_text(
            model_text, knockouts, continuous)
    except ParseError as e:
        return make_response(error_report(e.message))

    # compile and warm up the jitter
    # update_fn = numba.jit(update_fn)
    # update_fn(np.zeros(len(variables), dtype=np.int64))

    # associate variable names with their index in vectors
    variable_idx: Dict[str, int] = dict(zip(variables, range(len(variables))))

    constants: List[str] = equation_system.constant_variables()
    constants_vals: Dict[str, int] = {
        const: int(equation_system[const])
        for const in constants
    }

    # decide if we will perform a complete state space search or not
    state_space_size = 3**(len(variables) - len(constants))
    perform_complete_search = state_space_size <= num_iterations

    state_generator: Iterator[np.ndarray]
    if perform_complete_search:
        state_generator = complete_search_generator(
            variables=variables, constants_vals=constants_vals)
        num_iterations = state_space_size
    else:
        state_generator = random_search_generator(
            num_iterations=num_iterations,
            variables=variables,
            constants_vals=constants_vals,
        )

    max_threads = max(
        1,
        min(pathos.multiprocessing.cpu_count() - 1,
            math.floor(state_space_size / 1000)),
    )

    batch_generator = batcher(state_generator,
                              variables,
                              batch_size=min(1000,
                                             num_iterations // max_threads))

    trajectory_length_counts: Dict[HashableNdArray, BinCounter] = dict()
    data_counts: Dict[HashableNdArray, np.ndarray] = dict()
    means: Dict[HashableNdArray, np.ndarray] = dict()
    variances: Dict[HashableNdArray, np.ndarray] = dict()
    with pathos.multiprocessing.ProcessPool(nodes=max_threads) as pool:
        trajectory_length_counts, data_counts, means, variances = functools.reduce(
            reducer,
            pool.uimap(batch_trajectory_process, batch_generator,
                       itertools.repeat(update_fn)),
            TrajectoryStatistics(trajectory_length_counts, data_counts, means,
                                 variances),
        )

    phase_points = trajectory_length_counts.keys()

    # recreate limit cycles from phase point keys
    limit_cycles: Dict[
        HashableNdArray,
        List] = dict()  # record limit sets with their ordering. i.e. as cycles
    for phase_point in phase_points:
        cycle = list()
        t_state = phase_point
        state = phase_point.array
        # do-while
        cycle.append(t_state)
        state = update_fn(state)
        t_state = HashableNdArray(state)
        while t_state != phase_point:
            cycle.append(t_state)
            state = update_fn(state)
            t_state = HashableNdArray(state)
        # record cycle
        limit_cycles[phase_point] = cycle

    # create images for each variable
    limit_set_stats_images = {
        phase_point: dict()
        for phase_point in phase_points
    }
    with tempfile.TemporaryDirectory() as tmp_dir_name:
        plt.rcParams["svg.fonttype"] = "none"
        for phase_point, var in itertools.product(phase_points,
                                                  visualized_variables):
            cycle = limit_cycles[phase_point]
            var_idx = variable_idx[var]

            var_means = np.flipud(means[phase_point][:, var_idx])
            var_stdevs = np.flipud(np.sqrt(variances[phase_point][:, var_idx]))

            plt.figure(figsize=(6, 4))

            # means, since phased will fix on single value at end
            plt.plot(list(var_means) + [cycle[0][var_idx]], color="#1f3d87")
            plt.fill_between(
                range(len(var_means) + 1),
                list(var_means - var_stdevs) + [cycle[0][var_idx]],
                list(var_means + var_stdevs) + [cycle[0][var_idx]],
                color="grey",
                alpha=0.25,
            )
            # dashed cycle in `converging` region
            plt.plot(
                range(len(var_means) - len(cycle),
                      len(var_means) + 1),
                [cycle[idx][var_idx]
                 for idx in range(len(cycle))] + [cycle[0][var_idx]],
                color="#F24F00",
                linestyle="dashed",
            )
            # solid cycle in `converged` region
            plt.plot(
                range(len(var_means),
                      len(var_means) + len(cycle) + 1),
                [cycle[idx][var_idx]
                 for idx in range(len(cycle))] + [cycle[0][var_idx]],
                color="#F24F00",
            )

            plt.title(f"Phased Envelope of trajectories for {var}")

            plt.xlabel("Distance to phase-fixing point on limit cycle")
            max_tick_goal = 40
            tick_gap = max(
                1, ceil((len(var_means) + len(cycle) + 1) / max_tick_goal))
            x_tick_locations = [
                x for x in range(0,
                                 len(var_means) + len(cycle) + 1)
                if (x - len(var_means)) % tick_gap == 0
            ]
            x_tick_labels = list(
                map(lambda x: len(var_means) - x, x_tick_locations))
            plt.xticks(x_tick_locations, x_tick_labels, rotation=90)
            plt.xlim([0, len(var_means) + len(cycle)])
            plt.axvline(len(var_means), linestyle="dotted", color="grey")
            plt.axvline(len(var_means) - len(cycle),
                        linestyle="dotted",
                        color="grey")

            plt.ylabel("State")
            plt.yticks([0, 1, 2])
            for y_val in range(3):
                plt.axhline(y=y_val,
                            linestyle="dotted",
                            color="grey",
                            alpha=0.5)
            plt.ylim([0 - 0.1, 2 + 0.1])

            plt.tight_layout()

            image_filename = "dist-" + str(
                hash(phase_point)) + "-" + var + ".svg"
            plt.savefig(tmp_dir_name + "/" + image_filename,
                        transparent=True,
                        pad_inches=0.0)
            plt.close()
            with open(tmp_dir_name + "/" + image_filename, "r") as image:
                limit_set_stats_images[phase_point][var] = Markup(image.read())

    # print('var images complete')

    # cycle list sorted by frequency
    cycle_list = list(limit_cycles.values())
    cycle_list.sort(key=lambda cyc: trajectory_length_counts[cyc[0]].total(),
                    reverse=True)

    # tally of number of cycles of each length, sorted by cycle length
    cycle_len_counts = defaultdict(lambda: 0)
    for cycle in cycle_list:
        cycle_len_counts[len(cycle)] += 1
    cycle_len_counts = tuple(sorted(cycle_len_counts.items()))

    length_distribution_images = dict()
    with tempfile.TemporaryDirectory() as tmp_dir_name:
        # create length distribution plots
        plt.rcParams["svg.fonttype"] = "none"
        for phase_point in phase_points:
            length_dist = trajectory_length_counts[phase_point].trimmed_bins()
            plt.figure(figsize=(4, 3))
            plt.bar(x=range(len(length_dist)),
                    height=length_dist,
                    color="#002868")
            plt.title("Distribution of lengths of paths")
            plt.xlabel("Length of path")
            plt.ylabel("Number of states")
            plt.tight_layout()
            image_filename = "dist-" + str(hash(phase_point)) + ".svg"
            plt.savefig(tmp_dir_name + "/" + image_filename,
                        transparent=True,
                        pad_inches=0.0)
            plt.close()
            with open(tmp_dir_name + "/" + image_filename, "r") as image:
                length_distribution_images[phase_point] = Markup(image.read())

    def get_key(cyc):
        return cyc[0]

    def get_count(cyc):
        return trajectory_length_counts[get_key(cyc)].total()

    cycles = [{
        "states":
        cycle,
        "len":
        len(cycle),
        "count":
        get_count(cycle),
        "percent":
        100 * get_count(cycle) / num_iterations,
        "len-dist-image":
        length_distribution_images[get_key(cycle)]
        if get_key(cycle) in length_distribution_images else "",
        "limit-set-stats-images":
        limit_set_stats_images[get_key(cycle)]
        if get_key(cycle) in limit_set_stats_images else dict(),
    } for cycle in cycle_list]

    # respond with the results-of-computation page
    return make_response(
        render_template(
            "compute-cycles.html",
            cycles=cycles,
            variables=variables,
            cycle_len_counts=cycle_len_counts,
            complete_results=perform_complete_search,
        ))