python-sat 1.9.dev16__cp313-cp313-win_amd64.whl → 1.9.dev18__cp313-cp313-win_amd64.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.

Potentially problematic release.


This version of python-sat might be problematic. Click here for more details.

Files changed (27) hide show
  1. pycard.cp313-win_amd64.pyd +0 -0
  2. pyformula.cp313-win_amd64.pyd +0 -0
  3. pysat/__init__.py +1 -1
  4. pysat/card.py +25 -43
  5. pysat/integer.py +182 -20
  6. pysat/pb.py +122 -131
  7. pysat/solvers.py +221 -24
  8. pysolvers.cp313-win_amd64.pyd +0 -0
  9. {python_sat-1.9.dev16.dist-info → python_sat-1.9.dev18.dist-info}/METADATA +4 -2
  10. {python_sat-1.9.dev16.dist-info → python_sat-1.9.dev18.dist-info}/RECORD +27 -27
  11. {python_sat-1.9.dev16.dist-info → python_sat-1.9.dev18.dist-info}/WHEEL +1 -1
  12. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/approxmc.py +0 -0
  13. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/bbscan.py +0 -0
  14. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/bica.py +0 -0
  15. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/fm.py +0 -0
  16. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/genhard.py +0 -0
  17. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/lbx.py +0 -0
  18. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/lsu.py +0 -0
  19. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/mcsls.py +0 -0
  20. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/models.py +0 -0
  21. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/musx.py +0 -0
  22. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/optux.py +0 -0
  23. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/primer.py +0 -0
  24. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/rc2.py +0 -0
  25. {python_sat-1.9.dev16.data → python_sat-1.9.dev18.data}/scripts/unigen.py +0 -0
  26. {python_sat-1.9.dev16.dist-info → python_sat-1.9.dev18.dist-info}/licenses/LICENSE.txt +0 -0
  27. {python_sat-1.9.dev16.dist-info → python_sat-1.9.dev18.dist-info}/top_level.txt +0 -0
Binary file
Binary file
pysat/__init__.py CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  # current version
12
12
  #==============================================================================
13
- VERSION = (1, 9, 'dev', 16)
13
+ VERSION = (1, 9, 'dev', 18)
14
14
 
15
15
 
16
16
  # PEP440 Format
pysat/card.py CHANGED
@@ -195,46 +195,38 @@ class CardEnc(object):
195
195
  """
196
196
 
197
197
  @classmethod
198
- def _update_vids(cls, cnf, inp, vpool):
198
+ def _update_vids(cls, cnf, top, vpool):
199
199
  """
200
- Update variable ids in the given formula and id pool. The literals
201
- serving as the input to the cardinality constraint are not
202
- updated.
200
+ Update the id pool to account for the auxiliary variables of the
201
+ given formula, i.e. all variables larger than ``top``. If these
202
+ overlap with the ranges marked as occupied in the pool, they are
203
+ renumbered to free ids. Variables up to ``top``, e.g. the input
204
+ literals of the constraint, are not updated.
203
205
 
204
- :param cnf: a list of literals in the sum.
205
- :param inp: the list of input literals
206
- :param vpool: the value of bound :math:`k`.
206
+ :param cnf: the encoding of the constraint
207
+ :param top: the largest variable id of the input
208
+ :param vpool: variable pool to update
207
209
 
208
210
  :type cnf: :class:`.formula.CNFPlus`
209
- :type inp: list(int)
211
+ :type top: int
210
212
  :type vpool: :class:`.formula.IDPool`
211
213
  """
212
214
 
213
- top, vmap = max(inp + [vpool.top]), {} # current top and variable mapping
215
+ # the pool must not hand out the input ids, nor the rest of an
216
+ # occupied range that the input ids reach into
217
+ vpool.top = top
218
+ while vpool._occupied and vpool._occupied[0][0] <= vpool.top:
219
+ vpool.top = max(vpool.top, vpool._occupied[0][1])
220
+ vpool._occupied.pop(0)
214
221
 
215
- # we are going to use this to check if literals are input
216
- inp = set([abs(l) for l in inp])
217
-
218
- # creating a new variable mapping, taking into
219
- # account variables marked as "occupied"
220
- while top < cnf.nv:
221
- top += 1
222
-
223
- # skipping the input literals
224
- if top in inp:
225
- vmap[top] = top
226
- continue
227
-
228
- vpool.top += 1
229
-
230
- while vpool._occupied and vpool.top >= vpool._occupied[0][0]:
231
- if vpool.top <= vpool._occupied[0][1] + 1:
232
- vpool.top = vpool._occupied[0][1] + 1
233
-
234
- vpool._occupied.pop(0)
222
+ if vpool.top == top and not (vpool._occupied and vpool._occupied[0][0] <= cnf.nv):
223
+ # the auxiliary variables already have free ids
224
+ vpool.top = max(vpool.top, cnf.nv)
225
+ return
235
226
 
236
- # mapping this literal to a free one
237
- vmap[top] = vpool.top
227
+ # mapping each auxiliary variable to a free id,
228
+ # taking into account variables marked as "occupied"
229
+ vmap = {var: vpool._next() for var in range(top + 1, cnf.nv + 1)}
238
230
 
239
231
  # updating the clauses
240
232
  for cl in cnf.clauses:
@@ -296,12 +288,7 @@ class CardEnc(object):
296
288
 
297
289
  # updating vpool if necessary
298
290
  if vpool:
299
- if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
300
- cls._update_vids(ret, lits, vpool)
301
- else:
302
- # here, ret.nv id is assumed to be larger than the top id
303
- vpool.top = ret.nv - 1
304
- vpool._next()
291
+ cls._update_vids(ret, top_id, vpool)
305
292
 
306
293
  return ret
307
294
 
@@ -397,12 +384,7 @@ class CardEnc(object):
397
384
 
398
385
  # updating vpool if necessary
399
386
  if vpool:
400
- if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
401
- cls._update_vids(ret, lits, vpool)
402
- else:
403
- # here, ret.nv id is assumed to be larger than the top id
404
- vpool.top = ret.nv - 1
405
- vpool._next()
387
+ cls._update_vids(ret, top_id, vpool)
406
388
 
407
389
  return ret
408
390
 
pysat/integer.py CHANGED
@@ -228,7 +228,9 @@ import numbers
228
228
  from pysat.card import CardEnc, EncType as CardEncType
229
229
  from pysat.engines import BooleanEngine
230
230
  from pysat.formula import CNF, Formula
231
- from pysat.pb import PBEnc, EncType as PBEncType
231
+ from pysat.pb import PBEnc, EncType as PBEncType, NoSuchEncodingError, \
232
+ _pindakaas_db, _pindakaas_cnf
233
+ import pindakaas # after pysat.pb, which checks that it is installed
232
234
 
233
235
 
234
236
  # importable components
@@ -577,7 +579,8 @@ class Integer:
577
579
  """
578
580
 
579
581
  def is_satisfied(lit):
580
- return model[abs(lit) - 1] == lit
582
+ # a literal beyond the model occurs in no clause, so it is free
583
+ return abs(lit) <= len(model) and model[abs(lit) - 1] == lit
581
584
 
582
585
  if self.encoding == 'direct':
583
586
  for value in self.domain:
@@ -752,6 +755,45 @@ class Integer:
752
755
 
753
756
  return wmap, shift
754
757
 
