Skip to content

Commit

Permalink
Merge pull request #71 from jcrozum/stubs
Browse files Browse the repository at this point in the history
Lazy succession diagram
  • Loading branch information
jcrozum committed Jul 25, 2023
2 parents 64b2f80 + 1f194a9 commit a810dc0
Show file tree
Hide file tree
Showing 27 changed files with 1,453 additions and 380 deletions.
66 changes: 53 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@ on:
pull_request:

jobs:
linux:
# First, check if the types/formatting is correct.
types:
# TODO: Later we can add more python versions but for now one should be enough.
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.11
architecture: x64
- name: Install repo dependencies
run: pip install -r requirements.txt
- name: Install mypy
run: pip install mypy
- name: Run mypy check
run: mypy nfvsmotifs
# Then do a "small" test run with code coverage.
coverage:
runs-on: ubuntu-latest
timeout-minutes: 30
needs: types
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -30,23 +50,43 @@ jobs:
(cd ./mole-140428 && pwd >> $GITHUB_PATH)
- name: Install repo dependencies
run: pip install -r requirements.txt
- name: Install mypy
run: pip install mypy
- name: Run mypy check
run: mypy nfvsmotifs
- name: Install pytest
run: pip install pytest pytest-cov
# Correctness is tested on larger networks, while for coverage, we use
# a smaller subset, since it should not have a significant impact on it.
- name: Run pytest (without coverage)
run: python3 -m pytest --networksize 50 tests/
# The reason why we have to run pytest twice is that saving the coverage report will erase
# the exit code and so the workflow would succeed even if there are failing tests.
# (this could be of course solved using the magic of unix, but for now, this is simpler)
- name: Run pytest again with coverage
- name: Run pytest with coverage
run: python3 -m pytest --networksize 20 --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=nfvsmotifs tests/ | tee pytest-coverage.txt
- name: Pytest coverage comment
uses: MishaKav/pytest-coverage-comment@main
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml
# Then do a full test for correctness using larger networks.
tests:
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [types, coverage]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install clingo
run: sudo apt-get -y install gringo
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.11
architecture: x64
- name: Fetch and install native Pint
run: |
wget https://github.com/pauleve/pint/releases/download//2019-05-24/pint_2019-05-24_amd64.deb
sudo apt install ./pint_2019-05-24_amd64.deb
- name: Fetch and install Mole
run: |
wget http://www.lsv.fr/~schwoon/tools/mole/mole-140428.tar.gz
tar -xvf mole-140428.tar.gz
(cd ./mole-140428 && make)
(cd ./mole-140428 && pwd >> $GITHUB_PATH)
- name: Install repo dependencies
run: pip install -r requirements.txt
- name: Install pytest
run: pip install pytest
- name: Run pytest (without coverage)
run: python3 -m pytest --networksize 50 tests/
File renamed without changes.
File renamed without changes.
37 changes: 0 additions & 37 deletions bench_attractor_search.py

This file was deleted.

31 changes: 31 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Benchmarking

This folder contains several scripts useful for benchmarking.
All `bench_*` scripts solve one "benchmark problem" for a single given network file.
The last line of output of such computation is always a comma-separated list of values with relevant results.
You can then use `run_bench.py` to execute such benchmark script for all networks in a folder.
This will collect the full output of each script, as well as create an agggregate result table.
Each row of such table contains the network name, runtime of the whole script, and its output.

## Install `nfvsmotifs`

The benchmark scripts assume `nfvsmotifs` is installed as a package.
To do this, go to the repository root and run `pip install .`
(this step reqires at least `pip` version `23`).

## Example

For example, to test how long it takes to generate the succession diagram for minimal trap spaces,
you can use the following:

```
# 1h - timeout
# ../bbm-bnet-inputs-true - folder with networks
# bench_sd_expand_min.py - benchmark script
python3 run_bench.py 1h ../bbm-bnet-inputs-true bench_sd_expand_min.py
```

You can optionally add `-p X` or `-i` flag at the end of the argument list.
The `-p` flag causes the benchmark to run in parallel in `X` processes.
The `-i` flag causes the benchmark to run in "interactive" mode where the user can skip
individual benchmarks.
24 changes: 24 additions & 0 deletions benchmark/bench_aeon_attractors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from biodivine_aeon import BooleanNetwork, SymbolicAsyncGraph, find_attractors
import sys

