mpbn 3.8__tar.gz → 4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mpbn might be problematic. Click here for more details.
- {mpbn-3.8 → mpbn-4.0}/PKG-INFO +14 -4
- {mpbn-3.8 → mpbn-4.0}/README.md +1 -1
- {mpbn-3.8 → mpbn-4.0}/mpbn/__init__.py +24 -67
- mpbn-4.0/mpbn/boolfunclib/__init__.py +0 -0
- mpbn-4.0/mpbn/boolfunclib/aeon_impl.py +202 -0
- mpbn-3.8/mpbn/converters.py → mpbn-4.0/mpbn/boolfunclib/pyeda_impl.py +65 -9
- {mpbn-3.8 → mpbn-4.0}/mpbn/cli/__init__.py +4 -0
- mpbn-4.0/mpbn/converters.py +14 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/PKG-INFO +14 -4
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/SOURCES.txt +3 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/requires.txt +1 -1
- {mpbn-3.8 → mpbn-4.0}/setup.py +2 -2
- {mpbn-3.8 → mpbn-4.0}/MANIFEST.in +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/asplib/eval_circuit.asp +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/asplib/eval_mixed.asp +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/asplib/mp_attractor.asp +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/asplib/mp_eval.asp +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/asplib/mp_positivereach-np.asp +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/cli/sim.py +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn/simulation.py +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/dependency_links.txt +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/entry_points.txt +0 -0
- {mpbn-3.8 → mpbn-4.0}/mpbn.egg-info/top_level.txt +0 -0
- {mpbn-3.8 → mpbn-4.0}/setup.cfg +0 -0
{mpbn-3.8 → mpbn-4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: mpbn
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0
|
|
4
4
|
Summary: Simple implementation of Most Permissive Boolean networks
|
|
5
5
|
Home-page: https://github.com/bnediction/mpbn
|
|
6
6
|
Author: Loïc Paulevé
|
|
@@ -15,9 +15,19 @@ Requires-Dist: boolean.py
|
|
|
15
15
|
Requires-Dist: clingo
|
|
16
16
|
Requires-Dist: colomoto_jupyter>=0.8.0
|
|
17
17
|
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: biodivine_aeon>=1.0.1
|
|
19
19
|
Requires-Dist: scipy
|
|
20
20
|
Requires-Dist: tqdm
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: keywords
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
|
21
31
|
|
|
22
32
|
|
|
23
33
|
The `mpbn` Python module offers a simple implementation of reachability and attractor analysis (minimal trap spaces) in *Most Permissive Boolean Networks* ([doi:10.1038/s41467-020-18112-5](https://doi.org/10.1038/s41467-020-18112-5)). The `mpbn` Python module also offers a *Most Permissive* simulator, which provides trajectory sampling and computes attractor propensities (see paper [Variable-Depth Simulation of Most Permissive Boolean Networks](https://link.springer.com/chapter/10.1007/978-3-031-15034-0_7) for more details).
|
|
@@ -38,7 +48,7 @@ pip install mpbn
|
|
|
38
48
|
|
|
39
49
|
### Using conda
|
|
40
50
|
```
|
|
41
|
-
conda install -c colomoto -c potassco mpbn
|
|
51
|
+
conda install -c colomoto -c potassco -c daemontus mpbn
|
|
42
52
|
```
|
|
43
53
|
|
|
44
54
|
## Usage
|
{mpbn-3.8 → mpbn-4.0}/README.md
RENAMED
|
@@ -31,10 +31,6 @@ from colomoto import minibn
|
|
|
31
31
|
|
|
32
32
|
from boolean import boolean
|
|
33
33
|
import clingo
|
|
34
|
-
|
|
35
|
-
from pyeda.boolalg import bdd
|
|
36
|
-
import pyeda.boolalg.expr
|
|
37
|
-
from pyeda.boolalg.expr import expr
|
|
38
34
|
sys.setrecursionlimit(max(100000, sys.getrecursionlimit()))
|
|
39
35
|
|
|
40
36
|
__asplibdir__ = os.path.realpath(os.path.join(os.path.dirname(__file__), "asplib"))
|
|
@@ -78,7 +74,7 @@ def s2v(s):
|
|
|
78
74
|
def v2s(v):
|
|
79
75
|
return 1 if v > 0 else 0
|
|
80
76
|
|
|
81
|
-
def
|
|
77
|
+
def is_dnf_unate(ba, f):
|
|
82
78
|
pos_lits = set()
|
|
83
79
|
neg_lits = set()
|
|
84
80
|
def is_lit(f):
|
|
@@ -116,34 +112,6 @@ def is_unate(ba, f):
|
|
|
116
112
|
return test_monotonicity()
|
|
117
113
|
return False
|
|
118
114
|
|
|
119
|
-
def asp_of_bdd(bid, b):
|
|
120
|
-
_rules = dict()
|
|
121
|
-
def register(node, nid=None):
|
|
122
|
-
if node is bdd.BDDNODEONE:
|
|
123
|
-
if nid is not None:
|
|
124
|
-
_rules[bid] = f"bdd({clingo.String(nid)},1)"
|
|
125
|
-
return 1
|
|
126
|
-
elif node is bdd.BDDNODEZERO:
|
|
127
|
-
if nid is not None:
|
|
128
|
-
_rules[bid] = f"bdd({clingo.String(nid)},-1)"
|
|
129
|
-
return -1
|
|
130
|
-
nid = clingo.String(f"{bid}_n{id(node)}" if nid is None else nid)
|
|
131
|
-
if nid not in _rules:
|
|
132
|
-
var = clingo.String(bdd._VARS[node.root].qualname)
|
|
133
|
-
lo = register(node.lo)
|
|
134
|
-
hi = register(node.hi)
|
|
135
|
-
a = f"bdd({nid},{var},{lo},{hi})"
|
|
136
|
-
_rules[nid] = a
|
|
137
|
-
return nid
|
|
138
|
-
register(b.node, bid)
|
|
139
|
-
return _rules.values()
|
|
140
|
-
|
|
141
|
-
def bddasp_of_boolfunc(f, i):
|
|
142
|
-
e = expr(str(f).replace("!","~"))
|
|
143
|
-
b = bdd.expr2bdd(e)
|
|
144
|
-
atoms = asp_of_bdd(i, b)
|
|
145
|
-
return "\n".join((f"{a}." for a in atoms))
|
|
146
|
-
|
|
147
115
|
def circuitasp_of_boolfunc(f, i, ba):
|
|
148
116
|
atoms = []
|
|
149
117
|
fid = clingo.String(i)
|
|
@@ -176,28 +144,9 @@ def circuitasp_of_boolfunc(f, i, ba):
|
|
|
176
144
|
atoms.append(f"circuit({fid},root,{root}).\n")
|
|
177
145
|
return "\n".join(atoms)
|
|
178
146
|
|
|
179
|
-
|
|
180
|
-
def expr2bpy(ex, ba):
|
|
181
|
-
"""
|
|
182
|
-
converts a pyeda Boolean expression into a boolean.py one
|
|
183
|
-
"""
|
|
184
|
-
if isinstance(ex, pyeda.boolalg.expr.Variable):
|
|
185
|
-
return ba.Symbol(str(ex))
|
|
186
|
-
elif isinstance(ex, pyeda.boolalg.expr._One):
|
|
187
|
-
return ba.TRUE
|
|
188
|
-
elif isinstance(ex, pyeda.boolalg.expr._Zero):
|
|
189
|
-
return ba.FALSE
|
|
190
|
-
elif isinstance(ex, pyeda.boolalg.expr.Complement):
|
|
191
|
-
return ba.NOT(ba.Symbol(str(ex.__invert__())))
|
|
192
|
-
elif isinstance(ex, pyeda.boolalg.expr.NotOp):
|
|
193
|
-
return ba.NOT(expr2bpy(ex.x, ba))
|
|
194
|
-
elif isinstance(ex, pyeda.boolalg.expr.OrOp):
|
|
195
|
-
return ba.OR(*(expr2bpy(x, ba) for x in ex.xs))
|
|
196
|
-
elif isinstance(ex, pyeda.boolalg.expr.AndOp):
|
|
197
|
-
return ba.AND(*(expr2bpy(x, ba) for x in ex.xs))
|
|
198
|
-
raise NotImplementedError(str(ex), type(ex))
|
|
199
|
-
|
|
200
147
|
DEFAULT_ENCODING = "mixed-dnf-bdd"
|
|
148
|
+
DEFAULT_BOOLFUNCLIB = os.environ.get("MPBN_BOOLFUNCLIB", "aeon")
|
|
149
|
+
SUPPORTED_BOOLFUNCLIBS = ["aeon", "pyeda"]
|
|
201
150
|
|
|
202
151
|
class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
203
152
|
"""
|
|
@@ -218,7 +167,8 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
218
167
|
def __init__(self, bn=minibn.BooleanNetwork(), auto_dnf=True,
|
|
219
168
|
simplify=False,
|
|
220
169
|
try_unate_hard=False,
|
|
221
|
-
encoding=DEFAULT_ENCODING
|
|
170
|
+
encoding=DEFAULT_ENCODING,
|
|
171
|
+
boolfunclib=DEFAULT_BOOLFUNCLIB):
|
|
222
172
|
"""
|
|
223
173
|
Constructor for :py:class:`.MPBoooleanNetwork`.
|
|
224
174
|
|
|
@@ -227,6 +177,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
227
177
|
:py:class:`colomoto.minibn.BooleanNetwork` constructor
|
|
228
178
|
:param bool auto_dnf: if ``False``, turns off automatic DNF
|
|
229
179
|
transformation of local functions
|
|
180
|
+
:param str boolfunlib: library to use for Boolean function manipulation
|
|
181
|
+
among ``"aeon"`` (default) or ``"pyeda"``. Default can be overriden with
|
|
182
|
+
``MPBN_BOOLFUNCLIB`` environment variable.
|
|
230
183
|
|
|
231
184
|
Examples:
|
|
232
185
|
|
|
@@ -236,11 +189,21 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
236
189
|
>>> mbn = MPBooleanNetwork(bn)
|
|
237
190
|
"""
|
|
238
191
|
assert encoding in self.supported_encodings
|
|
192
|
+
assert boolfunclib in SUPPORTED_BOOLFUNCLIBS
|
|
239
193
|
self.auto_dnf = auto_dnf and encoding in self.dnf_encodings
|
|
240
194
|
self.encoding = encoding
|
|
241
195
|
self.try_unate_hard = try_unate_hard
|
|
242
196
|
self._simplify = simplify
|
|
243
197
|
self._is_unate = dict()
|
|
198
|
+
|
|
199
|
+
self._boolfunclib = boolfunclib
|
|
200
|
+
__boolfunclib_symbols = (
|
|
201
|
+
"make_dnf_boolfunc",
|
|
202
|
+
"bddasp_of_boolfunc",
|
|
203
|
+
)
|
|
204
|
+
self._bf_impl = __import__(f"mpbn.boolfunclib.{boolfunclib}_impl",
|
|
205
|
+
fromlist=__boolfunclib_symbols)
|
|
206
|
+
|
|
244
207
|
super(MPBooleanNetwork, self).__init__(bn)
|
|
245
208
|
|
|
246
209
|
def __setitem__(self, a, f):
|
|
@@ -253,18 +216,12 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
253
216
|
f = self.ba.parse(f)
|
|
254
217
|
f = self._autobool(f)
|
|
255
218
|
if self.auto_dnf:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
e = e.simplify()
|
|
260
|
-
f = expr2bpy(e, self.ba)
|
|
261
|
-
if self.try_unate_hard:
|
|
262
|
-
f = minibn.simplify_dnf(self.ba, f)
|
|
263
|
-
elif self._simplify:
|
|
264
|
-
f = f.simplify()
|
|
219
|
+
f = self._bf_impl.make_dnf_boolfunc(self.ba, f,
|
|
220
|
+
simplify=self._simplify,
|
|
221
|
+
try_unate_hard=self.try_unate_hard)
|
|
265
222
|
a = self._autokey(a)
|
|
266
223
|
if self.encoding in self.dnf_encodings:
|
|
267
|
-
self._is_unate[a] =
|
|
224
|
+
self._is_unate[a] = is_dnf_unate(self.ba, f)
|
|
268
225
|
if self.encoding == "unate-dnf":
|
|
269
226
|
assert self._is_unate[a], f"'{f}' seems not unate. Try simplify()?"
|
|
270
227
|
return super().__setitem__(a, f)
|
|
@@ -311,13 +268,13 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
311
268
|
elif f_encoding == "dnf":
|
|
312
269
|
facts.extend(encode_dnf(f))
|
|
313
270
|
elif f_encoding == "bdd":
|
|
314
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
271
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
315
272
|
elif f_encoding == "mixed-dnf-bdd":
|
|
316
273
|
facts.extend(encode_dnf(f))
|
|
317
274
|
if self._is_unate[n]:
|
|
318
275
|
facts.append(f"unate(\"{n}\").")
|
|
319
276
|
else:
|
|
320
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
277
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
321
278
|
elif f_encoding == "circuit":
|
|
322
279
|
facts.append(circuitasp_of_boolfunc(f, n, self.ba))
|
|
323
280
|
return "".join(facts)
|
|
File without changes
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import clingo
|
|
3
|
+
|
|
4
|
+
from boolean import boolean
|
|
5
|
+
from colomoto import minibn
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from biodivine_aeon import Bdd, BddPointer
|
|
10
|
+
from biodivine_aeon import BddVariableSet, BddValuation
|
|
11
|
+
|
|
12
|
+
def is_unate_symbolic(f: Bdd) -> boolean:
|
|
13
|
+
"""
|
|
14
|
+
Returns `True` if the given `biodivine_aeon.Bdd` represents a unate function
|
|
15
|
+
(i.e. all arguments are locally monotonic).
|
|
16
|
+
|
|
17
|
+
The way this is handled is that we test for positive/negative monotonicity by
|
|
18
|
+
symbolically expressing the inputs where decreasing the input increases the
|
|
19
|
+
output (i.e. a counterexample to positive monotonicity), or vice versa.
|
|
20
|
+
"""
|
|
21
|
+
variables = f.__ctx__().variable_ids()
|
|
22
|
+
f_false = f.l_not()
|
|
23
|
+
f_true = f
|
|
24
|
+
for var in f.support_set():
|
|
25
|
+
var_is_true = f.__ctx__().mk_literal(var, True)
|
|
26
|
+
var_is_false = f.__ctx__().mk_literal(var, False)
|
|
27
|
+
|
|
28
|
+
f_1_to_0 = f_false.l_and(var_is_true).r_exists(var)
|
|
29
|
+
f_0_to_1 = f_true.l_and(var_is_false).r_exists(var)
|
|
30
|
+
is_positive = f_0_to_1.l_and(f_1_to_0).r_exists(variables).l_not().is_true()
|
|
31
|
+
|
|
32
|
+
f_0_to_0 = f_false.l_and(var_is_false).r_exists(var)
|
|
33
|
+
f_1_to_1 = f_true.l_and(var_is_true).r_exists(var)
|
|
34
|
+
is_negative = f_0_to_0.l_and(f_1_to_1).r_exists(variables).l_not().is_true()
|
|
35
|
+
|
|
36
|
+
# An input cannot be both positive and negative at the same time.
|
|
37
|
+
assert not (is_positive and is_negative)
|
|
38
|
+
|
|
39
|
+
if (not is_positive) and (not is_negative):
|
|
40
|
+
return False
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def ba_to_bdd(ba: boolean.BooleanAlgebra, f: boolean.Expression, ctx: BddVariableSet | None = None) -> Bdd:
|
|
45
|
+
"""
|
|
46
|
+
Takes a `boolean.Expression` (with the associated `boolean.BooleanAlgebra`) and
|
|
47
|
+
converts it to a `biodivine_aeon.Bdd`.
|
|
48
|
+
|
|
49
|
+
Note that the `Bdd` has an associated `biodivine_aeon.BddVariableSet` context, which maps the
|
|
50
|
+
variable IDs to names. You can provide your own context, or one will be created for you
|
|
51
|
+
(to access the underlying context object, use `bdd.__ctx__()`).
|
|
52
|
+
"""
|
|
53
|
+
ba_vars = f.symbols
|
|
54
|
+
variables = sorted([ str(var) for var in ba_vars ])
|
|
55
|
+
if ctx is None:
|
|
56
|
+
ctx = BddVariableSet(variables)
|
|
57
|
+
else:
|
|
58
|
+
# Check that all variables that exist in `f` also exist in `ctx`.
|
|
59
|
+
assert all((ctx.find_variable(var) is not None) for var in variables)
|
|
60
|
+
def ba_to_bdd_rec(f: boolean.Expression) -> Bdd:
|
|
61
|
+
if type(f) is ba.TRUE or isinstance(f, minibn._TRUE):
|
|
62
|
+
return ctx.mk_const(True)
|
|
63
|
+
if type(f) is ba.FALSE or isinstance(f, minibn._FALSE):
|
|
64
|
+
return ctx.mk_const(False)
|
|
65
|
+
if type(f) is ba.Symbol:
|
|
66
|
+
return ctx.mk_literal(str(f.obj), True)
|
|
67
|
+
if type(f) is ba.NOT:
|
|
68
|
+
assert len(f.args) == 1, "Cannot transform NOT with more than one argument."
|
|
69
|
+
return ba_to_bdd_rec(f.args[0]).l_not()
|
|
70
|
+
if type(f) is ba.AND:
|
|
71
|
+
result = ctx.mk_const(True)
|
|
72
|
+
for arg in f.args:
|
|
73
|
+
result = result.l_and(ba_to_bdd_rec(arg))
|
|
74
|
+
return result
|
|
75
|
+
if type(f) is ba.OR:
|
|
76
|
+
result = ctx.mk_const(False)
|
|
77
|
+
for arg in f.args:
|
|
78
|
+
result = result.l_or(ba_to_bdd_rec(arg))
|
|
79
|
+
return result
|
|
80
|
+
raise NotImplementedError(str(f), type(f))
|
|
81
|
+
|
|
82
|
+
return ba_to_bdd_rec(f)
|
|
83
|
+
|
|
84
|
+
def bdd_to_dnf(ba: boolean.BooleanAlgebra, f: Bdd) -> boolean.Expression:
|
|
85
|
+
"""
|
|
86
|
+
Convert a `biodivine_aeon.Bdd` to a `boolean.Expression` in disjunctive normal form.
|
|
87
|
+
"""
|
|
88
|
+
if f.is_true():
|
|
89
|
+
return ba.TRUE
|
|
90
|
+
if f.is_false():
|
|
91
|
+
return ba.FALSE
|
|
92
|
+
ctx = f.__ctx__()
|
|
93
|
+
# Technically, `optimize=True` should be set by default, but just in case.
|
|
94
|
+
dnf = f.to_dnf(optimize=True)
|
|
95
|
+
# Maps BDD variables to BooleanAlgebra Symbols.
|
|
96
|
+
var_to_symbol = { var: ba.Symbol(ctx.get_variable_name(var)) for var in ctx.variable_ids() }
|
|
97
|
+
ba_clauses = []
|
|
98
|
+
for clause in dnf:
|
|
99
|
+
literals = []
|
|
100
|
+
for (var, value) in clause.items():
|
|
101
|
+
if value:
|
|
102
|
+
literals.append(var_to_symbol[var])
|
|
103
|
+
else:
|
|
104
|
+
literals.append(ba.NOT(var_to_symbol[var]))
|
|
105
|
+
assert len(literals) > 0
|
|
106
|
+
if len(literals) == 1:
|
|
107
|
+
ba_clauses.append(literals[0])
|
|
108
|
+
else:
|
|
109
|
+
ba_clauses.append(ba.AND(*literals))
|
|
110
|
+
assert len(ba_clauses) > 0
|
|
111
|
+
if len(ba_clauses) == 1:
|
|
112
|
+
return ba_clauses[0]
|
|
113
|
+
else:
|
|
114
|
+
return ba.OR(*ba_clauses)
|
|
115
|
+
|
|
116
|
+
def make_dnf_boolfunc(ba, f, **unused_opts):
|
|
117
|
+
bdd = ba_to_bdd(ba, f)
|
|
118
|
+
return bdd_to_dnf(ba, bdd)
|
|
119
|
+
|
|
120
|
+
def asp_of_bdd(var_name, bdd: Bdd) -> list[str]:
|
|
121
|
+
"""
|
|
122
|
+
Convert a `biodivine_aeon.Bdd` into a list of `clingo` atoms
|
|
123
|
+
representing the individual BDD nodes.
|
|
124
|
+
"""
|
|
125
|
+
if bdd.is_false():
|
|
126
|
+
return [f"bdd({clingo.String(var_name)},-1)"]
|
|
127
|
+
if bdd.is_true():
|
|
128
|
+
return [f"bdd({clingo.String(var_name)},1)"]
|
|
129
|
+
|
|
130
|
+
_rules = {}
|
|
131
|
+
def _rec(node: BddPointer, node_name: Optional[str] = None) -> str:
|
|
132
|
+
if node.is_zero():
|
|
133
|
+
return "-1"
|
|
134
|
+
if node.is_one():
|
|
135
|
+
return "1"
|
|
136
|
+
if node_name is None:
|
|
137
|
+
node_name = f"{var_name}_n{int(node)}"
|
|
138
|
+
node_name_clingo = clingo.String(node_name)
|
|
139
|
+
if node_name_clingo in _rules:
|
|
140
|
+
# The node was already declared.
|
|
141
|
+
return node_name_clingo
|
|
142
|
+
node_var = bdd.node_variable(node)
|
|
143
|
+
assert node_var is not None # Only `None` if node is terminal.
|
|
144
|
+
(lo, hi) = bdd.node_links(node)
|
|
145
|
+
var = clingo.String(bdd.__ctx__().get_variable_name(node_var))
|
|
146
|
+
lo = _rec(lo)
|
|
147
|
+
hi = _rec(hi)
|
|
148
|
+
atom = f"bdd({node_name_clingo},{var},{lo},{hi})"
|
|
149
|
+
_rules[node_name_clingo] = atom
|
|
150
|
+
return node_name_clingo
|
|
151
|
+
_rec(bdd.root(), var_name)
|
|
152
|
+
|
|
153
|
+
return list(_rules.values())
|
|
154
|
+
|
|
155
|
+
def bddasp_of_boolfunc(ba, f, var_name):
|
|
156
|
+
f_bdd = ba_to_bdd(ba, f)
|
|
157
|
+
atoms = asp_of_bdd(var_name, f_bdd)
|
|
158
|
+
return "\n".join((f"{a}." for a in atoms))
|
|
159
|
+
|
|
160
|
+
def bn_of_asynchronous_transition_graph(adyn, names,
|
|
161
|
+
parse_node=(lambda n: tuple(map(int, n))),
|
|
162
|
+
bn_class=minibn.BooleanNetwork,
|
|
163
|
+
simplify=True):
|
|
164
|
+
"""
|
|
165
|
+
Convert the transition graph of a (fully) asynchronous Boolean network to
|
|
166
|
+
a propositional logic representation.
|
|
167
|
+
|
|
168
|
+
The object `adyn` must be an instance of `networkx.DiGraph`.
|
|
169
|
+
The `parse_node` function must return a tuple of 0 and 1 from an `adyn`
|
|
170
|
+
node. By default, it is assumed that nodes are strings of binary values.
|
|
171
|
+
Returned object will be of `bn_class`, instantiated with a dictionnary
|
|
172
|
+
mapping component names to a string representation of their Boolean expression.
|
|
173
|
+
"""
|
|
174
|
+
relabel = {label: parse_node(label) for label in adyn.nodes()}
|
|
175
|
+
adyn = nx.relabel_nodes(adyn, relabel)
|
|
176
|
+
n = len(next(iter(adyn.nodes)))
|
|
177
|
+
assert n == len(names), "list of component names and dimension of configuraitons seem different"
|
|
178
|
+
assert adyn.number_of_nodes() == 2**n, "unexpected number of nodes in the transition graph"
|
|
179
|
+
|
|
180
|
+
bdd_ctx = BddVariableSet(names)
|
|
181
|
+
|
|
182
|
+
f = []
|
|
183
|
+
for i in range(n):
|
|
184
|
+
pos = []
|
|
185
|
+
for x in adyn.nodes():
|
|
186
|
+
dx = list(x)
|
|
187
|
+
dx[i] = 1-x[i]
|
|
188
|
+
y = dx if tuple(dx) in adyn[x] else x
|
|
189
|
+
target = y[i]
|
|
190
|
+
if target:
|
|
191
|
+
pos.append(BddValuation(bdd_ctx, list(x)))
|
|
192
|
+
if len(pos) == 0:
|
|
193
|
+
f.append(bdd_ctx.mk_false())
|
|
194
|
+
else:
|
|
195
|
+
f.append(bdd_ctx.mk_dnf(pos))
|
|
196
|
+
|
|
197
|
+
bn = bn_class()
|
|
198
|
+
for (i, name) in enumerate(names):
|
|
199
|
+
bn[name] = bdd_to_dnf(bn.ba, f[i])
|
|
200
|
+
if simplify:
|
|
201
|
+
bn = bn.simplify()
|
|
202
|
+
return bn
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import clingo
|
|
1
2
|
import networkx as nx
|
|
3
|
+
|
|
2
4
|
from pyeda.boolalg.minimization import *
|
|
3
5
|
import pyeda.boolalg.expr
|
|
4
6
|
from pyeda.inter import expr
|
|
7
|
+
from pyeda.boolalg import bdd
|
|
5
8
|
|
|
6
9
|
from colomoto import minibn
|
|
7
10
|
|
|
@@ -29,6 +32,68 @@ def expr2str(ex):
|
|
|
29
32
|
return " & ".join(map(_protect, ex.xs))
|
|
30
33
|
raise NotImplementedError(str(ex), type(ex))
|
|
31
34
|
|
|
35
|
+
def expr2bpy(ex, ba):
|
|
36
|
+
"""
|
|
37
|
+
converts a pyeda Boolean expression into a boolean.py one
|
|
38
|
+
"""
|
|
39
|
+
if isinstance(ex, pyeda.boolalg.expr.Variable):
|
|
40
|
+
return ba.Symbol(str(ex))
|
|
41
|
+
elif isinstance(ex, pyeda.boolalg.expr._One):
|
|
42
|
+
return ba.TRUE
|
|
43
|
+
elif isinstance(ex, pyeda.boolalg.expr._Zero):
|
|
44
|
+
return ba.FALSE
|
|
45
|
+
elif isinstance(ex, pyeda.boolalg.expr.Complement):
|
|
46
|
+
return ba.NOT(ba.Symbol(str(ex.__invert__())))
|
|
47
|
+
elif isinstance(ex, pyeda.boolalg.expr.NotOp):
|
|
48
|
+
return ba.NOT(expr2bpy(ex.x, ba))
|
|
49
|
+
elif isinstance(ex, pyeda.boolalg.expr.OrOp):
|
|
50
|
+
return ba.OR(*(expr2bpy(x, ba) for x in ex.xs))
|
|
51
|
+
elif isinstance(ex, pyeda.boolalg.expr.AndOp):
|
|
52
|
+
return ba.AND(*(expr2bpy(x, ba) for x in ex.xs))
|
|
53
|
+
raise NotImplementedError(str(ex), type(ex))
|
|
54
|
+
|
|
55
|
+
def asp_of_bdd(bid, b):
|
|
56
|
+
_rules = dict()
|
|
57
|
+
def register(node, nid=None):
|
|
58
|
+
if node is bdd.BDDNODEONE:
|
|
59
|
+
if nid is not None:
|
|
60
|
+
_rules[bid] = f"bdd({clingo.String(nid)},1)"
|
|
61
|
+
return 1
|
|
62
|
+
elif node is bdd.BDDNODEZERO:
|
|
63
|
+
if nid is not None:
|
|
64
|
+
_rules[bid] = f"bdd({clingo.String(nid)},-1)"
|
|
65
|
+
return -1
|
|
66
|
+
nid = clingo.String(f"{bid}_n{id(node)}" if nid is None else nid)
|
|
67
|
+
if nid not in _rules:
|
|
68
|
+
var = clingo.String(bdd._VARS[node.root].qualname)
|
|
69
|
+
lo = register(node.lo)
|
|
70
|
+
hi = register(node.hi)
|
|
71
|
+
a = f"bdd({nid},{var},{lo},{hi})"
|
|
72
|
+
_rules[nid] = a
|
|
73
|
+
return nid
|
|
74
|
+
register(b.node, bid)
|
|
75
|
+
return _rules.values()
|
|
76
|
+
|
|
77
|
+
def bddasp_of_boolfunc(ba, f, i):
|
|
78
|
+
e = expr(str(f).replace("!","~"))
|
|
79
|
+
b = bdd.expr2bdd(e)
|
|
80
|
+
atoms = asp_of_bdd(i, b)
|
|
81
|
+
return "\n".join((f"{a}." for a in atoms))
|
|
82
|
+
|
|
83
|
+
def make_dnf_boolfunc(ba, f, try_unate_hard=False, simplify=True):
|
|
84
|
+
"""
|
|
85
|
+
try_unate_hard: use costly CNF/DNF transformations
|
|
86
|
+
simplify: use boolean.py simplification method
|
|
87
|
+
"""
|
|
88
|
+
e = expr(str(f).replace("!","~"))
|
|
89
|
+
e = e.to_dnf()
|
|
90
|
+
e = e.simplify()
|
|
91
|
+
e = expr2bpy(e, ba)
|
|
92
|
+
if try_unate_hard:
|
|
93
|
+
e = minibn.simplify_dnf(self.ba, e)
|
|
94
|
+
elif simplify:
|
|
95
|
+
e = e.simplify()
|
|
96
|
+
return e
|
|
32
97
|
|
|
33
98
|
def bn_of_asynchronous_transition_graph(adyn, names,
|
|
34
99
|
parse_node=(lambda n: tuple(map(int, n))),
|
|
@@ -76,12 +141,3 @@ def bn_of_asynchronous_transition_graph(adyn, names,
|
|
|
76
141
|
f = f.simplify()
|
|
77
142
|
return f
|
|
78
143
|
|
|
79
|
-
if __name__ == "__main__":
|
|
80
|
-
import mpbn
|
|
81
|
-
|
|
82
|
-
f = mpbn.MPBooleanNetwork({
|
|
83
|
-
"x1": "x2",
|
|
84
|
-
"x2": "x3",
|
|
85
|
-
"x3": "x1"})
|
|
86
|
-
g = f.dynamics("asynchronous")
|
|
87
|
-
print(bn_of_asynchronous_transition_graph(g, list(f)))
|
|
@@ -17,6 +17,9 @@ def main():
|
|
|
17
17
|
ap.add_argument("--encoding", default=mpbn.DEFAULT_ENCODING,
|
|
18
18
|
choices=mpbn.MPBooleanNetwork.supported_encodings,
|
|
19
19
|
help=f"Encoding method (default: {mpbn.DEFAULT_ENCODING})")
|
|
20
|
+
ap.add_argument("--boolfunclib", default="aeon",
|
|
21
|
+
choices=mpbn.SUPPORTED_BOOLFUNCLIBS,
|
|
22
|
+
help=f"Backend lib for Boolean functions (default: {mpbn.DEFAULT_BOOLFUNCLIB})")
|
|
20
23
|
ap.add_argument("--input-is-dnf", action="store_true", default=False,
|
|
21
24
|
help="Functions are already in DNF form")
|
|
22
25
|
ap.add_argument("--simplify", action="store_true", default=False,
|
|
@@ -27,6 +30,7 @@ def main():
|
|
|
27
30
|
help="Returns only the number of solutions")
|
|
28
31
|
args = ap.parse_args()
|
|
29
32
|
mbn = mpbn.MPBooleanNetwork(args.bnet_file, encoding=args.encoding,
|
|
33
|
+
boolfunclib=args.boolfunclib,
|
|
30
34
|
auto_dnf=not args.input_is_dnf,
|
|
31
35
|
simplify=args.simplify,
|
|
32
36
|
try_unate_hard=args.try_unate_hard)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from mpbn.boolfunclib.aeon_impl import bn_of_asynchronous_transition_graph
|
|
3
|
+
except ImportError:
|
|
4
|
+
from mpbn.boolfunclib.pyeda_impl import bn_of_asynchronous_transition_graph
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__":
|
|
7
|
+
import mpbn
|
|
8
|
+
|
|
9
|
+
f = mpbn.MPBooleanNetwork({
|
|
10
|
+
"x1": "x2",
|
|
11
|
+
"x2": "x3",
|
|
12
|
+
"x3": "x1"})
|
|
13
|
+
g = f.dynamics("asynchronous")
|
|
14
|
+
print(bn_of_asynchronous_transition_graph(g, list(f)))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: mpbn
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0
|
|
4
4
|
Summary: Simple implementation of Most Permissive Boolean networks
|
|
5
5
|
Home-page: https://github.com/bnediction/mpbn
|
|
6
6
|
Author: Loïc Paulevé
|
|
@@ -15,9 +15,19 @@ Requires-Dist: boolean.py
|
|
|
15
15
|
Requires-Dist: clingo
|
|
16
16
|
Requires-Dist: colomoto_jupyter>=0.8.0
|
|
17
17
|
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: biodivine_aeon>=1.0.1
|
|
19
19
|
Requires-Dist: scipy
|
|
20
20
|
Requires-Dist: tqdm
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: keywords
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
|
21
31
|
|
|
22
32
|
|
|
23
33
|
The `mpbn` Python module offers a simple implementation of reachability and attractor analysis (minimal trap spaces) in *Most Permissive Boolean Networks* ([doi:10.1038/s41467-020-18112-5](https://doi.org/10.1038/s41467-020-18112-5)). The `mpbn` Python module also offers a *Most Permissive* simulator, which provides trajectory sampling and computes attractor propensities (see paper [Variable-Depth Simulation of Most Permissive Boolean Networks](https://link.springer.com/chapter/10.1007/978-3-031-15034-0_7) for more details).
|
|
@@ -38,7 +48,7 @@ pip install mpbn
|
|
|
38
48
|
|
|
39
49
|
### Using conda
|
|
40
50
|
```
|
|
41
|
-
conda install -c colomoto -c potassco mpbn
|
|
51
|
+
conda install -c colomoto -c potassco -c daemontus mpbn
|
|
42
52
|
```
|
|
43
53
|
|
|
44
54
|
## Usage
|
|
@@ -15,5 +15,8 @@ mpbn/asplib/eval_mixed.asp
|
|
|
15
15
|
mpbn/asplib/mp_attractor.asp
|
|
16
16
|
mpbn/asplib/mp_eval.asp
|
|
17
17
|
mpbn/asplib/mp_positivereach-np.asp
|
|
18
|
+
mpbn/boolfunclib/__init__.py
|
|
19
|
+
mpbn/boolfunclib/aeon_impl.py
|
|
20
|
+
mpbn/boolfunclib/pyeda_impl.py
|
|
18
21
|
mpbn/cli/__init__.py
|
|
19
22
|
mpbn/cli/sim.py
|
{mpbn-3.8 → mpbn-4.0}/setup.py
RENAMED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from setuptools import setup, find_packages
|
|
5
5
|
|
|
6
6
|
NAME = "mpbn"
|
|
7
|
-
VERSION = "
|
|
7
|
+
VERSION = "4.0"
|
|
8
8
|
|
|
9
9
|
setup(name=NAME,
|
|
10
10
|
version=VERSION,
|
|
@@ -27,7 +27,7 @@ setup(name=NAME,
|
|
|
27
27
|
"clingo",
|
|
28
28
|
"colomoto_jupyter>=0.8.0",
|
|
29
29
|
"numpy",
|
|
30
|
-
"
|
|
30
|
+
"biodivine_aeon>=1.0.1",
|
|
31
31
|
"scipy",
|
|
32
32
|
"tqdm"
|
|
33
33
|
],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mpbn-3.8 → mpbn-4.0}/setup.cfg
RENAMED
|
File without changes
|