Beispiel #1
0
def test_known_trap_spaces_for_mapk():
    primes = get_primes("grieco_mapk")

    tspaces = compute_trap_spaces(primes, type_="min")
    assert len(tspaces) == 18

    tspaces = compute_trap_spaces(primes, type_="max")
    assert len(tspaces) == 9
Beispiel #2
0
def test_trap_spaces_piped2():
    fname_in = get_tests_path_in(fname="trapspaces_tsfree.primes")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="min")
    tspaces.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces == [{}]
Beispiel #3
0
def test_trap_spaces_piped1():
    fname_in = get_tests_path_in(fname="trapspaces_posfeedback.primes")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="min")
    tspaces.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [{"v1": 0, "v2": 0, "v3": 0}, {"v1": 1, "v2": 1, "v3": 1}]

    assert tspaces == expected
Beispiel #4
0
def test_trap_spaces_positive_feedback_all():
    fname_in = get_tests_path_in(fname="trapspaces_posfeedback.primes")
    fname_out = get_tests_path_out(fname="trapspaces_posfeedback_all.asp")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="all", fname_asp=fname_out)
    tspaces.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces == [{}, {"v1": 0, "v2": 0, "v3": 0}, {"v1": 1, "v2": 1, "v3": 1}]
Beispiel #5
0
def test_trap_spaces_tsfree():
    fname_in = get_tests_path_in(fname="trapspaces_tsfree.primes")
    fname_out = get_tests_path_out(fname="trapspaces_tsfree_min.asp")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="min", fname_asp=fname_out)

    assert tspaces == [{}]

    fname_in = get_tests_path_in(fname="trapspaces_tsfree.primes")
    fname_out = get_tests_path_out(fname="trapspaces_tsfree_all.asp")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="all", fname_asp=fname_out)

    assert tspaces == [{}]

    fname_in = get_tests_path_in(fname="trapspaces_tsfree.primes")
    fname_out = get_tests_path_out(fname="trapspaces_tsfree_max.asp")
    primes = read_primes(fname_json=fname_in)
    tspaces = compute_trap_spaces(primes=primes, type_="max", fname_asp=fname_out)

    assert tspaces == []
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")
Beispiel #7
0
def test_encoding_bijection():
    """
    The mapping from stable and consistent prime implicant sets to trap spaces is surjective but not injective.
    Two different arc sets may lead to the same trap space.
    In the following example there are four trap stable+consistent arc sets but only two trap spaces.
    """

    bnet = "\n".join(["v1,v1|v2", "v2,v1"])
    primes = bnet2primes(bnet)

    result = compute_trap_spaces(primes, "all")
    result.sort(key=lambda x: tuple(sorted(x.items())))

    assert result == [{}, {"v1": 0, "v2": 0}, {"v1": 1}, {"v1": 1, "v2": 1}]

    result = compute_trap_spaces(primes, "min")
    result.sort(key=lambda x: tuple(sorted(x.items())))

    assert result == [{"v1": 0, "v2": 0}, {"v1": 1, "v2": 1}]

    result = compute_trap_spaces(primes, "max")
    result.sort(key=lambda x: tuple(sorted(x.items())))

    assert result == [{"v1": 0, "v2": 0}, {"v1": 1}]
Beispiel #8
0
def command_trap_spaces(type_: str, max_output: int, fname_asp: str,
                        representation: str, fname_bnet: str,
                        fname_primes: str, fname_trap_spaces: str):
    primes = compute_primes_or_exit(fname_bnet=fname_bnet,
                                    fname_primes=fname_primes)
    trap_spaces = compute_trap_spaces(primes=primes,
                                      type_=type_,
                                      max_output=max_output,
                                      representation=representation,
                                      fname_asp=fname_asp)
    lines = [str(x) for x in trap_spaces]
    for x in lines[:3]:
        click.echo(x)
    click.echo(f"found {len(lines)} trap spaces.")

    if fname_trap_spaces:
        with open(fname_trap_spaces, "r") as fp:
            fp.writelines(lines)
            click.echo(f"created {fname_trap_spaces}")