# This is a very basic script for running attractor detection usin AEON.
# The results should be comparable to `bench_attractor_search`.
#
# The script only takes one argument: a path to the network file.

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

stg = SymbolicAsyncGraph(bn)

attr = find_attractors(stg)

print("Attractors:", len(attr))

attr_states = stg.empty_colored_vertices()

for a in attr:
attr_states = attr_states.union(a)

print("attractors, attractor-states")
print(f"{len(attr)},{attr_states.cardinality()}")
33 changes: 33 additions & 0 deletions benchmark/bench_sd_attractors_full.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.SuccessionDiagram import SuccessionDiagram
import sys
import nfvsmotifs

# Print progress and succession diagram size.
nfvsmotifs.SuccessionDiagram.DEBUG = True

NODE_LIMIT = 1_000_000
DEPTH_LIMIT = 10_000

# This is unfortunately necessary for PyEDA Boolean expression parser (for now).
sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
fully_expanded = sd.expand_bfs(bfs_level_limit=DEPTH_LIMIT, size_limit=NODE_LIMIT)
assert fully_expanded

attractor_count = 0
motif_avoidant_count = 0

for node in sd.node_ids():
attr = sd.node_attractor_seeds(node, compute=True)
attractor_count += len(attr)
if not sd.node_is_minimal(node):
motif_avoidant_count += len(attr)

print("nodes, attractors, motif-avoidant")
print(f"{len(sd)}, {attractor_count}, {motif_avoidant_count}")
35 changes: 35 additions & 0 deletions benchmark/bench_sd_attractors_minimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.SuccessionDiagram import SuccessionDiagram
import sys
import nfvsmotifs

# Print progress and succession diagram size.
nfvsmotifs.SuccessionDiagram.DEBUG = True

NODE_LIMIT = 1_000_000

# This is unfortunately necessary for PyEDA Boolean expression parser (for now).
sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
fully_expanded = sd.expand_minimal_spaces(size_limit=NODE_LIMIT)
assert fully_expanded

attractor_count = 0
motif_avoidant_count = 0

# Note that this can contain some motif-avoidant attractors
# multiple times. But we haven't seen motif-avoidant attractors
# in real-world networks so far...
for node in sd.node_ids():
attr = sd.node_attractor_seeds(node, compute=True)
attractor_count += len(attr)
if not sd.node_is_minimal(node):
motif_avoidant_count += len(attr)

print("nodes, expanded, attractors, motif-avoidant")
print(f"{len(sd)}, {len(list(sd.expanded_ids()))}, {attractor_count}, {motif_avoidant_count}")
32 changes: 32 additions & 0 deletions benchmark/bench_sd_attractors_simplified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.SuccessionDiagram import SuccessionDiagram
import sys
import nfvsmotifs

# Print progress and succession diagram size.
nfvsmotifs.SuccessionDiagram.DEBUG = True

NODE_LIMIT = 1_000_000

# This is unfortunately necessary for PyEDA Boolean expression parser (for now).
sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
fully_expanded = sd.expand_attractor_seeds(size_limit=NODE_LIMIT)
assert fully_expanded

attractor_count = 0
motif_avoidant_count = 0

for node in sd.expanded_ids():
attr = sd.node_attractor_seeds(node, compute=True)
attractor_count += len(attr)
if not sd.node_is_minimal(node):
motif_avoidant_count += len(attr)

print("nodes, expanded, attractors, motif-avoidant")
print(f"{len(sd)}, {len(list(sd.expanded_ids()))}, {attractor_count}, {motif_avoidant_count}")
27 changes: 27 additions & 0 deletions benchmark/bench_sd_expand_bfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.SuccessionDiagram import SuccessionDiagram
import sys
import nfvsmotifs

# Print progress and succession diagram size.
nfvsmotifs.SuccessionDiagram.DEBUG = True

NODE_LIMIT = 1_000_000
DEPTH_LIMIT = 10_000