758
+ def _linear_term(self, kind, wmap):
759
+ """
760
+ Inverse of :meth:`linearize` for one kind of literal. Given a map
761
+ from domain values ``v`` to pairs ``(lit, weight)``, where ``lit``
762
+ is the literal of :math:`x \\geq v` (``kind == 'ge'``) or of
763
+ :math:`x = v` (``kind == 'eq'``), possibly negated, return
764
+ ``(coeff, const)`` such that the weighted sum of the literals
765
+ equals ``coeff * X + const``. Return ``None`` if the map does not
766
+ have this form.
767
+
768
+ :param kind: ``'ge'`` or ``'eq'``
769
+ :param wmap: literals and their weights by domain value
770
+ :type kind: str
771
+ :type wmap: dict
772
+
773
+ :rtype: tuple or None
774
+ """
775
+
776
+ if kind == 'ge':
777
+ # x = lb + sum_k [x >= k] and ub - x = sum_k -[x >= k]
778
+ signs = {lit > 0 for lit, _ in wmap.values()}
779
+ wvals = {wght for _, wght in wmap.values()}
780
+ if len(wmap) != self.ub - self.lb or len(signs) != 1 or len(wvals) != 1:
781
+ return None
782
+
783
+ wght = wvals.pop()
784
+ return (wght, -wght * self.lb) if signs.pop() else (-wght, wght * self.ub)
785
+
786
+ # direct literals, the weights must be affine in the values
787
+ if any(lit < 0 for lit, _ in wmap.values()):
788
+ return None
789
+
790
+ wvals = [wmap[v][1] if v in wmap else 0 for v in self.domain]
791
+ coeff = wvals[1] - wvals[0] if len(wvals) > 1 else 0
792
+ if any(w != wvals[0] + coeff * i for i, w in enumerate(wvals)):
793
+ return None
794
+
795
+ return coeff, wvals[0] - coeff * self.lb
796
+
755
797
  def _add_channeling(self):
756
798
  """
757
799
  Clauses linking direct and order encodings.
@@ -2142,26 +2184,45 @@ class IntegerEngine(BooleanEngine):
2142
2184
  self._add_clause([s1, s2], selv=selv)
2143
2185
  return selv
2144
2186
 
2145
- def clausify(self, cardenc=CardEncType.seqcounter, pbenc=PBEncType.best):
2187
+ def clausify(self, cardenc=CardEncType.seqcounter, pbenc=PBEncType.best,
2188
+ intenc=PBEncType.best):
2146
2189
  """
2147
- Clausify all constraints and return a CNF encoding. All linear
2148
- constraints are converted using the user-specified cardinality and
2149
- pseudo-Boolean encoding. The former are utilized for unweighted
2150
- constraints, while the latter are utilized for weighted
2151
- constraints.
2190
+ Clausify all constraints and return a CNF encoding.
2191
+
2192
+ Linear constraints over :class:`Integer` variables are encoded by
2193
+ Pindakaas as *integer* linear constraints, using the encoding
2194
+ ``intenc``. The literals of each :class:`Integer` are passed to
2195
+ Pindakaas as an integer variable, which lets it use integer
2196
+ encodings, e.g. the mixed radix encoding.
2197
+
2198
+ The remaining linear constraints, and all of them if ``intenc``
2199
+ is ``None``, are converted using the user-specified cardinality
2200
+ and pseudo-Boolean encoding. The former are utilized for
2201
+ unweighted constraints, while the latter are utilized for
2202
+ weighted constraints.
2152
2203
 
2153
- Note that parameters ``cardenc`` and ``pbenc`` default to
2154
- :attr:`.card.EncType.seqcounter` and
2155
- :class:`pysat.pb.EncType.best`, respectively.
2204
+ Note that parameters ``cardenc``, ``pbenc`` and ``intenc``
2205
+ default to :attr:`.card.EncType.seqcounter`,
2206
+ :class:`pysat.pb.EncType.best` and
2207
+ :class:`pysat.pb.EncType.best`, respectively. The *native*
2208
+ encodings produce no clauses and raise :class:`ValueError`.
2156
2209
 
2157
2210
  :param cardenc: cardinality encoding for unweighted constraints
2158
2211
  :param pbenc: PB encoding for weighted constraints
2212
+ :param intenc: encoding for integer linear constraints, or ``None``
2159
2213
  :type cardenc: int
2160
2214
  :type pbenc: int
2215
+ :type intenc: int or None
2161
2216
 
2162
2217
  :rtype: :class:`pysat.formula.CNF`
2163
2218
  """
2164
2219
 
2220
+ # native constraints are not clauses, and would be lost
2221
+ if cardenc == CardEncType.native or PBEncType.native in (pbenc, intenc):
2222
+ raise ValueError('Native encodings cannot be clausified')
2223
+ if intenc is not None and intenc not in PBEncType._to_encoder:
2224
+ raise NoSuchEncodingError(intenc)
2225
+
2165
2226
  # we are going to store the resulting formula here
2166
2227
  res = CNF()
2167
2228
 
@@ -2184,8 +2245,19 @@ class IntegerEngine(BooleanEngine):
2184
2245
  # (e.g. reified disjunctions) already cached in the engine
2185
2246
  res.extend(self.clauses)
2186
2247
 
2248
+ # separating integer linear constraints from Boolean ones
2249
+ lcons, iconss = self._lcons, []
2250
+ if intenc is not None:
2251
+ lcons = []
2252
+ for cs in self._lcons:
2253
+ icons = self._integer_terms(cs, vpool)
2254
+ if icons:
2255
+ iconss.append(icons)
2256
+ else:
2257
+ lcons.append(cs)
2258
+
2187
2259
  # the main part is to encode all linear constraints
2188
- for cs in self._lcons:
2260
+ for cs in lcons:
2189
2261
  lits, bound = cs[1][0], cs[1][1]
2190
2262
  weights = {} if len(cs[1]) == 2 else cs[1][2]
2191
2263
 
@@ -2200,13 +2272,11 @@ class IntegerEngine(BooleanEngine):
2200
2272
 
2201
2273
  # we have a common weight wght => cardinality constraint
2202
2274
  if wght is not None:
2203
- new_bound = bound // wght
2204
- if isinstance(new_bound, Decimal):
2205
- new_bound = int(new_bound)
2206
- elif isinstance(new_bound, float):
2207
- new_bound = int(math.floor(new_bound))
2275
+ if isinstance(bound, Decimal):
2276
+ # Decimal's // rounds towards zero, not down
2277
+ new_bound = math.floor(bound / wght)
2208
2278
  else:
2209
- new_bound = int(new_bound)
2279
+ new_bound = int(math.floor(bound // wght))
2210
2280
 
2211
2281
  if new_bound < 0:
2212
2282
  # the bound is negative => constraint is unsatisfiable
@@ -2235,20 +2305,112 @@ class IntegerEngine(BooleanEngine):
2235
2305
  wlist = [int(w) if not isinstance(w, Decimal) else int(w)
2236
2306
  for w in wlist]
2237
2307
 
2308
+ if bound_int < 0:
2309
+ # the weights are non-negative => unsatisfiable
2310
+ res.append([])
2311
+ continue
2312
+
2238
2313
  enc = PBEnc.atmost(lits=lits, weights=wlist, bound=bound_int,
2239
2314
  vpool=vpool, encoding=pbenc)
2240
2315
  res.extend(enc.clauses)
2241
2316
 
2317
+ # integer linear constraints go last, as they share one encoding
2318
+ if iconss:
2319
+ res.extend(self._clausify_integer(iconss, vpool, intenc))
2320
+
2242
2321
  return res
2243
2322
 
2323
+ def _integer_terms(self, cs, vpool):
2324
+ """
2325
+ Recover the integer structure of a linear constraint. The
2326
+ literals of each :class:`Integer` recognized by
2327
+ :meth:`Integer._linear_term` become a single integer term.
2328
+ Return a triple ``(terms, rest, bound)``, where ``terms`` is a
2329
+ list of ``(var, kind, coeff)``, ``rest`` is a list of the
2330
+ remaining weighted literals, and the constraint is ``terms +
2331
+ rest <= bound``. Return ``None`` if there are no integer terms or
2332
+ if the numbers are not integers.
2333
+ """
2334
+
2335
+ _, _, bound, lits, weights = self._linear_key(cs)
2336
+
2337
+ if not self._intlike(bound) or not all(self._intlike(weights[l]) for l in lits):
2338
+ return None
2339
+
2340
+ # grouping the literals by their integer variable and kind
2341
+ groups, rest = defaultdict(dict), []
2342
+ for l in lits:
2343
+ obj, wght = vpool.obj(abs(l)), int(weights[l])
2344
+ if isinstance(obj, tuple) and len(obj) == 3 and isinstance(obj[0], Integer):
2345
+ group = groups[obj[0], obj[1]]
2346
+ if obj[2] not in group:
2347
+ group[obj[2]] = (l, wght)
2348
+ continue
2349
+
2350
+ # a Boolean literal, or the negation of one already in its group
2351
+ rest.append((l, wght))
2352
+
2353
+ terms, bound = [], int(bound)
2354
+ for (var, kind), wmap in groups.items():
2355
+ term = var._linear_term(kind, wmap)
2356
+ if term is None:
2357
+ rest.extend(wmap.values())
2358
+ else:
2359
+ coeff, const = term
2360
+ if coeff:
2361
+ terms.append((var, kind, coeff))
2362
+ bound -= const
2363
+
2364
+ return (terms, rest, bound) if terms else None
2365
+
2366
+ def _clausify_integer(self, iconss, vpool, intenc):
2367
+ """
2368
+ Encode integer linear constraints, as returned by
2369
+ :meth:`_integer_terms`, with Pindakaas. Return the list of
2370
+ clauses.
2371
+ """
2372
+
2373
+ encoder, options = PBEncType._to_encoder[intenc]
2374
+ Lit = pindakaas.Lit.from_raw
2375
+
2376
+ # new variables must also avoid literals that are not in the pool
2377
+ top_id = max([vpool.top] + [abs(l) for _, rest, _ in iconss for l, _ in rest])
2378
+ db = _pindakaas_db(top_id)
2379
+ ivars = {} # integer variables, created lazily
2380
+
2381
+ def ivar(var, kind):
2382
+ if (var, kind) not in ivars:
2383
+ if kind == 'ge':
2384
+ lits = [var.ge(k) for k in range(var.lb + 1, var.ub + 1)]
2385
+ make = db.int_var_from_order_literals
2386
+ else:
2387
+ lits = [var.equals(v) for v in var.domain]
2388
+ make = db.int_var_from_direct_literals
2389
+ ivars[var, kind] = make(var.domain, [Lit(l) for l in lits])
2390
+ return ivars[var, kind]
2391
+
2392
+ try:
2393
+ for terms, rest, bound in iconss:
2394
+ lhs = 0
2395
+ for var, kind, coeff in terms:
2396
+ lhs += coeff * ivar(var, kind)
2397
+ for l, w in rest:
2398
+ lhs += w * Lit(l)
2399
+
2400
+ db.add_encoding(lhs <= bound, encoder, **options)
2401
+ except pindakaas.Unsatisfiable:
2402
+ return [[]]
2403
+
2404
+ return _pindakaas_cnf(db, top_id, vpool=vpool).clauses
2405
+
2244
2406
  @staticmethod
2245
2407
  def _intlike(value):
2246
2408
  """
