mpbn 3.1.1__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.1.1 → mpbn-4.3}/PKG-INFO +23 -6
- {mpbn-3.1.1 → mpbn-4.3}/README.md +2 -2
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/__init__.py +272 -108
- mpbn-4.3/mpbn/asplib/eval_circuit.asp +28 -0
- mpbn-4.3/mpbn/asplib/eval_mixed.asp +14 -0
- mpbn-4.3/mpbn/boolfunclib/__init__.py +0 -0
- mpbn-4.3/mpbn/boolfunclib/aeon_impl.py +202 -0
- mpbn-4.3/mpbn/boolfunclib/pyeda_impl.py +143 -0
- mpbn-4.3/mpbn/cli/__init__.py +46 -0
- mpbn-4.3/mpbn/converters.py +14 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/simulation.py +51 -4
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/PKG-INFO +23 -6
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/SOURCES.txt +6 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/requires.txt +1 -1
- {mpbn-3.1.1 → mpbn-4.3}/setup.py +2 -2
- mpbn-3.1.1/mpbn/cli/__init__.py +0 -23
- {mpbn-3.1.1 → mpbn-4.3}/MANIFEST.in +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/asplib/mp_attractor.asp +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/asplib/mp_eval.asp +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/asplib/mp_positivereach-np.asp +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn/cli/sim.py +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/dependency_links.txt +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/entry_points.txt +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/mpbn.egg-info/top_level.txt +0 -0
- {mpbn-3.1.1 → mpbn-4.3}/setup.cfg +0 -0
{mpbn-3.1.1 → 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é
|
|
@@ -11,9 +11,26 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
11
11
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
12
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Requires-Dist: boolean.py
|
|
15
|
+
Requires-Dist: clingo
|
|
16
|
+
Requires-Dist: colomoto_jupyter>=0.8.0
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: biodivine_aeon>=1.0.1
|
|
19
|
+
Requires-Dist: scipy
|
|
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
|
|
31
|
+
|
|
32
|
+
|
|
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).
|
|
17
34
|
|
|
18
35
|
It is built on the `minibn` module from [colomoto-jupyter](https://github.com/colomoto/colomoto-jupyter) which allows importation of Boolean networks in many formats. See http://colomoto.org/notebook.
|
|
19
36
|
|
|
@@ -31,7 +48,7 @@ pip install mpbn
|
|
|
31
48
|
|
|
32
49
|
### Using conda
|
|
33
50
|
```
|
|
34
|
-
conda install -c colomoto -c potassco mpbn
|
|
51
|
+
conda install -c colomoto -c potassco -c daemontus mpbn
|
|
35
52
|
```
|
|
36
53
|
|
|
37
54
|
## Usage
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
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)).
|
|
2
|
+
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).
|
|
3
3
|
|
|
4
4
|
It is built on the `minibn` module from [colomoto-jupyter](https://github.com/colomoto/colomoto-jupyter) which allows importation of Boolean networks in many formats. See http://colomoto.org/notebook.
|
|
5
5
|
|
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides a simple implementation of Most Permissive Boolean Networks
|
|
3
|
-
(MPBNs) for computing reachability properties, attractors,
|
|
4
|
-
|
|
3
|
+
(MPBNs) for computing reachability properties, attractors, reachable attractors.
|
|
4
|
+
Attractors of MPBNs are the *minimal* trap spaces of the underlying Boolean maps.
|
|
5
5
|
See http://dx.doi.org/10.1101/2020.03.22.998377 and https://arxiv.org/abs/1808.10240 for technical details.
|
|
6
6
|
|
|
7
7
|
It relies on clingo Answer-Set Programming solver
|
|
@@ -15,7 +15,7 @@ Quick example:
|
|
|
15
15
|
"a": "!b",
|
|
16
16
|
"b": "!a",
|
|
17
17
|
"c": "!a & b"})
|
|
18
|
-
>>> list(mbn.attractors())
|
|
18
|
+
>>> list(mbn.attractors()) # minimal trap spaces
|
|
19
19
|
[{'a': 0, 'b': 1, 'c': 1}, {'a': 1, 'b': 0, 'c': 0}]
|
|
20
20
|
>>> mbn.reachability({'a': 0, 'b': 1, 'c': 1}, {'a': 1, 'b': 0, 'c': 0})
|
|
21
21
|
False
|
|
@@ -31,9 +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
|
-
from pyeda.boolalg.expr import expr
|
|
37
34
|
sys.setrecursionlimit(max(100000, sys.getrecursionlimit()))
|
|
38
35
|
|
|
39
36
|
__asplibdir__ = os.path.realpath(os.path.join(os.path.dirname(__file__), "asplib"))
|
|
@@ -45,15 +42,21 @@ if hasattr(clingo, "version") and clingo.version() >= (5,5,0):
|
|
|
45
42
|
def aspf(basename):
|
|
46
43
|
return os.path.join(__asplibdir__, basename)
|
|
47
44
|
|
|
48
|
-
def
|
|
49
|
-
s = clingo.Control(clingo_options)
|
|
45
|
+
def _clingo_domrec(mod, limit=0, project=False, extra_opts=[]):
|
|
46
|
+
s = clingo.Control(clingo_options + extra_opts)
|
|
50
47
|
s.configuration.solve.models = limit
|
|
51
|
-
|
|
48
|
+
if project:
|
|
49
|
+
s.configuration.solve.project = 1
|
|
52
50
|
s.configuration.solve.enum_mode = "domRec"
|
|
53
51
|
s.configuration.solver[0].heuristic = "Domain"
|
|
54
|
-
s.configuration.solver[0].dom_mod = "
|
|
52
|
+
s.configuration.solver[0].dom_mod = f"{mod},{16 if project else 0}"
|
|
55
53
|
return s
|
|
56
54
|
|
|
55
|
+
def clingo_subsets(**opts):
|
|
56
|
+
return _clingo_domrec(5, **opts)
|
|
57
|
+
def clingo_supsets(**opts):
|
|
58
|
+
return _clingo_domrec(3, **opts)
|
|
59
|
+
|
|
57
60
|
def clingo_exists():
|
|
58
61
|
s = clingo.Control(clingo_options)
|
|
59
62
|
s.configuration.solve.models = 1
|
|
@@ -71,7 +74,8 @@ def s2v(s):
|
|
|
71
74
|
def v2s(v):
|
|
72
75
|
return 1 if v > 0 else 0
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
|
|
78
|
+
def is_dnf(ba, f, test_unate=False):
|
|
75
79
|
pos_lits = set()
|
|
76
80
|
neg_lits = set()
|
|
77
81
|
def is_lit(f):
|
|
@@ -95,6 +99,8 @@ def is_unate(ba, f):
|
|
|
95
99
|
return False
|
|
96
100
|
|
|
97
101
|
def test_monotonicity():
|
|
102
|
+
if not test_unate:
|
|
103
|
+
return True
|
|
98
104
|
both = pos_lits.intersection(neg_lits)
|
|
99
105
|
return not both
|
|
100
106
|
|
|
@@ -109,33 +115,44 @@ def is_unate(ba, f):
|
|
|
109
115
|
return test_monotonicity()
|
|
110
116
|
return False
|
|
111
117
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
def is_dnf_unate(ba, f):
|
|
119
|
+
return is_dnf(ba, f, test_unate=True)
|
|
120
|
+
|
|
121
|
+
def circuitasp_of_boolfunc(f, i, ba):
|
|
122
|
+
atoms = []
|
|
123
|
+
fid = clingo.String(i)
|
|
124
|
+
def encode(expr):
|
|
125
|
+
if expr == ba.TRUE:
|
|
126
|
+
nodeid = "(constant,1)"
|
|
127
|
+
atoms.append(f"circuit({nodeid}).")
|
|
128
|
+
elif expr == ba.FALSE:
|
|
129
|
+
nodeid = "(constant,-1)"
|
|
130
|
+
atoms.append(f"circuit({nodeid}).")
|
|
131
|
+
elif isinstance(expr, ba.Symbol):
|
|
132
|
+
nodeid = f"(var,{clingo.String(expr.obj)})"
|
|
133
|
+
atoms.append(f"circuit({fid},{nodeid}).")
|
|
134
|
+
else:
|
|
135
|
+
nodeid = f"n{id(expr)}"
|
|
136
|
+
if isinstance(expr, ba.NOT):
|
|
137
|
+
nodetype = "neg"
|
|
138
|
+
elif isinstance(expr, ba.AND):
|
|
139
|
+
nodetype = "and"
|
|
140
|
+
elif isinstance(expr, ba.OR):
|
|
141
|
+
nodetype = "or"
|
|
142
|
+
else:
|
|
143
|
+
raise NotImplementedError(type(expr))
|
|
144
|
+
atoms.append(f"circuit({fid},{nodeid},{nodetype}).")
|
|
145
|
+
for child in expr.args:
|
|
146
|
+
cid = encode(child)
|
|
147
|
+
atoms.append(f"circuitedge({fid},{nodeid},{cid}).")
|
|
148
|
+
return nodeid
|
|
149
|
+
root = encode(f)
|
|
150
|
+
atoms.append(f"circuit({fid},root,{root}).\n")
|
|
151
|
+
return "\n".join(atoms)
|
|
152
|
+
|
|
153
|
+
DEFAULT_ENCODING = "mixed-dnf-bdd"
|
|
154
|
+
DEFAULT_BOOLFUNCLIB = os.environ.get("MPBN_BOOLFUNCLIB", "pyeda")
|
|
155
|
+
SUPPORTED_BOOLFUNCLIBS = ["aeon", "pyeda"]
|
|
139
156
|
|
|
140
157
|
class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
141
158
|
"""
|
|
@@ -143,17 +160,21 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
143
160
|
|
|
144
161
|
Extends ``colomoto.minibn.BooleanNetwork`` class by adding methods for
|
|
145
162
|
computing reachable and attractor properties with the Most Permissive
|
|
146
|
-
|
|
147
|
-
It requires that the Boolean network is *locally monotonic*.
|
|
148
|
-
|
|
149
|
-
Ensures that the Boolean functions are monotonic and in disjunctive normal
|
|
150
|
-
form (DNF).
|
|
151
|
-
The local-monotonic checking requires that a literal never appears
|
|
152
|
-
with both signs in a same Boolean function.
|
|
163
|
+
update mode.
|
|
153
164
|
"""
|
|
165
|
+
supported_encodings = [
|
|
166
|
+
"unate-dnf", "bdd", "circuit",
|
|
167
|
+
"dnf-bdd", "mixed-dnf-bdd",
|
|
168
|
+
"force-unate-dnf"]
|
|
169
|
+
dnf_encodings = ["dnf-bdd", "unate-dnf", "force-unate-dnf",
|
|
170
|
+
"mixed-dnf-bdd"]
|
|
171
|
+
nonpc_encodings = ["circuit"]
|
|
172
|
+
|
|
154
173
|
def __init__(self, bn=minibn.BooleanNetwork(), auto_dnf=True,
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
simplify=False,
|
|
175
|
+
try_unate_hard=False,
|
|
176
|
+
encoding=DEFAULT_ENCODING,
|
|
177
|
+
boolfunclib=DEFAULT_BOOLFUNCLIB):
|
|
157
178
|
"""
|
|
158
179
|
Constructor for :py:class:`.MPBoooleanNetwork`.
|
|
159
180
|
|
|
@@ -162,6 +183,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
162
183
|
:py:class:`colomoto.minibn.BooleanNetwork` constructor
|
|
163
184
|
:param bool auto_dnf: if ``False``, turns off automatic DNF
|
|
164
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.
|
|
165
189
|
|
|
166
190
|
Examples:
|
|
167
191
|
|
|
@@ -170,11 +194,22 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
170
194
|
>>> bn["a"] = ".."; ...
|
|
171
195
|
>>> mbn = MPBooleanNetwork(bn)
|
|
172
196
|
"""
|
|
173
|
-
assert encoding in
|
|
174
|
-
|
|
197
|
+
assert encoding in self.supported_encodings
|
|
198
|
+
assert boolfunclib in SUPPORTED_BOOLFUNCLIBS
|
|
199
|
+
self.auto_dnf = auto_dnf and encoding in self.dnf_encodings
|
|
175
200
|
self.encoding = encoding
|
|
176
201
|
self.try_unate_hard = try_unate_hard
|
|
202
|
+
self._simplify = simplify
|
|
177
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
|
+
|
|
178
213
|
super(MPBooleanNetwork, self).__init__(bn)
|
|
179
214
|
|
|
180
215
|
def __setitem__(self, a, f):
|
|
@@ -186,18 +221,21 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
186
221
|
if isinstance(f, str):
|
|
187
222
|
f = self.ba.parse(f)
|
|
188
223
|
f = self._autobool(f)
|
|
189
|
-
if self.auto_dnf:
|
|
190
|
-
f = self.
|
|
191
|
-
|
|
192
|
-
|
|
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)
|
|
193
228
|
a = self._autokey(a)
|
|
194
|
-
if self.encoding
|
|
195
|
-
self._is_unate[a] =
|
|
229
|
+
if self.encoding in self.dnf_encodings:
|
|
230
|
+
self._is_unate[a] = is_dnf_unate(self.ba, f)
|
|
196
231
|
if self.encoding == "unate-dnf":
|
|
197
232
|
assert self._is_unate[a], f"'{f}' seems not unate. Try simplify()?"
|
|
198
233
|
return super().__setitem__(a, f)
|
|
199
234
|
|
|
200
|
-
def asp_of_bn(self):
|
|
235
|
+
def asp_of_bn(self, encoding=None):
|
|
236
|
+
if encoding is None:
|
|
237
|
+
encoding = self.encoding
|
|
238
|
+
|
|
201
239
|
def clauses_of_dnf(f):
|
|
202
240
|
if isinstance(f, boolean.OR):
|
|
203
241
|
return f.args
|
|
@@ -221,36 +259,65 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
221
259
|
facts = []
|
|
222
260
|
for n, f in self.items():
|
|
223
261
|
facts.append("node(\"{}\").".format(n))
|
|
224
|
-
if
|
|
225
|
-
f_encoding = "bdd"
|
|
226
|
-
elif self.encoding == "unate-dnf":
|
|
262
|
+
if encoding in ["unate-dnf", "force-unate-dnf"]:
|
|
227
263
|
f_encoding = "dnf"
|
|
228
|
-
|
|
264
|
+
elif encoding == "dnf-bdd":
|
|
229
265
|
f_encoding = "dnf" if self._is_unate[n] else "bdd"
|
|
266
|
+
else:
|
|
267
|
+
f_encoding = encoding
|
|
230
268
|
if f == self.ba.FALSE:
|
|
231
269
|
f = False
|
|
232
270
|
elif f == self.ba.TRUE:
|
|
233
271
|
f = True
|
|
234
272
|
if isinstance(f, bool):
|
|
235
|
-
facts.append("
|
|
273
|
+
facts.append("constant(\"{}\",{}).".format(n, s2v(f)))
|
|
236
274
|
elif f_encoding == "dnf":
|
|
237
275
|
facts.extend(encode_dnf(f))
|
|
238
276
|
elif f_encoding == "bdd":
|
|
239
|
-
facts.append(bddasp_of_boolfunc(f, n))
|
|
277
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
278
|
+
elif f_encoding == "mixed-dnf-bdd":
|
|
279
|
+
facts.extend(encode_dnf(f))
|
|
280
|
+
if self._is_unate[n]:
|
|
281
|
+
facts.append(f"unate(\"{n}\").")
|
|
282
|
+
else:
|
|
283
|
+
facts.append(self._bf_impl.bddasp_of_boolfunc(self.ba, f, n))
|
|
284
|
+
elif f_encoding == "circuit":
|
|
285
|
+
facts.append(circuitasp_of_boolfunc(f, n, self.ba))
|
|
240
286
|
return "".join(facts)
|
|
241
287
|
|
|
288
|
+
def _file_eval(self):
|
|
289
|
+
if self.encoding == "circuit":
|
|
290
|
+
f = aspf("eval_circuit.asp")
|
|
291
|
+
elif self.encoding == "mixed-dnf-bdd":
|
|
292
|
+
f = aspf("eval_mixed.asp")
|
|
293
|
+
else:
|
|
294
|
+
f = aspf("mp_eval.asp")
|
|
295
|
+
return f
|
|
296
|
+
|
|
297
|
+
def rules_eval(self):
|
|
298
|
+
f = self._file_eval()
|
|
299
|
+
with open(f, "r") as fp:
|
|
300
|
+
return fp.read()
|
|
301
|
+
def load_eval(self, solver):
|
|
302
|
+
f = self._file_eval()
|
|
303
|
+
solver.load(f)
|
|
304
|
+
|
|
305
|
+
def assert_pc_encoding(self):
|
|
306
|
+
assert self.encoding not in self.nonpc_encodings, "Unsupported encoding"
|
|
307
|
+
|
|
242
308
|
def asp_of_cfg(self, e, t, c):
|
|
243
309
|
facts = ["timepoint({},{}).".format(e,t)]
|
|
244
310
|
facts += [" mp_state({},{},\"{}\",{}).".format(e,t,n,s2v(s))
|
|
245
311
|
for (n,s) in c.items()]
|
|
246
|
-
facts += ["
|
|
247
|
-
|
|
312
|
+
facts += [f"1 {{mp_state({e},{t},N,(-1;1))}} 1 :- node(N)."]
|
|
313
|
+
#facts += [" 1 {{mp_state({},{},\"{}\",(-1;1))}} 1 :- node(N).".format(e,t,n)
|
|
314
|
+
# for n in self if n not in c]
|
|
248
315
|
return "".join(facts)
|
|
249
316
|
|
|
250
317
|
def reachability(self, x, y):
|
|
251
318
|
"""
|
|
252
319
|
Returns ``True`` whenever the configuration `y` is reachable from `x`
|
|
253
|
-
with the Most Permissive
|
|
320
|
+
with the Most Permissive update mode.
|
|
254
321
|
Configurations can be partially defined.
|
|
255
322
|
In that case, returns ``True`` whenever there exists a configuration
|
|
256
323
|
matching with `y` which is reachable with at least one configuration
|
|
@@ -259,8 +326,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
259
326
|
:param dict[str,int] x: initial configuration
|
|
260
327
|
:param dict[str,int] y: target configuration
|
|
261
328
|
"""
|
|
329
|
+
self.assert_pc_encoding()
|
|
262
330
|
s = clingo_exists()
|
|
263
|
-
|
|
331
|
+
self.load_eval(s)
|
|
264
332
|
s.load(aspf("mp_positivereach-np.asp"))
|
|
265
333
|
s.add("base", [], self.asp_of_bn())
|
|
266
334
|
e = "default"
|
|
@@ -273,6 +341,32 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
273
341
|
res = s.solve()
|
|
274
342
|
return res.satisfiable
|
|
275
343
|
|
|
344
|
+
def _ground_rules(self, ctl, rules):
|
|
345
|
+
rules = "\n".join(rules)
|
|
346
|
+
ctl.add("base", [], rules)
|
|
347
|
+
ctl.ground([("base",[])])
|
|
348
|
+
|
|
349
|
+
def _fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
350
|
+
e = "fp"
|
|
351
|
+
t2 = "fp"
|
|
352
|
+
rules = [self.asp_of_cfg(e, t2, constraints)]
|
|
353
|
+
rules.append(f"mp_reach({e},{t2},N,V) :- mp_state({e},{t2},N,V).")
|
|
354
|
+
rules.append(f":- mp_state({e},{t2},N,V), mp_eval({e},{t2},N,-V).")
|
|
355
|
+
rules.append(self.asp_of_bn())
|
|
356
|
+
if reachable_from:
|
|
357
|
+
self.assert_pc_encoding()
|
|
358
|
+
t1 = "0"
|
|
359
|
+
rules.append(open(aspf("mp_positivereach-np.asp")).read())
|
|
360
|
+
rules.append(self.asp_of_cfg(e,t1,reachable_from))
|
|
361
|
+
rules.append("is_reachable({},{},{}).".format(e,t1,t2))
|
|
362
|
+
rules.append(f"#show. #show fixpoint(N,V) : mp_state({e},{t2},N,V).")
|
|
363
|
+
rules.append(open(aspf("mp_eval.asp")).read())
|
|
364
|
+
|
|
365
|
+
project = reachable_from and set(self.keys()).difference(reachable_from)
|
|
366
|
+
s = clingo_enum(limit=limit, project=project)
|
|
367
|
+
self._ground_rules(s, rules)
|
|
368
|
+
return s
|
|
369
|
+
|
|
276
370
|
def fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
277
371
|
"""
|
|
278
372
|
Iterator over fixed points of the MPBN (i.e., of f)
|
|
@@ -285,22 +379,8 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
285
379
|
the given constraints.
|
|
286
380
|
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
287
381
|
"""
|
|
288
|
-
s =
|
|
289
|
-
|
|
290
|
-
e = "fp"
|
|
291
|
-
t2 = "fp"
|
|
292
|
-
s.add("base", [], self.asp_of_cfg(e, t2, constraints))
|
|
293
|
-
s.load(aspf("mp_eval.asp"))
|
|
294
|
-
s.add("base", [], f"mp_reach({e},{t2},N,V) :- mp_state({e},{t2},N,V).")
|
|
295
|
-
s.add("base", [], f":- mp_state({e},{t2},N,V), mp_eval({e},{t2},N,-V).")
|
|
296
|
-
if reachable_from:
|
|
297
|
-
t1 = "0"
|
|
298
|
-
s.load(aspf("mp_positivereach-np.asp"))
|
|
299
|
-
s.add("base", [], self.asp_of_cfg(e,t1,reachable_from))
|
|
300
|
-
s.add("base", [], "is_reachable({},{},{}).".format(e,t1,t2))
|
|
301
|
-
s.add("base", [], f"#show. #show fixpoint(N,V) : mp_state({e},{t2},N,V).")
|
|
302
|
-
|
|
303
|
-
s.ground([("base",[])])
|
|
382
|
+
s = self._fixedpoints(reachable_from=reachable_from,
|
|
383
|
+
constraints=constraints, limit=limit)
|
|
304
384
|
for sol in s.solve(yield_=True):
|
|
305
385
|
x = {n: None for n in self}
|
|
306
386
|
data = sol.symbols(shown=True)
|
|
@@ -314,11 +394,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
314
394
|
x[n] = v
|
|
315
395
|
yield x
|
|
316
396
|
|
|
317
|
-
def
|
|
397
|
+
def count_fixedpoints(self, reachable_from=None, constraints={}, limit=0):
|
|
318
398
|
"""
|
|
319
|
-
|
|
320
|
-
An attractor is an hypercube, represented by a dictionnary mapping every
|
|
321
|
-
component of the network to either ``0``, ``1``, or ``star``.
|
|
399
|
+
Returns number of fixed points
|
|
322
400
|
|
|
323
401
|
:param dict[str,int] reachable_from: restrict to the attractors
|
|
324
402
|
reachable from the given configuration. Whenever partial, restrict
|
|
@@ -327,29 +405,48 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
327
405
|
:param dict[str,int] constraints: consider only attractors matching with
|
|
328
406
|
the given constraints.
|
|
329
407
|
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
330
|
-
:param str star: value to use for components which are free in the
|
|
331
|
-
attractor
|
|
332
408
|
"""
|
|
333
|
-
s =
|
|
334
|
-
|
|
335
|
-
s.
|
|
336
|
-
|
|
409
|
+
s = self._fixedpoints(reachable_from=reachable_from,
|
|
410
|
+
constraints=constraints, limit=limit)
|
|
411
|
+
return sum((1 for _ in s.solve(yield_=True)))
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _trapspaces(self, reachable_from=None, subcube={}, limit=0,
|
|
415
|
+
mode="min", exclude_full=False):
|
|
416
|
+
self.assert_pc_encoding()
|
|
417
|
+
|
|
418
|
+
rules = []
|
|
419
|
+
rules.append(self.asp_of_bn())
|
|
420
|
+
rules.append(self.rules_eval())
|
|
421
|
+
rules.append(open(aspf("mp_attractor.asp")).read())
|
|
422
|
+
rules.append("#show attractor/2.")
|
|
423
|
+
|
|
337
424
|
e = "__a"
|
|
338
425
|
t2 = "final"
|
|
426
|
+
if exclude_full and not subcube:
|
|
427
|
+
rules.append(f"{{ mp_reach({e},{t2},N,(-1;1)): node(N) }} {len(self)*2-1}.")
|
|
339
428
|
if reachable_from:
|
|
340
429
|
t1 = "0"
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for n, b in
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
430
|
+
rules.append(open(aspf("mp_positivereach-np.asp")).read())
|
|
431
|
+
rules.append(self.asp_of_cfg(e,t1,reachable_from))
|
|
432
|
+
rules.append("is_reachable({},{},{}).".format(e,t1,t2))
|
|
433
|
+
rules.append("mp_state({},{},N,V) :- attractor(N,V).".format(e,t2))
|
|
434
|
+
|
|
435
|
+
for n, b in subcube.items():
|
|
436
|
+
if isinstance(b, str):
|
|
437
|
+
b = int(b)
|
|
438
|
+
if b not in [0,1]:
|
|
439
|
+
continue
|
|
440
|
+
rules.append(":- mp_reach({},{},\"{}\",{}).".format(e,t2,n,s2v(1-b)))
|
|
441
|
+
|
|
442
|
+
project = reachable_from and set(self.keys()).difference(reachable_from)
|
|
443
|
+
solver = clingo_subsets if mode == "min" else clingo_supsets
|
|
444
|
+
s = solver(limit=limit, project=project)
|
|
445
|
+
self._ground_rules(s, rules)
|
|
446
|
+
return s
|
|
447
|
+
|
|
448
|
+
def _yield_trapspaces(self, *args, star="*", **kwargs):
|
|
449
|
+
s = self._trapspaces(*args, **kwargs)
|
|
353
450
|
for sol in s.solve(yield_=True):
|
|
354
451
|
attractor = {n: None for n in self}
|
|
355
452
|
data = sol.symbols(shown=True)
|
|
@@ -364,11 +461,77 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
364
461
|
else:
|
|
365
462
|
v = 1 if v == 1 else 0
|
|
366
463
|
if attractor[n] is not None:
|
|
367
|
-
|
|
464
|
+
if star is not None:
|
|
465
|
+
attractor[n] = star
|
|
466
|
+
else:
|
|
467
|
+
del attractor[n]
|
|
368
468
|
else:
|
|
369
469
|
attractor[n] = v
|
|
370
470
|
yield attractor
|
|
371
471
|
|
|
472
|
+
def _count_trapspaces(self, *args, **kwargs):
|
|
473
|
+
s = self._trapspaces(*args, **kwargs)
|
|
474
|
+
return sum((1 for _ in s.solve(yield_=True)))
|
|
475
|
+
|
|
476
|
+
def attractors(self, reachable_from=None, constraints={}, limit=0, star='*'):
|
|
477
|
+
"""
|
|
478
|
+
Iterator over attractors of the MPBN (minimal trap spaces of the BN).
|
|
479
|
+
An attractor is an hypercube, represented by a dictionnary mapping every
|
|
480
|
+
component of the network to either ``0``, ``1``, or ``star``.
|
|
481
|
+
|
|
482
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
483
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
484
|
+
attractors to the one reachable by at least one matching
|
|
485
|
+
configuration.
|
|
486
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
487
|
+
the given constraints.
|
|
488
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
489
|
+
:param str star: value to use for components which are free in the
|
|
490
|
+
attractor
|
|
491
|
+
"""
|
|
492
|
+
return self._yield_trapspaces(reachable_from=reachable_from,
|
|
493
|
+
subcube=constraints, limit=limit, star=star,
|
|
494
|
+
mode="min")
|
|
495
|
+
minimal_trapspaces = attractors
|
|
496
|
+
|
|
497
|
+
def maximal_trapspaces(self, limit=0, subcube={}, star="*",
|
|
498
|
+
exclude_full=True):
|
|
499
|
+
return self._yield_trapspaces(subcube=subcube, limit=limit, star=star,
|
|
500
|
+
mode="max", exclude_full=exclude_full)
|
|
501
|
+
|
|
502
|
+
def count_attractors(self, reachable_from=None, constraints={}, limit=0):
|
|
503
|
+
"""
|
|
504
|
+
Returns number of attractors of the MPBN (minimal trap spaces of the BN).
|
|
505
|
+
|
|
506
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
507
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
508
|
+
attractors to the one reachable by at least one matching
|
|
509
|
+
configuration.
|
|
510
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
511
|
+
the given constraints.
|
|
512
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
513
|
+
"""
|
|
514
|
+
return self._count_trapspaces(reachable_from=reachable_from,
|
|
515
|
+
subcube=constraints, limit=limit,
|
|
516
|
+
mode="min")
|
|
517
|
+
count_minimal_trapspaces = count_attractors
|
|
518
|
+
|
|
519
|
+
def count_maximal_trapspaces(self, reachable_from=None, constraints={}, limit=0):
|
|
520
|
+
"""
|
|
521
|
+
Returns number of attractors of the MPBN (minimal trap spaces of the BN).
|
|
522
|
+
|
|
523
|
+
:param dict[str,int] reachable_from: restrict to the attractors
|
|
524
|
+
reachable from the given configuration. Whenever partial, restrict
|
|
525
|
+
attractors to the one reachable by at least one matching
|
|
526
|
+
configuration.
|
|
527
|
+
:param dict[str,int] constraints: consider only attractors matching with
|
|
528
|
+
the given constraints.
|
|
529
|
+
:param int limit: maximum number of solutions, ``0`` for unlimited.
|
|
530
|
+
"""
|
|
531
|
+
return self._count_trapspaces(reachable_from=reachable_from,
|
|
532
|
+
subcube=constraints, limit=limit,
|
|
533
|
+
mode="max")
|
|
534
|
+
|
|
372
535
|
def has_cyclic_attractor(self):
|
|
373
536
|
for a in self.attractors():
|
|
374
537
|
if "*" in a.values():
|
|
@@ -378,7 +541,7 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
378
541
|
def reachable_from(self, x, reversed=False):
|
|
379
542
|
"""
|
|
380
543
|
Returns an iterator over the configurations reachable from ``x`` with the
|
|
381
|
-
Most Permissive
|
|
544
|
+
Most Permissive update mode.
|
|
382
545
|
Configuration ``x`` can be partially defined: in that case a configuration
|
|
383
546
|
is yielded whnever it is reachable from at least one configuration
|
|
384
547
|
matching with ``x``.
|
|
@@ -386,8 +549,9 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
386
549
|
Whenever ``reversed`` is ``True``, yields over the configurations that can
|
|
387
550
|
reach `x` instead.
|
|
388
551
|
"""
|
|
552
|
+
self.assert_pc_encoding()
|
|
389
553
|
s = clingo_enum()
|
|
390
|
-
|
|
554
|
+
self.load_eval(s)
|
|
391
555
|
s.load(aspf("mp_positivereach-np.asp"))
|
|
392
556
|
s.add("base", [], self.asp_of_bn())
|
|
393
557
|
e = "default"
|
|
@@ -410,7 +574,7 @@ class MPBooleanNetwork(minibn.BooleanNetwork):
|
|
|
410
574
|
def dynamics(self, update_mode="mp", **kwargs):
|
|
411
575
|
"""
|
|
412
576
|
Returns a :py:class:`networkx.DiGraph` object representing the transitions between
|
|
413
|
-
the configurations using the Most Permissive
|
|
577
|
+
the configurations using the Most Permissive update mode by default.
|
|
414
578
|
See :py:meth:`colomoto.minibn.BooleanNetwork.dynamics`.
|
|
415
579
|
"""
|
|
416
580
|
if update_mode in ["mp", "most-permissive"]:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
mp_eval(E,T,N,V) :- timepoint(E,T), node(N), circuit(N,root,C), evalcircuit(E,T,N,C,V).
|
|
2
|
+
mp_eval(E,T,N,V) :- timepoint(E,T), node(N), constant(N,V).
|
|
3
|
+
|
|
4
|
+
%
|
|
5
|
+
% leafs
|
|
6
|
+
%
|
|
7
|
+
evalcircuit(E,T,N,(var,M),V) :- circuit(N,(var,M)), mp_reach(E,T,M,V).
|
|
8
|
+
evalcircuit(E,T,N,(constant,V),V) :- timepoint(E,T),node(N),circuit((constant,V)).
|
|
9
|
+
|
|
10
|
+
%
|
|
11
|
+
% operators
|
|
12
|
+
%
|
|
13
|
+
|
|
14
|
+
% negation
|
|
15
|
+
evalcircuit(E,T,N,C,-V) :- circuit(N,C,neg), circuitedge(N,C,D),
|
|
16
|
+
evalcircuit(E,T,N,D,V).
|
|
17
|
+
|
|
18
|
+
% disjunction
|
|
19
|
+
evalcircuit(E,T,N,C,1) :- circuit(N,C,or), circuitedge(N,C,D),
|
|
20
|
+
evalcircuit(E,T,N,D,1).
|
|
21
|
+
evalcircuit(E,T,N,C,-1) :- timepoint(E,T), circuit(N,C,or);
|
|
22
|
+
evalcircuit(E,T,N,D,-1): circuitedge(N,C,D).
|
|
23
|
+
|
|
24
|
+
% conjunction
|
|
25
|
+
evalcircuit(E,T,N,C,-1) :- circuit(N,C,and), circuitedge(N,C,D),
|
|
26
|
+
evalcircuit(E,T,N,D,-1).
|
|
27
|
+
evalcircuit(E,T,N,C,1) :- timepoint(E,T), circuit(N,C,and);
|
|
28
|
+
evalcircuit(E,T,N,D,1): circuitedge(N,C,D).
|