# This is unfortunately necessary for PyEDA Boolean expression parser (for now).
sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
fully_expanded = sd.expand_bfs(bfs_level_limit=DEPTH_LIMIT, size_limit=NODE_LIMIT)
assert fully_expanded

print(f"Succession diagram size:", len(sd))
print(f"Minimal traps:", len(sd.minimal_trap_spaces()))

print("size, expanded, minimal")
print(f"{len(sd)},{len(list(sd.expanded_ids()))},{len(sd.minimal_trap_spaces())}")
27 changes: 27 additions & 0 deletions benchmark/bench_sd_expand_dfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.SuccessionDiagram import SuccessionDiagram
import sys
import nfvsmotifs

# Print progress and succession diagram size.
nfvsmotifs.SuccessionDiagram.DEBUG = True

NODE_LIMIT = 1_000_000
DEPTH_LIMIT = 10_000

# This is unfortunately necessary for PyEDA Boolean expression parser (for now).
sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
fully_expanded = sd.expand_dfs(dfs_stack_limit=DEPTH_LIMIT, size_limit=NODE_LIMIT)
assert fully_expanded

print(f"Succession diagram size:", len(sd))
print(f"Minimal traps:", len(sd.minimal_trap_spaces()))

print("size, expanded, minimal")
print(f"{len(sd)},{len(list(sd.expanded_ids()))},{len(sd.minimal_trap_spaces())}")
8 changes: 6 additions & 2 deletions bench_sd_construction.py → benchmark/bench_sd_expand_min.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

# Compute the succession diagram.
sd = SuccessionDiagram(bn)
expanded = sd.expand_node(sd.root(), depth_limit=DEPTH_LIMIT, node_limit=NODE_LIMIT)
sd.expand_minimal_spaces()

print(f"Succession diagram size:", expanded)
print(f"Succession diagram size:", len(sd))
print(f"Minimal traps:", len(sd.minimal_trap_spaces()))

print("size, expanded, minimal")
print(f"{len(sd)}, {len(list(sd.expanded_ids()))}, {len(sd.minimal_trap_spaces())}")
28 changes: 28 additions & 0 deletions benchmark/bench_trappist_min.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from biodivine_aeon import BooleanNetwork
from nfvsmotifs.trappist_core import trappist_async
import sys

# A simple script to benchmark minimal trap space detection using trappist.
# The output should be comparable to the output of `bench_sd_construction.py`
# in terms of minimal trap spaces.
#
# There is only one command line argument: a path to the network file.

sys.setrecursionlimit(150000)

bn = BooleanNetwork.from_file(sys.argv[1])
bn = bn.infer_regulatory_graph()

COUNT = 0

def count_all(x):
global COUNT
COUNT += 1
return True

trappist_async(bn, on_solution=count_all, problem="min")

print("Minimal traps:", COUNT)

print("traps")
print(f"{COUNT}")
Loading

1 comment on commit a810dc0

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
nfvsmotifs
   SuccessionDiagram.py154497%6–7, 191, 411
   interaction_graph_utils.py142894%6–9, 57, 70, 95–96
   motif_avoidant.py164696%24–25, 125, 142, 179, 303
   petri_net_translation.py84693%23–24, 52, 63–64, 94
   pyeda_utils.py953464%12, 56–66, 90, 95, 98–112, 140–144
   space_utils.py118596%15–16, 198, 213, 270
   state_utils.py681282%15, 55–66, 98, 105, 114
   terminal_restriction_space.py44393%6–7, 80
   trappist_core.py1862288%10–11, 39, 41, 81, 122, 182, 184, 186, 221–223, 249, 260–261, 306, 308, 338, 378, 380, 411, 440
nfvsmotifs/FVSpython3
   FVS.py481079%90–91, 97, 133, 183–189
   FVS_localsearch_10_python.py90199%179
nfvsmotifs/_sd_algorithms
   compute_attractor_seeds.py28196%6
   expand_attractor_seeds.py51492%6, 86–89
   expand_bfs.py28196%6
   expand_dfs.py30197%6
   expand_minimal_spaces.py37197%6
   expand_to_target.py30390%6, 34, 39
TOTAL149412292% 

Tests Skipped Failures Errors Time
353 0 💤 0 ❌ 0 🔥 3m 3s ⏱️

Please sign in to comment.