mpbn 3.8__tar.gz → 4.3.1__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mpbn
3
- Version: 3.8
3
+ Version: 4.3.1
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: pyeda
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
@@ -17,7 +17,7 @@ pip install mpbn
17
17
 
18
18
  ### Using conda
19
19
  ```
20
- conda install -c colomoto -c potassco mpbn
20
+ conda install -c colomoto -c potassco -c daemontus mpbn
21
21
  ```
22
22
 
23
23
  ## Usage
@@ -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
- def is_unate(ba, f):
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 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))
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
- e = expr(str(f).replace("!","~"))
257
- e = e.to_dnf()
258
- if self._simplify is not None:
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] = is_unate(self.ba, f)
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
1
+ Metadata-Version: 2.4
2
2
  Name: mpbn
3
- Version: 3.8
3
+ Version: 4.3.1
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: pyeda
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
@@ -2,6 +2,6 @@ boolean.py
2
2
  clingo
3
3
  colomoto_jupyter>=0.8.0
4
4
  numpy
5
- pyeda
5
+ biodivine_aeon>=1.0.1
6
6
  scipy
7
7
  tqdm
@@ -4,7 +4,7 @@
4
4
  from setuptools import setup, find_packages
5
5
 
6
6
  NAME = "mpbn"
7
- VERSION = "3.8"
7
+ VERSION = "4.3.1"
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
- "pyeda",
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