python-sat 1.8.dev24__tar.gz → 1.8.dev26__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.
- {python_sat-1.8.dev24/python_sat.egg-info → python_sat-1.8.dev26}/PKG-INFO +1 -1
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/rc2.py +160 -19
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/__init__.py +1 -1
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/formula.py +28 -3
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/pb.py +33 -12
- {python_sat-1.8.dev24 → python_sat-1.8.dev26/python_sat.egg-info}/PKG-INFO +1 -1
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/python_sat.egg-info/SOURCES.txt +2 -0
- python_sat-1.8.dev26/tests/test_encode_pb_conditional.py +54 -0
- python_sat-1.8.dev26/tests/test_idpool.py +27 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/LICENSE.txt +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/MANIFEST.in +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/README.rst +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/allies/__init__.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/allies/approxmc.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/allies/unigen.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/bitwise.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/card.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/clset.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/common.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/itot.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/ladder.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/mto.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/pairwise.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/ptypes.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/pycard.cc +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/seqcounter.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/sortcard.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/cardenc/utils.hh +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/__init__.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/bbscan.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/bica.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/fm.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/genhard.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/hitman.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/lbx.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/lsu.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/mcsls.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/models.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/musx.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/optux.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/primer.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/examples/usage.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/_fileio.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/_utils.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/card.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/engines.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/process.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/pysat/solvers.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/python_sat.egg-info/dependency_links.txt +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/python_sat.egg-info/requires.txt +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/python_sat.egg-info/top_level.txt +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/requirements.txt +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/setup.cfg +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/setup.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/cadical103.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/cadical153.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/cadical195.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/glucose30.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/glucose41.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/glucose421.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/lingeling.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/maplechrono.zip +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/maplecm.zip +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/maplesat.zip +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/mergesat3.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/minicard.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/minisat22.tar.gz +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/minisatgh.zip +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/cadical103.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/cadical153.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/cadical195.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/glucose30.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/glucose41.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/glucose421.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/gluecard30.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/gluecard41.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/lingeling.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/maplechrono.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/maplecm.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/maplesat.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/mergesat3.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/minicard.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/minisat22.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/patches/minisatgh.patch +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/prepare.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/solvers/pysolvers.cc +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_accum_stats.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_atmost.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_atmost1.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_atmostk.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_boolengine.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_clausification.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_cnf.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_equals1.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_formula_unique.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_process.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_propagate.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_unique_model.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_unique_mus.py +0 -0
- {python_sat-1.8.dev24 → python_sat-1.8.dev26}/tests/test_warmstart.py +0 -0
|
@@ -140,6 +140,7 @@ from pysat.formula import CNFPlus, WCNFPlus, IDPool
|
|
|
140
140
|
from pysat.card import ITotalizer
|
|
141
141
|
from pysat.process import Processor
|
|
142
142
|
from pysat.solvers import Solver, SolverNames
|
|
143
|
+
from threading import Timer
|
|
143
144
|
import re
|
|
144
145
|
import six
|
|
145
146
|
from six.moves import range
|
|
@@ -222,6 +223,9 @@ class RC2(object):
|
|
|
222
223
|
# oracles are initialised to be None
|
|
223
224
|
self.oracle, self.processor = None, None
|
|
224
225
|
|
|
226
|
+
# parameters related to asynchronous interruption
|
|
227
|
+
self.expect_interrupt, self.interrupted = False, False
|
|
228
|
+
|
|
225
229
|
# clause selectors and mapping from selectors to clause ids
|
|
226
230
|
# .sall, .s2cl, and .sneg are required only for model enumeration
|
|
227
231
|
self.sels, self.smap, self.sall, self.s2cl, self.sneg = [], {}, [], {}, set([])
|
|
@@ -248,6 +252,23 @@ class RC2(object):
|
|
|
248
252
|
if not formula.hard and len(self.sels) > 100000 and min(wght) == max(wght):
|
|
249
253
|
self.minz = False
|
|
250
254
|
|
|
255
|
+
def _call_oracle(self, assumptions=[], expect_interrupt=False):
|
|
256
|
+
"""
|
|
257
|
+
Makes a call to the internal SAT solver by means of invoking
|
|
258
|
+
`oracle.solve_limited()`. The two arguments are the list of
|
|
259
|
+
assumption literals and the Boolean flag indicating whether the
|
|
260
|
+
call can be interrupted.
|
|
261
|
+
|
|
262
|
+
:param assumptions: a list of assumption literals.
|
|
263
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
264
|
+
|
|
265
|
+
:type assumptions: iterable(int)
|
|
266
|
+
:type expect_interrupt: bool
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
return self.oracle.solve_limited(assumptions=assumptions,
|
|
270
|
+
expect_interrupt=expect_interrupt)
|
|
271
|
+
|
|
251
272
|
def __del__(self):
|
|
252
273
|
"""
|
|
253
274
|
Destructor.
|
|
@@ -469,7 +490,7 @@ class RC2(object):
|
|
|
469
490
|
self.processor.delete()
|
|
470
491
|
self.processor = None
|
|
471
492
|
|
|
472
|
-
def compute(self):
|
|
493
|
+
def compute(self, expect_interrupt=False):
|
|
473
494
|
"""
|
|
474
495
|
This method can be used for computing one MaxSAT solution,
|
|
475
496
|
i.e. for computing an assignment satisfying all hard
|
|
@@ -484,6 +505,12 @@ class RC2(object):
|
|
|
484
505
|
can enumerate top-:math:`k` MaxSAT solutions (this can
|
|
485
506
|
also be done by calling :meth:`enumerate()`).
|
|
486
507
|
|
|
508
|
+
This MaxSAT call can be asynchronously interrupted, in which case
|
|
509
|
+
the value of ``expect_interrupt`` must be set to ``True``.
|
|
510
|
+
|
|
511
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
512
|
+
:type expect_interrupt: bool
|
|
513
|
+
|
|
487
514
|
:return: a MaxSAT model
|
|
488
515
|
:rtype: list(int)
|
|
489
516
|
|
|
@@ -509,6 +536,9 @@ class RC2(object):
|
|
|
509
536
|
>>> rc2.delete()
|
|
510
537
|
"""
|
|
511
538
|
|
|
539
|
+
# keeping the current interruption preference
|
|
540
|
+
self.expect_interrupt = expect_interrupt
|
|
541
|
+
|
|
512
542
|
# simply apply MaxSAT only once
|
|
513
543
|
res = self.compute_()
|
|
514
544
|
|
|
@@ -532,7 +562,7 @@ class RC2(object):
|
|
|
532
562
|
|
|
533
563
|
return self.model
|
|
534
564
|
|
|
535
|
-
def enumerate(self, block=0):
|
|
565
|
+
def enumerate(self, block=0, expect_interrupt=False):
|
|
536
566
|
"""
|
|
537
567
|
Enumerate top MaxSAT solutions (from best to worst). The
|
|
538
568
|
method works as a generator, which iteratively calls
|
|
@@ -546,8 +576,15 @@ class RC2(object):
|
|
|
546
576
|
it to ``-1``. By the default (for blocking MaxSAT models),
|
|
547
577
|
``block`` is set to ``0``.
|
|
548
578
|
|
|
579
|
+
This MaxSAT enumeration call can be asynchronously interrupted, in
|
|
580
|
+
which case the value of ``expect_interrupt`` must be set to
|
|
581
|
+
``True``.
|
|
582
|
+
|
|
549
583
|
:param block: preferred way to block solutions when enumerating
|
|
584
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
585
|
+
|
|
550
586
|
:type block: int
|
|
587
|
+
:type expect_interrupt: bool
|
|
551
588
|
|
|
552
589
|
:return: a MaxSAT model
|
|
553
590
|
:rtype: list(int)
|
|
@@ -577,7 +614,7 @@ class RC2(object):
|
|
|
577
614
|
|
|
578
615
|
done = False
|
|
579
616
|
while not done:
|
|
580
|
-
model = self.compute()
|
|
617
|
+
model = self.compute(expect_interrupt=expect_interrupt)
|
|
581
618
|
|
|
582
619
|
if model != None:
|
|
583
620
|
if block == 1:
|
|
@@ -631,8 +668,21 @@ class RC2(object):
|
|
|
631
668
|
if self.adapt:
|
|
632
669
|
self.adapt_am1()
|
|
633
670
|
|
|
671
|
+
# at the beginning, the solver is not interrupted
|
|
672
|
+
self.interrupted = False
|
|
673
|
+
|
|
634
674
|
# main solving loop
|
|
635
|
-
while not self.
|
|
675
|
+
while not self._call_oracle(assumptions=self.sels + self.sums,
|
|
676
|
+
expect_interrupt=self.expect_interrupt):
|
|
677
|
+
|
|
678
|
+
# even if the call has been interrupted, we
|
|
679
|
+
# still need to finish the current iteration
|
|
680
|
+
if self.oracle.get_status() is None:
|
|
681
|
+
self.clear_interrupt()
|
|
682
|
+
|
|
683
|
+
if self.verbose > 1:
|
|
684
|
+
print('c interrupted; processing the last found core')
|
|
685
|
+
|
|
636
686
|
self.get_core()
|
|
637
687
|
|
|
638
688
|
if not self.core:
|
|
@@ -645,6 +695,10 @@ class RC2(object):
|
|
|
645
695
|
print('c cost: {0}; core sz: {1}; soft sz: {2}'.format(self.cost,
|
|
646
696
|
len(self.core), len(self.sels) + len(self.sums)))
|
|
647
697
|
|
|
698
|
+
# the solver got interrupted => returning None
|
|
699
|
+
if self.interrupted:
|
|
700
|
+
return # None
|
|
701
|
+
|
|
648
702
|
return True
|
|
649
703
|
|
|
650
704
|
def get_core(self):
|
|
@@ -892,7 +946,7 @@ class RC2(object):
|
|
|
892
946
|
for i in range(self.trim):
|
|
893
947
|
# call solver with core assumption only
|
|
894
948
|
# it must return 'unsatisfiable'
|
|
895
|
-
self.
|
|
949
|
+
self._call_oracle(assumptions=self.core)
|
|
896
950
|
|
|
897
951
|
# extract a new core
|
|
898
952
|
new_core = self.oracle.get_core()
|
|
@@ -930,13 +984,16 @@ class RC2(object):
|
|
|
930
984
|
while i < len(self.core):
|
|
931
985
|
to_test = self.core[:i] + self.core[(i + 1):]
|
|
932
986
|
|
|
933
|
-
if self.
|
|
987
|
+
if self._call_oracle(assumptions=to_test) == False:
|
|
934
988
|
self.core = to_test
|
|
935
989
|
elif self.oracle.get_status() == True:
|
|
936
990
|
i += 1
|
|
937
991
|
else:
|
|
938
992
|
break
|
|
939
993
|
|
|
994
|
+
# disabling the budget
|
|
995
|
+
self.oracle.conf_budget(budget=-1)
|
|
996
|
+
|
|
940
997
|
def exhaust_core(self, tobj):
|
|
941
998
|
"""
|
|
942
999
|
Exhaust core by increasing its bound as much as possible.
|
|
@@ -962,7 +1019,7 @@ class RC2(object):
|
|
|
962
1019
|
"""
|
|
963
1020
|
|
|
964
1021
|
# the first case is simpler
|
|
965
|
-
if self.
|
|
1022
|
+
if self._call_oracle(assumptions=[-tobj.rhs[1]]):
|
|
966
1023
|
return 1
|
|
967
1024
|
else:
|
|
968
1025
|
self.cost += self.minw
|
|
@@ -975,7 +1032,7 @@ class RC2(object):
|
|
|
975
1032
|
# increasing the bound
|
|
976
1033
|
self.update_sum(-tobj.rhs[i - 1])
|
|
977
1034
|
|
|
978
|
-
if self.
|
|
1035
|
+
if self._call_oracle(assumptions=[-tobj.rhs[i]]):
|
|
979
1036
|
# the bound should be equal to i
|
|
980
1037
|
return i
|
|
981
1038
|
|
|
@@ -1259,6 +1316,65 @@ class RC2(object):
|
|
|
1259
1316
|
|
|
1260
1317
|
return int(copysign(i, l))
|
|
1261
1318
|
|
|
1319
|
+
def interrupt(self):
|
|
1320
|
+
"""
|
|
1321
|
+
Interrupt the execution of the current *limited* SAT call in the
|
|
1322
|
+
RC2 algorithm. Can be used to enforce time limits using timer
|
|
1323
|
+
objects. The interrupt must be cleared before performing another
|
|
1324
|
+
`compute` call (see :meth:`clear_interrupt`).
|
|
1325
|
+
|
|
1326
|
+
Importantly, interruption is implemented such that it can work
|
|
1327
|
+
incrementally with multiple MaxSAT calls, i.e. upon an interrupted
|
|
1328
|
+
invocation a user may extend the resources / time and call the
|
|
1329
|
+
solver again. To make this work, none of the SAT calls used by the
|
|
1330
|
+
heuristics are interrupted. For this reason, the solver may take
|
|
1331
|
+
slightly more time than assumed (spent to properly finish the
|
|
1332
|
+
processing of the last unsatisfiable core).
|
|
1333
|
+
|
|
1334
|
+
**Note** that this method can be called if the `compute` call was
|
|
1335
|
+
made with the option ``expect_interrupt`` set to ``True``.
|
|
1336
|
+
Behaviour is **undefined** if used to ``expect_interrupt`` was set
|
|
1337
|
+
to ``False``.
|
|
1338
|
+
|
|
1339
|
+
Example:
|
|
1340
|
+
|
|
1341
|
+
.. code-block:: python
|
|
1342
|
+
|
|
1343
|
+
>>> from pysat.examples.rc2 import RC2
|
|
1344
|
+
>>> from pysat.formula import WCNF
|
|
1345
|
+
>>> from threading import Timer
|
|
1346
|
+
>>>
|
|
1347
|
+
>>> wcnf = WCNF(from_file='somefile.wcnf')
|
|
1348
|
+
>>>
|
|
1349
|
+
>>> with RC2(wcnf) as rc2:
|
|
1350
|
+
>>>
|
|
1351
|
+
>>> def interrupt(s):
|
|
1352
|
+
>>> print('interrupted!')
|
|
1353
|
+
>>> s.interrupt()
|
|
1354
|
+
>>>
|
|
1355
|
+
>>> timer = Timer(1, interrupt, [rc2])
|
|
1356
|
+
>>> timer.start()
|
|
1357
|
+
>>> print('computing...')
|
|
1358
|
+
>>> rc2.compute(expect_interrupt=True)
|
|
1359
|
+
>>> print('done')
|
|
1360
|
+
"""
|
|
1361
|
+
|
|
1362
|
+
if self.oracle:
|
|
1363
|
+
self.oracle.interrupt()
|
|
1364
|
+
|
|
1365
|
+
# recording the interruption request
|
|
1366
|
+
self.interrupted = True
|
|
1367
|
+
|
|
1368
|
+
def clear_interrupt(self):
|
|
1369
|
+
"""
|
|
1370
|
+
Clears a previous interrupt. Technically, this method should be
|
|
1371
|
+
used every time after the previous MaxSAT call gets interrupted.
|
|
1372
|
+
**However**, the current implementation handles this on its own.
|
|
1373
|
+
"""
|
|
1374
|
+
|
|
1375
|
+
if self.oracle:
|
|
1376
|
+
self.oracle.clear_interrupt()
|
|
1377
|
+
|
|
1262
1378
|
|
|
1263
1379
|
#
|
|
1264
1380
|
#==============================================================================
|
|
@@ -1343,7 +1459,7 @@ class RC2Stratified(RC2, object):
|
|
|
1343
1459
|
# number of finished levels
|
|
1344
1460
|
self.done = 0
|
|
1345
1461
|
|
|
1346
|
-
def compute(self):
|
|
1462
|
+
def compute(self, expect_interrupt=False):
|
|
1347
1463
|
"""
|
|
1348
1464
|
This method solves the MaxSAT problem iteratively. Each
|
|
1349
1465
|
optimization level is tackled the standard way, i.e. by
|
|
@@ -1354,6 +1470,9 @@ class RC2Stratified(RC2, object):
|
|
|
1354
1470
|
:func:`activate_clauses`.
|
|
1355
1471
|
"""
|
|
1356
1472
|
|
|
1473
|
+
# keeping the current interruption preference
|
|
1474
|
+
self.expect_interrupt = expect_interrupt
|
|
1475
|
+
|
|
1357
1476
|
if self.done == 0 and self.levl != None:
|
|
1358
1477
|
# it is a fresh start of the solver
|
|
1359
1478
|
# i.e. no optimization level is finished yet
|
|
@@ -1369,7 +1488,7 @@ class RC2Stratified(RC2, object):
|
|
|
1369
1488
|
print('c wght str:', self.blop[self.levl])
|
|
1370
1489
|
|
|
1371
1490
|
# call RC2
|
|
1372
|
-
if self.compute_()
|
|
1491
|
+
if self.compute_() != True: # can be either False or None
|
|
1373
1492
|
return
|
|
1374
1493
|
|
|
1375
1494
|
# updating the list of distinct weight levels
|
|
@@ -1397,7 +1516,7 @@ class RC2Stratified(RC2, object):
|
|
|
1397
1516
|
# i.e. all levels are finished and so all clauses are present
|
|
1398
1517
|
# thus, we need to simply call RC2 for the next model
|
|
1399
1518
|
self.done = -1 # we are done with stratification, disabling it
|
|
1400
|
-
if self.compute_()
|
|
1519
|
+
if self.compute_() != True:
|
|
1401
1520
|
return
|
|
1402
1521
|
|
|
1403
1522
|
# extracting a model
|
|
@@ -1679,10 +1798,10 @@ def parse_options():
|
|
|
1679
1798
|
"""
|
|
1680
1799
|
|
|
1681
1800
|
try:
|
|
1682
|
-
opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:mp:s:t:vx',
|
|
1801
|
+
opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:mp:s:t:T:vx',
|
|
1683
1802
|
['adapt', 'block=', 'comp=', 'enum=', 'exhaust', 'help',
|
|
1684
1803
|
'incr', 'blo=', 'minimize', 'process=', 'solver=',
|
|
1685
|
-
'trim=', 'verbose', 'vnew'])
|
|
1804
|
+
'trim=', 'timeout=', 'verbose', 'vnew'])
|
|
1686
1805
|
except getopt.GetoptError as err:
|
|
1687
1806
|
sys.stderr.write(str(err).capitalize())
|
|
1688
1807
|
usage()
|
|
@@ -1699,6 +1818,7 @@ def parse_options():
|
|
|
1699
1818
|
process = 0
|
|
1700
1819
|
solver = 'g3'
|
|
1701
1820
|
trim = 0
|
|
1821
|
+
timeout = None
|
|
1702
1822
|
verbose = 1
|
|
1703
1823
|
vnew = False
|
|
1704
1824
|
|
|
@@ -1730,6 +1850,9 @@ def parse_options():
|
|
|
1730
1850
|
solver = str(arg)
|
|
1731
1851
|
elif opt in ('-t', '--trim'):
|
|
1732
1852
|
trim = int(arg)
|
|
1853
|
+
elif opt in ('-T', '--timeout'):
|
|
1854
|
+
if str(arg) != 'none':
|
|
1855
|
+
timeout = float(arg)
|
|
1733
1856
|
elif opt in ('-v', '--verbose'):
|
|
1734
1857
|
verbose += 1
|
|
1735
1858
|
elif opt == '--vnew':
|
|
@@ -1745,7 +1868,7 @@ def parse_options():
|
|
|
1745
1868
|
block = bmap[block]
|
|
1746
1869
|
|
|
1747
1870
|
return adapt, blo, block, cmode, to_enum, exhaust, incr, minz, \
|
|
1748
|
-
process, solver, trim, verbose, vnew, args
|
|
1871
|
+
process, solver, trim, timeout, verbose, vnew, args
|
|
1749
1872
|
|
|
1750
1873
|
|
|
1751
1874
|
#
|
|
@@ -1775,6 +1898,8 @@ def usage():
|
|
|
1775
1898
|
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
|
|
1776
1899
|
print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
|
|
1777
1900
|
print(' Available values: [0 .. INT_MAX] (default = 0)')
|
|
1901
|
+
print(' -T, --timeout=<float> Set time limit for MaxSAT solver')
|
|
1902
|
+
print(' Available values: [0 .. FLOAT_MAX], none (default: none)')
|
|
1778
1903
|
print(' -v, --verbose Be verbose')
|
|
1779
1904
|
print(' --vnew Print v-line in the new format')
|
|
1780
1905
|
print(' -x, --exhaust Exhaust new unsatisfiable cores')
|
|
@@ -1784,7 +1909,7 @@ def usage():
|
|
|
1784
1909
|
#==============================================================================
|
|
1785
1910
|
if __name__ == '__main__':
|
|
1786
1911
|
adapt, blo, block, cmode, to_enum, exhaust, incr, minz, process, solver, \
|
|
1787
|
-
trim, verbose, vnew, files = parse_options()
|
|
1912
|
+
trim, timeout, verbose, vnew, files = parse_options()
|
|
1788
1913
|
|
|
1789
1914
|
if files:
|
|
1790
1915
|
# parsing the input formula
|
|
@@ -1826,14 +1951,26 @@ if __name__ == '__main__':
|
|
|
1826
1951
|
print('c hardening is disabled for model enumeration')
|
|
1827
1952
|
rc2.hard = False
|
|
1828
1953
|
|
|
1954
|
+
# setting a timer if necessary
|
|
1955
|
+
if timeout is not None:
|
|
1956
|
+
if verbose > 1:
|
|
1957
|
+
print('c timeout: {0}'.format(timeout))
|
|
1958
|
+
|
|
1959
|
+
timer = Timer(timeout, lambda s: s.interrupt(), [rc2])
|
|
1960
|
+
timer.start()
|
|
1961
|
+
else:
|
|
1962
|
+
timer = None
|
|
1963
|
+
|
|
1829
1964
|
optimum_found = False
|
|
1830
|
-
for i, model in enumerate(rc2.enumerate(block=block
|
|
1965
|
+
for i, model in enumerate(rc2.enumerate(block=block,
|
|
1966
|
+
expect_interrupt=timeout is not None), 1):
|
|
1831
1967
|
optimum_found = True
|
|
1832
1968
|
|
|
1833
1969
|
if verbose:
|
|
1834
1970
|
if i == 1:
|
|
1835
|
-
|
|
1836
|
-
|
|
1971
|
+
if not rc2.interrupted:
|
|
1972
|
+
print('s OPTIMUM FOUND')
|
|
1973
|
+
print('o {0}'.format(rc2.cost))
|
|
1837
1974
|
|
|
1838
1975
|
if verbose > 2:
|
|
1839
1976
|
if vnew: # new format of the v-line
|
|
@@ -1849,10 +1986,14 @@ if __name__ == '__main__':
|
|
|
1849
1986
|
print('v')
|
|
1850
1987
|
|
|
1851
1988
|
if verbose:
|
|
1852
|
-
if not optimum_found:
|
|
1989
|
+
if not optimum_found and not rc2.interrupted:
|
|
1853
1990
|
print('s UNSATISFIABLE')
|
|
1854
1991
|
elif to_enum != 1:
|
|
1855
1992
|
print('c models found:', i)
|
|
1856
1993
|
|
|
1857
1994
|
if verbose > 1:
|
|
1858
1995
|
print('c oracle time: {0:.4f}'.format(rc2.oracle_time()))
|
|
1996
|
+
|
|
1997
|
+
# cancelling the timer (if any) because we are done
|
|
1998
|
+
if timer:
|
|
1999
|
+
timer.cancel()
|
|
@@ -297,19 +297,28 @@ class IDPool(object):
|
|
|
297
297
|
necessary the top variable ID can be accessed directly using the
|
|
298
298
|
``top`` variable.
|
|
299
299
|
|
|
300
|
+
The final parameter ``with_neg``, if set to ``True``, indicates that
|
|
301
|
+
the *negation* of an object assigned a variable ID ``n`` is to be
|
|
302
|
+
represented using the negative integer ``-n``. For this to work, the
|
|
303
|
+
object must have the method ``__neg__()`` implemented. This behaviour
|
|
304
|
+
is disabled by default.
|
|
305
|
+
|
|
300
306
|
:param start_from: the smallest ID to assign.
|
|
301
307
|
:param occupied: a list of occupied intervals.
|
|
308
|
+
:param with_neg: whether to support automatic negation handling
|
|
302
309
|
|
|
303
310
|
:type start_from: int
|
|
304
311
|
:type occupied: list(list(int))
|
|
312
|
+
:type with_neg: bool
|
|
305
313
|
"""
|
|
306
314
|
|
|
307
|
-
def __init__(self, start_from=1, occupied=[]):
|
|
315
|
+
def __init__(self, start_from=1, occupied=[], with_neg=False):
|
|
308
316
|
"""
|
|
309
317
|
Constructor.
|
|
310
318
|
"""
|
|
311
319
|
|
|
312
|
-
self.restart(start_from=start_from, occupied=occupied
|
|
320
|
+
self.restart(start_from=start_from, occupied=occupied,
|
|
321
|
+
with_neg=with_neg)
|
|
313
322
|
|
|
314
323
|
def __repr__(self):
|
|
315
324
|
"""
|
|
@@ -318,7 +327,7 @@ class IDPool(object):
|
|
|
318
327
|
|
|
319
328
|
return f'IDPool(start_from={self.top+1}, occupied={self._occupied})'
|
|
320
329
|
|
|
321
|
-
def restart(self, start_from=1, occupied=[]):
|
|
330
|
+
def restart(self, start_from=1, occupied=[], with_neg=False):
|
|
322
331
|
"""
|
|
323
332
|
Restart the manager from scratch. The arguments replicate those of
|
|
324
333
|
the constructor of :class:`IDPool`.
|
|
@@ -337,6 +346,10 @@ class IDPool(object):
|
|
|
337
346
|
# (if for whatever reason necessary)
|
|
338
347
|
self.id2obj = {}
|
|
339
348
|
|
|
349
|
+
# flag to indicate whether this IDPool object
|
|
350
|
+
# should support automatic negation handling
|
|
351
|
+
self.with_neg = with_neg
|
|
352
|
+
|
|
340
353
|
def id(self, obj=None):
|
|
341
354
|
"""
|
|
342
355
|
The method is to be used to assign an integer variable ID for a
|
|
@@ -385,6 +398,11 @@ class IDPool(object):
|
|
|
385
398
|
|
|
386
399
|
if vid not in self.id2obj:
|
|
387
400
|
self.id2obj[vid] = obj
|
|
401
|
+
|
|
402
|
+
# adding the object's negation, if required and supported
|
|
403
|
+
if self.with_neg and hasattr(obj, '__neg__'):
|
|
404
|
+
self.obj2id[-obj] = -vid
|
|
405
|
+
self.id2obj[-vid] = -obj
|
|
388
406
|
else:
|
|
389
407
|
# no object is provided => simply return a new ID
|
|
390
408
|
vid = self._next()
|
|
@@ -1022,6 +1040,13 @@ class Formula(object):
|
|
|
1022
1040
|
return Neg(self)
|
|
1023
1041
|
return self.subformula
|
|
1024
1042
|
|
|
1043
|
+
def __neg__(self):
|
|
1044
|
+
"""
|
|
1045
|
+
Negation operator. Takes the same effect as ``__invert__()``.
|
|
1046
|
+
"""
|
|
1047
|
+
|
|
1048
|
+
return self.__invert__()
|
|
1049
|
+
|
|
1025
1050
|
def __and__(self, other):
|
|
1026
1051
|
"""
|
|
1027
1052
|
Logical conjunction operator overloaded for class
|
|
@@ -137,6 +137,8 @@ class EncType(object):
|
|
|
137
137
|
cases, this invokes the ``bdd`` encoder).
|
|
138
138
|
"""
|
|
139
139
|
|
|
140
|
+
assert pblib_present, 'Package \'pypblib\' is unavailable. Check your installation.'
|
|
141
|
+
|
|
140
142
|
best = 0
|
|
141
143
|
bdd = 1
|
|
142
144
|
seqcounter = 2
|
|
@@ -242,7 +244,7 @@ class PBEnc(object):
|
|
|
242
244
|
|
|
243
245
|
@classmethod
|
|
244
246
|
def _encode(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
245
|
-
encoding=EncType.best, comparator='<'):
|
|
247
|
+
encoding=EncType.best, comparator='<', conditionals=None):
|
|
246
248
|
"""
|
|
247
249
|
This is the method that wraps the encoder of PyPBLib. Although the
|
|
248
250
|
method can be invoked directly, a user is expected to call one of
|
|
@@ -310,7 +312,9 @@ class PBEnc(object):
|
|
|
310
312
|
top_id = vpool.top
|
|
311
313
|
|
|
312
314
|
# choosing the maximum id among the current top and the list of literals
|
|
313
|
-
|
|
315
|
+
if conditionals is None:
|
|
316
|
+
conditionals = []
|
|
317
|
+
top_id = max(map(lambda x: abs(x), conditionals + lits + [top_id if top_id != None else 0]))
|
|
314
318
|
|
|
315
319
|
# native representation
|
|
316
320
|
if encoding == 6:
|
|
@@ -331,8 +335,13 @@ class PBEnc(object):
|
|
|
331
335
|
# pseudo-Boolean constraint and variable manager
|
|
332
336
|
constr = pblib.PBConstraint([pblib.WeightedLit(*wl) for wl in wlits],
|
|
333
337
|
EncType._to_pbcmp[comparator], bound)
|
|
338
|
+
|
|
334
339
|
varmgr = pblib.AuxVarManager(top_id + 1)
|
|
335
340
|
|
|
341
|
+
# add optional conditionals
|
|
342
|
+
if len(conditionals) > 0:
|
|
343
|
+
constr.add_conditionals(conditionals)
|
|
344
|
+
|
|
336
345
|
# encoder configuration
|
|
337
346
|
config = pblib.PBConfig()
|
|
338
347
|
config.set_PB_Encoder(EncType._to_pbenc[encoding])
|
|
@@ -358,7 +367,7 @@ class PBEnc(object):
|
|
|
358
367
|
|
|
359
368
|
@classmethod
|
|
360
369
|
def leq(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
361
|
-
encoding=EncType.best):
|
|
370
|
+
encoding=EncType.best, conditionals=None):
|
|
362
371
|
"""
|
|
363
372
|
This method can be used for creating a CNF encoding of a LEQ
|
|
364
373
|
(weighted AtMostK) constraint, i.e. of
|
|
@@ -374,12 +383,18 @@ class PBEnc(object):
|
|
|
374
383
|
``EncType.best``, i.e. it is up to the PBLib encoder to choose the
|
|
375
384
|
encoding type.
|
|
376
385
|
|
|
386
|
+
The final optional argument is ``conditionals``, where a list of
|
|
387
|
+
literals can be passed to be used as the antecedent for the PB
|
|
388
|
+
constraint, which makes it *"reified"*. If the argument is set to
|
|
389
|
+
``None``, the constraint won't be reified.
|
|
390
|
+
|
|
377
391
|
:param lits: a list of literals in the sum.
|
|
378
392
|
:param weights: a list of weights
|
|
379
393
|
:param bound: the value of bound :math:`k`.
|
|
380
394
|
:param top_id: top variable identifier used so far.
|
|
381
395
|
:param vpool: variable pool for counting the number of variables.
|
|
382
396
|
:param encoding: identifier of the encoding to use.
|
|
397
|
+
:param conditionals: a list of variables that imply this constraint.
|
|
383
398
|
|
|
384
399
|
:type lits: iterable(int)
|
|
385
400
|
:type weights: iterable(int)
|
|
@@ -387,26 +402,29 @@ class PBEnc(object):
|
|
|
387
402
|
:type top_id: integer or None
|
|
388
403
|
:type vpool: :class:`.IDPool`
|
|
389
404
|
:type encoding: integer
|
|
405
|
+
:type conditionals: list(int) or None
|
|
390
406
|
|
|
391
407
|
:rtype: :class:`pysat.formula.CNFPlus`
|
|
392
408
|
"""
|
|
393
409
|
|
|
394
410
|
return cls._encode(lits, weights=weights, bound=bound, top_id=top_id,
|
|
395
|
-
|
|
411
|
+
vpool=vpool, encoding=encoding, comparator='<',
|
|
412
|
+
conditionals=conditionals)
|
|
396
413
|
|
|
397
414
|
@classmethod
|
|
398
415
|
def atmost(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
399
|
-
encoding=EncType.best):
|
|
416
|
+
encoding=EncType.best, conditionals=None):
|
|
400
417
|
"""
|
|
401
418
|
A synonim for :meth:`PBEnc.leq`.
|
|
402
419
|
"""
|
|
403
420
|
|
|
404
421
|
return cls.leq(lits, weights=weights, bound=bound, top_id=top_id,
|
|
405
|
-
|
|
422
|
+
vpool=vpool, encoding=encoding,
|
|
423
|
+
conditionals=conditionals)
|
|
406
424
|
|
|
407
425
|
@classmethod
|
|
408
426
|
def geq(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
409
|
-
encoding=EncType.best):
|
|
427
|
+
encoding=EncType.best, conditionals=None):
|
|
410
428
|
"""
|
|
411
429
|
This method can be used for creating a CNF encoding of a GEQ
|
|
412
430
|
(weighted AtLeastK) constraint, i.e. of
|
|
@@ -416,21 +434,23 @@ class PBEnc(object):
|
|
|
416
434
|
"""
|
|
417
435
|
|
|
418
436
|
return cls._encode(lits, weights=weights, bound=bound, top_id=top_id,
|
|
419
|
-
|
|
437
|
+
vpool=vpool, encoding=encoding, comparator='>',
|
|
438
|
+
conditionals=conditionals)
|
|
420
439
|
|
|
421
440
|
@classmethod
|
|
422
441
|
def atleast(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
423
|
-
encoding=EncType.best):
|
|
442
|
+
encoding=EncType.best, conditionals=None):
|
|
424
443
|
"""
|
|
425
444
|
A synonym for :meth:`PBEnc.geq`.
|
|
426
445
|
"""
|
|
427
446
|
|
|
428
447
|
return cls.geq(lits, weights=weights, bound=bound, top_id=top_id,
|
|
429
|
-
|
|
448
|
+
vpool=vpool, encoding=encoding,
|
|
449
|
+
conditionals=conditionals)
|
|
430
450
|
|
|
431
451
|
@classmethod
|
|
432
452
|
def equals(cls, lits, weights=None, bound=1, top_id=None, vpool=None,
|
|
433
|
-
encoding=EncType.best):
|
|
453
|
+
encoding=EncType.best, conditionals=None):
|
|
434
454
|
"""
|
|
435
455
|
This method can be used for creating a CNF encoding of a weighted
|
|
436
456
|
EqualsK constraint, i.e. of :math:`\\sum_{i=1}^{n}{a_i\\cdot x_i}=
|
|
@@ -439,4 +459,5 @@ class PBEnc(object):
|
|
|
439
459
|
"""
|
|
440
460
|
|
|
441
461
|
return cls._encode(lits, weights=weights, bound=bound, top_id=top_id,
|
|
442
|
-
|
|
462
|
+
vpool=vpool, encoding=encoding, comparator='=',
|
|
463
|
+
conditionals=conditionals)
|
|
@@ -88,8 +88,10 @@ tests/test_atmostk.py
|
|
|
88
88
|
tests/test_boolengine.py
|
|
89
89
|
tests/test_clausification.py
|
|
90
90
|
tests/test_cnf.py
|
|
91
|
+
tests/test_encode_pb_conditional.py
|
|
91
92
|
tests/test_equals1.py
|
|
92
93
|
tests/test_formula_unique.py
|
|
94
|
+
tests/test_idpool.py
|
|
93
95
|
tests/test_process.py
|
|
94
96
|
tests/test_propagate.py
|
|
95
97
|
tests/test_unique_model.py
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pysat.formula import IDPool
|
|
2
|
+
from pysat.card import CardEnc
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
@pytest.mark.skip(reason="PBLIB not installed in CI")
|
|
6
|
+
def test_pbenc_conditional():
|
|
7
|
+
from pysat.pb import PBEnc, EncType
|
|
8
|
+
L = 3
|
|
9
|
+
LITS = list(range(1, L + 1)) # Variables x1, x2, x3, x4
|
|
10
|
+
WEIGHTS = [2, 1, 1] # Coefficients: 2*x1 + 3*x2 + 5*x3 + 8*x4
|
|
11
|
+
bound = 10 # Constraint: ... <= 10
|
|
12
|
+
|
|
13
|
+
CONDS = [L+1]
|
|
14
|
+
|
|
15
|
+
# We need a vpool even if we don't use it, to handle PySAT internal structure
|
|
16
|
+
vpool = IDPool()
|
|
17
|
+
|
|
18
|
+
# Generate the CNF clauses
|
|
19
|
+
cnf = PBEnc.atleast(
|
|
20
|
+
lits=LITS,
|
|
21
|
+
weights=WEIGHTS,
|
|
22
|
+
bound=bound,
|
|
23
|
+
vpool=vpool,
|
|
24
|
+
conditionals=CONDS
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert cnf.clauses == [[-4]] # without PR to pblib this will be [[3, -4], [1, -4], [2, -4], [-4]]
|
|
28
|
+
|
|
29
|
+
bound = 2
|
|
30
|
+
|
|
31
|
+
# Generate the CNF clauses
|
|
32
|
+
cnf = PBEnc.atleast(
|
|
33
|
+
lits=LITS,
|
|
34
|
+
weights=WEIGHTS,
|
|
35
|
+
bound=bound,
|
|
36
|
+
vpool=vpool,
|
|
37
|
+
conditionals=CONDS
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
assert cnf.clauses == [[5], [-5, 6], [3, 6], [-5, 3, 7], [2, 6], [-5, 2, 7], [3, 2, 7], [-5, 3, 2, 8], [1, 9], [-7, 9], [1, -7, 10], [-10, -4]]
|
|
41
|
+
|
|
42
|
+
bound = 0
|
|
43
|
+
|
|
44
|
+
# Generate the CNF clauses
|
|
45
|
+
cnf = PBEnc.atleast(
|
|
46
|
+
lits=LITS,
|
|
47
|
+
weights=WEIGHTS,
|
|
48
|
+
bound=bound,
|
|
49
|
+
vpool=vpool,
|
|
50
|
+
conditionals=CONDS
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
assert cnf.clauses == []
|
|
54
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pysat.formula import IDPool
|
|
2
|
+
|
|
3
|
+
def test_idpool_neg():
|
|
4
|
+
pool = IDPool(with_neg=True)
|
|
5
|
+
|
|
6
|
+
pool.id('hello')
|
|
7
|
+
pool.id(+25)
|
|
8
|
+
pool.id(-42)
|
|
9
|
+
pool.id('world')
|
|
10
|
+
|
|
11
|
+
assert pool.id('hello') == 1
|
|
12
|
+
assert pool.id(25) == -pool.id(-25) == +2
|
|
13
|
+
assert pool.id(42) == -pool.id(-42) == -3
|
|
14
|
+
assert pool.id('world') == 4
|
|
15
|
+
|
|
16
|
+
def test_idpool_occupied():
|
|
17
|
+
pool = IDPool(start_from=5, occupied=[[2, 10], [12, 20], [22, 30]], with_neg=True)
|
|
18
|
+
|
|
19
|
+
pool.id('hello')
|
|
20
|
+
pool.id(+25)
|
|
21
|
+
pool.id(-42)
|
|
22
|
+
pool.id('world')
|
|
23
|
+
|
|
24
|
+
assert pool.id('hello') == 11
|
|
25
|
+
assert pool.id(25) == -pool.id(-25) == +21
|
|
26
|
+
assert pool.id(42) == -pool.id(-42) == -31
|
|
27
|
+
assert pool.id('world') == 32
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|