python-sat 1.8.dev25__cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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 (48) hide show
  1. pycard.cpython-312-x86_64-linux-gnu.so +0 -0
  2. pysat/__init__.py +24 -0
  3. pysat/_fileio.py +209 -0
  4. pysat/_utils.py +58 -0
  5. pysat/allies/__init__.py +0 -0
  6. pysat/allies/approxmc.py +385 -0
  7. pysat/allies/unigen.py +435 -0
  8. pysat/card.py +802 -0
  9. pysat/engines.py +1302 -0
  10. pysat/examples/__init__.py +0 -0
  11. pysat/examples/bbscan.py +663 -0
  12. pysat/examples/bica.py +691 -0
  13. pysat/examples/fm.py +527 -0
  14. pysat/examples/genhard.py +516 -0
  15. pysat/examples/hitman.py +653 -0
  16. pysat/examples/lbx.py +638 -0
  17. pysat/examples/lsu.py +496 -0
  18. pysat/examples/mcsls.py +610 -0
  19. pysat/examples/models.py +189 -0
  20. pysat/examples/musx.py +344 -0
  21. pysat/examples/optux.py +710 -0
  22. pysat/examples/primer.py +620 -0
  23. pysat/examples/rc2.py +1858 -0
  24. pysat/examples/usage.py +63 -0
  25. pysat/formula.py +5619 -0
  26. pysat/pb.py +463 -0
  27. pysat/process.py +363 -0
  28. pysat/solvers.py +7591 -0
  29. pysolvers.cpython-312-x86_64-linux-gnu.so +0 -0
  30. python_sat-1.8.dev25.data/scripts/approxmc.py +385 -0
  31. python_sat-1.8.dev25.data/scripts/bbscan.py +663 -0
  32. python_sat-1.8.dev25.data/scripts/bica.py +691 -0
  33. python_sat-1.8.dev25.data/scripts/fm.py +527 -0
  34. python_sat-1.8.dev25.data/scripts/genhard.py +516 -0
  35. python_sat-1.8.dev25.data/scripts/lbx.py +638 -0
  36. python_sat-1.8.dev25.data/scripts/lsu.py +496 -0
  37. python_sat-1.8.dev25.data/scripts/mcsls.py +610 -0
  38. python_sat-1.8.dev25.data/scripts/models.py +189 -0
  39. python_sat-1.8.dev25.data/scripts/musx.py +344 -0
  40. python_sat-1.8.dev25.data/scripts/optux.py +710 -0
  41. python_sat-1.8.dev25.data/scripts/primer.py +620 -0
  42. python_sat-1.8.dev25.data/scripts/rc2.py +1858 -0
  43. python_sat-1.8.dev25.data/scripts/unigen.py +435 -0
  44. python_sat-1.8.dev25.dist-info/METADATA +45 -0
  45. python_sat-1.8.dev25.dist-info/RECORD +48 -0
  46. python_sat-1.8.dev25.dist-info/WHEEL +6 -0
  47. python_sat-1.8.dev25.dist-info/licenses/LICENSE.txt +21 -0
  48. python_sat-1.8.dev25.dist-info/top_level.txt +3 -0
