mpbn 3.8__tar.gz → 4.3__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.
- {mpbn-3.8 → mpbn-4.3}/PKG-INFO +14 -4
- {mpbn-3.8 → mpbn-4.3}/README.md +1 -1
- {mpbn-3.8 → mpbn-4.3}/mpbn/__init__.py +30 -67
- mpbn-4.3/mpbn/boolfunclib/__init__.py +0 -0
- mpbn-4.3/mpbn/boolfunclib/aeon_impl.py +202 -0
- mpbn-3.8/mpbn/converters.py → mpbn-4.3/mpbn/boolfunclib/pyeda_impl.py +65 -9
- {mpbn-3.8 → mpbn-4.3}/mpbn/cli/__init__.py +4 -0
- mpbn-4.3/mpbn/converters.py +14 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/simulation.py +4 -2
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/PKG-INFO +14 -4
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/SOURCES.txt +3 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/requires.txt +1 -1
- {mpbn-3.8 → mpbn-4.3}/setup.py +2 -2
- {mpbn-3.8 → mpbn-4.3}/MANIFEST.in +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/asplib/eval_circuit.asp +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/asplib/eval_mixed.asp +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/asplib/mp_attractor.asp +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/asplib/mp_eval.asp +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/asplib/mp_positivereach-np.asp +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn/cli/sim.py +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/dependency_links.txt +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/entry_points.txt +0 -0
- {mpbn-3.8 → mpbn-4.3}/mpbn.egg-info/top_level.txt +0 -0
- {mpbn-3.8 → mpbn-4.3}/setup.cfg +0 -0
{mpbn-3.8 → mpbn-4.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mpbn
|
|
3
|
-
Version: 3
|
|
3
|
+
Version: 4.3
|
|
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.3}/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,8 @@ def s2v(s):
|
|
|
78
74
|
def v2s(v):
|
|
79
75
|
return 1 if v > 0 else 0
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
|
|
78
|
+
def is_dnf(ba, f, test_unate=False):
|
|
82
79
|
pos_lits = set()
|
|
83
80
|
neg_lits = set()
|
|
84
81
|
def is_lit(f):
|
|
@@ -102,6 +99,8 @@ def is_unate(ba, f):
|
|
|
102
99
|
return False
|
|
103
100
|
|
|
104
101
|
def test_monotonicity():
|
|
102
|
+
if not test_unate:
|
|
103
|
+
return True
|
|
105
104
|
both = pos_lits.intersection(neg_lits)
|
|
106
105
|
return not both
|
|
107
106
|
|
|
@@ -116,33 +115,8 @@ def is_unate(ba, f):
|
|
|
116
115
|
return test_monotonicity()
|
|
117
116
|
return False
|
|
118
117
|
|
|
119
|
-
def
|
|
120
|
-
|
|
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))
|
|
118
|
+
def is_dnf_unate(ba, f):
|
|
119
|
+
return is_dnf(ba, f, test_unate=True)
|
|
146
120
|
|
|
147
121
|
def circuitasp_of_boolfunc(f, i, ba):
|
|
148
122
|
atoms = []
|
|
@@ -176,28 +150,9 @@ def circuitasp_of_boolfunc(f, i, ba):
|
|
|
176
150
|
atoms.append(f"circuit({fid},root,{root}).\n")
|
|
177
151
|
return "\n".join(atoms)
|
|
178
152
|
|
|
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
153
|
DEFAULT_ENCODING = "mixed-dnf-bdd"
|
|
154
|
+
DEFAULT_BOOLFUNCLIB = os.environ.get("MPBN_BOOLFUNCLIB", "pyeda")
|
|
155
|
+
SUPPORTED_BOOLFUNCLIBS = ["aeon", "pyeda"]
|
|
201
156
|
|
|
202
157
|
class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
203
158
|
"""
|
|
@@ -218,7 +173,8 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
218
173
|
def __init__(self, bn=minibn.BooleanNetwork(), auto_dnf=True,
|
|
219
174
|
simplify=False,
|
|
220
175
|
try_unate_hard=False,
|
|
221
|
-
encoding=DEFAULT_ENCODING
|
|
176
|
+
encoding=DEFAULT_ENCODING,
|
|
177
|
+
boolfunclib=DEFAULT_BOOLFUNCLIB):
|
|
222
178
|
"""
|
|
223
179
|
Constructor for :py:class:`.MPBoooleanNetwork`.
|
|
224
180
|
|
|
@@ -227,6 +183,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
227
183
|
:py:class:`colomoto.minibn.BooleanNetwork` constructor
|
|
228
184
|
:param bool auto_dnf: if ``False``, turns off automatic DNF
|
|
229
185
|
transformation of local functions
|
|
186
|
+
:param str boolfunlib: library to use for Boolean function manipulation
|
|
187
|
+
among ``"aeon"`` (default) or ``"pyeda"``. Default can be overriden with
|
|
188
|
+
``MPBN_BOOLFUNCLIB`` environment variable.
|
|
230
189
|
|
|
231
190
|
Examples:
|
|
232
191
|
|
|
@@ -236,11 +195,21 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
236
195
|
>>> mbn = MPBooleanNetwork(bn)
|
|
237
196
|
"""
|
|
238
197
|
assert encoding in self.supported_encodings
|
|
198
|
+
assert boolfunclib in SUPPORTED_BOOLFUNCLIBS
|
|
239
199
|
self.auto_dnf = auto_dnf and encoding in self.dnf_encodings
|
|
240
200
|
self.encoding = encoding
|
|
241
201
|
self.try_unate_hard = try_unate_hard
|
|
242
202
|
self._simplify = simplify
|
|
243
203
|
self._is_unate = dict()
|
|
204
|
+
|
|
205
|
+
self._boolfunclib = boolfunclib
|
|
206
|
+
__boolfunclib_symbols = (
|
|
207
|
+
"make_dnf_boolfunc",
|
|
208
|
+
"bddasp_of_boolfunc",
|
|
209
|
+
)
|
|
210
|
+
self._bf_impl = __import__(f"mpbn.boolfunclib.{boolfunclib}_impl",
|
|
211
|
+
fromlist=__boolfunclib_symbols)
|
|
212
|
+
|
|
244
213
|
super(MPBooleanNetwork, self).__init__(bn)
|
|
245
214
|
|
|
246
215
|
def __setitem__(self, a, f):
|
|
@@ -252,19 +221,13 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
252
221
|
if isinstance(f, str):
|
|
253
222
|
f = self.ba.parse(f)
|
|
254
223
|
f = self._autobool(f)
|
|
255
|
-
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()
|
|
224
|
+
if self.auto_dnf and (not is_dnf(self.ba, f) or self._simplify):
|
|
225
|
+
f = self._bf_impl.make_dnf_boolfunc(self.ba, f,
|
|
226
|
+
simplify=self._simplify,
|
|
227
|
+
try_unate_hard=self.try_unate_hard)
|
|
265
228
|
a = self._autokey(a)
|
|
266
229
|
if self.encoding in self.dnf_encodings:
|
|
267
|
-
self._is_unate[a] =
|
|
230
|
+
self._is_unate[a] = is_dnf_unate(self.ba, f)
|
|
268
231
|
if self.encoding == "unate-dnf":
|
|
269
232
|
assert self._is_unate[a], f"'{f}' seems not unate. Try simplify()?"
|
|
270
233
|
return super().__setitem__(a, f)
|
|
@@ -311,13 +274,13 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
311
274
|
elif f_encoding == "dnf":
|
|
312
275
|
facts.extend(encode_dnf(f))
|
|
313
276
|
elif f_encoding == "bdd":
|
|
314
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
277
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
315
278
|
elif f_encoding == "mixed-dnf-bdd":
|
|
316
279
|
facts.extend(encode_dnf(f))
|
|
317
280
|
if self._is_unate[n]:
|
|
318
281
|
facts.append(f"unate(\"{n}\").")
|
|
319
282
|
else:
|
|
320
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
283
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
321
284
|
elif f_encoding == "circuit":
|
|
322
285
|
facts.append(circuitasp_of_boolfunc(f, n, self.ba))
|
|
323
286
|
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, boolean._TRUE):
|
|
62
|
+
return ctx.mk_const(True)
|
|
63
|
+
if type(f) is ba.FALSE or isinstance(f, boolean._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)))
|
|
@@ -188,7 +188,7 @@ def sample_reachable_attractor(f, mem, x, A, depth, W, refresh_rate=10, emit=Non
|
|
|
188
188
|
I = set(f)
|
|
189
189
|
n = len(f)
|
|
190
190
|
def filter_reachable_attractors(A, x):
|
|
191
|
-
H = spread(f, x, I, n)
|
|
191
|
+
H = spread(f, x, I, n)
|
|
192
192
|
return [(ia,a) for (ia,a) in A if is_subhypercube(a, (x,H))]
|
|
193
193
|
k = 1
|
|
194
194
|
x = x.copy()
|
|
@@ -201,7 +201,7 @@ def sample_reachable_attractor(f, mem, x, A, depth, W, refresh_rate=10, emit=Non
|
|
|
201
201
|
if k % refresh_rate == 0:
|
|
202
202
|
A = filter_reachable_attractors(A, x)
|
|
203
203
|
k += 1
|
|
204
|
-
return A[0][0]
|
|
204
|
+
return None if (len(A)==0) else A[0][0]
|
|
205
205
|
|
|
206
206
|
def sample_trace(f, mem, x, A, depth, W):
|
|
207
207
|
if not isinstance(f, MPBNSim): f = MPBNSim(f)
|
|
@@ -257,6 +257,8 @@ def estimate_reachable_attractors_probabilities(f, x, A, nb_sims, depth, W):
|
|
|
257
257
|
C = {ia: 0 for (ia,_) in A}
|
|
258
258
|
for _ in tqdm(range(nb_sims)):
|
|
259
259
|
ia = sample_reachable_attractor(f, mem, x, A, depth, W)
|
|
260
|
+
if (ia is None):
|
|
261
|
+
continue
|
|
260
262
|
C[ia] += 1
|
|
261
263
|
for ia, _ in A:
|
|
262
264
|
C[ia] = (C[ia]*100) / nb_sims
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mpbn
|
|
3
|
-
Version: 3
|
|
3
|
+
Version: 4.3
|
|
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.3}/setup.py
RENAMED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from setuptools import setup, find_packages
|
|
5
5
|
|
|
6
6
|
NAME = "mpbn"
|
|
7
|
-
VERSION = "3
|
|
7
|
+
VERSION = "4.3"
|
|
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
|
{mpbn-3.8 → mpbn-4.3}/setup.cfg
RENAMED
|
File without changes
|