python-sat 1.8.dev15__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 (42) hide show
  1. pycard.cp313-win_amd64.pyd +0 -0
  2. pysat/__init__.py +24 -0
  3. pysat/_fileio.py +193 -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/fm.py +528 -0
  12. pysat/examples/genhard.py +516 -0
  13. pysat/examples/hitman.py +643 -0
  14. pysat/examples/lbx.py +601 -0
  15. pysat/examples/lsu.py +494 -0
  16. pysat/examples/mcsls.py +576 -0
  17. pysat/examples/models.py +189 -0
  18. pysat/examples/musx.py +344 -0
  19. pysat/examples/optux.py +675 -0
  20. pysat/examples/rc2.py +1804 -0
  21. pysat/examples/usage.py +63 -0
  22. pysat/formula.py +5417 -0
  23. pysat/pb.py +442 -0
  24. pysat/process.py +349 -0
  25. pysat/solvers.py +7589 -0
  26. pysolvers.cp313-win_amd64.pyd +0 -0
  27. python_sat-1.8.dev15.data/scripts/approxmc.py +385 -0
  28. python_sat-1.8.dev15.data/scripts/fm.py +528 -0
  29. python_sat-1.8.dev15.data/scripts/genhard.py +516 -0
  30. python_sat-1.8.dev15.data/scripts/lbx.py +601 -0
  31. python_sat-1.8.dev15.data/scripts/lsu.py +494 -0
  32. python_sat-1.8.dev15.data/scripts/mcsls.py +576 -0
  33. python_sat-1.8.dev15.data/scripts/models.py +189 -0
  34. python_sat-1.8.dev15.data/scripts/musx.py +344 -0
  35. python_sat-1.8.dev15.data/scripts/optux.py +675 -0
  36. python_sat-1.8.dev15.data/scripts/rc2.py +1804 -0
  37. python_sat-1.8.dev15.data/scripts/unigen.py +435 -0
  38. python_sat-1.8.dev15.dist-info/LICENSE.txt +21 -0
  39. python_sat-1.8.dev15.dist-info/METADATA +44 -0
  40. python_sat-1.8.dev15.dist-info/RECORD +42 -0
  41. python_sat-1.8.dev15.dist-info/WHEEL +5 -0
  42. python_sat-1.8.dev15.dist-info/top_level.txt +3 -0