@@ -0,0 +1,653 @@
1
+ #!/usr/bin/env python
2
+ #-*- coding:utf-8 -*-
3
+ ##
4
+ ## hitman.py
5
+ ##
6
+ ## A minimum/minimal hitting set enumerator based on MaxSAT solving
7
+ ## and also MCS enumeration (LBX- or CLD-like). MaxSAT-based hitting
8
+ ## set enumeration computes hitting sets in a sorted manner, e.g. from
9
+ ## smallest size to largest size. MCS-based hitting set solver computes
10
+ ## arbitrary hitting sets, with no respect to their size.
11
+ ##
12
+ ## Created on: Aug 23, 2018
13
+ ## Author: Alexey S. Ignatiev
14
+ ## E-mail: aignatiev@ciencias.ulisboa.pt
15
+ ##
16
+
17
+ """
18
+ ===============
19
+ List of classes
20
+ ===============
21
+
22
+ .. autosummary::
23
+ :nosignatures:
24
+
25
+ Hitman
26
+
27
+ ==================
28
+ Module description
29
+ ==================
30
+
31
+ A SAT-based implementation of an implicit minimal hitting set [1]_
32
+ enumerator. The implementation is capable of computing/enumerating
33
+ cardinality- and subset-minimal hitting sets of a given set of sets.
34
+ Cardinality-minimal hitting set enumeration can be seen as ordered (sorted
35
+ by size) subset-minimal hitting enumeration.
36
+
37
+ The minimal hitting set problem is trivially formulated as a MaxSAT formula
38
+ in WCNF, as follows. Assume :math:`E=\\{e_1,\\ldots,e_n\\}` to be a universe
39
+ of elements. Also assume there are :math:`k` sets to hit:
40
+ :math:`s_i=\\{e_{i,1},\\ldots,e_{i,j_i}\\}` s.t. :math:`e_{i,l}\\in E`. Every
41
+ set :math:`s_i=\\{e_{i,1},\\ldots,e_{i,j_i}\\}` is translated into a hard
42
+ clause :math:`(e_{i,1} \\vee \\ldots \\vee e_{i,j_i})`. This results in the
43
+ set of hard clauses having size :math:`k`. The set of soft clauses
44
+ comprises unit clauses of the form :math:`(\\neg{e_{j}})` s.t.
45
+ :math:`e_{j}\\in E`, each having weight 1.
46
+
47
+ Taking into account this problem formulation as MaxSAT, ordered hitting
48
+ enumeration is done with the use of the state-of-the-art MaxSAT solver
49
+ called :class:`.RC2` [2]_ [3]_ [4]_ while unordered hitting set enumeration
50
+ is done through the *minimal correction subset* (MCS) enumeration, e.g.
51
+ using the :class:`.LBX`- [5]_ or :class:`.MCSls`-like [6]_ MCS enumerators.
52
+
53
+ Note that this implementation additionally supports *pure* SAT-based
54
+ minimal hitting set enumeration with the use of preferred variable
55
+ polarity setting following the approach of [7]_.
56
+
57
+ .. [1] Erick Moreno-Centeno, Richard M. Karp. *The Implicit Hitting Set
58
+ Approach to Solve Combinatorial Optimization Problems with an
59
+ Application to Multigenome Alignment*. Operations Research 61(2). 2013.
60
+ pp. 453-468
61
+
62
+ .. [2] António Morgado, Carmine Dodaro, Joao Marques-Silva. *Core-Guided
63
+ MaxSAT with Soft Cardinality Constraints*. CP 2014. pp. 564-573
64
+
65
+ .. [3] António Morgado, Alexey Ignatiev, Joao Marques-Silva. *MSCG: Robust
66
+ Core-Guided MaxSAT Solving*. JSAT 9. 2014. pp. 129-134
67
+
68
+ .. [4] Alexey Ignatiev, António Morgado, Joao Marques-Silva. *RC2: a
69
+ Python-based MaxSAT Solver*. MaxSAT Evaluation 2018. p. 22
70
+
71
+ .. [5] Carlos Mencía, Alessandro Previti, Joao Marques-Silva.
72
+ *Literal-Based MCS Extraction*. IJCAI. 2015. pp. 1973-1979
73
+
74
+ .. [6] Joao Marques-Silva, Federico Heras, Mikolás Janota,
75
+ Alessandro Previti, Anton Belov. *On Computing Minimal Correction
76
+ Subsets*. IJCAI. 2013. pp. 615-622
77
+
78
+ .. [7] Enrico Giunchiglia, Marco Maratea. *Solving Optimization Problems
79
+ with DLL*. ECAI 2006. pp. 377-381
80
+
81
+ :class:`Hitman` supports hitting set enumeration in the *implicit* manner,
82
+ i.e. when sets to hit can be added on the fly as well as hitting sets can
83
+ be blocked on demand.
84
+
85
+ An example usage of :class:`Hitman` through the Python ``import`` interface
86
+ is shown below. Here we target unordered subset-minimal hitting set
87
+ enumeration.
88
+
89
+ .. code-block:: python
90
+
91
+ >>> from pysat.examples.hitman import Hitman
92
+ >>>
93
+ >>> h = Hitman(solver='m22', htype='lbx')
94
+ >>> # adding sets to hit
95
+ >>> h.hit([1, 2, 3])
96
+ >>> h.hit([1, 4])
97
+ >>> h.hit([5, 6, 7])
98
+ >>>
99
+ >>> h.get()
100
+ [1, 5]
101
+ >>>
102
+ >>> h.block([1, 5])
103
+ >>>
104
+ >>> h.get()
105
+ [2, 4, 5]
106
+ >>>
107
+ >>> h.delete()
108
+
109
+ Enumerating cardinality-minimal hitting sets can be done as follows:
110
+
111
+ .. code-block:: python
112
+
113
+ >>> from pysat.examples.hitman import Hitman
114
+ >>>
115
+ >>> sets = [[1, 2, 3], [1, 4], [5, 6, 7]]
116
+ >>> with Hitman(bootstrap_with=sets, htype='sorted') as hitman:
117
+ ... for hs in hitman.enumerate():
118
+ ... print(hs)
119
+ ...
120
+ [1, 5]
121
+ [1, 6]
122
+ [1, 7]
123
+ [3, 4, 7]
124
+ [2, 4, 7]
125
+ [3, 4, 6]
126
+ [3, 4, 5]
127
+ [2, 4, 6]
128
+ [2, 4, 5]
129
+
130
+ Finally, implicit hitting set enumeration can be used in practical problem
131
+ solving. As an example, let us show the basic flow of a MaxHS-like [8]_
132
+ algorithm for MaxSAT:
133
+
134
+ .. code-block:: python
135
+
136
+ >>> from pysat.examples.hitman import Hitman
137
+ >>> from pysat.solvers import Solver
138
+ >>>
139
+ >>> hitman = Hitman(htype='sorted')
140
+ >>> oracle = Solver()
141
+ >>>
142
+ >>> # here we assume that the SAT oracle
143
+ >>> # is initialized with a MaxSAT formula,
144
+ >>> # whose soft clauses are extended with
145
+ >>> # selector literals stored in "sels"
146
+ >>> while True:
147
+ ... hs = hitman.get() # hitting the set of unsatisfiable cores
148
+ ... ts = set(sels).difference(set(hs)) # soft clauses to try
149
+ ...
150
+ ... if oracle.solve(assumptions=ts):
151
+ ... print('s OPTIMUM FOUND')
152
+ ... print('o', len(hs))
153
+ ... break
154
+ ... else:
155
+ ... core = oracle.get_core()
156
+ ... hitman.hit(core)
157
+
158
+ .. [8] Jessica Davies, Fahiem Bacchus. *Solving MAXSAT by Solving a
159
+ Sequence of Simpler SAT Instances*. CP 2011. pp. 225-239
160
+
161
+ ==============
162
+ Module details
163
+ ==============
164
+ """
165
+
166
+ #
167
+ #==============================================================================
168
+ import collections
169
+ from pysat.examples.rc2 import RC2, RC2Stratified
170
+ from pysat.examples.lbx import LBX
171
+ from pysat.examples.mcsls import MCSls
172
+ from pysat.formula import IDPool, WCNFPlus
173
+ from pysat.solvers import Solver, SolverNames
174
+ import six
175
+
176
+
177
+ #
178
+ #==============================================================================
179
+ class Atom(object):
180
+ """
181
+ Atoms are elementary (signed) objects necessary when dealing with
182
+ hitting sets subject to hard constraints.
183
+ """
184
+
185
+ def __init__(self, obj, sign=True):
186
+ """
187
+ Simple atom initialiser.
188
+ """
189
+
190
+ self.obj = obj
191
+ self.sign = sign
192
+
193
+
194
+ #
195
+ #==============================================================================
196
+ class Hitman(object):
197
+ """
198
+
199
+ A cardinality-/subset-minimal hitting set enumerator. The enumerator
200
+ can be set up to use either a MaxSAT solver :class:`.RC2` or an MCS
201
+ enumerator (either :class:`.LBX` or :class:`.MCSls`). In the former
202
+ case, the hitting sets enumerated are ordered by size (smallest size
203
+ hitting sets are computed first), i.e. *sorted*. In the latter case,
204
+ subset-minimal hitting are enumerated in an arbitrary order, i.e.
205
+ *unsorted*. Additionally, Hitman supports pure SAT-based minimal
206
+ hitting set enumeration with the use of polarity preferences.
207
+
208
+ This is handled with the use of parameter ``htype``, which is set to
209
+ be ``'sorted'`` by default. The MaxSAT-based enumerator can be chosen
210
+ by setting ``htype`` to one of the following values: ``'maxsat'``,
211
+ ``'mxsat'``, or ``'rc2'``. Alternatively, by setting it to ``'mcs'``
212
+ or ``'lbx'``, a user can enforce using the :class:`.LBX` MCS
213
+ enumerator. If ``htype`` is set to ``'mcsls'``, the :class:`.MCSls`
214
+ enumerator is used. Finally, value ``'sat'`` can be given, in which
215
+ case minimal hitting set enumeration will performed by means of a SAT
216
+ solver (can be either MiniSat-GH, or Lingeling, or CaDiCaL 153) with
217
+ polarity setting.
218
+
219
+ In either case, unless pure SAT-based hitting set enumeration is
220
+ selected, an underlying problem solver can use a SAT oracle specified
221
+ as an input parameter ``solver``. The default SAT solver is Glucose3
222
+ (specified as ``g3``, see :class:`.SolverNames` for details). For
223
+ SAT-based enumeration, MinisatGH is used as an underlying SAT solver.
224
+
225
+ Objects of class :class:`Hitman` can be bootstrapped with an iterable
226
+ of iterables, e.g. a list of lists. This is handled using the
227
+ ``bootstrap_with`` parameter. Each set to hit can comprise elements of
228
+ any type, e.g. integers, strings or objects of any Python class, as
229
+ well as their combinations. The bootstrapping phase is done in
230
+ :func:`init`.
231
+
232
+ Another optional parameter ``subject_to`` can be used to specify
233
+ arbitrary hard constraints that must be respected when computing
234
+ hitting sets of the given sets. Note that ``subject_to`` should be an
235
+ iterable containing pure clauses and/or native AtMostK constraints.
236
+ Note that native cardinality constraints supported only by
237
+ MiniCard-like solvers. Finally, note that these hard constraints must
238
+ be defined over the set of signed atomic objects, i.e. instances of
239
+ class :class:`.Atom`.
240
+
241
+ A few other optional parameters include the possible options for RC2
242
+ as well as for LBX- and MCSls-like MCS enumerators that control the
243
+ behaviour of the underlying solvers.
244
+
245
+ :param bootstrap_with: input set of sets to hit
246
+ :param weights: a mapping from objects to their weights (if weighted)
247
+ :param subject_to: hard constraints (either clauses or native AtMostK constraints)
248
+ :param solver: name of SAT solver
249
+ :param htype: enumerator type
250
+ :param mxs_adapt: detect and process AtMost1 constraints in RC2
251
+ :param mxs_exhaust: apply unsatisfiable core exhaustion in RC2
252
+ :param mxs_minz: apply heuristic core minimization in RC2
253
+ :param mxs_trim: trim unsatisfiable cores at most this number of times
254
+ :param mcs_usecld: use clause-D heuristic in the MCS enumerator
255
+
256
+ :type bootstrap_with: iterable(iterable(obj))
257
+ :type weights: dict(obj)
258
+ :type subject_to: iterable(iterable(Atom))
259
+ :type solver: str
260
+ :type htype: str
261
+ :type mxs_adapt: bool
262
+ :type mxs_exhaust: bool
263
+ :type mxs_minz: bool
264
+ :type mxs_trim: int
265
+ :type mcs_usecld: bool
266
+ """
267
+
268
+ def __init__(self, bootstrap_with=[], weights=None, subject_to=[],
269
+ solver='g3', htype='sorted', mxs_adapt=False, mxs_exhaust=False,
270
+ mxs_minz=False, mxs_trim=0, mcs_usecld=False):
271
+ """
272
+ Constructor.
273
+ """
274
+
275
+ # hitting set solver
276
+ self.oracle = None
277
+
278
+ # name of SAT solver
279
+ self.solver = solver
280
+
281
+ # various oracle options
282
+ self.adapt = mxs_adapt
283
+ self.exhaust = mxs_exhaust
284
+ self.minz = mxs_minz
285
+ self.trim = mxs_trim
286
+ self.usecld = mcs_usecld
287
+
288
+ # enumeration phase, for SAT-based oracles only
289
+ # (can be equal either to 1 or to -1)
290
+ self.phase = 1
291
+
292
+ # hitman type: either a MaxSAT solver or an MCS enumerator
293
+ if htype in ('maxsat', 'mxsat', 'rc2', 'sorted'):
294
+ self.htype = 'rc2'
295
+ elif htype in ('mcs', 'lbx'):
296
+ self.htype = 'lbx'
297
+ elif htype == 'mcsls':
298
+ self.htype = 'mcsls'
299
+ else: # 'sat'
300
+ self.htype = 'sat'
301
+
302
+ # pool of variable identifiers (for objects to hit)
303
+ self.idpool = IDPool()
304
+
305
+ # initialize hitting set solver
306
+ self.init(bootstrap_with, weights=weights, subject_to=subject_to)
307
+
308
+ def __del__(self):
309
+ """
310
+ Destructor.
311
+ """
312
+
313
+ self.delete()
314
+
315
+ def __enter__(self):
316
+ """
317
+ 'with' constructor.
318
+ """
319
+
320
+ return self
321
+
322
+ def __exit__(self, exc_type, exc_value, traceback):
323
+ """
324
+ 'with' destructor.
325
+ """
326
+
327
+ self.delete()
328
+
329
+ def init(self, bootstrap_with, weights=None, subject_to=[]):
330
+ """
331
+ This method serves for initializing the hitting set solver with a
332
+ given list of sets to hit. Concretely, the hitting set problem is
333
+ encoded into partial MaxSAT as outlined above, which is then fed
334
+ either to a MaxSAT solver or an MCS enumerator.
335
+
336
+ An additional optional parameter is ``weights``, which can be used
337
+ to specify non-unit weights for the target objects in the sets to
338
+ hit. This only works if ``'sorted'`` enumeration of hitting sets
339
+ is applied.
340
+
341
+ Another optional parameter is available, namely, ``subject_to``.
342
+ It can be used to specify arbitrary hard constraints that must be
343
+ respected when computing hitting sets of the given sets. Note that
344
+ ``subject_to`` should be an iterable containing pure clauses
345
+ and/or native AtMostK constraints. Finally, note that these hard
346
+ constraints must be defined over the set of signed atomic objects,
347
+ i.e. instances of class :class:`.Atom`.
348
+
349
+ :param bootstrap_with: input set of sets to hit
350
+ :param weights: weights of the objects in case the problem is weighted
351
+ :param subject_to: hard constraints (either clauses or native AtMostK constraints)
352
+ :type bootstrap_with: iterable(iterable(obj))
353
+ :type weights: dict(obj)
354
+ :type subject_to: iterable(iterable(Atom))
355
+ """
356
+
357
+ # formula encoding the sets to hit
358
+ formula = WCNFPlus()
359
+
360
+ # hard clauses
361
+ for to_hit in bootstrap_with:
362
+ to_hit = map(lambda obj: self.idpool.id(obj), to_hit)
363
+
364
+ formula.append([self.phase * vid for vid in to_hit])
365
+
366
+ # additional hard constraints
367
+ for cl in subject_to:
368
+ if not len(cl) == 2 or not type(cl[0]) in (list, tuple, set):
369
+ # this is a pure clause
370
+ formula.append(list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), cl)))
371
+ else:
372
+ # this is a native AtMostK constraint
373
+ formula.append([list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), cl[0])), cl[1]], is_atmost=True)
374
+
375
+ # soft clauses
376
+ for obj_id in six.iterkeys(self.idpool.id2obj):
377
+ formula.append([-obj_id],
378
+ weight=1 if not weights else weights[self.idpool.obj(obj_id)])
379
+
380
+ if self.htype == 'rc2':
381
+ if not weights or min(weights.values()) == max(weights.values()):
382
+ self.oracle = RC2(formula, solver=self.solver, adapt=self.adapt,
383
+ exhaust=self.exhaust, minz=self.minz, trim=self.trim)
384
+ else:
385
+ self.oracle = RC2Stratified(formula, solver=self.solver,
386
+ adapt=self.adapt, exhaust=self.exhaust, minz=self.minz,
387
+ nohard=True, trim=self.trim)
388
+ elif self.htype == 'lbx':
389
+ self.oracle = LBX(formula, solver_name=self.solver,
390
+ use_cld=self.usecld, use_timer=True)
391
+ elif self.htype == 'mcsls':
392
+ self.oracle = MCSls(formula, solver_name=self.solver,
393
+ use_cld=self.usecld, use_timer=True)
394
+ else: # 'sat'
395
+ assert self.solver in SolverNames.minisatgh + \
396
+ SolverNames.lingeling + SolverNames.cadical153 + \
397
+ SolverNames.cadical195, 'Hard polarity setting is unsupported by {0}'.format(self.solver)
398
+
399
+ # setting up a SAT solver, so that it supports the same interface
400
+ if formula.atms == []:
401
+ self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
402
+ use_timer=True)
403
+ elif self.solver in SolverNames.cadical195:
404
+ self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
405
+ use_timer=True, native_card=True)
406
+
407
+ for atm in formula.atms:
408
+ self.oracle.add_atmost(*atm)
409
+ else:
410
+ assert 0, 'Native AtMostK constraints aren\'t supported by MinisatGH, Lingeling, or CaDiCaL 153'
411
+
412
+ # MinisatGH supports warm start mode
413
+ if self.solver in SolverNames.minisatgh:
414
+ self.oracle.start_mode(warm=True)
415
+
416
+ # soft clauses are enforced by means of setting polarities
417
+ self.oracle.set_phases(literals=[self.phase * cl[0] for cl in formula.soft])
418
+
419
+ # "adding" the missing compute() and oracle_time() methods
420
+ self.oracle.compute = lambda: [self.oracle.solve(), self.oracle.get_model()][-1]
421
+ self.oracle.oracle_time = self.oracle.time_accum
422
+
423
+ # adding a dummy VariableMap, as is in RC2 and LBX/MCSls
424
+ VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
425
+ self.oracle.vmap = VariableMap(e2i={}, i2e={})
426
+ for vid in self.idpool.id2obj.keys():
427
+ self.oracle.vmap.e2i[vid] = vid
428
+ self.oracle.vmap.i2e[vid] = vid
429
+
430
+ def switch_phase(self):
431
+ """
432
+ If a pure SAT-based hitting set enumeration is used, it is
433
+ possible to instruct it to switch from enumerating target sets to
434
+ enumerating dual sets, by polarity switching. This is what this
435
+ method enables a user to do.
436
+ """
437
+
438
+ if self.htype == 'sat':
439
+ if self.solver in SolverNames.minisatgh:
440
+ # resetting the mode forces the solver to backtrack to level 0
441
+ self.oracle.start_mode(warm=True)
442
+
443
+ # switching the phase value
444
+ self.phase *= -1
445
+
446
+ # updating the preferences
447
+ self.oracle.set_phases(literals=[self.phase * (-v) for v in self.idpool.id2obj])
448
+
449
+ def add_hard(self, clause, weights=None, no_obj=False):
450
+ """
451
+ Add a hard constraint, which can be either a pure clause or an
452
+ AtMostK constraint.
453
+
454
+ Note that an optional parameter that can be passed to this method
455
+ is ``weights``, which contains a mapping the objects under
456
+ question into weights. Also note that the weight of an object must
457
+ not change from one call of :meth:`hit` to another.
458
+
459
+ Finally, note that if ``no_obj`` is set to True, no new objects
460
+ will be added to the hitting set enumerator. This is useful if the
461
+ variables in the clause are either already present or are
462
+ auxiliary. This is turned off by default.
463
+
464
+ :param clause: hard constraint (either a clause or a native AtMostK constraint)
465
+ :param weights: a mapping from objects to weights
466
+ :param no_obj: if True, no new objects are added to the oracle
467
+
468
+ :type clause: iterable(obj)
469
+ :type weights: dict(obj)
470
+ :type no_obj: bool
471
+ """
472
+
473
+ if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set):
474
+ # this is a pure clause
475
+ clause = list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause))
476
+
477
+ if no_obj is False:
478
+ # a soft clause should be added for each new object
479
+ new_obj = filter(lambda vid: abs(vid) not in self.oracle.vmap.e2i, clause)
480
+ else:
481
+ # this is a native AtMostK constraint
482
+ clause = [list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause[0])), clause[1]]
483
+
484
+ if no_obj is False:
485
+ # a soft clause should be added for each new object
486
+ new_obj = filter(lambda vid: abs(vid) not in self.oracle.vmap.e2i, clause[0])
487
+
488
+ # there may be duplicate literals if the constraint is weighted
489
+ new_obj = list(set(new_obj))
490
+
491
+ # adding the hard clause
492
+ self.oracle.add_clause(clause)
493
+
494
+ if no_obj is False:
495
+ # some of the literals may also have the opposite polarity
496
+ new_obj = [l if l in self.idpool.obj2id else -l for l in new_obj]
497
+
498
+ if self.htype != 'sat':
499
+ # new soft clauses
500
+ for vid in new_obj:
501
+ self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
502
+ else:
503
+ # dummy variable id mapping
504
+ for vid in new_obj:
505
+ self.oracle.vmap.e2i[vid] = vid
506
+ self.oracle.vmap.i2e[vid] = vid
507
+
508
+ # setting variable polarities
509
+ self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
510
+
511
+ def delete(self):
512
+ """
513
+ Explicit destructor of the internal hitting set oracle.
514
+ """
515
+
516
+ if self.oracle:
517
+ self.oracle.delete()
518
+ self.oracle = None
519
+
520
+ def get(self):
521
+ """
522
+ This method computes and returns a hitting set. The hitting set is
523
+ obtained using the underlying oracle operating the MaxSAT problem
524
+ formulation. The computed solution is mapped back to objects of the
525
+ problem domain.
526
+
527
+ :rtype: list(obj)
528
+ """
529
+
530
+ model = self.oracle.compute()
531
+
532
+ if model is not None:
533
+ if self.htype in ('rc2', 'sat'):
534
+ # extracting a hitting set; the use of map may look
535
+ # silly but this is to support negative phases too
536
+ self.hset = map(lambda v: abs(v), filter(lambda v: v * self.phase > 0, model))
537
+ else:
538
+ self.hset = model
539
+
540
+ return list(map(lambda vid: self.idpool.id2obj[vid], self.hset))
541
+
542
+ def hit(self, to_hit, weights=None):
543
+ """
544
+ This method adds a new set to hit to the hitting set solver. This
545
+ is done by translating the input iterable of objects into a list of
546
+ Boolean variables in the MaxSAT problem formulation.
547
+
548
+ Note that an optional parameter that can be passed to this method
549
+ is ``weights``, which contains a mapping the objects under
550
+ question into weights. Also note that the weight of an object must
551
+ not change from one call of :meth:`hit` to another.
552
+
553
+ :param to_hit: a new set to hit
554
+ :param weights: a mapping from objects to weights
555
+
556
+ :type to_hit: iterable(obj)
557
+ :type weights: dict(obj)
558
+ """
559
+
560
+ # translating objects to variables
561
+ to_hit = list(map(lambda obj: self.idpool.id(obj), to_hit))
562
+
563
+ # a soft clause should be added for each new object
564
+ new_obj = [vid for vid in to_hit if vid not in self.oracle.vmap.e2i]
565
+
566
+ # new hard clause; phase multiplication is needed
567
+ # for making phase switching possible (pure SAT only)
568
+ self.oracle.add_clause([self.phase * vid for vid in to_hit])
569
+
570
+ # new soft clauses
571
+ if self.htype != 'sat':
572
+ # new soft clauses
573
+ for vid in new_obj:
574
+ self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
575
+ else:
576
+ # dummy variable id mapping
577
+ for vid in new_obj:
578
+ self.oracle.vmap.e2i[vid] = vid
579
+ self.oracle.vmap.i2e[vid] = vid
580
+
581
+ # setting variable polarities
582
+ self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
583
+
584
+ def block(self, to_block, weights=None):
585
+ """
586
+ The method serves for imposing a constraint forbidding the hitting
587
+ set solver to compute a given hitting set. Each set to block is
588
+ encoded as a hard clause in the MaxSAT problem formulation, which
589
+ is then added to the underlying oracle.
590
+
591
+ Note that an optional parameter that can be passed to this method
592
+ is ``weights``, which contains a mapping the objects under
593
+ question into weights. Also note that the weight of an object must
594
+ not change from one call of :meth:`hit` to another.
595
+
596
+ :param to_block: a set to block
597
+ :param weights: a mapping from objects to weights
598
+
599
+ :type to_block: iterable(obj)
600
+ :type weights: dict(obj)
601
+ """
602
+
603
+ # translating objects to variables
604
+ to_block = list(map(lambda obj: self.idpool.id(obj), to_block))
605
+
606
+ # a soft clause should be added for each new object
607
+ new_obj = [vid for vid in to_block if vid not in self.oracle.vmap.e2i]
608
+
609
+ # new hard clause; phase multiplication is needed
610
+ # for making phase switching possible (pure SAT only)
611
+ self.oracle.add_clause([self.phase * (-vid) for vid in to_block])
612
+
613
+ # new soft clauses
614
+ if self.htype != 'sat':
615
+ for vid in new_obj:
616
+ self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
617
+ else:
618
+ # dummy variable id mapping
619
+ for vid in new_obj:
620
+ self.oracle.vmap.e2i[vid] = vid
621
+ self.oracle.vmap.i2e[vid] = vid
622
+
623
+ # setting variable polarities
624
+ self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
625
+
626
+ def enumerate(self):
627
+ """
628
+ The method can be used as a simple iterator computing and blocking
629
+ the hitting sets on the fly. It essentially calls :func:`get`
630
+ followed by :func:`block`. Each hitting set is reported as a list
631
+ of objects in the original problem domain, i.e. it is mapped back
632
+ from the solutions over Boolean variables computed by the
633
+ underlying oracle.
634
+
635
+ :rtype: list(obj)
636
+ """
637
+
638
+ done = False
639
+ while not done:
640
+ hset = self.get()
641
+
642
+ if hset != None:
643
+ self.block(hset)
644
+ yield hset
645
+ else:
646
+ done = True
647
+
648
+ def oracle_time(self):
649
+ """
650
+ Report the total SAT solving time.
651
+ """
652
+
653
+ return self.oracle.oracle_time()