2247
2409
  Checking if the value can be converted to integer.
2248
2410
  """
2249
2411
 
2250
- # yes, nothing to do
2251
- if isinstance(value, numbers.Integral):
2412
+ # yes, nothing to do (plain ints first, as the ABC check is slow)
2413
+ if type(value) is int or isinstance(value, numbers.Integral):
2252
2414
  return True
2253
2415
 
2254
2416
  # yes, it's a convertable decimal
pysat/pb.py CHANGED
@@ -25,28 +25,23 @@
25
25
 
26
26
  .. note::
27
27
 
28
- Functionality of this module is available only if the `PyPBLib`
28
+ Functionality of this module is available only if the `Pindakaas`
29
29
  package is installed, e.g. from PyPI:
30
30
 
31
31
  .. code-block::
32
32
 
33
- $ pip install pypblib
33
+ $ pip install python-sat[pb]
34
34
 
35
- This module provides access to the basic functionality of the `PyPBLib
36
- library <https://pypi.org/project/pypblib/>`__ developed by the `Logic
37
- Optimization Group <http://ulog.udl.cat/>`__ of the University of Lleida.
38
- PyPBLib provides a user with an extensive Python API to the well-known
39
- `PBLib library <http://tools.computational-logic.org/content/pblib.php>`__
40
- [1]_. Note the PyPBLib has a number of `additional features
41
- <http://hardlog.udl.cat/static/doc/pypblib/html/index.html>`__ that cannot
42
- be accessed through PySAT *at this point*. (One concrete example is a
43
- range of cardinality encodings, which clash with the internal
44
- :mod:`pysat.card` module.) If a user needs some functionality of PyPBLib
45
- missing in this module, he/she may apply PyPBLib as a standalone library,
46
- when working with PySAT.
35
+ This module provides access to the pseudo-Boolean encodings of the
36
+ `Pindakaas library <https://pypi.org/project/pindakaas/>`__ [1]_, a Rust
37
+ library for encoding pseudo-Boolean and integer constraints into CNF.
38
+ Pindakaas has a number of additional features (e.g. integer variables
39
+ and further encodings) that are not accessible through this module. If
40
+ a user needs any of them, Pindakaas can be used as a standalone library
41
+ alongside PySAT.
47
42
 
48
- .. [1] Tobias Philipp, Peter Steinke. *PBLib - A Library for Encoding
49
- Pseudo-Boolean Constraints into CNF*. SAT 2015. pp. 9-16
43
+ .. [1] Hendrik Bierlee, Jip J. Dekker. *Pindakaas*. Software.
44
+ https://doi.org/10.5281/zenodo.10851855
50
45
 
51
46
  A *pseudo-Boolean constraint* is a constraint of the form:
52
47
  :math:`\\left(\\sum_{i=1}^n{a_i\\cdot x_i}\\right)\\circ k`, where
@@ -56,7 +51,9 @@
56
51
  applications. Thus, several *encodings* of pseudo-Boolean constraints into
57
52
  CNF formulas are known [2]_. The list of pseudo-Boolean encodings
58
53
  supported by this module include BDD [3]_ [4]_, sequential weight counters
59
- [5]_, sorting networks [3]_, adder networks [3]_, and binary merge [6]_.
54
+ [5]_, sorting networks [3]_, adder networks [3]_, binary merge [6]_, the
55
+ watchdog encoding [7]_, the generalized totalizer [8]_, and the mixed
56
+ radix encoding [9]_.
60
57
  Access to all cardinality encodings can be made through the main class of
61
58
  this module, which is :class:`.PBEnc`.
62
59
 
@@ -79,6 +76,17 @@
79
76
  Translation of Pseudo-Boolean Constraints into CNF Such That
80
77
  Generalized Arc Consistency Is Maintained*. KI. 2014. pp. 123-134
81
78
 
79
+ .. [7] Olivier Bailleux, Yacine Boufkhad, Olivier Roussel. *New
80
+ Encodings of Pseudo-Boolean Constraints into CNF*. SAT 2009.
81
+ pp. 181-194
82
+
83
+ .. [8] Saurabh Joshi, Ruben Martins, Vasco Manquinho. *Generalized Totalizer
84
+ Encoding for Pseudo-Boolean Constraints*. CP 2015. pp. 200-209
85
+
86
+ .. [9] Anpeng Zha, Miyuki Koshimura, Hiroshi Fujita. *N-level Modulo-based
87
+ CNF Encodings of Pseudo-Boolean Constraints for MaxSAT*. Constraints
88
+ 24(2). 2019. pp. 133-161
89
+
82
90
  ==============
83
91
  Module details
84
92
  ==============
@@ -86,15 +94,16 @@
86
94
 
87
95
  #
88
96
  #==============================================================================
89
- import math
97
+ import operator
98
+ from pysat.card import CardEnc
90
99
  from pysat.formula import CNFPlus
91
100
 
92
- # checking whether or not pypblib is available and working as expected
93
- pblib_present = True
101
+ # checking whether or not pindakaas is available
102
+ pindakaas_present = True
94
103
  try:
95
- from pypblib import pblib
104
+ import pindakaas
96
105
  except ImportError:
97
- pblib_present = False
106
+ pindakaas_present = False
98
107
 
99
108
 
100
109
  #
@@ -124,6 +133,9 @@ class EncType(object):
124
133
  adder = 4
125
134
  binmerge = 5
126
135
  native = 6
136
+ watchdog = 7
137
+ totalizer = 8
138
+ mixedradix = 9
127
139
 
128
140
  The desired encoding can be selected either directly by its integer
129
141
  identifier, e.g. ``2``, or by its alphabetical name, e.g.
@@ -132,12 +144,16 @@ class EncType(object):
132
144
  All the encodings are produced and returned as a list of clauses in
133
145
  the :class:`pysat.formula.CNFPlus` format.
134
146
 
135
- Note that the encoding type can be set to ``best``, in which case the
136
- encoder selects one of the other encodings from the list (in most
137
- cases, this invokes the ``bdd`` encoder).
147
+ Note that the encoding type can be set to ``best``, in which case
148
+ Pindakaas uses its default encoder for the shape of the constraint
149
+ (e.g. the totalizer for PB and cardinality constraints). Any other
150
+ encoding is applied to *every* shape of constraint, including
151
+ cardinality and AtMostOne constraints. Finally, ``binmerge`` is the
152
+ ``watchdog`` encoding of Pindakaas, counting with sorting networks
153
+ rather than totalizers.
138
154
  """