@@ -0,0 +1,576 @@
1
+ #!python
2
+ #-*- coding:utf-8 -*-
3
+ ##
4
+ ## mcsls.py
5
+ ##
6
+ ## Created on: Jan 9, 2017
7
+ ## Author: Alexey S. Ignatiev
8
+ ## E-mail: aignatiev@ciencias.ulisboa.pt
9
+ ##
10
+
11
+ """
12
+ ===============
13
+ List of classes
14
+ ===============
15
+
16
+ .. autosummary::
17
+ :nosignatures:
18
+
19
+ MCSls
20
+
21
+ ==================
22
+ Module description
23
+ ==================
24
+
25
+ This module implements a prototype of a BLS- and CLD-like algorithm for the
26
+ computation of a *minimal correction subset* (MCS) and/or MCS enumeration.
27
+ More concretely, the implementation follows the *basic linear search* (BLS)
28
+ for MCS exctraction augmented with *clause D* (CLD) oracle calls. As a
29
+ result, the algorithm is not an implementation of the BLS or CLD algorithms
30
+ as described in [1]_ but a mixture of both. Note that the corresponding
31
+ original low-level implementations of both can be found `online
32
+ <https://reason.di.fc.ul.pt/wiki/doku.php?id=mcsls>`_.
33
+
34
+ .. [1] Joao Marques-Silva, Federico Heras, Mikolas Janota, Alessandro
35
+ Previti, Anton Belov. *On Computing Minimal Correction Subsets*.
36
+ IJCAI 2013. pp. 615-622
37
+
38
+ The implementation can be used as an executable (the list of available
39
+ command-line options can be shown using ``mcsls.py -h``) in the following
40
+ way:
41
+
42
+ ::
43
+
44
+ $ xzcat formula.wcnf.xz
45
+ p wcnf 3 6 4
46
+ 1 1 0
47
+ 1 2 0
48
+ 1 3 0
49
+ 4 -1 -2 0
50
+ 4 -1 -3 0
51
+ 4 -2 -3 0
52
+
53
+ $ mcsls.py -d -e all -s glucose3 -vv formula.wcnf.xz
54
+ c MCS: 1 3 0
55
+ c cost: 2
56
+ c MCS: 2 3 0
57
+ c cost: 2
58
+ c MCS: 1 2 0
59
+ c cost: 2
60
+ c oracle time: 0.0002
61
+
62
+ Alternatively, the algorithm can be accessed and invoked through the
63
+ standard ``import`` interface of Python, e.g.
64
+
65
+ .. code-block:: python
66
+
67
+ >>> from pysat.examples.mcsls import MCSls
68
+ >>> from pysat.formula import WCNF
69
+ >>>
70
+ >>> wcnf = WCNF(from_file='formula.wcnf.xz')
71
+ >>>
72
+ >>> mcsls = MCSls(wcnf, use_cld=True, solver_name='g3')
73
+ >>> for mcs in mcsls.enumerate():
74
+ ... mcsls.block(mcs)
75
+ ... print(mcs)
76
+ [1, 3]
77
+ [2, 3]
78
+ [1, 2]
79
+
80
+ ==============
81
+ Module details
82
+ ==============
83
+ """
84
+
85
+ #
86
+ #==============================================================================
87
+ from __future__ import print_function
88
+ import collections
89
+ import getopt
90
+ from math import copysign
91
+ import os
92
+ from pysat.formula import CNFPlus, WCNFPlus
93
+ from pysat.solvers import Solver, SolverNames
94
+ import re
95
+ import sys
96
+
97
+
98
+ #
99
+ #==============================================================================
100
+ class MCSls(object):
101
+ """
102
+ Algorithm BLS for computing MCSes, augmented with "clause :math:`D`"
103
+ calls. Given an unsatisfiable partial CNF formula, i.e. formula in the
104
+ :class:`.WCNF` format, this class can be used to compute a given number
105
+ of MCSes of the formula. The implementation follows the description of
106
+ the basic linear search (BLS) algorithm description in [1]_. It can use
107
+ any SAT solver available in PySAT. Additionally, the "clause :math:`D`"
108
+ heuristic can be used when enumerating MCSes.
109
+
110
+ The default SAT solver to use is ``m22`` (see :class:`.SolverNames`).
111
+ The "clause :math:`D`" heuristic is disabled by default, i.e.
112
+ ``use_cld`` is set to ``False``. Internal SAT solver's timer is also
113
+ disabled by default, i.e. ``use_timer`` is ``False``.
114
+
115
+ :param formula: unsatisfiable partial CNF formula
116
+ :param use_cld: whether or not to use "clause :math:`D`"
117
+ :param solver_name: SAT oracle name
118
+ :param use_timer: whether or not to use SAT solver's timer
119
+
120
+ :type formula: :class:`.WCNF`
121
+ :type use_cld: bool
122
+ :type solver_name: str
123
+ :type use_timer: bool
124
+ """
125
+
126
+ def __init__(self, formula, use_cld=False, solver_name='m22', use_timer=False):
127
+ """
128
+ Constructor.
129
+ """
130
+
131
+ # bootstrapping the solver with hard clauses
132
+ self.oracle = Solver(name=solver_name, bootstrap_with=formula.hard,
133
+ use_timer=use_timer)
134
+ self.solver = solver_name
135
+
136
+ # adding native cardinality constraints (if any) as hard clauses
137
+ # this can be done only if the Minicard solver is in use
138
+ if isinstance(formula, WCNFPlus) and formula.atms:
139
+ # we are using CaDiCaL195 and it can use external linear engine
140
+ if solver_name in SolverNames.cadical195:
141
+ self.oracle.activate_atmost()
142
+
143
+ assert self.oracle.supports_atmost(), \
144
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)
145
+
146
+ for atm in formula.atms:
147
+ self.oracle.add_atmost(*atm)
148
+
149
+ self.topv = formula.nv # top variable id
150
+ self.sels = []
151
+ self.ucld = use_cld
152
+ self.smap = {}
153
+
154
+ # mappings between internal and external variables
155
+ VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
156
+ self.vmap = VariableMap(e2i={}, i2e={})
157
+
158
+ # at this point internal and external variables are the same
159
+ for v in range(1, formula.nv + 1):
160
+ self.vmap.e2i[v] = v
161
+ self.vmap.i2e[v] = v
162
+
163
+ for cl in formula.soft:
164
+ new_cl = cl[:]
165
+ if len(cl) > 1 or cl[0] < 0:
166
+ self.topv += 1
167
+ sel = self.topv
168
+
169
+ new_cl.append(-sel) # creating a new selector
170
+ self.oracle.add_clause(new_cl)
171
+ else:
172
+ sel = cl[0]
173
+
174
+ self.sels.append(sel)
175
+ self.smap[sel] = len(self.sels)
176
+
177
+ def __del__(self):
178
+ """
179
+ Destructor.
180
+ """
181
+
182
+ self.delete()
183
+
184
+ def __enter__(self):
185
+ """
186
+ 'with' constructor.
187
+ """
188
+
189
+ return self
190
+
191
+ def __exit__(self, exc_type, exc_value, traceback):
192
+ """
193
+ 'with' destructor.
194
+ """
195
+
196
+ self.delete()
197
+
198
+ def delete(self):
199
+ """
200
+ Explicit destructor of the internal SAT oracle.
201
+ """
202
+
203
+ if self.oracle:
204
+ self.oracle.delete()
205
+ self.oracle = None
206
+
207
+ def add_clause(self, clause, soft=False):
208
+ """
209
+ The method for adding a new hard of soft clause to the problem
210
+ formula. Although the input formula is to be specified as an
211
+ argument of the constructor of :class:`MCSls`, adding clauses may
212
+ be helpful when *enumerating* MCSes of the formula. This way, the
213
+ clauses are added incrementally, i.e. *on the fly*.
214
+
215
+ The clause to add can be any iterable over integer literals. The
216
+ additional Boolean parameter ``soft`` can be set to ``True``
217
+ meaning the the clause being added is soft (note that parameter
218
+ ``soft`` is set to ``False`` by default).
219
+
220
+ Also note that besides pure clauses, the method can also expect
221
+ native cardinality constraints represented as a pair ``(lits,
222
+ bound)``. Only hard cardinality constraints can be added.
223
+
224
+ :param clause: a clause to add
225
+ :param soft: whether or not the clause is soft
226
+
227
+ :type clause: iterable(int)
228
+ :type soft: bool
229
+ """
230
+
231
+ # first, map external literals to internal literals
232
+ # introduce new variables if necessary
233
+ cl = list(map(lambda l: self._map_extlit(l), clause if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set) else clause[0]))
234
+
235
+ if not soft:
236
+ if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set):
237
+ # the clause is hard, and so we simply add it to the SAT oracle
238
+ self.oracle.add_clause(cl)
239
+ else:
240
+ # this should be a native cardinality constraint,
241
+ # which can be used only together with Minicard
242
+ assert self.oracle.supports_atmost(), \
243
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
244
+
245
+ self.oracle.add_atmost(cl, clause[1])
246
+ else:
247
+ # soft clauses should be augmented with a selector
248
+ sel = cl[0]
249
+ if len(cl) > 1 or cl[0] < 0:
250
+ self.topv += 1
251
+ sel = self.topv
252
+
253
+ self.oracle.add_clause(cl + [-sel])
254
+
255
+ self.sels.append(sel)
256
+ self.smap[sel] = len(self.sels)
257
+
258
+ def compute(self, enable=[]):
259
+ """
260
+ Compute and return one solution. This method checks whether the
261
+ hard part of the formula is satisfiable, i.e. an MCS can be
262
+ extracted. If the formula is satisfiable, the model computed by the
263
+ SAT call is used as an *over-approximation* of the MCS in the
264
+ method :func:`_compute` invoked here, which implements the BLS
265
+
266
+ An MCS is reported as a list of integers, each representing a soft
267
+ clause index (the smallest index is ``1``).
268
+
269
+ An optional input parameter is ``enable``, which represents a
270
+ sequence (normally a list) of soft clause indices that a user
271
+ would prefer to enable/satisfy. Note that this may result in an
272
+ unsatisfiable oracle call, in which case ``None`` will be reported
273
+ as solution. Also, the smallest clause index is assumed to be
274
+ ``1``.
275
+
276
+ :param enable: a sequence of clause ids to enable
277
+ :type enable: iterable(int)
278
+
279
+ :rtype: list(int)
280
+ """
281
+
282
+ self.setd = []
283
+ self.solution = None
284
+ self.bb_assumps = [] # backbone assumptions
285
+ self.ss_assumps = [] # satisfied soft clause assumptions
286
+
287
+ if self.oracle.solve(assumptions=[self.sels[cl_id - 1] for cl_id in enable]):
288
+ # hard part is satisfiable => there is a solution
289
+ self._overapprox()
290
+ self._compute()
291
+
292
+ self.solution = [self.smap[-l] for l in self.bb_assumps]
293
+
294
+ return self.solution
295
+
296
+ def enumerate(self):
297
+ """
298
+ This method iterates through MCSes enumerating them until the
299
+ formula has no more MCSes. The method iteratively invokes
300
+ :func:`compute`. Note that the method does not block the MCSes
301
+ computed - this should be explicitly done by a user.
302
+ """
303
+
304
+ done = False
305
+ while not done:
306
+ mcs = self.compute()
307
+
308
+ if mcs != None:
309
+ yield mcs
310
+ else:
311
+ done = True
312
+
313
+ def block(self, mcs):
314
+ """
315
+ Block a (previously computed) MCS. The MCS should be given as an
316
+ iterable of integers. Note that this method is not automatically
317
+ invoked from :func:`enumerate` because a user may want to block
318
+ some of the MCSes conditionally depending on the needs. For
319
+ example, one may want to compute disjoint MCSes only in which case
320
+ this standard blocking is not appropriate.
321
+
322
+ :param mcs: an MCS to block
323
+ :type mcs: iterable(int)
324
+ """
325
+
326
+ self.oracle.add_clause([self.sels[cl_id - 1] for cl_id in mcs])
327
+
328
+ def _overapprox(self):
329
+ """
330
+ The method extracts a model corresponding to an over-approximation
331
+ of an MCS, i.e. it is the model of the hard part of the formula
332
+ (the corresponding oracle call is made in :func:`compute`).
333
+
334
+ Here, the set of selectors is divided into two parts:
335
+ ``self.ss_assumps``, which is an under-approximation of an MSS
336
+ (maximal satisfiable subset) and ``self.setd``, which is an
337
+ over-approximation of the target MCS. Both will be further refined
338
+ in :func:`_compute`.
339
+ """
340
+
341
+ model = self.oracle.get_model()
342
+
343
+ for sel in self.sels:
344
+ if len(model) < sel or model[sel - 1] > 0:
345
+ # soft clauses contain positive literals
346
+ # so if var is true then the clause is satisfied
347
+ self.ss_assumps.append(sel)
348
+ else:
349
+ self.setd.append(sel)
350
+
351
+ def _compute(self):
352
+ """
353
+ The main method of the class, which computes an MCS given its
354
+ over-approximation. The over-approximation is defined by a model
355
+ for the hard part of the formula obtained in :func:`_overapprox`
356
+ (the corresponding oracle is made in :func:`compute`).
357
+
358
+ The method is essentially a simple loop going over all literals
359
+ unsatisfied by the previous model, i.e. the literals of
360
+ ``self.setd`` and checking which literals can be satisfied. This
361
+ process can be seen a refinement of the over-approximation of the
362
+ MCS. The algorithm follows the pseudo-code of the BLS algorithm
363
+ presented in [1]_.
364
+
365
+ Additionally, if :class:`MCSls` was constructed with the
366
+ requirement to make "clause :math:`D`" calls, the method calls
367
+ :func:`do_cld_check` at every iteration of the loop using the
368
+ literals of ``self.setd`` not yet checked, as the contents of
369
+ "clause :math:`D`".
370
+ """
371
+
372
+ # unless clause D checks are used, test one literal at a time
373
+ # and add it either to satisfied of backbone assumptions
374
+ i = 0
375
+ while i < len(self.setd):
376
+ if self.ucld:
377
+ self.do_cld_check(self.setd[i:])
378
+ i = 0
379
+
380
+ if self.setd:
381
+ # if may be empty after the clause D check
382
+
383
+ self.ss_assumps.append(self.setd[i])
384
+ if not self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps):
385
+ self.ss_assumps.pop()
386
+ self.bb_assumps.append(-self.setd[i])
387
+
388
+ i += 1
389
+
390
+ def do_cld_check(self, cld):
391
+ """
392
+ Do the "clause :math:`D`" check. This method receives a list of
393
+ literals, which serves a "clause :math:`D`" [1]_, and checks
394
+ whether the formula conjoined with :math:`D` is satisfiable.
395
+
396
+ If clause :math:`D` cannot be satisfied together with the formula,
397
+ then negations of all of its literals are backbones of the formula
398
+ and the MCSls algorithm can stop. Otherwise, the literals satisfied
399
+ by the new model refine the MCS further.
400
+
401
+ Every time the method is called, a new fresh selector variable
402
+ :math:`s` is introduced, which augments the current clause
403
+ :math:`D`. The SAT oracle then checks if clause :math:`(D \\vee
404
+ \\neg{s})` can be satisfied together with the internal formula.
405
+ The :math:`D` clause is then disabled by adding a hard clause
406
+ :math:`(\\neg{s})`.
407
+
408
+ :param cld: clause :math:`D` to check
409
+ :type cld: list(int)
410
+ """
411
+
412
+ # adding a selector literal to clause D
413
+ # selector literals for clauses D currently
414
+ # cannot be reused, but this may change later
415
+ self.topv += 1
416
+ sel = self.topv
417
+ cld.append(-sel)
418
+
419
+ # adding clause D
420
+ self.oracle.add_clause(cld)
421
+ self.ss_assumps.append(sel)
422
+
423
+ self.setd = []
424
+ self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps)
425
+
426
+ self.ss_assumps.pop() # removing clause D assumption
427
+ if self.oracle.get_status() == True:
428
+ model = self.oracle.get_model()
429
+
430
+ for l in cld[:-1]:
431
+ # filtering all satisfied literals
432
+ if model[abs(l) - 1] > 0:
433
+ self.ss_assumps.append(l)
434
+ else:
435
+ self.setd.append(l)
436
+ else:
437
+ # clause D is unsatisfiable => all literals are backbones
438
+ self.bb_assumps.extend([-l for l in cld[:-1]])
439
+
440
+ # deactivating clause D
441
+ self.oracle.add_clause([-sel])
442
+
443
+ def _map_extlit(self, l):
444
+ """
445
+ Map an external variable to an internal one if necessary.
446
+
447
+ This method is used when new clauses are added to the formula
448
+ incrementally, which may result in introducing new variables
449
+ clashing with the previously used *clause selectors*. The method
450
+ makes sure no clash occurs, i.e. it maps the original variables
451
+ used in the new problem clauses to the newly introduced auxiliary
452
+ variables (see :func:`add_clause`).
453
+
454
+ Given an integer literal, a fresh literal is returned. The returned
455
+ integer has the same sign as the input literal.
456
+
457
+ :param l: literal to map
458
+ :type l: int
459
+
460
+ :rtype: int
461
+ """
462
+
463
+ v = abs(l)
464
+
465
+ if v in self.vmap.e2i:
466
+ return int(copysign(self.vmap.e2i[v], l))
467
+ else:
468
+ self.topv += 1
469
+
470
+ self.vmap.e2i[v] = self.topv
471
+ self.vmap.i2e[self.topv] = v
472
+
473
+ return int(copysign(self.topv, l))
474
+
475
+ def oracle_time(self):
476
+ """
477
+ Report the total SAT solving time.
478
+ """
479
+
480
+ return self.oracle.time_accum()
481
+
482
+
483
+ #
484
+ #==============================================================================
485
+ def parse_options():
486
+ """
487
+ Parses command-line options.
488
+ """
489
+
490
+ try:
491
+ opts, args = getopt.getopt(sys.argv[1:],
492
+ 'de:hs:v',
493
+ ['dcalls',
494
+ 'enum=',
495
+ 'help',
496
+ 'solver=',
497
+ 'verbose'])
498
+ except getopt.GetoptError as err:
499
+ sys.stderr.write(str(err).capitalize() + '\n')
500
+ usage()
501
+ sys.exit(1)
502
+
503
+ dcalls = False
504
+ to_enum = 1
505
+ solver = 'm22'
506
+ verbose = 0
507
+
508
+ for opt, arg in opts:
509
+ if opt in ('-d', '--dcalls'):
510
+ dcalls = True
511
+ elif opt in ('-e', '--enum'):
512
+ to_enum = str(arg)
513
+ if to_enum != 'all':
514
+ to_enum = int(to_enum)
515
+ elif opt in ('-h', '--help'):
516
+ usage()
517
+ sys.exit(0)
518
+ elif opt in ('-s', '--solver'):
519
+ solver = str(arg)
520
+ elif opt in ('-v', '--verbose'):
521
+ verbose += 1
522
+ else:
523
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
524
+
525
+ return dcalls, to_enum, solver, verbose, args
526
+
527
+
528
+ #
529
+ #==============================================================================
530
+ def usage():
531
+ """
532
+ Prints help message.
533
+ """
534
+
535
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
536
+ print('Options:')
537
+ print(' -d, --dcalls Apply clause D calls')
538
+ print(' -e, --enum=<string> How many solutions to compute')
539
+ print(' Available values: [1 .. all] (default: 1)')
540
+ print(' -h, --help')
541
+ print(' -s, --solver SAT solver to use')
542
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
543
+ print(' -v, --verbose Be verbose')
544
+
545
+
546
+ #
547
+ #==============================================================================
548
+ if __name__ == '__main__':
549
+ dcalls, to_enum, solver, verbose, files = parse_options()
550
+
551
+ if type(to_enum) == str:
552
+ to_enum = 0
553
+
554
+ if files:
555
+ # reading standard CNF, WCNF, or (W)CNF+
556
+ if re.search('cnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
557
+ if re.search('\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
558
+ formula = WCNFPlus(from_file=files[0])
559
+ else: # expecting '*.cnf[,p,+].*'
560
+ formula = CNFPlus(from_file=files[0]).weighted()
561
+
562
+ with MCSls(formula, use_cld=dcalls, solver_name=solver, use_timer=True) as mcsls:
563
+ for i, mcs in enumerate(mcsls.enumerate()):
564
+ if verbose:
565
+ print('c MCS:', ' '.join([str(cl_id) for cl_id in mcs]), '0')
566
+
567
+ if verbose > 1:
568
+ cost = sum([formula.wght[cl_id - 1] for cl_id in mcs])
569
+ print('c cost:', cost)
570
+
571
+ if to_enum and i + 1 == to_enum:
572
+ break
573
+
574
+ mcsls.block(mcs)
575
+
576
+ print('c oracle time: {0:.4f}'.format(mcsls.oracle_time()))