Beispiel #9
0
def add_style_mintrapspaces(primes: dict,
                            stg: networkx.DiGraph,
                            max_output: int = 100):
    """
    A convenience function that combines :ref:`add_style_subspaces` and :ref:`trap_spaces <trap_spaces>`.
    It adds a *dot* subgraphs for every minimal trap space to *stg* - subgraphs that already exist are overwritten.

    **arguments**:
        * *primes*: prime implicants
        * *stg*: state transition graph
        * *MaxOutput*: maximal number of minimal trap spaces, see :ref:`trap_spaces <sec:trap_spaces>`

    **example**:

        >>> add_style_mintrapspaces(primes, stg)
    """

    states = stg.nodes()

    for tspace in compute_trap_spaces(primes, "min", max_output=max_output):
        subgraph = networkx.DiGraph()
        subgraph.add_nodes_from([
            x for x in list_states_in_subspace(primes, tspace) if x in states
        ])
        if not subgraph.nodes():
            continue

        subgraph.graph["color"] = "black"
        if len(tspace) == len(primes):
            subgraph.graph["label"] = "steady state"
        else:
            subgraph.graph["label"] = "min trap space %s" % subspace2str(
                primes, tspace)

        if not stg.graph["subgraphs"]:
            stg.graph["subgraphs"] = []

        for x in list(stg.graph["subgraphs"]):
            if sorted(x.nodes()) == sorted(subgraph.nodes()):
                stg.graph["subgraphs"].remove(x)

        stg.graph["subgraphs"].append(subgraph)
Beispiel #10
0
def print_info(markdown: bool = False):
    """
    prints repository info
    """

    header = [("name", "size", "inputs", "constants", "steady states", "cyclic attractors (mints)")]
    data = []
    for name in get_all_names():
        primes = get_primes(name)
        tspaces = compute_trap_spaces(primes, "min", max_output=MAX_OUTPUT)

        size = str(len(primes))
        inputs = str(len(find_inputs(primes)))
        constants = str(len(find_constants(primes)))
        steady = len([x for x in tspaces if len(x) == len(primes)])
        steady = str(steady) + '+'*(steady == MAX_OUTPUT)
        cyclic = len([x for x in tspaces if len(x) < len(primes)])
        cyclic = str(cyclic) + '+'*(steady == MAX_OUTPUT)

        data.append((name, size, inputs, constants, steady, cyclic))

    data.sort(key=lambda x: int(x[1]))
    data = header + data

    width = {}
    for i in range(len(data[0])):
        width[i] = max(len(x[i]) for x in data) + 2

    if markdown:
        header = '| ' + ' | '.join(x.ljust(width[i]) for i, x in enumerate(data[0])) + ' |'
        print(header)
        print('| ' + ' | '.join('-'*width[i] for i, x in enumerate(data[0])) + ' |')

        body   = data[1:]
        for row in body:
            print('| ' + ' | '.join(x.ljust(width[i]) for i, x in enumerate(row)) + ' |')

    else:
        for row in data:
            print(''.join(x.ljust(width[i]) for i,x in enumerate(row)))