139
155
 
140
- assert pblib_present, 'Package \'pypblib\' is unavailable. Check your installation.'
156
+ assert pindakaas_present, 'Package \'pindakaas\' is unavailable. Install it with \'pip install python-sat[pb]\'.'
141
157
 
142
158
  best = 0
143
159
  bdd = 1
@@ -146,23 +162,61 @@ class EncType(object):
146
162
  adder = 4
147
163
  binmerge = 5
148
164
  native = 6
149
-
150
- # mapping from internal encoding identifiers to the ones of PyPBLib
151
- _to_pbenc = {
152
- best: pblib.PB_BEST,
153
- bdd: pblib.PB_BDD,
154
- seqcounter: pblib.PB_SWC,
155
- sortnetwrk: pblib.PB_SORTINGNETWORKS,
156
- adder: pblib.PB_ADDER,
157
- binmerge: pblib.PB_BINARY_MERGE
165
+ watchdog = 7
166
+ totalizer = 8
167
+ mixedradix = 9
168
+
169
+ # mapping from internal encoding identifiers to Pindakaas encoders and
170
+ # their options
171
+ _to_encoder = {
172
+ best: (None, {}),
173
+ bdd: (pindakaas.Encoder.DECISION_DIAGRAM, {}),
174
+ seqcounter: (pindakaas.Encoder.SEQUENTIAL_COUNTER, {}),
175
+ sortnetwrk: (pindakaas.Encoder.SORTING_NETWORK, {}),
176
+ adder: (pindakaas.Encoder.ADDER, {}),
177
+ binmerge: (pindakaas.Encoder.WATCHDOG, {'sorting_networks': True}),
178
+ watchdog: (pindakaas.Encoder.WATCHDOG, {}),
179
+ totalizer: (pindakaas.Encoder.TOTALIZER, {}),
180
+ mixedradix: (pindakaas.Encoder.MIXED_RADIX, {})
158
181
  }
159
182
 
160
- # mapping from internal comparator identifiers to the ones of PyPBLib
161
- _to_pbcmp = {
162
- '<': pblib.LEQ,
163
- '>': pblib.GEQ,
164
- '=': pblib.BOTH
165
- }
183
+ # mapping from internal comparator identifiers to operators
184
+ _to_cmp = {'<': operator.le, '>': operator.ge, '=': operator.eq}
185
+
186
+
187
+ #
188
+ #==============================================================================
189
+ def _pindakaas_db(top_id):
190
+ """
191
+ Create an empty ``pindakaas.CNF``, in which all new (auxiliary)
192
+ variables are larger than ``top_id``.
193
+ """
194
+
195
+ db = pindakaas.CNF()
196
+ db.new_vars(top_id)
197
+
198
+ return db
199
+
200
+
201
+ #
202
+ #==============================================================================
203
+ def _pindakaas_cnf(db, top_id, vpool=None):
204
+ """
205
+ Convert the clauses of a ``pindakaas.CNF`` created by
206
+ :func:`_pindakaas_db` into a :class:`pysat.formula.CNFPlus`. If
207
+ ``vpool`` is given, it is updated to account for the auxiliary
208
+ variables, i.e. those larger than ``top_id``.
209
+ """
210
+
211
+ ret = CNFPlus()
212
+ ret.clauses = db._clauses_as_int()
213
+ ret.nv = len(db.variables())
214
+
215
+ # updating vpool if necessary
216
+ if vpool:
217
+ CardEnc._update_vids(ret, top_id, vpool)
218
+
219
+ return ret
166
220
 
167
221
 
168
222
  #
@@ -187,66 +241,17 @@ class PBEnc(object):
187
241
  >>> from pysat.pb import *
188
242
  >>> cnf = PBEnc.atmost(lits=[1, 2, 3], weights=[1, 2, 3], bound=3)
189
243
  >>> print(cnf.clauses)
190
- [[4], [-1, -5], [-2, -5], [5, -3, -6], [6]]
244
+ [[-5, 4], [-6, 5], [-2, 5], [-1, 4], [-1, -2, 6], [-4, -3]]
191
245
  >>> cnf = PBEnc.equals(lits=[1, 2, 3], weights=[1, 2, 3], bound=3, encoding=EncType.bdd)
192
246
  >>> print(cnf.clauses)
193
- [[4], [-5, -2], [-5, 2, -1], [-5, -1], [-6, 1], [-7, -2, 6], [-7, 2], [-7, 6], [-8, -3, 5], [-8, 3, 7], [-8, 5, 7], [8]]
247
+ [[-1, 5], [1, -5], [-2, 4], [-5, 4], [2, -4], [5, -4], [-4, -3], [4, 3]]
194
248
  """
195
249
 
196
- @classmethod
197
- def _update_vids(cls, cnf, inp, vpool):
198
- """
199
- Update variable ids in the given formula and id pool. The literals
200
- serving as the input to the cardinality constraint are not
201
- updated.
202
-
203
- :param cnf: a list of literals in the sum.
204
- :param inp: the list of input literals
205
- :param vpool: the value of bound :math:`k`.
206
-
207
- :type cnf: :class:`.formula.CNFPlus`
208
- :type inp: list(int)
209
- :type vpool: :class:`.formula.IDPool`
210
- """
211
-
212
- top, vmap = max(inp + [vpool.top]), {} # current top and variable mapping
213
-
214
- # we are going to use this to check if literals are input
215
- inp = set([abs(l) for l in inp])
216
-
217
- # creating a new variable mapping, taking into
218
- # account variables marked as "occupied"
219
- while top < cnf.nv:
220
- top += 1
221
-
222
- # skipping the input literals
223
- if top in inp:
224
- vmap[top] = top
225
- continue
226
-
227
- vpool.top += 1
228
-
229
- while vpool._occupied and vpool.top >= vpool._occupied[0][0]:
230
- if vpool.top <= vpool._occupied[0][1] + 1:
231
- vpool.top = vpool._occupied[0][1] + 1
232
-
233
- vpool._occupied.pop(0)
234
-
235
- # mapping this literal to a free one
236
- vmap[top] = vpool.top
237
-
238
- # updating the clauses
239
- for cl in cnf.clauses:
240
- cl[:] = map(lambda l: int(math.copysign(vmap[abs(l)], l)) if abs(l) in vmap else l, cl)
241
-
242
- # updating the number of variables
243
- cnf.nv = vpool.top
244
-
245
250
  @classmethod
246
251
  def _encode(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
247
252
  encoding=EncType.best, comparator='<', conditionals=None):
248
253
  """
249
- This is the method that wraps the encoder of PyPBLib. Although the
254
+ This is the method that wraps the encoders of Pindakaas. Although the
250
255
  method can be invoked directly, a user is expected to call one of
251
256
  the following methods instead: :meth:`atmost`, :meth:`atleast`, or
252
257
  :meth:`equals`.
@@ -275,7 +280,7 @@ class PBEnc(object):
275
280
  :rtype: :class:`pysat.formula.CNFPlus`
276
281
  """
277
282
 
278
- if encoding < 0 or encoding > 6:
283
+ if encoding < 0 or encoding > 9:
279
284
  raise(NoSuchEncodingError(encoding))
280
285
 
281
286
  # checking if the bound is meaningless for any encoding
@@ -295,15 +300,14 @@ class PBEnc(object):
295
300
  # preparing weighted literals
296
301
  if weights:
297
302
  assert len(lits) == len(weights), 'Same number of literals and weights is expected.'
298
- wlits = [(l, w) for l, w in zip(lits, weights)]
303
+ lits, weights = list(lits), list(weights)
299
304
  else:
300
305
  if all(map(lambda lw: (type(lw) in (list, tuple)) and len(lw) == 2, lits)):
301
306
  # literals are already weighted
302
- lits, weight = zip(*lits) # unweighted literals for getting top_id
307
+ lits, weights = map(list, zip(*lits))
303
308
  elif all(map(lambda l: type(l) is int, lits)):
304
309
  # no weights are provided => all weights are units
305
- wlits = [(l, 1) for l in lits]
306
- weights = [1 for l in lits]
310
+ lits, weights = list(lits), [1 for l in lits]
307
311
  else:
308
312
  assert 0, 'Incorrect literals given.'
309
313
 
@@ -330,40 +334,27 @@ class PBEnc(object):
330
334
 
331
335
  return ret
332
336
 
333
- assert pblib_present, 'Package \'pypblib\' is unavailable. Check your installation.'
334
-
335
- # pseudo-Boolean constraint and variable manager
336
- constr = pblib.PBConstraint([pblib.WeightedLit(*wl) for wl in wlits],
337
- EncType._to_pbcmp[comparator], bound)
338
-
339
- varmgr = pblib.AuxVarManager(top_id + 1)
337
+ Lit = pindakaas.Lit.from_raw
340
338
 
341
- # add optional conditionals
342
- if len(conditionals) > 0:
343
- constr.add_conditionals(conditionals)
339
+ # the weighted sum (in place, as `+` copies the sum)
340
+ lhs = 0
341
+ for l, w in zip(lits, weights):
342
+ lhs += w * Lit(l)
344
343
 
345
- # encoder configuration
346
- config = pblib.PBConfig()
347
- config.set_PB_Encoder(EncType._to_pbenc[encoding])
344
+ constr = EncType._to_cmp[comparator](lhs, bound)
345
+ encoder, options = EncType._to_encoder[encoding]
348
346
 
349
- # encoding
350
- result = pblib.VectorClauseDatabase(config)
351
- pb2cnf = pblib.Pb2cnf(config)
352
- pb2cnf.encode(constr, result, varmgr)
353
-
354
- # extracting clauses
355
- ret.clauses = result.get_clauses()
356
- ret.nv = max(ret.nv, top_id, varmgr.get_biggest_returned_auxvar()) # needed if no auxiliary variable is used
357
-
358
- # updating vpool if necessary
359
- if vpool:
360
- if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
361
- cls._update_vids(ret, vpool, lits)
362
- else:
363
- vpool.top = ret.nv - 1
364
- vpool._next()
347
+ db = _pindakaas_db(top_id)
348
+ try:
349
+ db.add_encoding(constr, encoder,
350
+ conditions=[Lit(l) for l in conditionals], **options)
351
+ except pindakaas.Unsatisfiable:
352
+ # the constraint cannot hold, so neither can all conditionals
353
+ ret = _pindakaas_cnf(db, top_id, vpool=vpool)
354
+ ret.clauses = [[-l for l in conditionals]]
355
+ return ret
365
356
 
366
- return ret
357
+ return _pindakaas_cnf(db, top_id, vpool=vpool)
367
358
 
368
359
  @classmethod
369
360
  def leq(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
@@ -373,14 +364,14 @@ class PBEnc(object):
373
364
  (weighted AtMostK) constraint, i.e. of
374
365
  :math:`\\sum_{i=1}^{n}{a_i\\cdot x_i}\\leq k`. The resulting set
375
366
  of clauses is returned as an object of class
376
- :class:`.formula.CNF`.
367
+ :class:`.formula.CNFPlus`.
377
368
 
378
369
  The input list of literals can contain either integers or pairs
379
370
  ``(l, w)``, where ``l`` is an integer literal and ``w`` is an
380
371
  integer weight. The latter can be done only if no ``weights`` are
381
372
  specified separately. The type of encoding to use can be specified
382
373
  using the ``encoding`` parameter. By default, it is set to
383
- ``EncType.best``, i.e. it is up to the PBLib encoder to choose the
374
+ ``EncType.best``, i.e. it is up to Pindakaas to choose the
384
375
  encoding type.
385
376
 
386
377
  The final optional argument is ``conditionals``, where a list of
@@ -415,7 +406,7 @@ class PBEnc(object):
415
406
  def atmost(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
416
407
  encoding=EncType.best, conditionals=None):
417
408
  """
418
- A synonim for :meth:`PBEnc.leq`.
409
+ A synonym for :meth:`PBEnc.leq`.
419
410
  """
420
411
 
421
412
  return cls.leq(lits, weights=weights, bound=bound, top_id=top_id,
pysat/solvers.py CHANGED
@@ -710,7 +710,26 @@ class Solver(object):
710
710
  else:
711
711
  raise NotImplementedError('Extension-variable mining is unsupported by this solver')
712
712
 
713
- def xpairs(self):
713
+ def shift_xscope(self, old, new):
714
+ """
715
+ Move the scope of extension-variable mining from the variables
716
+ of the literals in ``old`` to those of the literals in ``new``,
717
+ e.g. from one call's assumptions to the next one's. Only the
718
+ difference is passed on, so a scope that stays put costs nothing.
719
+
720
+ :param old: literals whose variables are in scope now
721
+ :param new: literals whose variables should be in scope instead
722
+ :type old: iterable(int)
723
+ :type new: iterable(int)
724
+ """
725
+
726
+ if self.solver:
727
+ if type(self.solver) in (Ergo,):
728
+ self.solver.shift_xscope(old, new)
729
+ else:
730
+ raise NotImplementedError('Extension-variable mining is unsupported by this solver')
731
+
732
+ def xpairs(self, lits=None):
714
733
  """
715
734
  Return the literal pairs the extension-variable miner is currently
716
735
  tracking, as a list of ``(la, lb, z, count)`` tuples. A pair that
@@ -720,17 +739,64 @@ class Solver(object):
720
739
  0`` and a ``count`` saying how reliably it has recurred so far.
721
740
  Promoted pairs come first.
722
741
 
742
+ If parameter ``lits`` is given, only the pairs over those literals
743
+ are reported, which is normally what a caller wants: the literals
744
+ it is about to encode. The literals must be distinct.
745
+
723
746
  The result is valid until the next call to :meth:`solve`,
724
747
  :meth:`solve_limited` or :meth:`propagate`, like a model or an
725
748
  unsatisfiable core. Adopting a ``z`` beyond that point requires
726
749
  :meth:`xpin`.
727
750
 
751
+ :param lits: literals to ask about, or ``None`` for every pair
752
+ :type lits: iterable(int)
753
+
728
754
  :rtype: list(tuple(int, int, int, float))
729
755
  """
730
756
 
731
757
  if self.solver:
732
758
  if type(self.solver) in (Ergo,):
733
- return self.solver.xpairs()
759
+ return self.solver.xpairs(lits)
760
+ else:
761
+ raise NotImplementedError('Extension-variable mining is unsupported by this solver')
762
+
763
+ def xname(self, pairs):
764
+ """
765
+ Tell the solver about the names of the literal pairs given
766
+ externally. The solver adds the definition clauses, stops its
767
+ miner from introducing other names for the same pairs, and
768
+ rewrites its learnt clauses accordingly. Pair names adopted this
769
+ way are never retired.
770
+
771
+ .. note::
772
+
773
+ A pair that already has a name, and a ``z`` that already
774
+ names another pair, are both ignored.
775
+
776
+ :param pairs: the pairs to name
777
+ :type pairs: iterable(tuple(int, int, int))
778
+ """
779
+
780
+ if self.solver:
781
+ if type(self.solver) in (Ergo,):
782
+ self.solver.xname(pairs)
783
+ else:
784
+ raise NotImplementedError('Extension-variable mining is unsupported by this solver')
785
+
786
+ def xblock(self, pairs):
787
+ """
788
+ Tell the solver that the given literal pairs are encoded
789
+ externally, so its miner must never consider them again. Unlike
790
+ :meth:`xname`, nothing is defined, recorded or rewritten: the
791
+ caller's own names for the pairs stay ordinary variables.
792
+
793
+ :param pairs: the pairs to keep the miner off
794
+ :type pairs: iterable(tuple(int, int))
795
+ """
796
+
797
+ if self.solver:
798
+ if type(self.solver) in (Ergo,):
799
+ self.solver.xblock(pairs)
734
800
  else:
735
801
  raise NotImplementedError('Extension-variable mining is unsupported by this solver')
736
802
 
@@ -1293,7 +1359,18 @@ class Solver(object):
1293
1359
  if self.solver:
1294
1360
  return self.solver.propagate(assumptions, phase_saving)
1295
1361
 
1296
- def set_phases(self, literals=[]):
1362
+ def apply_jw(self):
1363
+ """
1364
+ Recompute and apply JW scores to the current formula.
1365
+ """
1366
+
1367
+ if self.solver:
1368
+ if not isinstance(self.solver, Ergo):
1369
+ raise NotImplementedError('Applying JW scores is unsupported by this solver')
1370
+
1371
+ self.solver.apply_jw()
1372
+
1373
+ def set_phases(self, literals=[], hard=False):
1297
1374
  """
1298
1375
  The method takes a list of literals as an argument and sets
1299
1376
  *phases* (or MiniSat-like *polarities*) of the corresponding
@@ -1312,7 +1389,12 @@ class Solver(object):
1312
1389
  Also **note** that :class:`Cadical103`, :class:`Cadical153`, and
1313
1390
  :class:`Cadical195` do not support this functionality.
1314
1391
 
1315
- :param literals: a list of literals.
1392
+ Experimental default parameter ``hard=True`` is not currently
1393
+ supported and is ignored.
1394
+
1395
+ :param hard: persistent branching preferences
1396
+ :type hard: bool
1397
+ :param literals: a list of literals
1316
1398
  :type literals: iterable(int)
1317
1399
 
1318
1400
  Usage example:
@@ -1334,6 +1416,11 @@ class Solver(object):
1334
1416
  """
1335
1417
 
1336
1418
  if self.solver:
1419
+ if hard:
1420
+ if not isinstance(self.solver, Ergo):
1421
+ raise NotImplementedError('Setting persistent phases is unsupported by this solver')
1422
+ return self.solver.set_phases(literals, hard=True)
1423
+
1337
1424
  return self.solver.set_phases(literals)
1338
1425
 
1339
1426
  def get_status(self):
@@ -3708,12 +3795,21 @@ class Ergo(object):
3708
3795
  # creating the engine
3709
3796
  self.activate_atmost()
3710
3797
 
3711
- for clause in bootstrap_with:
3712
- if not clause or isinstance(clause[0], int): # it is a clause
3713
- self.add_clause(clause)
3714
- else:
3715
- self.add_atmost(clause[0], clause[1],
3716
- weights=clause[2] if len(clause) == 3 else [])
3798
+ if not self.supports_atmost() and getattr(self.add_clause, '__func__', None) is Ergo.add_clause:
3799
+ for clause in bootstrap_with:
3800
+ try:
3801
+ self.add_clause(clause)
3802
+ except TypeError:
3803
+ if not clause or isinstance(clause[0], int):
3804
+ raise
3805
+ self.add_atmost(clause[0], clause[1], weights=clause[2] if len(clause) == 3 else [])
3806
+ else:
3807
+ for clause in bootstrap_with:
3808
+ if not clause or isinstance(clause[0], int): # it is a clause
3809
+ self.add_clause(clause)
3810
+ else:
3811
+ self.add_atmost(clause[0], clause[1],
3812
+ weights=clause[2] if len(clause) == 3 else [])
3717
3813
 
3718
3814
  if warm_start:
3719
3815
  self.start_mode(warm=True)
@@ -3915,13 +4011,22 @@ class Ergo(object):
3915
4011
 
3916
4012
  return bool(st), props if props != None else []
3917
4013
 
3918
- def set_phases(self, literals=[]):
4014
+ def apply_jw(self):
3919
4015
  """
3920
- Sets polarities of a given list of variables.
4016
+ Apply the selected JW mode now; repeat after adding clauses if needed.
3921
4017
  """
3922
4018
 
3923
4019
  if self.ergo:
3924
- pysolvers.ergo_setphases(self.ergo, literals)
4020
+ pysolvers.ergo_apply_jw(self.ergo)
4021
+
4022
+ def set_phases(self, literals=[], hard=False):
4023
+ """
4024
+ Set branching polarities; ``hard=True`` overrides phase saving.
4025
+ ``hard=False`` releases persistent preferences for these variables.
4026
+ """
4027
+
4028
+ if self.ergo:
4029
+ pysolvers.ergo_setphases(self.ergo, literals, bool(hard))
3925
4030
 
3926
4031
  def set_activities(self, activities={}):
3927
4032
  """
@@ -3974,6 +4079,13 @@ class Ergo(object):
3974
4079
  applied first, so any other parameter given in the same call
3975
4080
  overrides the emulated preset.
3976
4081
 
4082
+ .. note::
4083
+
4084
+ A JW mode, if requested, is applied (after all parameters) to
4085
+ the formula loaded at this call. Use ``apply_jw()`` directly
4086
+ to recompute after adding clauses. Mode 'none' performs no
4087
+ scoring and preserves existing phases.
4088
+
3977
4089
  The list of available options:
3978
4090
 
3979
4091
  * ``emulate``: emulate another solver's tuning -- one of
@@ -4033,6 +4145,18 @@ class Ergo(object):
4033
4145
  * ``ext-escalate``: how quickly ``ext-threshold`` grows
4034
4146
  with the number of variables already introduced (float)
4035
4147
 
4148
+ * ``ext-rewrite``: whether a promoted or named pair may
4149
+ be substituted into the clause database (default
4150
+ ``True``); switching it off also stops mining from
4151
+ promoting, since a variable nothing references retires
4152
+ the moment it is created
4153
+
4154
+ * ``ext-equiv``: whether an extension variable gets the
4155
+ ``la | lb -> z`` half of its definition (default
4156
+ ``True``); without it ``z`` is only the upper bound a
4157
+ totalizer would have encoded itself, which a rewrite
4158
+ cannot use, so it forces ``ext-rewrite`` off
4159
+
4036
4160
  :param parameters: parameter names mapped to their values
4037
4161
  :type parameters: dict
4038
4162
  """
@@ -4046,6 +4170,10 @@ class Ergo(object):
4046
4170
  if name != 'emulate':
4047
4171
  pysolvers.ergo_set(self.ergo, name, value)
4048
4172
 
4173
+ # setting JW mode if not 'none'
4174
+ if parameters.get('jw-mode', 'none') != 'none':
4175
+ self.apply_jw()
4176
+
4049
4177
  def set_xscope(self, add=[], remove=[]):
4050
4178
  """
4051
4179
  Adjust the scope of extension-variable mining: a pair of
@@ -4070,7 +4198,21 @@ class Ergo(object):
4070
4198
  if self.ergo:
4071
4199
  pysolvers.ergo_setxscope(self.ergo, add, remove)
4072
4200
 
4073
- def xpairs(self):
4201
+ def shift_xscope(self, old, new):
4202
+ """
4203
+ Move the mining scope from the variables of ``old`` to those of
4204
+ ``new``; only the difference reaches Ergo.
4205
+
4206
+ :param old: literals whose variables are in scope now
4207
+ :param new: literals whose variables should be in scope instead
4208
+ :type old: iterable(int)
4209
+ :type new: iterable(int)
4210
+ """
4211
+
4212
+ if self.ergo:
4213
+ pysolvers.ergo_shiftxscope(self.ergo, old, new)
4214
+
4215
+ def xpairs(self, lits=None):
4074
4216
  """
4075
4217
  Return the literal pairs the extension-variable miner is currently
4076
4218
  tracking, as a list of ``(la, lb, z, count)`` tuples. A pair that
@@ -4080,16 +4222,57 @@ class Ergo(object):
4080
4222
  0`` and a ``count`` saying how reliably it has recurred so far.
4081
4223
  Promoted pairs come first.
4082
4224
 
4225
+ If ``lits`` is given, only the pairs over those literals are
4226
+ reported, which is normally what a caller wants: the literals it
4227
+ is about to encode. Asking narrowly is also much cheaper than
4228
+ taking the whole database home and sifting it there. The
4229
+ literals must be distinct.
4230
+
4083
4231
  The result is valid until the next call to :meth:`solve`,
4084
4232
  :meth:`solve_limited` or :meth:`propagate`, like a model or an
4085
4233
  unsatisfiable core. Adopting a ``z`` beyond that point requires
4086
4234
  :meth:`xpin`.
4087
4235
 
4236
+ :param lits: literals to ask about, or ``None`` for every pair
4237
+ :type lits: iterable(int)
4238
+
4088
4239
  :rtype: list(tuple(int, int, int, float))
4089
4240
  """
4090
4241
 
4091
4242
  if self.ergo:
4092
- return pysolvers.ergo_xpairs(self.ergo)
4243
+ return pysolvers.ergo_xpairs(self.ergo, lits)
4244
+
4245
+ def xname(self, pairs):
4246
+ """
4247
+ Adopt variables of the caller's own as names for literal pairs,
4248
+ i.e. register ``z <-> la | lb`` for a pair the caller has encoded
4249
+ itself. Ergo adds ``z``'s definition clauses, records the pair so
4250
+ its miner never mints a second name for it, and rewrites its
4251
+ learnt clauses onto ``z``. An adopted name is pinned on arrival
4252
+ and never retires.
4253
+
4254
+ A pair that already has a name, and a ``z`` that already names
4255
+ another pair, are both ignored.
4256
+
4257
+ :param pairs: the pairs to name
4258
+ :type pairs: iterable(tuple(int, int, int))
4259
+ """
4260
+
4261
+ if self.ergo:
4262
+ pysolvers.ergo_xname(self.ergo, [l for p in pairs for l in p])
4263
+
4264
+ def xblock(self, pairs):
4265
+ """
4266
+ Keep the miner off literal pairs the caller encodes itself, for
4267
+ good. Nothing is defined, recorded or rewritten, and the
4268
+ caller's own variable for a pair is none of Ergo's business.
4269
+
4270
+ :param pairs: the pairs to keep the miner off
4271
+ :type pairs: iterable(tuple(int, int))
4272
+ """
4273
+
4274
+ if self.ergo:
4275
+ pysolvers.ergo_xblock(self.ergo, [l for p in pairs for l in p[:2]])
4093
4276
 
4094
4277
  def xpin(self, variables):
4095
4278
  """
@@ -4272,16 +4455,30 @@ class Ergo(object):
4272
4455
  if not self.supports_atmost():
4273
4456
  raise NotImplementedError('Native atmost constraints are currently disabled')
4274
4457
 
4275
- for clause in formula:
4276
- if not clause or isinstance(clause[0], int): # it is a clause
4277
- res = self.add_clause(clause, no_return)
4278
- else:
4279
- res = self.add_atmost(clause[0], clause[1],
4280
- weights=clause[2] if len(clause) == 3 else [],
4281
- no_return=no_return)
4458
+ if not self.supports_atmost() and getattr(self.add_clause, '__func__', None) is Ergo.add_clause:
4459
+ for clause in formula:
4460
+ try:
4461
+ res = self.add_clause(clause, no_return)
4462
+ except TypeError:
4463
+ if not clause or isinstance(clause[0], int):
4464
+ raise
4465
+ res = self.add_atmost(clause[0], clause[1],
4466
+ weights=clause[2] if len(clause) == 3 else [],
4467
+ no_return=no_return)
4282
4468
 
4283
- if not no_return and res == False:
4284
- return res
4469
+ if not no_return and res == False:
4470
+ return res
4471
+ else:
4472
+ for clause in formula:
4473
+ if not clause or isinstance(clause[0], int): # it is a clause
4474
+ res = self.add_clause(clause, no_return)
4475
+ else:
4476
+ res = self.add_atmost(clause[0], clause[1],
4477
+ weights=clause[2] if len(clause) == 3 else [],
4478
+ no_return=no_return)
4479
+
4480
+ if not no_return and res == False:
4481
+ return res
4285
4482
 
4286
4483
  if not no_return:
4287
4484
  return res
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-sat
3
- Version: 1.9.dev16
3
+ Version: 1.9.dev18
4
4
  Summary: A Python library for prototyping with SAT oracles
5
5
  Home-page: https://github.com/pysathq/pysat
6
6
  Author: Alexey Ignatiev, Joao Marques-Silva, Antonio Morgado
@@ -15,8 +15,10 @@ Provides-Extra: approxmc
15
15
  Requires-Dist: pyapproxmc>=4.1.8; extra == "approxmc"
16
16
  Provides-Extra: cryptosat
17
17
  Requires-Dist: pycryptosat>=5.11.18; extra == "cryptosat"
18
+ Provides-Extra: pb
19
+ Requires-Dist: pindakaas>=0.6.1; extra == "pb"
18
20
  Provides-Extra: pblib
19
- Requires-Dist: pypblib>=0.0.3; extra == "pblib"
21
+ Requires-Dist: pindakaas>=0.6.1; extra == "pblib"
20
22
  Provides-Extra: unigen
21
23
  Requires-Dist: pyunigen>=4.1.20; extra == "unigen"
22
24
  Dynamic: author
@@ -1,16 +1,16 @@
1
- pycard.cp313-win_amd64.pyd,sha256=-21c04ZDTCkbLY-PrhzRBwcrr8JMYth7F67HhQgEXrg,71680
2
- pyformula.cp313-win_amd64.pyd,sha256=1YTbw9DofgYjA7leKm8AtmMZ0qp2eRFZMMKzEUoyHpY,31232
3
- pysolvers.cp313-win_amd64.pyd,sha256=J8Hx4wiiQfSJS4Jz_LYlXs4vlu2LrYJuDo1uNtuapZM,3341824
4
- pysat/__init__.py,sha256=4JqjNiGlyx3erAPAvKC8uCaQB-3IozEaQNRn_ANOeYU,681
1
+ pycard.cp313-win_amd64.pyd,sha256=PZ50XA6fybcRvw5SPc9j0nJDSNVqB2juDezaMoUVkEM,71680
2
+ pyformula.cp313-win_amd64.pyd,sha256=zveu2NsNepVzqxV08-yfzqzkwgNge1bq5-rNFBKrb5E,31232
3
+ pysolvers.cp313-win_amd64.pyd,sha256=MIEeH7EN00zm9PaBz4kNWgKvMeR71bpooummkosh_PM,3341824
4
+ pysat/__init__.py,sha256=GVL8Ti2mVtexajShuobNXoYUQxcG9MV244bA72LC44M,681
5
5
  pysat/_fileio.py,sha256=pXRGGAaP7U8r3bpn5ShseCSHROXqi7ygaYe6T6QORto,6700
6
6
  pysat/_utils.py,sha256=4cv40p70FKvjCTKq8GqzS_jlxF74cp_lojTZSim_qJE,1340
7
- pysat/card.py,sha256=rav3_t_mCzbLw8SAdXcOh-PNbFtaATVSAJzpbygqiQ4,30066
7
+ pysat/card.py,sha256=j39uYFhgol08nxReT1g7tMEfEdtMmEkQzI_JJ219Uac,29639
8
8
  pysat/engines.py,sha256=yAYA_PzIGr_uRF85M7kL2bipjpLbY7jdfFKTdY-yW8I,46364
9
9
  pysat/formula.py,sha256=qtADpavl5m-pHRDnZ7PDEiosT7TbaCwqY_xpv_--Nlw,215586
10
- pysat/integer.py,sha256=um6FXUzmBHUFlWXNKs0xLh9Jss3jiEin2Er_U41KtkA,90203
11
- pysat/pb.py,sha256=DMy8UfbbpfJo5cTZul9W1o_mlr-B0lCIEseZjrQYd8A,17816
10
+ pysat/integer.py,sha256=_1qPuYuUT8jT0PfbQhHS1ujQBxNyoYRbrEvnD2aIuEQ,97091
11
+ pysat/pb.py,sha256=cVpCFc0gAARaQa9VU0Vx-P5wAWoLlXcMKpuhgtRqez4,17520
12
12
  pysat/process.py,sha256=8lNbpEKipFZjbQ9DCJPfdL8eZHx-ltNuu7h4geWWdJY,12739
13
- pysat/solvers.py,sha256=uE8HkMcLWvL4WrgtDhRBrqh0wq5kg9kNpAKGoBOX4GU,310003
13
+ pysat/solvers.py,sha256=Mt9QPzxXosIfSJmPJ1kWN10M6oCKu1BRoHHGqkNFE4I,318480
14
14
  pysat/allies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  pysat/allies/approxmc.py,sha256=LtUss6PiqmgvFwLTe4KUHmhavdzlotRii2UUOSb9gTU,13586
16
16
  pysat/allies/unigen.py,sha256=qOGc_WwcIi-AgFSJDBhhY2n6Q6UeYVMpMPuem4-Vf6c,16718
@@ -29,22 +29,22 @@ pysat/examples/optux.py,sha256=cZMwxzOxuTzo6TDUvZVVuF3V1tOAlAdZpW6cm6qW1HM,29771
29
29
  pysat/examples/primer.py,sha256=0OeV9R6mWyIr2BKIitkrfgY3q2XE-sV1ZLSiybGRFXw,23375
30
30
  pysat/examples/rc2.py,sha256=dZMslLc2P7PYdH82lekWSaE6dTz8MyDVzj56nwOkYuw,77292
31
31
  pysat/examples/usage.py,sha256=x9luw6pyKAAndlxAeUWFhS4aLSVmD2t_j_5uA9YddHg,2183
32
- python_sat-1.9.dev16.data/scripts/approxmc.py,sha256=h6aMnSpIaYXkTMq-tCSzNWwKb0AG-vTH4vuLWGn5Q4s,13958
33
- python_sat-1.9.dev16.data/scripts/bbscan.py,sha256=xGxklG6Gc_UBaM8QvEJDOtQhEamxAncfsd65E-0JcAc,22696
34
- python_sat-1.9.dev16.data/scripts/bica.py,sha256=659K6gdRBgYeNiLjQc_VnulXhbTHxnUljNYhXx-frwk,28413
35
- python_sat-1.9.dev16.data/scripts/fm.py,sha256=ek1QDsuxiq1zm1PBLbrSXXEE89RP9_DYwVNnyB3eO4s,18650
36
- python_sat-1.9.dev16.data/scripts/genhard.py,sha256=YswufT272W4xTJrSBlOC2ucJx4RK4ntfRv73rASrx30,19520
37
- python_sat-1.9.dev16.data/scripts/lbx.py,sha256=rCQ6zH_DXoVjBrzhZHSDJMfP92-hz2JVwh6e4m44AUc,23874
38
- python_sat-1.9.dev16.data/scripts/lsu.py,sha256=EpXQCu8ZdYoNgi70Uc1b8drOwp1fo82mhGbpjonjIzw,16687
39
- python_sat-1.9.dev16.data/scripts/mcsls.py,sha256=cTJQMIO-4iho412wG0JhvhqXWy81U0RQV8wwOgkri3Q,22690
40
- python_sat-1.9.dev16.data/scripts/models.py,sha256=UmMmklExzyXAIhh-0i038nQb7hTK6CbdV1M6m2GxxSI,5917
41
- python_sat-1.9.dev16.data/scripts/musx.py,sha256=LwK10YuQ8pIf1TJwp0HKp54O0hdk8zBy3TIfGsF1yW0,11277
42
- python_sat-1.9.dev16.data/scripts/optux.py,sha256=9-4ynzXVYq7y67vy-emMbMx_gSlOBMDBX4VWkjSyHgE,30508
43
- python_sat-1.9.dev16.data/scripts/primer.py,sha256=iE9Vy0XyeySIY-7-L_VMwRahsIVCEmm9k04OhLwDvuw,23982
44
- python_sat-1.9.dev16.data/scripts/rc2.py,sha256=YQ3UkYWVd2ge64u-SdqwVzZkHmj0tikYxXZS0O2pXy4,79322
45
- python_sat-1.9.dev16.data/scripts/unigen.py,sha256=e-HC2UxWA8MTN90VslkHX1ZlTPjdhI4etzkn6G8YAX4,17140
46
- python_sat-1.9.dev16.dist-info/licenses/LICENSE.txt,sha256=6QMvEzxqdPXEoiAZUBaZLeLF4hW2S81fjKz_THER0uQ,1109
47
- python_sat-1.9.dev16.dist-info/METADATA,sha256=PMvSMSHwEAfxi9k9cTiE0RLkz2zvgR8DYBYYAjBTGoE,1784
48
- python_sat-1.9.dev16.dist-info/WHEEL,sha256=0LUNoHxLvcpw9db_x0XozE5jW73-_GOAO7fDDJ4Xt4Q,101
49
- python_sat-1.9.dev16.dist-info/top_level.txt,sha256=0SLg3yVqNpeiqOwFcAo3OxXhqAnflCmb5Fjl6gsl-Do,33
50
- python_sat-1.9.dev16.dist-info/RECORD,,
32
+ python_sat-1.9.dev18.data/scripts/approxmc.py,sha256=h6aMnSpIaYXkTMq-tCSzNWwKb0AG-vTH4vuLWGn5Q4s,13958
33
+ python_sat-1.9.dev18.data/scripts/bbscan.py,sha256=xGxklG6Gc_UBaM8QvEJDOtQhEamxAncfsd65E-0JcAc,22696
34
+ python_sat-1.9.dev18.data/scripts/bica.py,sha256=659K6gdRBgYeNiLjQc_VnulXhbTHxnUljNYhXx-frwk,28413
35
+ python_sat-1.9.dev18.data/scripts/fm.py,sha256=ek1QDsuxiq1zm1PBLbrSXXEE89RP9_DYwVNnyB3eO4s,18650
36
+ python_sat-1.9.dev18.data/scripts/genhard.py,sha256=YswufT272W4xTJrSBlOC2ucJx4RK4ntfRv73rASrx30,19520
37
+ python_sat-1.9.dev18.data/scripts/lbx.py,sha256=rCQ6zH_DXoVjBrzhZHSDJMfP92-hz2JVwh6e4m44AUc,23874
38
+ python_sat-1.9.dev18.data/scripts/lsu.py,sha256=EpXQCu8ZdYoNgi70Uc1b8drOwp1fo82mhGbpjonjIzw,16687
39
+ python_sat-1.9.dev18.data/scripts/mcsls.py,sha256=cTJQMIO-4iho412wG0JhvhqXWy81U0RQV8wwOgkri3Q,22690
40
+ python_sat-1.9.dev18.data/scripts/models.py,sha256=UmMmklExzyXAIhh-0i038nQb7hTK6CbdV1M6m2GxxSI,5917
41
+ python_sat-1.9.dev18.data/scripts/musx.py,sha256=LwK10YuQ8pIf1TJwp0HKp54O0hdk8zBy3TIfGsF1yW0,11277
42
+ python_sat-1.9.dev18.data/scripts/optux.py,sha256=9-4ynzXVYq7y67vy-emMbMx_gSlOBMDBX4VWkjSyHgE,30508
43
+ python_sat-1.9.dev18.data/scripts/primer.py,sha256=iE9Vy0XyeySIY-7-L_VMwRahsIVCEmm9k04OhLwDvuw,23982
44
+ python_sat-1.9.dev18.data/scripts/rc2.py,sha256=YQ3UkYWVd2ge64u-SdqwVzZkHmj0tikYxXZS0O2pXy4,79322
45
+ python_sat-1.9.dev18.data/scripts/unigen.py,sha256=e-HC2UxWA8MTN90VslkHX1ZlTPjdhI4etzkn6G8YAX4,17140
46
+ python_sat-1.9.dev18.dist-info/licenses/LICENSE.txt,sha256=6QMvEzxqdPXEoiAZUBaZLeLF4hW2S81fjKz_THER0uQ,1109
47
+ python_sat-1.9.dev18.dist-info/METADATA,sha256=y415j-u4ZKAjyEiYy8qhEhrBP6T2A5RYZE8HyhGngEc,1854
48
+ python_sat-1.9.dev18.dist-info/WHEEL,sha256=fOyJWI5N6P4iPL9_doBC3lf0L4Z-hcIkjNV5ilMSlqY,101
49
+ python_sat-1.9.dev18.dist-info/top_level.txt,sha256=0SLg3yVqNpeiqOwFcAo3OxXhqAnflCmb5Fjl6gsl-Do,33
50
+ python_sat-1.9.dev18.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.0.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp313-cp313-win_amd64
5
5