python-sat 1.9.dev7__tar.gz → 1.9.dev9__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.9.dev7/python_sat.egg-info → python_sat-1.9.dev9}/PKG-INFO +1 -1
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/rc2.py +6 -4
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/__init__.py +1 -1
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/formula.py +209 -106
- {python_sat-1.9.dev7 → python_sat-1.9.dev9/python_sat.egg-info}/PKG-INFO +1 -1
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_clausification.py +4 -4
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_formula_unique.py +32 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/LICENSE.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/MANIFEST.in +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/README.rst +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/__init__.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/approxmc.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/unigen.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/bitwise.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/card.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/clset.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/common.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/itot.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/ladder.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/mto.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/pairwise.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/ptypes.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/pycard.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/seqcounter.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/sortcard.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/utils.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/__init__.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/bbscan.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/bica.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/fm.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/genhard.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/hitman.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/lbx.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/lsu.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/mcsls.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/models.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/musx.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/optux.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/primer.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/usage.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_parse.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_plus.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_plus.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_py.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_scan.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_weight.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_weight.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pyformula.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pyformula.hh +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/_fileio.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/_utils.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/card.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/engines.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/integer.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/pb.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/process.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/solvers.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/SOURCES.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/dependency_links.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/requires.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/top_level.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/requirements.txt +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/setup.cfg +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/setup.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical103.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical153.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical195.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical300.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose30.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose41.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose421.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/lingeling.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplechrono.zip +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplecm.zip +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplesat.zip +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/mergesat3.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minicard.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minisat22.tar.gz +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minisatgh.zip +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical103.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical153.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical195.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical300.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose30.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose41.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose421.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/gluecard30.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/gluecard41.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/kissat404.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/lingeling.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplechrono.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplecm.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplesat.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/mergesat3.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minicard.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisat22.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisatep.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisatgh.patch +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/prepare.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/pysolvers.cc +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_accum_stats.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmost.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmost1.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmostk.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_boolengine.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_cnf.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_empty_extclause.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_encode_pb_conditional.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_equals1.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_formula_parsing.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_idpool.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_integer.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_oom.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_process.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_propagate.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_unique_model.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_unique_mus.py +0 -0
- {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_warmstart.py +0 -0
|
@@ -134,7 +134,6 @@ from __future__ import print_function
|
|
|
134
134
|
import bisect
|
|
135
135
|
import collections
|
|
136
136
|
import getopt
|
|
137
|
-
import itertools
|
|
138
137
|
from math import copysign
|
|
139
138
|
import os
|
|
140
139
|
from pysat.formula import CNFPlus, WCNFPlus, IDPool
|
|
@@ -740,9 +739,12 @@ class RC2(object):
|
|
|
740
739
|
self.minw = min(map(lambda l: self.wght[l], self.core))
|
|
741
740
|
|
|
742
741
|
# dividing the core into two parts
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
742
|
+
self.core_sels, self.core_sums = [], []
|
|
743
|
+
for l in self.core:
|
|
744
|
+
if l in self.sels_set:
|
|
745
|
+
self.core_sels.append(l)
|
|
746
|
+
else:
|
|
747
|
+
self.core_sums.append(l)
|
|
746
748
|
|
|
747
749
|
def process_core(self):
|
|
748
750
|
"""
|
|
@@ -810,6 +810,13 @@ class Formula(object):
|
|
|
810
810
|
:type type: :class:`FormulaType`
|
|
811
811
|
"""
|
|
812
812
|
|
|
813
|
+
# Formula.__new__ may return an existing structurally identical
|
|
814
|
+
# formula; by default, Python still invokes __init__ in that case,
|
|
815
|
+
# which is harmful; if an identical object exists, we should
|
|
816
|
+
# immediately return
|
|
817
|
+
if hasattr(self, 'type'):
|
|
818
|
+
return
|
|
819
|
+
|
|
813
820
|
assert 'type' in kwargs, 'No \'type\' of the formula is provided'
|
|
814
821
|
self.type = kwargs['type']
|
|
815
822
|
|
|
@@ -883,11 +890,11 @@ class Formula(object):
|
|
|
883
890
|
@staticmethod
|
|
884
891
|
def _get_key(*args, **kwargs):
|
|
885
892
|
"""
|
|
886
|
-
The method is used to extract and return
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
893
|
+
The method is used to extract and return an immutable structural
|
|
894
|
+
key associated with the formula requested by a user. The key is
|
|
895
|
+
composed of tuples, frozensets, and scalar values rather than
|
|
896
|
+
string representations. The method is meant to be internal and
|
|
897
|
+
not to be used by formula users.
|
|
891
898
|
"""
|
|
892
899
|
|
|
893
900
|
def _hashable(entity):
|
|
@@ -918,7 +925,6 @@ class Formula(object):
|
|
|
918
925
|
if isinstance(value, Iterable) and value:
|
|
919
926
|
items.extend(_flatten(value, prefix=new_key, sep=sep))
|
|
920
927
|
else:
|
|
921
|
-
|
|
922
928
|
items.append((new_key, value))
|
|
923
929
|
|
|
924
930
|
# finally, it is a simple non-empty object
|
|
@@ -945,58 +951,121 @@ class Formula(object):
|
|
|
945
951
|
elif item[0] != 'merge':
|
|
946
952
|
subfs.append(item)
|
|
947
953
|
else:
|
|
948
|
-
extra = [('merge',
|
|
954
|
+
extra = [('merge', item[1])]
|
|
955
|
+
|
|
956
|
+
def _object_key(obj):
|
|
957
|
+
"""
|
|
958
|
+
Get a hashable key for an atom object.
|
|
959
|
+
"""
|
|
960
|
+
|
|
961
|
+
if type(obj) in (bool, int, float, str, bytes, type(None)):
|
|
962
|
+
return ('val', type(obj), obj)
|
|
963
|
+
if type(obj) in (tuple, list):
|
|
964
|
+
return ('seq', tuple(_object_key(item) for item in obj))
|
|
965
|
+
if type(obj) in (set, frozenset):
|
|
966
|
+
return ('set', frozenset(_object_key(item) for item in obj))
|
|
967
|
+
if isinstance(obj, dict):
|
|
968
|
+
return ('map', frozenset((_object_key(key), _object_key(val)) for key, val in obj.items()))
|
|
969
|
+
try:
|
|
970
|
+
hash(obj)
|
|
971
|
+
except TypeError:
|
|
972
|
+
return ('identity', type(obj), id(obj))
|
|
973
|
+
return ('obj', type(obj), obj)
|
|
974
|
+
|
|
975
|
+
def _formula_key(form):
|
|
976
|
+
"""
|
|
977
|
+
Get a structural key without a formula's merge flag.
|
|
978
|
+
"""
|
|
979
|
+
|
|
980
|
+
if not isinstance(form, Formula):
|
|
981
|
+
return ('identity', type(form), id(form))
|
|
982
|
+
|
|
983
|
+
if form.type == FormulaType.CNF:
|
|
984
|
+
# CNF and CNFPlus are mutable containers and deliberately
|
|
985
|
+
# bypass Formula's unique-object constructor; using their
|
|
986
|
+
# object identity as it looks like the cheapest option
|
|
987
|
+
return ('cnf-object', type(form), id(form))
|
|
988
|
+
|
|
989
|
+
if form.type == FormulaType.ATOM:
|
|
990
|
+
return (FormulaType.ATOM, _object_key(form.object))
|
|
991
|
+
if form.type == FormulaType.NEG:
|
|
992
|
+
return (FormulaType.NEG, _formula_key(form.subformula))
|
|
993
|
+
if form.type == FormulaType.IMPL:
|
|
994
|
+
return (FormulaType.IMPL, _formula_key(form.left),
|
|
995
|
+
_formula_key(form.right))
|
|
996
|
+
if form.type == FormulaType.ITE:
|
|
997
|
+
return (FormulaType.ITE, _formula_key(form.cond),
|
|
998
|
+
_formula_key(form.cons1),
|
|
999
|
+
_formula_key(form.cons2))
|
|
1000
|
+
|
|
1001
|
+
counts = collections.Counter(_formula_key(subform) for subform in form.subformulas)
|
|
1002
|
+
return (form.type, frozenset(counts.items()))
|
|
1003
|
+
|
|
1004
|
+
def _merged_operands(operands, formula_type):
|
|
1005
|
+
"""Flatten same-type operands for a merged connective."""
|
|
1006
|
+
result = []
|
|
1007
|
+
for operand in operands:
|
|
1008
|
+
if isinstance(operand, Formula) and operand.type == formula_type:
|
|
1009
|
+
result.extend(_merged_operands(operand.subformulas,
|
|
1010
|
+
formula_type))
|
|
1011
|
+
else:
|
|
1012
|
+
result.append(_formula_key(operand))
|
|
1013
|
+
return result
|
|
949
1014
|
|
|
950
|
-
# the
|
|
951
|
-
|
|
952
|
-
if
|
|
1015
|
+
# Reconstruct a structural key depending on the Formula type.
|
|
1016
|
+
formula_type = ftype[1]
|
|
1017
|
+
if formula_type == FormulaType.ATOM:
|
|
953
1018
|
assert len(subfs) == 1, 'A single object is required for an Atom formula'
|
|
954
|
-
|
|
955
|
-
|
|
1019
|
+
return (formula_type, _object_key(subfs[0][1]))
|
|
1020
|
+
|
|
1021
|
+
if formula_type == FormulaType.NEG:
|
|
956
1022
|
assert len(subfs) == 1, 'A single subformula is required for a Neg formula'
|
|
957
|
-
|
|
958
|
-
|
|
1023
|
+
return (formula_type, _formula_key(subfs[0][1]))
|
|
1024
|
+
|
|
1025
|
+
if formula_type == FormulaType.IMPL:
|
|
959
1026
|
assert len(subfs) == 2, 'Two subformulas are required for an Implies formula'
|
|
960
1027
|
if subfs[0][0] == '' and subfs[1][0] == '':
|
|
961
|
-
|
|
962
|
-
subfs[1] = ('right', id(subfs[1][1]))
|
|
1028
|
+
operands = [('left', subfs[0][1]), ('right', subfs[1][1])]
|
|
963
1029
|
elif subfs[1][0] == 'left':
|
|
964
|
-
|
|
1030
|
+
operands = [('right', subfs[0][1]), subfs[1]]
|
|
965
1031
|
elif subfs[1][0] == 'right':
|
|
966
|
-
|
|
967
|
-
|
|
1032
|
+
operands = [('left', subfs[0][1]), subfs[1]]
|
|
1033
|
+
else:
|
|
1034
|
+
operands = subfs
|
|
1035
|
+
role_order = {'left': 0, 'right': 1}
|
|
1036
|
+
return (formula_type, tuple( (label, _formula_key(value)) for \
|
|
1037
|
+
label, value in sorted(operands,
|
|
1038
|
+
key=lambda pair: role_order[pair[0]])))
|
|
1039
|
+
|
|
1040
|
+
if formula_type == FormulaType.ITE:
|
|
968
1041
|
assert len(subfs) == 3, 'Three subformulas are required for an ITE formula'
|
|
969
|
-
if
|
|
970
|
-
subfs
|
|
971
|
-
subfs[1] = ('cons1', id(subfs[1][1]))
|
|
972
|
-
subfs[2] = ('cons2', id(subfs[2][1]))
|
|
1042
|
+
if all(item[0] == '' for item in subfs):
|
|
1043
|
+
subfs = [('cond', subfs[0][1]), ('cons1', subfs[1][1]), ('cons2', subfs[2][1])]
|
|
973
1044
|
elif subfs[0][0] == subfs[1][0] == '':
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1045
|
+
role = subfs[2][0]
|
|
1046
|
+
missing = [r for r in ('cond', 'cons1', 'cons2') if r != role]
|
|
1047
|
+
subfs = [(missing[0], subfs[0][1]), (missing[1], subfs[1][1]), subfs[2]]
|
|
1048
|
+
elif subfs[0][0] == '':
|
|
1049
|
+
roles = {item[0] for item in subfs[1:]}
|
|
1050
|
+
missing = next(r for r in ('cond', 'cons1', 'cons2') if r not in roles)
|
|
1051
|
+
subfs[0] = (missing, subfs[0][1])
|
|
1052
|
+
|
|
1053
|
+
role_order = {'cond': 0, 'cons1': 1, 'cons2': 2}
|
|
1054
|
+
return (formula_type, tuple((label, _formula_key(value)) for \
|
|
1055
|
+
label, value in sorted(subfs,
|
|
1056
|
+
key=lambda pair: role_order[pair[0]])))
|
|
1057
|
+
|
|
1058
|
+
# these are commutative connectives; representing operands as a
|
|
1059
|
+
# multiset so that argument order is irrelevant while duplicate
|
|
1060
|
+
# operands remain significant; a merged connective also flattens nested operands.
|
|
1061
|
+
assert len(subfs) >= 1, 'At least one subformula is required for an And/Equals/Or/XOr formula'
|
|
1062
|
+
merge = dict(extra).get('merge', False)
|
|
1063
|
+
operands = [value for _, value in subfs]
|
|
1064
|
+
if merge:
|
|
1065
|
+
operands = _merged_operands(operands, formula_type)
|
|
990
1066
|
else:
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
subfs = sorted(map(lambda p: (p[0], repr(id(p[1]))), subfs))
|
|
994
|
-
|
|
995
|
-
if not extra:
|
|
996
|
-
extra = [('merge', repr(False))]
|
|
997
|
-
|
|
998
|
-
# the key is a string combining all the parts
|
|
999
|
-
return ' '.join([f'{repr(p[0])}:{repr(p[1])}' for p in [ftype] + subfs + extra])
|
|
1067
|
+
operands = [_formula_key(operand) for operand in operands]
|
|
1068
|
+
return (formula_type, frozenset(collections.Counter(operands).items()), merge)
|
|
1000
1069
|
|
|
1001
1070
|
def __hash__(self):
|
|
1002
1071
|
"""
|
|
@@ -1708,50 +1777,83 @@ class And(Formula):
|
|
|
1708
1777
|
|
|
1709
1778
|
def _clausify(self, name_required=True):
|
|
1710
1779
|
"""
|
|
1711
|
-
|
|
1780
|
+
Conjunction clausification.
|
|
1712
1781
|
|
|
1713
1782
|
If ``name_required`` is ``False``, the method recursively encodes
|
|
1714
|
-
the subformulas
|
|
1715
|
-
|
|
1716
|
-
subformula, thus
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1783
|
+
the subformulas expanding self's list of clauses and also
|
|
1784
|
+
populates self's clauses with unit clauses, each containing the
|
|
1785
|
+
auxiliary "name" of the corresponding subformula, thus
|
|
1786
|
+
representing their conjunction. However, nested :class:`And` and
|
|
1787
|
+
:class:`CNF` formulas are expanded without introducing
|
|
1788
|
+
intermediate Boolean names - instead, they contribute their
|
|
1789
|
+
clausal representations to the list of self's clauses unchanged.
|
|
1790
|
+
|
|
1791
|
+
If ``name_required`` is set to ``True``, the method introduces one
|
|
1792
|
+
name for the complete conjunction and encodes the equivalence
|
|
1793
|
+
between that name and the collected clauses.
|
|
1723
1794
|
|
|
1724
1795
|
:param name_required: whether or not a Tseitin variable is needed
|
|
1725
|
-
:
|
|
1796
|
+
:type name_required: bool
|
|
1726
1797
|
"""
|
|
1727
1798
|
|
|
1728
1799
|
save_clauses = bool(self.clauses)
|
|
1729
1800
|
|
|
1730
1801
|
if not self.clauses:
|
|
1731
|
-
#
|
|
1802
|
+
# recursively collect a clausal representation; nested `And` and
|
|
1803
|
+
# `CNF` objects are conjunctions already, so they do not need a
|
|
1804
|
+
# Boolean name at this level.
|
|
1732
1805
|
for sub in self.subformulas:
|
|
1733
|
-
sub
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1806
|
+
if type(sub) in (And, CNF):
|
|
1807
|
+
sub._clausify(name_required=False)
|
|
1808
|
+
self.clauses.extend(clause.copy() for clause in sub.clauses)
|
|
1809
|
+
else:
|
|
1810
|
+
sub._clausify(name_required=True)
|
|
1811
|
+
self.clauses.append([sub.name])
|
|
1737
1812
|
|
|
1738
|
-
# introducing a
|
|
1813
|
+
# introducing a name is needed only when this conjunction is itself
|
|
1814
|
+
# used by another connective; encode each non-unit clause with its own
|
|
1815
|
+
# selector, then encode the conjunction of those selectors with the
|
|
1816
|
+
# single name for this `And` formula
|
|
1739
1817
|
if name_required and not self.name:
|
|
1740
|
-
|
|
1741
|
-
self.encoded = [clause.copy() for clause in self.clauses]
|
|
1742
|
-
else:
|
|
1743
|
-
self.encoded = self.clauses
|
|
1744
|
-
self.clauses = []
|
|
1818
|
+
self.encoded, enclits = [], []
|
|
1745
1819
|
|
|
1746
|
-
|
|
1820
|
+
for cl in self.clauses:
|
|
1821
|
+
auxv = cl[0]
|
|
1822
|
+
if len(cl) > 1:
|
|
1823
|
+
auxv = Formula._vpool[Formula._context].id()
|
|
1824
|
+
|
|
1825
|
+
# direct implication
|
|
1826
|
+
for l in cl:
|
|
1827
|
+
self.encoded.append([-l, auxv])
|
|
1828
|
+
|
|
1829
|
+
# opposite implication
|
|
1830
|
+
self.encoded.append(cl + [-auxv])
|
|
1747
1831
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
cl.append(-self.encoded[i][0]) # updating final clause
|
|
1751
|
-
self.encoded[i].append(-self.name) # updating direct implication
|
|
1832
|
+
# literals representing the clauses
|
|
1833
|
+
enclits.append(auxv)
|
|
1752
1834
|
|
|
1753
|
-
#
|
|
1754
|
-
|
|
1835
|
+
# encoding the conjunction
|
|
1836
|
+
if len(enclits) > 1:
|
|
1837
|
+
self.name = Formula._vpool[Formula._context].id(self)
|
|
1838
|
+
|
|
1839
|
+
for lit in enclits:
|
|
1840
|
+
self.encoded.append([lit, -self.name])
|
|
1841
|
+
self.encoded.append([self.name] + [-lit for lit in enclits])
|
|
1842
|
+
elif len(enclits) == 1: # single clause - nothing left to encode
|
|
1843
|
+
self.name = enclits[0] # existing variable
|
|
1844
|
+
|
|
1845
|
+
# connecting it to the `And` object as its name
|
|
1846
|
+
Formula._vpool[Formula._context].obj2id[self] = self.name
|
|
1847
|
+
if self.name not in Formula._vpool[Formula._context].id2obj:
|
|
1848
|
+
Formula._vpool[Formula._context].id2obj[self.name] = self
|
|
1849
|
+
else:
|
|
1850
|
+
self.name = PYSAT_TRUE.name
|
|
1851
|
+
|
|
1852
|
+
# preserve clauses that existed before this call;
|
|
1853
|
+
# they are still needed if the same formula is later iterated as
|
|
1854
|
+
# an outermost formula
|
|
1855
|
+
if not save_clauses:
|
|
1856
|
+
self.clauses = []
|
|
1755
1857
|
|
|
1756
1858
|
def _atoms(self, dest):
|
|
1757
1859
|
"""
|
|
@@ -1947,18 +2049,18 @@ class Or(Formula):
|
|
|
1947
2049
|
Disjunction clausification.
|
|
1948
2050
|
|
|
1949
2051
|
If ``name_required`` is ``False``, the method recursively encodes
|
|
1950
|
-
the subformulas and populates self's clauses with unit
|
|
1951
|
-
|
|
1952
|
-
|
|
2052
|
+
the subformulas and populates self's clauses with a unit clause
|
|
2053
|
+
containing the auxiliary "name" of each corresponding subformula,
|
|
2054
|
+
thus representing their disjunction.
|
|
1953
2055
|
|
|
1954
2056
|
If ``name_required`` is set to ``True``, the method encodes the
|
|
1955
|
-
|
|
2057
|
+
disjunction using the standard logic: :math:`x \\equiv
|
|
1956
2058
|
\\bigvee{y_i}`, if :math:`x` is the new auxiliary variable
|
|
1957
2059
|
encoding ``self`` and :math:`y_i` is the auxiliary variable
|
|
1958
2060
|
representing :math:`i`'s subformula.
|
|
1959
2061
|
|
|
1960
2062
|
:param name_required: whether or not a Tseitin variable is needed
|
|
1961
|
-
:
|
|
2063
|
+
:type name_required: bool
|
|
1962
2064
|
"""
|
|
1963
2065
|
|
|
1964
2066
|
save_clauses = bool(self.clauses)
|
|
@@ -2136,7 +2238,7 @@ class Neg(Formula):
|
|
|
2136
2238
|
is equal to the negation of subformula's name.
|
|
2137
2239
|
|
|
2138
2240
|
:param name_required: whether or not a Tseitin variable is needed
|
|
2139
|
-
:
|
|
2241
|
+
:type name_required: bool
|
|
2140
2242
|
"""
|
|
2141
2243
|
|
|
2142
2244
|
if not self.clauses:
|
|
@@ -2307,7 +2409,7 @@ class Implies(Formula):
|
|
|
2307
2409
|
|
|
2308
2410
|
If ``name_required`` is ``False``, the method recursively encodes
|
|
2309
2411
|
the left and right subformulas giving them names, say, :math:`x`
|
|
2310
|
-
and :math:`y` respectively and
|
|
2412
|
+
and :math:`y` respectively, and populates self's clauses with a
|
|
2311
2413
|
single binary clause :math:`(\\neg{x}\\lor y)`.
|
|
2312
2414
|
|
|
2313
2415
|
If ``name_required`` is set to ``True``, the method removes this
|
|
@@ -2315,7 +2417,7 @@ class Implies(Formula):
|
|
|
2315
2417
|
Tseitin-encoding it, i.e. :math:`n \\equiv (\\neg{x}\\lor y)`.
|
|
2316
2418
|
|
|
2317
2419
|
:param name_required: whether or not a Tseitin variable is needed
|
|
2318
|
-
:
|
|
2420
|
+
:type name_required: bool
|
|
2319
2421
|
"""
|
|
2320
2422
|
|
|
2321
2423
|
save_clauses = bool(self.clauses)
|
|
@@ -2407,7 +2509,7 @@ class Equals(Formula):
|
|
|
2407
2509
|
>>> from pysat.formula import *
|
|
2408
2510
|
>>> a1 = Equals(Equals(Atom('x'), Atom('y')), Atom('z'))
|
|
2409
2511
|
>>> a2 = Equals(Equals(Atom('x'), Atom('y')), Atom('z'), merge=True)
|
|
2410
|
-
>>> a3 = Atom('x')
|
|
2512
|
+
>>> a3 = Atom('x') @ Atom('y') @ Atom('z')
|
|
2411
2513
|
>>>
|
|
2412
2514
|
>>> print(a1)
|
|
2413
2515
|
(x @ y) @ z
|
|
@@ -2571,7 +2673,7 @@ class Equals(Formula):
|
|
|
2571
2673
|
relating it with the names of subformulas.
|
|
2572
2674
|
|
|
2573
2675
|
:param name_required: whether or not a Tseitin variable is needed
|
|
2574
|
-
:
|
|
2676
|
+
:type name_required: bool
|
|
2575
2677
|
"""
|
|
2576
2678
|
|
|
2577
2679
|
save_clauses = bool(self.clauses)
|
|
@@ -2652,13 +2754,12 @@ class XOr(Formula):
|
|
|
2652
2754
|
|
|
2653
2755
|
If an additional Boolean keyword argument ``merge`` is provided set to
|
|
2654
2756
|
``True``, the toolkit will try to flatten the current :class:`XOr`
|
|
2655
|
-
formula merging its *
|
|
2656
|
-
operands. For example, if ``XOr(XOr(x, y), z, merge=True)`` is
|
|
2657
|
-
a new Formula object will be created with two operands:
|
|
2658
|
-
and ``z``, followed by merging ``x`` and ``y`` into the
|
|
2659
|
-
root-level ``XOr``. This will result in a formula
|
|
2660
|
-
|
|
2661
|
-
used to create ``XOr`` formulas.
|
|
2757
|
+
formula merging its *exclusive-disjunction* sub-operands into the
|
|
2758
|
+
list of operands. For example, if ``XOr(XOr(x, y), z, merge=True)`` is
|
|
2759
|
+
called, a new Formula object will be created with two operands:
|
|
2760
|
+
``XOr(x, y)`` and ``z``, followed by merging ``x`` and ``y`` into the
|
|
2761
|
+
list of root-level ``XOr``. This will result in a formula
|
|
2762
|
+
``XOr(x, y, z)``. Bitwise ``^`` operations also enable merging.
|
|
2662
2763
|
|
|
2663
2764
|
Example:
|
|
2664
2765
|
|
|
@@ -2679,8 +2780,8 @@ class XOr(Formula):
|
|
|
2679
2780
|
>>> id(a1) == id(a2)
|
|
2680
2781
|
False
|
|
2681
2782
|
>>>
|
|
2682
|
-
>>> id(
|
|
2683
|
-
True # formulas
|
|
2783
|
+
>>> id(a2) == id(a3)
|
|
2784
|
+
True # formulas a2 and a3 refer to the same object
|
|
2684
2785
|
|
|
2685
2786
|
.. note::
|
|
2686
2787
|
|
|
@@ -2766,7 +2867,7 @@ class XOr(Formula):
|
|
|
2766
2867
|
>>> a = x ^ y ^ z
|
|
2767
2868
|
>>>
|
|
2768
2869
|
>>> print(a.simplified(assumptions=[y]))
|
|
2769
|
-
~x ^ z
|
|
2870
|
+
~(x ^ z)
|
|
2770
2871
|
>>> print(a.simplified(assumptions=[~y]))
|
|
2771
2872
|
x ^ z
|
|
2772
2873
|
"""
|
|
@@ -2817,7 +2918,7 @@ class XOr(Formula):
|
|
|
2817
2918
|
variable for each pair of operands.
|
|
2818
2919
|
|
|
2819
2920
|
:param name_required: whether or not a Tseitin variable is needed
|
|
2820
|
-
:
|
|
2921
|
+
:type name_required: bool
|
|
2821
2922
|
"""
|
|
2822
2923
|
|
|
2823
2924
|
save_clauses = bool(self.clauses)
|
|
@@ -2943,7 +3044,7 @@ class ITE(Formula):
|
|
|
2943
3044
|
>>> ite = ITE(x, y, z)
|
|
2944
3045
|
>>>
|
|
2945
3046
|
>>> print(ite)
|
|
2946
|
-
|
|
3047
|
+
(x >> y) & (~x >> z)
|
|
2947
3048
|
"""
|
|
2948
3049
|
|
|
2949
3050
|
def __new__(cls, *args, **kwargs):
|
|
@@ -3071,17 +3172,17 @@ class ITE(Formula):
|
|
|
3071
3172
|
|
|
3072
3173
|
If ``name_required`` is ``False``, the method recursively encodes
|
|
3073
3174
|
the ``cond``, ``cons1``, and ``cons2`` subformulas giving them
|
|
3074
|
-
names, say, :math:`x`, :math:`y`, and :math:`z`,
|
|
3075
|
-
|
|
3076
|
-
:math:`(\\neg{x}\\lor y)` and :math:`(x \\lor
|
|
3175
|
+
names, say, :math:`x`, :math:`y`, and :math:`z`, respectively, and
|
|
3176
|
+
populates self's clauses with two binary clauses
|
|
3177
|
+
:math:`(\\neg{x}\\lor y)` and :math:`(x \\lor z)`.
|
|
3077
3178
|
|
|
3078
3179
|
If ``name_required`` is set to ``True``, the method removes these
|
|
3079
3180
|
clauses and instead gives a name to the ITE by Tseitin-encoding
|
|
3080
3181
|
it, i.e. encoding :math:`n \\equiv \\left[(\\neg{x}\\lor
|
|
3081
|
-
y)\\land(x\\lor
|
|
3182
|
+
y)\\land(x\\lor z)\\right]`.
|
|
3082
3183
|
|
|
3083
3184
|
:param name_required: whether or not a Tseitin variable is needed
|
|
3084
|
-
:
|
|
3185
|
+
:type name_required: bool
|
|
3085
3186
|
"""
|
|
3086
3187
|
|
|
3087
3188
|
save_clauses = bool(self.clauses)
|
|
@@ -3136,7 +3237,7 @@ class ITE(Formula):
|
|
|
3136
3237
|
"""
|
|
3137
3238
|
Informal representation of the ``ITE`` formula. Returns a string
|
|
3138
3239
|
representation of the subformulas as two implications connected
|
|
3139
|
-
with the
|
|
3240
|
+
with the conjunction symbol ``' & '``
|
|
3140
3241
|
"""
|
|
3141
3242
|
|
|
3142
3243
|
cons1 = '{0} >> {1}'.format(f'{str(self.cond)}' if self.cond.type in (FormulaType.ATOM, FormulaType.NEG) else f'({str(self.cond)})',
|
|
@@ -3898,7 +3999,7 @@ class CNF(Formula, object):
|
|
|
3898
3999
|
for lit in enclits:
|
|
3899
4000
|
clauses.append([-self.name, lit])
|
|
3900
4001
|
clauses.append([self.name] + [-lit for lit in enclits])
|
|
3901
|
-
|
|
4002
|
+
elif len(enclits) == 1: # single clause - nothing left to encode
|
|
3902
4003
|
self.name = enclits[0] # existing variable
|
|
3903
4004
|
|
|
3904
4005
|
# connecting it to the CNF object as its name
|
|
@@ -3907,6 +4008,8 @@ class CNF(Formula, object):
|
|
|
3907
4008
|
|
|
3908
4009
|
# just in case, marking all ids below self.name as occupied
|
|
3909
4010
|
Formula._vpool[Formula._context].occupy(1, self.name)
|
|
4011
|
+
else:
|
|
4012
|
+
self.name = PYSAT_TRUE.name
|
|
3910
4013
|
|
|
3911
4014
|
self.encoded = clauses
|
|
3912
4015
|
self.auxvars = auxvars
|
|
@@ -34,8 +34,8 @@ def test_clausification():
|
|
|
34
34
|
|
|
35
35
|
# multiple successive inner/outermost clausification
|
|
36
36
|
f = And(Atom('1'), Atom('2'))
|
|
37
|
-
g = And(f, Atom('3'))
|
|
38
|
-
compare(list(f), [[1], [2]])
|
|
39
|
-
compare(list(g), [[1, -3], [2, -3], [3, -1, -2], [3], [4]])
|
|
40
37
|
compare(list(f), [[1], [2]])
|
|
41
|
-
|
|
38
|
+
|
|
39
|
+
# nested conjunctions are inherited directly by the outer conjunction
|
|
40
|
+
g = And(f, Atom('3'))
|
|
41
|
+
compare(list(g), [[1], [2], [4]])
|
|
@@ -59,6 +59,27 @@ def test_ite_uniqueness():
|
|
|
59
59
|
|
|
60
60
|
assert ITE(*comb) is not a, 'ITE is not commutative'
|
|
61
61
|
|
|
62
|
+
def test_cnf_as_and_subformula():
|
|
63
|
+
Formula.cleanup()
|
|
64
|
+
cnf = CNF(from_clauses=[[-1, 2], [3]])
|
|
65
|
+
formula = And(And(cnf, Atom(4)), Atom(5))
|
|
66
|
+
|
|
67
|
+
assert list(formula) == [[-1, 2], [3], [4], [5]]
|
|
68
|
+
assert formula.name is None
|
|
69
|
+
assert cnf.name is None
|
|
70
|
+
assert cnf.encoded == []
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_ite_call_syntax_uniqueness():
|
|
74
|
+
x, y, z = Atom('x'), Atom('y'), Atom('z')
|
|
75
|
+
|
|
76
|
+
a = ITE(x, y, z)
|
|
77
|
+
|
|
78
|
+
assert ITE(cond=x, cons1=y, cons2=z) is a
|
|
79
|
+
assert ITE(x, cons1=y, cons2=z) is a
|
|
80
|
+
assert ITE(cons2=z, cond=x, cons1=y) is a
|
|
81
|
+
|
|
82
|
+
|
|
62
83
|
def test_xor_uniqueness():
|
|
63
84
|
x, y, z = Atom('x'), Atom('y'), Atom('z')
|
|
64
85
|
|
|
@@ -72,6 +93,17 @@ def test_xor_uniqueness():
|
|
|
72
93
|
assert XOr(*comb, merge=False) is b, 'All 3-xor\'s should be the same object'
|
|
73
94
|
assert XOr(*comb) is b, 'All 3-xor\'s should be the same object'
|
|
74
95
|
|
|
96
|
+
def test_nested_merge_uniqueness():
|
|
97
|
+
x, y, z = Atom('x'), Atom('y'), Atom('z')
|
|
98
|
+
|
|
99
|
+
a = Equals(Equals(x, y), z, merge=True)
|
|
100
|
+
assert Equals(Equals(y, z), x, merge=True) is a
|
|
101
|
+
assert x @ y @ z is a
|
|
102
|
+
|
|
103
|
+
unmerged = Equals(Equals(x, y), z, merge=False)
|
|
104
|
+
assert unmerged is not a
|
|
105
|
+
|
|
106
|
+
|
|
75
107
|
def test_equals_uniqueness():
|
|
76
108
|
x, y, z = Atom('x'), Atom('y'), Atom('z')
|
|
77
109
|
|
|
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
|
|
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
|