Beispiel #11
0
def create_attractor_report(primes: dict,
                            fname_txt: Optional[str] = None) -> str:
    """
    Creates an attractor report for the network defined by *primes*.

    **arguments**:
        * *primes*: prime implicants
        * *fname_txt*: the name of the report file or *None*

    **returns**:
        * *txt*: attractor report as text

    **example**::
         >>> create_attractor_report(primes, "report.txt")
    """

    min_trap_spaces = compute_trap_spaces(primes, "min")
    steady = sorted(x for x in min_trap_spaces if len(x) == len(primes))
    cyclic = sorted(x for x in min_trap_spaces if len(x) < len(primes))
    width = max([12, len(primes)])

    lines = ["", ""]
    lines += ["### Attractor Report"]
    lines += [
        f" * created on {datetime.date.today().strftime('%d. %b. %Y')} using pyboolnet {VERSION}, see https://github.com/hklarner/pyboolnet"
    ]
    lines += [""]

    lines += ["### Steady States"]
    if not steady:
        lines += [" * there are no steady states"]
    else:
        lines += ["| " + "steady state".ljust(width) + " |"]
        lines += ["| " + width * "-" + " | "]

    for x in steady:
        lines += ["| " + subspace2str(primes, x).ljust(width) + " |"]

    lines += [""]
    lines += ["### Asynchronous STG"]
    answer = completeness(primes, "asynchronous")
    lines += [f" * completeness: {answer}"]

    if not cyclic:
        lines += [" * there are only steady states"]
    else:
        lines += [""]
        line = "| " + "trapspace".ljust(width) + " | univocal  | faithful  |"
        lines += [line]
        lines += ["| " + width * "-" + " | --------- | --------- |"]

    for x in cyclic:
        line = "| " + subspace2str(primes, x).ljust(width)
        line += " | " + str(univocality(primes, "asynchronous", x)).ljust(9)
        line += " | " + str(faithfulness(primes, "asynchronous",
                                         x)).ljust(9) + " |"
        lines += [line]

    lines += [""]

    lines += ["### Synchronous STG"]
    lines += [f" * completeness: {completeness(primes, 'synchronous')}"]

    if not cyclic:
        lines += [" * there are only steady states"]
    else:
        lines += [""]
        line = "| " + "trapspace".ljust(width) + " | univocal  | faithful  |"
        lines += [line]
        lines += ["| " + width * "-" + "  | --------- | --------- |"]

    for x in cyclic:
        line = "| " + (subspace2str(primes, x)).ljust(width)
        line += " | " + str(univocality(primes, "synchronous", x)).ljust(9)
        line += " | " + str(faithfulness(primes, "synchronous",
                                         x)).ljust(9) + " |"
        lines += [line]

    lines += [""]

    bnet = []

    for row in primes2bnet(primes=primes).split("\n"):
        row = row.strip()
        if not row:
            continue

        t, f = row.split(",")
        bnet.append((t.strip(), f.strip()))

    t_width = max([7] + [len(x) for x, _ in bnet])
    f_width = max([7] + [len(x) for _, x in bnet])
    lines += ["### Network"]
    t, f = bnet.pop(0)
    lines += ["| " + t.ljust(t_width) + " | " + f.ljust(f_width) + " |"]
    lines += ["| " + t_width * "-" + " | " + f_width * "-" + " |"]
    for t, f in bnet:
        lines += ["| " + t.ljust(t_width) + " | " + f.ljust(f_width) + " |"]

    lines += ["", ""]
    text = "\n".join(lines)

    if fname_txt:
        with open(fname_txt, "w") as f:
            f.writelines(text)

        log.info(f"created {fname_txt}")

    return text
Beispiel #12
0
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
Beispiel #13
0
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
Beispiel #14
0
from pyboolnet.repository import get_primes
from pyboolnet.trap_spaces import compute_trap_spaces, compute_steady_states

if __name__ == "__main__":
    # compute minimal and maximal trap spaces

    primes = get_primes("remy_tumorigenesis")
    mints = compute_trap_spaces(primes, "min")
    print(len(mints))

    max_trap_spaces = compute_trap_spaces(primes, "max")
    print(len(max_trap_spaces))
    print(max_trap_spaces)

    # compute steady states using the ASP solver

    steady = compute_steady_states(primes)
    print(len(steady))
