mpbn 3.5__py3-none-any.whl → 4.0__py3-none-any.whl
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/__init__.py +153 -119
- mpbn/boolfunclib/__init__.py +0 -0
- mpbn/boolfunclib/aeon_impl.py +202 -0
- mpbn/boolfunclib/pyeda_impl.py +143 -0
- mpbn/cli/__init__.py +21 -6
- mpbn/converters.py +4 -77
- {mpbn-3.5.dist-info → mpbn-4.0.dist-info}/METADATA +15 -5
- {mpbn-3.5.dist-info → mpbn-4.0.dist-info}/RECORD +11 -8
- {mpbn-3.5.dist-info → mpbn-4.0.dist-info}/WHEEL +1 -1
- {mpbn-3.5.dist-info → mpbn-4.0.dist-info}/entry_points.txt +0 -0
- {mpbn-3.5.dist-info → mpbn-4.0.dist-info}/top_level.txt +0 -0
mpbn/__init__.py
CHANGED
|
@@ -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"))
|
|
@@ -46,23 +42,20 @@ if hasattr(clingo, "version") and clingo.version() >= (5,5,0):
|
|
|
46
42
|
def aspf(basename):
|
|
47
43
|
return os.path.join(__asplibdir__, basename)
|
|
48
44
|
|
|
49
|
-
def
|
|
50
|
-
s = clingo.Control(clingo_options)
|
|
45
|
+
def _clingo_domrec(mod, limit=0, project=False, extra_opts=[]):
|
|
46
|
+
s = clingo.Control(clingo_options + extra_opts)
|
|
51
47
|
s.configuration.solve.models = limit
|
|
52
|
-
|
|
48
|
+
if project:
|
|
49
|
+
s.configuration.solve.project = 1
|
|
53
50
|
s.configuration.solve.enum_mode = "domRec"
|
|
54
51
|
s.configuration.solver[0].heuristic = "Domain"
|
|
55
|
-
s.configuration.solver[0].dom_mod = "
|
|
52
|
+
s.configuration.solver[0].dom_mod = f"{mod},{16 if project else 0}"
|
|
56
53
|
return s
|
|
57
54
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
s.configuration.solve.enum_mode = "domRec"
|
|
63
|
-
s.configuration.solver[0].heuristic = "Domain"
|
|
64
|
-
s.configuration.solver[0].dom_mod = "3,16"
|
|
65
|
-
return s
|
|
55
|
+
def clingo_subsets(**opts):
|
|
56
|
+
return _clingo_domrec(5, **opts)
|
|
57
|
+
def clingo_supsets(**opts):
|
|
58
|
+
return _clingo_domrec(3, **opts)
|
|
66
59
|
|
|
67
60
|
def clingo_exists():
|
|
68
61
|
s = clingo.Control(clingo_options)
|
|
@@ -81,7 +74,7 @@ def s2v(s):
|
|
|
81
74
|
def v2s(v):
|
|
82
75
|
return 1 if v > 0 else 0
|
|
83
76
|
|
|
84
|
-
def
|
|
77
|
+
def is_dnf_unate(ba, f):
|
|
85
78
|
pos_lits = set()
|
|
86
79
|
neg_lits = set()
|
|
87
80
|
def is_lit(f):
|
|
@@ -119,34 +112,6 @@ def is_unate(ba, f):
|
|
|
119
112
|
return test_monotonicity()
|
|
120
113
|
return False
|
|
121
114
|
|
|
122
|
-
def asp_of_bdd(bid, b):
|
|
123
|
-
_rules = dict()
|
|
124
|
-
def register(node, nid=None):
|
|
125
|
-
if node is bdd.BDDNODEONE:
|
|
126
|
-
if nid is not None:
|
|
127
|
-
_rules[bid] = f"bdd({clingo.String(nid)},1)"
|
|
128
|
-
return 1
|
|
129
|
-
elif node is bdd.BDDNODEZERO:
|
|
130
|
-
if nid is not None:
|
|
131
|
-
_rules[bid] = f"bdd({clingo.String(nid)},-1)"
|
|
132
|
-
return -1
|
|
133
|
-
nid = clingo.String(f"{bid}_n{id(node)}" if nid is None else nid)
|
|
134
|
-
if nid not in _rules:
|
|
135
|
-
var = clingo.String(bdd._VARS[node.root].qualname)
|
|
136
|
-
lo = register(node.lo)
|
|
137
|
-
hi = register(node.hi)
|
|
138
|
-
a = f"bdd({nid},{var},{lo},{hi})"
|
|
139
|
-
_rules[nid] = a
|
|
140
|
-
return nid
|
|
141
|
-
register(b.node, bid)
|
|
142
|
-
return _rules.values()
|
|
143
|
-
|
|
144
|
-
def bddasp_of_boolfunc(f, i):
|
|
145
|
-
e = expr(str(f).replace("!","~"))
|
|
146
|
-
b = bdd.expr2bdd(e)
|
|
147
|
-
atoms = asp_of_bdd(i, b)
|
|
148
|
-
return "\n".join((f"{a}." for a in atoms))
|
|
149
|
-
|
|
150
115
|
def circuitasp_of_boolfunc(f, i, ba):
|
|
151
116
|
atoms = []
|
|
152
117
|
fid = clingo.String(i)
|
|
@@ -179,28 +144,9 @@ def circuitasp_of_boolfunc(f, i, ba):
|
|
|
179
144
|
atoms.append(f"circuit({fid},root,{root}).\n")
|
|
180
145
|
return "\n".join(atoms)
|
|
181
146
|
|
|
182
|
-
|
|
183
|
-
def expr2bpy(ex, ba):
|
|
184
|
-
"""
|
|
185
|
-
converts a pyeda Boolean expression into a boolean.py one
|
|
186
|
-
"""
|
|
187
|
-
if isinstance(ex, pyeda.boolalg.expr.Variable):
|
|
188
|
-
return ba.Symbol(str(ex))
|
|
189
|
-
elif isinstance(ex, pyeda.boolalg.expr._One):
|
|
190
|
-
return ba.TRUE
|
|
191
|
-
elif isinstance(ex, pyeda.boolalg.expr._Zero):
|
|
192
|
-
return ba.FALSE
|
|
193
|
-
elif isinstance(ex, pyeda.boolalg.expr.Complement):
|
|
194
|
-
return ba.NOT(ba.Symbol(str(ex.__invert__())))
|
|
195
|
-
elif isinstance(ex, pyeda.boolalg.expr.NotOp):
|
|
196
|
-
return ba.NOT(expr2bpy(ex.x, ba))
|
|
197
|
-
elif isinstance(ex, pyeda.boolalg.expr.OrOp):
|
|
198
|
-
return ba.OR(*(expr2bpy(x, ba) for x in ex.xs))
|
|
199
|
-
elif isinstance(ex, pyeda.boolalg.expr.AndOp):
|
|
200
|
-
return ba.AND(*(expr2bpy(x, ba) for x in ex.xs))
|
|
201
|
-
raise NotImplementedError(str(ex), type(ex))
|
|
202
|
-
|
|
203
147
|
DEFAULT_ENCODING = "mixed-dnf-bdd"
|
|
148
|
+
DEFAULT_BOOLFUNCLIB = os.environ.get("MPBN_BOOLFUNCLIB", "aeon")
|
|
149
|
+
SUPPORTED_BOOLFUNCLIBS = ["aeon", "pyeda"]
|
|
204
150
|
|
|
205
151
|
class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
206
152
|
"""
|
|
@@ -221,7 +167,8 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
221
167
|
def __init__(self, bn=minibn.BooleanNetwork(), auto_dnf=True,
|
|
222
168
|
simplify=False,
|
|
223
169
|
try_unate_hard=False,
|
|
224
|
-
encoding=DEFAULT_ENCODING
|
|
170
|
+
encoding=DEFAULT_ENCODING,
|
|
171
|
+
boolfunclib=DEFAULT_BOOLFUNCLIB):
|
|
225
172
|
"""
|
|
226
173
|
Constructor for :py:class:`.MPBoooleanNetwork`.
|
|
227
174
|
|
|
@@ -230,6 +177,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
230
177
|
:py:class:`colomoto.minibn.BooleanNetwork` constructor
|
|
231
178
|
:param bool auto_dnf: if ``False``, turns off automatic DNF
|
|
232
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.
|
|
233
183
|
|
|
234
184
|
Examples:
|
|
235
185
|
|
|
@@ -239,11 +189,21 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
239
189
|
>>> mbn = MPBooleanNetwork(bn)
|
|
240
190
|
"""
|
|
241
191
|
assert encoding in self.supported_encodings
|
|
192
|
+
assert boolfunclib in SUPPORTED_BOOLFUNCLIBS
|
|
242
193
|
self.auto_dnf = auto_dnf and encoding in self.dnf_encodings
|
|
243
194
|
self.encoding = encoding
|
|
244
195
|
self.try_unate_hard = try_unate_hard
|
|
245
196
|
self._simplify = simplify
|
|
246
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
|
+
|
|
247
207
|
super(MPBooleanNetwork, self).__init__(bn)
|
|
248
208
|
|
|
249
209
|
def __setitem__(self, a, f):
|
|
@@ -256,18 +216,12 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
256
216
|
f = self.ba.parse(f)
|
|
257
217
|
f = self._autobool(f)
|
|
258
218
|
if self.auto_dnf:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
e = e.simplify()
|
|
263
|
-
f = expr2bpy(e, self.ba)
|
|
264
|
-
if self.try_unate_hard:
|
|
265
|
-
f = minibn.simplify_dnf(self.ba, f)
|
|
266
|
-
elif self._simplify:
|
|
267
|
-
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)
|
|
268
222
|
a = self._autokey(a)
|
|
269
223
|
if self.encoding in self.dnf_encodings:
|
|
270
|
-
self._is_unate[a] =
|
|
224
|
+
self._is_unate[a] = is_dnf_unate(self.ba, f)
|
|
271
225
|
if self.encoding == "unate-dnf":
|
|
272
226
|
assert self._is_unate[a], f"'{f}' seems not unate. Try simplify()?"
|
|
273
227
|
return super().__setitem__(a, f)
|
|
@@ -314,24 +268,33 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
314
268
|
elif f_encoding == "dnf":
|
|
315
269
|
facts.extend(encode_dnf(f))
|
|
316
270
|
elif f_encoding == "bdd":
|
|
317
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
271
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
318
272
|
elif f_encoding == "mixed-dnf-bdd":
|
|
319
273
|
facts.extend(encode_dnf(f))
|
|
320
274
|
if self._is_unate[n]:
|
|
321
275
|
facts.append(f"unate(\"{n}\").")
|
|
322
276
|
else:
|
|
323
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
277
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
324
278
|
elif f_encoding == "circuit":
|
|
325
279
|
facts.append(circuitasp_of_boolfunc(f, n, self.ba))
|
|
326
280
|
return "".join(facts)
|
|
327
281
|
|
|
328
|
-
def
|
|
282
|
+
def _file_eval(self):
|
|
329
283
|
if self.encoding == "circuit":
|
|
330
|
-
|
|
284
|
+
f = aspf("eval_circuit.asp")
|
|
331
285
|
elif self.encoding == "mixed-dnf-bdd":
|
|
332
|
-
|
|
286
|
+
f = aspf("eval_mixed.asp")
|
|
333
287
|
else:
|
|
334
|
-
|
|
288
|
+
f = aspf("mp_eval.asp")
|
|
289
|
+
return f
|
|
290
|
+
|
|
291
|
+
def rules_eval(self):
|
|
292
|
+
f = self._file_eval()
|
|
293
|
+
with open(f, "r") as fp:
|
|
294
|
+
return fp.read()
|
|
295
|
+
def load_eval(self, solver):
|
|
296
|
+
f = self._file_eval()
|
|
297
|
+
solver.load(f)
|
|
335
298
|
|
|
336
299
|
def assert_pc_encoding(self):
|
|
337
300
|
assert self.encoding not in self.nonpc_encodings, "Unsupported encoding"
|
|
@@ -340,8 +303,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
340
303
|
facts = ["timepoint({},{}).".format(e,t)]
|
|
341
304
|
facts += [" mp_state({},{},\"{}\",{}).".format(e,t,n,s2v(s))
|
|
342
305
|
for (n,s) in c.items()]
|
|
343
|
-
facts += ["
|
|
344
|
-
|
|
306
|
+
facts += [f"1 {{mp_state({e},{t},N,(-1;1))}} 1 :- node(N)."]
|
|
307
|
+
#facts += [" 1 {{mp_state({},{},\"{}\",(-1;1))}} 1 :- node(N).".format(e,t,n)
|
|
308
|
+
# for n in self if n not in c]
|
|
345
309
|
return "".join(facts)
|
|
346
310
|
|
|
347
311
|
def reachability(self, x, y):
|
|
@@ -371,6 +335,32 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
371
335
|
res = s.solve()
|
|
372
336
|
return res.satisfiable
|
|
373
337
|
|
|
338
|
+
def _ground_rules(self, ctl, rules):
|
|
339
|
+
rules = "\n".join(rules)
|
|
340
|
+
ctl.add("base", [], rules)
|
|
341
|
+
ctl.ground([("base",[])])
|
|
342
|
+
|
|
343
|
+
def _fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
344
|
+
e = "fp"
|
|
345
|
+
t2 = "fp"
|
|
346
|
+
rules = [self.asp_of_cfg(e, t2, constraints)]
|
|
347
|
+
rules.append(f"mp_reach({e},{t2},N,V) :- mp_state({e},{t2},N,V).")
|
|
348
|
+
rules.append(f":- mp_state({e},{t2},N,V), mp_eval({e},{t2},N,-V).")
|
|
349
|
+
rules.append(self.asp_of_bn())
|
|
350
|
+
if reachable_from:
|
|
351
|
+
self.assert_pc_encoding()
|
|
352
|
+
t1 = "0"
|
|
353
|
+
rules.append(open(aspf("mp_positivereach-np.asp")).read())
|
|
354
|
+
rules.append(self.asp_of_cfg(e,t1,reachable_from))
|
|
355
|
+
rules.append("is_reachable({},{},{}).".format(e,t1,t2))
|
|
356
|
+
rules.append(f"#show. #show fixpoint(N,V) : mp_state({e},{t2},N,V).")
|
|
357
|
+
rules.append(open(aspf("mp_eval.asp")).read())
|
|
358
|
+
|
|
359
|
+
project = reachable_from and set(self.keys()).difference(reachable_from)
|
|
360
|
+
s = clingo_enum(limit=limit, project=project)
|
|
361
|
+
self._ground_rules(s, rules)
|
|
362
|
+
return s
|
|
363
|
+
|
|
374
364
|
def fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
375
365
|
"""
|
|
376
366
|
Iterator over fixed points of the MPBN (i.e., of f)
|
|
@@ -383,23 +373,8 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
383
373
|
the given constraints.
|
|
384
374
|
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
385
375
|
"""
|
|
386
|
-
s =
|
|
387
|
-
|
|
388
|
-
e = "fp"
|
|
389
|
-
t2 = "fp"
|
|
390
|
-
self.load_eval(s)
|
|
391
|
-
s.add("base", [], self.asp_of_cfg(e, t2, constraints))
|
|
392
|
-
s.add("base", [], f"mp_reach({e},{t2},N,V) :- mp_state({e},{t2},N,V).")
|
|
393
|
-
s.add("base", [], f":- mp_state({e},{t2},N,V), mp_eval({e},{t2},N,-V).")
|
|
394
|
-
if reachable_from:
|
|
395
|
-
self.assert_pc_encoding()
|
|
396
|
-
t1 = "0"
|
|
397
|
-
s.load(aspf("mp_positivereach-np.asp"))
|
|
398
|
-
s.add("base", [], self.asp_of_cfg(e,t1,reachable_from))
|
|
399
|
-
s.add("base", [], "is_reachable({},{},{}).".format(e,t1,t2))
|
|
400
|
-
s.add("base", [], f"#show. #show fixpoint(N,V) : mp_state({e},{t2},N,V).")
|
|
401
|
-
|
|
402
|
-
s.ground([("base",[])])
|
|
376
|
+
s = self._fixedpoints(reachable_from=reachable_from,
|
|
377
|
+
constraints=constraints, limit=limit)
|
|
403
378
|
for sol in s.solve(yield_=True):
|
|
404
379
|
x = {n: None for n in self}
|
|
405
380
|
data = sol.symbols(shown=True)
|
|
@@ -413,36 +388,59 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
413
388
|
x[n] = v
|
|
414
389
|
yield x
|
|
415
390
|
|
|
391
|
+
def count_fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
392
|
+
"""
|
|
393
|
+
Returns number of fixed points
|
|
416
394
|
|
|
417
|
-
|
|
395
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
396
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
397
|
+
attractors to the one reachable by at least one matching
|
|
398
|
+
configuration.
|
|
399
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
400
|
+
the given constraints.
|
|
401
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
402
|
+
"""
|
|
403
|
+
s = self._fixedpoints(reachable_from=reachable_from,
|
|
404
|
+
constraints=constraints, limit=limit)
|
|
405
|
+
return sum((1 for _ in s.solve(yield_=True)))
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _trapspaces(self, reachable_from=None, subcube={}, limit=0,
|
|
418
409
|
mode="min", exclude_full=False):
|
|
419
410
|
self.assert_pc_encoding()
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
self.
|
|
423
|
-
|
|
424
|
-
|
|
411
|
+
|
|
412
|
+
rules = []
|
|
413
|
+
rules.append(self.asp_of_bn())
|
|
414
|
+
rules.append(self.rules_eval())
|
|
415
|
+
rules.append(open(aspf("mp_attractor.asp")).read())
|
|
416
|
+
rules.append("#show attractor/2.")
|
|
417
|
+
|
|
425
418
|
e = "__a"
|
|
426
419
|
t2 = "final"
|
|
427
420
|
if exclude_full and not subcube:
|
|
428
|
-
|
|
421
|
+
rules.append(f"{{ mp_reach({e},{t2},N,(-1;1)): node(N) }} {len(self)*2-1}.")
|
|
429
422
|
if reachable_from:
|
|
430
423
|
t1 = "0"
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
424
|
+
rules.append(open(aspf("mp_positivereach-np.asp")).read())
|
|
425
|
+
rules.append(self.asp_of_cfg(e,t1,reachable_from))
|
|
426
|
+
rules.append("is_reachable({},{},{}).".format(e,t1,t2))
|
|
427
|
+
rules.append("mp_state({},{},N,V) :- attractor(N,V).".format(e,t2))
|
|
435
428
|
|
|
436
429
|
for n, b in subcube.items():
|
|
437
430
|
if isinstance(b, str):
|
|
438
431
|
b = int(b)
|
|
439
432
|
if b not in [0,1]:
|
|
440
433
|
continue
|
|
441
|
-
|
|
434
|
+
rules.append(":- mp_reach({},{},\"{}\",{}).".format(e,t2,n,s2v(1-b)))
|
|
442
435
|
|
|
443
|
-
|
|
436
|
+
project = reachable_from and set(self.keys()).difference(reachable_from)
|
|
437
|
+
solver = clingo_subsets if mode == "min" else clingo_supsets
|
|
438
|
+
s = solver(limit=limit, project=project)
|
|
439
|
+
self._ground_rules(s, rules)
|
|
440
|
+
return s
|
|
444
441
|
|
|
445
|
-
|
|
442
|
+
def _yield_trapspaces(self, *args, star="*", **kwargs):
|
|
443
|
+
s = self._trapspaces(*args, **kwargs)
|
|
446
444
|
for sol in s.solve(yield_=True):
|
|
447
445
|
attractor = {n: None for n in self}
|
|
448
446
|
data = sol.symbols(shown=True)
|
|
@@ -465,6 +463,10 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
465
463
|
attractor[n] = v
|
|
466
464
|
yield attractor
|
|
467
465
|
|
|
466
|
+
def _count_trapspaces(self, *args, **kwargs):
|
|
467
|
+
s = self._trapspaces(*args, **kwargs)
|
|
468
|
+
return sum((1 for _ in s.solve(yield_=True)))
|
|
469
|
+
|
|
468
470
|
def attractors(self, reachable_from=None, constraints={}, limit=0, star='*'):
|
|
469
471
|
"""
|
|
470
472
|
Iterator over attractors of the MPBN (minimal trap spaces of the BN).
|
|
@@ -481,17 +483,49 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
481
483
|
:param str star: value to use for components which are free in the
|
|
482
484
|
attractor
|
|
483
485
|
"""
|
|
484
|
-
return self.
|
|
486
|
+
return self._yield_trapspaces(reachable_from=reachable_from,
|
|
485
487
|
subcube=constraints, limit=limit, star=star,
|
|
486
488
|
mode="min")
|
|
487
|
-
|
|
488
489
|
minimal_trapspaces = attractors
|
|
489
490
|
|
|
490
491
|
def maximal_trapspaces(self, limit=0, subcube={}, star="*",
|
|
491
492
|
exclude_full=True):
|
|
492
|
-
return self.
|
|
493
|
+
return self._yield_trapspaces(subcube=subcube, limit=limit, star=star,
|
|
493
494
|
mode="max", exclude_full=exclude_full)
|
|
494
495
|
|
|
496
|
+
def count_attractors(self, reachable_from=None, constraints={}, limit=0):
|
|
497
|
+
"""
|
|
498
|
+
Returns number of attractors of the MPBN (minimal trap spaces of the BN).
|
|
499
|
+
|
|
500
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
501
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
502
|
+
attractors to the one reachable by at least one matching
|
|
503
|
+
configuration.
|
|
504
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
505
|
+
the given constraints.
|
|
506
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
507
|
+
"""
|
|
508
|
+
return self._count_trapspaces(reachable_from=reachable_from,
|
|
509
|
+
subcube=constraints, limit=limit,
|
|
510
|
+
mode="min")
|
|
511
|
+
count_minimal_trapspaces = count_attractors
|
|
512
|
+
|
|
513
|
+
def count_maximal_trapspaces(self, reachable_from=None, constraints={}, limit=0):
|
|
514
|
+
"""
|
|
515
|
+
Returns number of attractors of the MPBN (minimal trap spaces of the BN).
|
|
516
|
+
|
|
517
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
518
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
519
|
+
attractors to the one reachable by at least one matching
|
|
520
|
+
configuration.
|
|
521
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
522
|
+
the given constraints.
|
|
523
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
524
|
+
"""
|
|
525
|
+
return self._count_trapspaces(reachable_from=reachable_from,
|
|
526
|
+
subcube=constraints, limit=limit,
|
|
527
|
+
mode="max")
|
|
528
|
+
|
|
495
529
|
def has_cyclic_attractor(self):
|
|
496
530
|
for a in self.attractors():
|
|
497
531
|
if "*" in a.values():
|
|
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
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import clingo
|
|
2
|
+
import networkx as nx
|
|
3
|
+
|
|
4
|
+
from pyeda.boolalg.minimization import *
|
|
5
|
+
import pyeda.boolalg.expr
|
|
6
|
+
from pyeda.inter import expr
|
|
7
|
+
from pyeda.boolalg import bdd
|
|
8
|
+
|
|
9
|
+
from colomoto import minibn
|
|
10
|
+
|
|
11
|
+
def expr2str(ex):
|
|
12
|
+
"""
|
|
13
|
+
converts a pyeda Boolean expression to string representation
|
|
14
|
+
"""
|
|
15
|
+
def _protect(e):
|
|
16
|
+
if isinstance(e, (pyeda.boolalg.expr.OrOp, pyeda.boolalg.expr.AndOp)):
|
|
17
|
+
return f"({expr2str(e)})"
|
|
18
|
+
return expr2str(e)
|
|
19
|
+
if isinstance(ex, pyeda.boolalg.expr.Variable):
|
|
20
|
+
return str(ex)
|
|
21
|
+
elif isinstance(ex, pyeda.boolalg.expr._One):
|
|
22
|
+
return "1"
|
|
23
|
+
elif isinstance(ex, pyeda.boolalg.expr._Zero):
|
|
24
|
+
return "0"
|
|
25
|
+
elif isinstance(ex, pyeda.boolalg.expr.Complement):
|
|
26
|
+
return f"!{_protect(ex.__invert__())}"
|
|
27
|
+
elif isinstance(ex, pyeda.boolalg.expr.NotOp):
|
|
28
|
+
return f"!{_protect(ex.x)}"
|
|
29
|
+
elif isinstance(ex, pyeda.boolalg.expr.OrOp):
|
|
30
|
+
return " | ".join(map(_protect, ex.xs))
|
|
31
|
+
elif isinstance(ex, pyeda.boolalg.expr.AndOp):
|
|
32
|
+
return " & ".join(map(_protect, ex.xs))
|
|
33
|
+
raise NotImplementedError(str(ex), type(ex))
|
|
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
|
|
97
|
+
|
|
98
|
+
def bn_of_asynchronous_transition_graph(adyn, names,
|
|
99
|
+
parse_node=(lambda n: tuple(map(int, n))),
|
|
100
|
+
bn_class=minibn.BooleanNetwork,
|
|
101
|
+
simplify=True):
|
|
102
|
+
"""
|
|
103
|
+
Convert the transition graph of a (fully) asynchronous Boolean network to
|
|
104
|
+
a propositional logic representation.
|
|
105
|
+
|
|
106
|
+
The object `adyn` must be an instance of `networkx.DiGraph`.
|
|
107
|
+
The `parse_node` function must return a tuple of 0 and 1 from an `adyn`
|
|
108
|
+
node. By default, it is assumed that nodes are strings of binary values.
|
|
109
|
+
Returned object will be of `bn_class`, instantiated with a dictionnary
|
|
110
|
+
mapping component names to a string representation of their Boolean expression.
|
|
111
|
+
"""
|
|
112
|
+
relabel = {label: parse_node(label) for label in adyn.nodes()}
|
|
113
|
+
adyn = nx.relabel_nodes(adyn, relabel)
|
|
114
|
+
n = len(next(iter(adyn.nodes)))
|
|
115
|
+
assert n == len(names), "list of component names and dimension of configuraitons seem different"
|
|
116
|
+
assert adyn.number_of_nodes() == 2**n, "unexpected number of nodes in the transition graph"
|
|
117
|
+
|
|
118
|
+
def expr_of_cfg(x):
|
|
119
|
+
e = "&".join(f"{'~' if not v else ''}{names[i]}" for i, v in enumerate(x))
|
|
120
|
+
return f"({e})"
|
|
121
|
+
|
|
122
|
+
f = []
|
|
123
|
+
for i in range(n):
|
|
124
|
+
pos = []
|
|
125
|
+
for x in adyn.nodes():
|
|
126
|
+
dx = list(x)
|
|
127
|
+
dx[i] = 1-x[i]
|
|
128
|
+
y = dx if tuple(dx) in adyn[x] else x
|
|
129
|
+
target = y[i]
|
|
130
|
+
if target:
|
|
131
|
+
pos.append(x)
|
|
132
|
+
if not pos:
|
|
133
|
+
f.append(expr("0"))
|
|
134
|
+
else:
|
|
135
|
+
e = expr("|".join(map(expr_of_cfg,pos)))
|
|
136
|
+
e, = espresso_exprs(e.to_dnf())
|
|
137
|
+
f.append(e)
|
|
138
|
+
f = map(expr2str, f)
|
|
139
|
+
f = bn_class(dict(zip(names, f)))
|
|
140
|
+
if simplify:
|
|
141
|
+
f = f.simplify()
|
|
142
|
+
return f
|
|
143
|
+
|
mpbn/cli/__init__.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
import mpbn
|
|
3
3
|
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
from argparse import ArgumentParser
|
|
6
7
|
|
|
7
8
|
def main():
|
|
9
|
+
if "CLINGO_OPTS" in os.environ:
|
|
10
|
+
mpbn.clingo_options += os.environ["CLINGO_OPTS"].split(" ")
|
|
11
|
+
|
|
8
12
|
ap = ArgumentParser(prog=sys.argv[0])
|
|
9
13
|
ap.add_argument("bnet_file")
|
|
10
14
|
ap.add_argument("method", choices=["attractors", "fixedpoints", "bn2asp"])
|
|
@@ -13,19 +17,30 @@ def main():
|
|
|
13
17
|
ap.add_argument("--encoding", default=mpbn.DEFAULT_ENCODING,
|
|
14
18
|
choices=mpbn.MPBooleanNetwork.supported_encodings,
|
|
15
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})")
|
|
23
|
+
ap.add_argument("--input-is-dnf", action="store_true", default=False,
|
|
24
|
+
help="Functions are already in DNF form")
|
|
16
25
|
ap.add_argument("--simplify", action="store_true", default=False,
|
|
17
26
|
help="Try costly Boolean function simplifications to improve encoding")
|
|
18
27
|
ap.add_argument("--try-unate-hard", action="store_true", default=False,
|
|
19
28
|
help="Try even more costly Boolean function simplifications")
|
|
29
|
+
ap.add_argument("--count", action="store_true",
|
|
30
|
+
help="Returns only the number of solutions")
|
|
20
31
|
args = ap.parse_args()
|
|
21
32
|
mbn = mpbn.MPBooleanNetwork(args.bnet_file, encoding=args.encoding,
|
|
33
|
+
boolfunclib=args.boolfunclib,
|
|
34
|
+
auto_dnf=not args.input_is_dnf,
|
|
22
35
|
simplify=args.simplify,
|
|
23
36
|
try_unate_hard=args.try_unate_hard)
|
|
24
|
-
if args.method
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
if args.method in ["attractors", "fixedpoints"]:
|
|
38
|
+
if args.count:
|
|
39
|
+
func = getattr(mbn, f"count_{args.method}")
|
|
40
|
+
print(func(limit=args.limit))
|
|
41
|
+
else:
|
|
42
|
+
func = getattr(mbn, args.method)
|
|
43
|
+
for obj in func(limit=args.limit):
|
|
44
|
+
print(obj)
|
|
30
45
|
elif args.method == "bn2asp":
|
|
31
46
|
print(mbn.asp_of_bn())
|
mpbn/converters.py
CHANGED
|
@@ -1,80 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
from colomoto import minibn
|
|
7
|
-
|
|
8
|
-
def expr2str(ex):
|
|
9
|
-
"""
|
|
10
|
-
converts a pyeda Boolean expression to string representation
|
|
11
|
-
"""
|
|
12
|
-
def _protect(e):
|
|
13
|
-
if isinstance(e, (pyeda.boolalg.expr.OrOp, pyeda.boolalg.expr.AndOp)):
|
|
14
|
-
return f"({expr2str(e)})"
|
|
15
|
-
return expr2str(e)
|
|
16
|
-
if isinstance(ex, pyeda.boolalg.expr.Variable):
|
|
17
|
-
return str(ex)
|
|
18
|
-
elif isinstance(ex, pyeda.boolalg.expr._One):
|
|
19
|
-
return "1"
|
|
20
|
-
elif isinstance(ex, pyeda.boolalg.expr._Zero):
|
|
21
|
-
return "0"
|
|
22
|
-
elif isinstance(ex, pyeda.boolalg.expr.Complement):
|
|
23
|
-
return f"!{_protect(ex.__invert__())}"
|
|
24
|
-
elif isinstance(ex, pyeda.boolalg.expr.NotOp):
|
|
25
|
-
return f"!{_protect(ex.x)}"
|
|
26
|
-
elif isinstance(ex, pyeda.boolalg.expr.OrOp):
|
|
27
|
-
return " | ".join(map(_protect, ex.xs))
|
|
28
|
-
elif isinstance(ex, pyeda.boolalg.expr.AndOp):
|
|
29
|
-
return " & ".join(map(_protect, ex.xs))
|
|
30
|
-
raise NotImplementedError(str(ex), type(ex))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def bn_of_asynchronous_transition_graph(adyn, names,
|
|
34
|
-
parse_node=(lambda n: tuple(map(int, n))),
|
|
35
|
-
bn_class=minibn.BooleanNetwork,
|
|
36
|
-
simplify=True):
|
|
37
|
-
"""
|
|
38
|
-
Convert the transition graph of a (fully) asynchronous Boolean network to
|
|
39
|
-
a propositional logic representation.
|
|
40
|
-
|
|
41
|
-
The object `adyn` must be an instance of `networkx.DiGraph`.
|
|
42
|
-
The `parse_node` function must return a tuple of 0 and 1 from an `adyn`
|
|
43
|
-
node. By default, it is assumed that nodes are strings of binary values.
|
|
44
|
-
Returned object will be of `bn_class`, instantiated with a dictionnary
|
|
45
|
-
mapping component names to a string representation of their Boolean expression.
|
|
46
|
-
"""
|
|
47
|
-
relabel = {label: parse_node(label) for label in adyn.nodes()}
|
|
48
|
-
adyn = nx.relabel_nodes(adyn, relabel)
|
|
49
|
-
n = len(next(iter(adyn.nodes)))
|
|
50
|
-
assert n == len(names), "list of component names and dimension of configuraitons seem different"
|
|
51
|
-
assert adyn.number_of_nodes() == 2**n, "unexpected number of nodes in the transition graph"
|
|
52
|
-
|
|
53
|
-
def expr_of_cfg(x):
|
|
54
|
-
e = "&".join(f"{'~' if not v else ''}{names[i]}" for i, v in enumerate(x))
|
|
55
|
-
return f"({e})"
|
|
56
|
-
|
|
57
|
-
f = []
|
|
58
|
-
for i in range(n):
|
|
59
|
-
pos = []
|
|
60
|
-
for x in adyn.nodes():
|
|
61
|
-
dx = list(x)
|
|
62
|
-
dx[i] = 1-x[i]
|
|
63
|
-
y = dx if tuple(dx) in adyn[x] else x
|
|
64
|
-
target = y[i]
|
|
65
|
-
if target:
|
|
66
|
-
pos.append(x)
|
|
67
|
-
if not pos:
|
|
68
|
-
f.append(expr("0"))
|
|
69
|
-
else:
|
|
70
|
-
e = expr("|".join(map(expr_of_cfg,pos)))
|
|
71
|
-
e, = espresso_exprs(e.to_dnf())
|
|
72
|
-
f.append(e)
|
|
73
|
-
f = map(expr2str, f)
|
|
74
|
-
f = bn_class(dict(zip(names, f)))
|
|
75
|
-
if simplify:
|
|
76
|
-
f = f.simplify()
|
|
77
|
-
return f
|
|
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
|
|
78
5
|
|
|
79
6
|
if __name__ == "__main__":
|
|
80
7
|
import mpbn
|
|
@@ -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é
|
|
@@ -13,11 +13,21 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
Requires-Dist: boolean.py
|
|
15
15
|
Requires-Dist: clingo
|
|
16
|
-
Requires-Dist:
|
|
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
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
mpbn/__init__.py,sha256=
|
|
2
|
-
mpbn/converters.py,sha256=
|
|
1
|
+
mpbn/__init__.py,sha256=kbHB9JR5z2XP3FNaf2F06X9TaJM3PVVHzAsltT0_awg,22903
|
|
2
|
+
mpbn/converters.py,sha256=6uCjxaJ2MI19_I1h0Pk9YN6PcDx42_C-Ml8ZRMA9uxg,416
|
|
3
3
|
mpbn/simulation.py,sha256=3lk6yu7k2POrSytmtnsGIh596B-qcAgBtl16iZ0vzM4,10594
|
|
4
4
|
mpbn/asplib/eval_circuit.asp,sha256=5rIbkVmvobaQwE_gHr0_USccPuI0QUPuzfw_1011hSE,989
|
|
5
5
|
mpbn/asplib/eval_mixed.asp,sha256=n23pbjtxuWSCerL36Gb6O32bx8_1ukc52l1hQMMs8OE,787
|
|
6
6
|
mpbn/asplib/mp_attractor.asp,sha256=CawLNxlYBZG2cl8XQAxA6EXEZ89Y9iw20CZ73n97Nu8,185
|
|
7
7
|
mpbn/asplib/mp_eval.asp,sha256=PNa1APcKS9bgtCB3BtAdxq0mkg3p_x3G6fWSF3uAgzw,769
|
|
8
8
|
mpbn/asplib/mp_positivereach-np.asp,sha256=SxLzZMszZxy_J14WTO7Me4aXldzxhqnL-ihqcnHCb8I,453
|
|
9
|
-
mpbn/
|
|
9
|
+
mpbn/boolfunclib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
mpbn/boolfunclib/aeon_impl.py,sha256=X41J1tIeKIHomOpL9zrTzYaW12dkE79t_Wp9VXrP7XA,7660
|
|
11
|
+
mpbn/boolfunclib/pyeda_impl.py,sha256=NkgE46MKrSOVInAF5W0QnTbAjVMtLM8ox05oJUMtPHo,5003
|
|
12
|
+
mpbn/cli/__init__.py,sha256=v5C9U2YLR-J02JV-ciZkZoCvEcx7emgEy402mv4CdAg,2124
|
|
10
13
|
mpbn/cli/sim.py,sha256=CHsbE4qp1L0iXyGLL1_zPontFjHL58qlt1_QVSWna68,6086
|
|
11
14
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
15
|
tests/test_attractors.py,sha256=0ga4j0T6db6ljXlAs5jJG0BLB5xZchSdlhwbC-4lj8w,255
|
|
@@ -15,8 +18,8 @@ tests/test_fixpoints.py,sha256=bdDttzimiby48nVkVL-HXZ2rBldICBRbIZrVVbTGfso,786
|
|
|
15
18
|
tests/test_input.py,sha256=mwMKd5UwAIY4Z1uZjYr09Ue8DLxD52CiPSoE-iXytfo,337
|
|
16
19
|
tests/test_reachability.py,sha256=X7anTwFSIYcV2rltJBOW8TcAJnrt3SjYLqttB0eIL_Q,588
|
|
17
20
|
tests/test_reachable_attractors.py,sha256=h78kvgmx9rTJWi3r2DZe_abYDxr02MLJ2iLDJUdLESY,540
|
|
18
|
-
mpbn-
|
|
19
|
-
mpbn-
|
|
20
|
-
mpbn-
|
|
21
|
-
mpbn-
|
|
22
|
-
mpbn-
|
|
21
|
+
mpbn-4.0.dist-info/METADATA,sha256=DJlh3LPEMO30F7gkqQMPlfCfBrrSuQBmG-1ty-Chlew,2364
|
|
22
|
+
mpbn-4.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
23
|
+
mpbn-4.0.dist-info/entry_points.txt,sha256=CpzAc9SkB-mH_dojzt1N3YgDxy8hniDrIGzSHcPDo8g,68
|
|
24
|
+
mpbn-4.0.dist-info/top_level.txt,sha256=oe3jlFHbQ6oIXyE1q7yAAnf1m49oP_jBPUU05d71n74,11
|
|
25
|
+
mpbn-4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|