Beispiel #15
0
def test_trap_spaces_bounded():
    fname_in = get_tests_path_in(fname="trapspaces_bounded.bnet")
    fname_out = get_tests_path_out(fname="trapspaces_bounded.primes")
    primes = bnet2primes(fname_in, fname_out)
    tspaces_all = compute_trap_spaces(primes, "all")
    tspaces_all.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [{},
                {"v3": 1},
                {"v3": 0},
                {"v1": 1},
                {"v1": 1, "v2": 1},
                {"v1": 0, "v2": 0},
                {"v3": 1, "v4": 1},
                {"v1": 1, "v3": 0},
                {"v1": 1, "v3": 1},
                {"v1": 1, "v2": 1, "v3": 1},
                {"v1": 1, "v3": 1, "v4": 1},
                {"v1": 1, "v2": 1, "v3": 0},
                {"v1": 0, "v2": 0, "v3": 0},
                {"v1": 0, "v2": 0, "v3": 1},
                {"v1": 1, "v2": 1, "v4": 1},
                {"v1": 0, "v2": 0, "v3": 1, "v4": 1},
                {"v1": 1, "v2": 1, "v3": 0, "v4": 1},
                {"v1": 1, "v2": 1, "v3": 1, "v4": 1},
                {"v1": 0, "v2": 0, "v3": 0, "v4": 0}]

    expected.sort(key=lambda x: tuple(sorted(x.items())))
    
    assert tspaces_all == expected

    tspaces_min = compute_trap_spaces(primes, "min")
    tspaces_min.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [
        {"v1": 0, "v2": 0, "v3": 0, "v4": 0},
        {"v1": 1, "v2": 1, "v3": 1, "v4": 1},
        {"v1": 0, "v2": 0, "v3": 1, "v4": 1},
        {"v1": 1, "v2": 1, "v3": 0, "v4": 1}]

    expected.sort(key=lambda x: tuple(sorted(x.items())))
    
    assert tspaces_min == expected

    tspaces_max = compute_trap_spaces(primes, "max")
    tspaces_max.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [{"v3": 1}, {"v3": 0}, {"v1": 1}, {"v1": 0, "v2": 0}]
    expected.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces_max == expected

    tspaces_bounded = compute_trap_spaces_bounded(primes, "max", bounds=(1, 1))
    tspaces_bounded.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [{"v3": 1}, {"v3": 0}, {"v1": 1}]
    expected.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces_bounded == expected

    tspaces_bounded = compute_trap_spaces_bounded(primes, "max", bounds=(2, 3))
    tspaces_bounded.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [{"v1": 1, "v2": 1},
                {"v1": 0, "v2": 0},
                {"v3": 1, "v4": 1},
                {"v1": 1, "v3": 0},
                {"v1": 1, "v3": 1}]

    expected.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces_bounded == expected

    tspaces_bounded = compute_trap_spaces_bounded(primes, "all", bounds=(2, 3))
    tspaces_bounded.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [
        {"v1": 1, "v2": 1},
        {"v1": 0, "v2": 0},
        {"v3": 1, "v4": 1},
        {"v1": 1, "v3": 0},
        {"v1": 1, "v3": 1},
        {"v1": 1, "v2": 1, "v3": 1},
        {"v1": 1, "v3": 1, "v4": 1},
        {"v1": 1, "v2": 1, "v3": 0},
        {"v1": 0, "v2": 0, "v3": 0},
        {"v1": 0, "v2": 0, "v3": 1},
        {"v1": 1, "v2": 1, "v4": 1}]

    expected.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces_bounded == expected

    tspaces_bounded = compute_trap_spaces_bounded(primes, "min", bounds=(2, 3))
    tspaces_bounded.sort(key=lambda x: tuple(sorted(x.items())))
    expected = [
        {"v1": 1, "v2": 1, "v3": 1},
        {"v1": 1, "v3": 1, "v4": 1},
        {"v1": 1, "v2": 1, "v3": 0},
        {"v1": 0, "v2": 0, "v3": 0},
        {"v1": 0, "v2": 0, "v3": 1},
        {"v1": 1, "v2": 1, "v4": 1}]

    expected.sort(key=lambda x: tuple(sorted(x.items())))

    assert tspaces_bounded == expected