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.
Files changed (118) hide show
  1. {python_sat-1.9.dev7/python_sat.egg-info → python_sat-1.9.dev9}/PKG-INFO +1 -1
  2. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/rc2.py +6 -4
  3. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/__init__.py +1 -1
  4. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/formula.py +209 -106
  5. {python_sat-1.9.dev7 → python_sat-1.9.dev9/python_sat.egg-info}/PKG-INFO +1 -1
  6. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_clausification.py +4 -4
  7. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_formula_unique.py +32 -0
  8. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/LICENSE.txt +0 -0
  9. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/MANIFEST.in +0 -0
  10. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/README.rst +0 -0
  11. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/__init__.py +0 -0
  12. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/approxmc.py +0 -0
  13. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/allies/unigen.py +0 -0
  14. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/bitwise.hh +0 -0
  15. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/card.hh +0 -0
  16. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/clset.hh +0 -0
  17. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/common.hh +0 -0
  18. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/itot.hh +0 -0
  19. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/ladder.hh +0 -0
  20. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/mto.hh +0 -0
  21. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/pairwise.hh +0 -0
  22. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/ptypes.hh +0 -0
  23. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/pycard.cc +0 -0
  24. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/seqcounter.hh +0 -0
  25. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/sortcard.hh +0 -0
  26. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/cardenc/utils.hh +0 -0
  27. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/__init__.py +0 -0
  28. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/bbscan.py +0 -0
  29. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/bica.py +0 -0
  30. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/fm.py +0 -0
  31. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/genhard.py +0 -0
  32. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/hitman.py +0 -0
  33. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/lbx.py +0 -0
  34. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/lsu.py +0 -0
  35. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/mcsls.py +0 -0
  36. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/models.py +0 -0
  37. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/musx.py +0 -0
  38. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/optux.py +0 -0
  39. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/primer.py +0 -0
  40. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/examples/usage.py +0 -0
  41. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_parse.cc +0 -0
  42. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_plus.cc +0 -0
  43. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_plus.hh +0 -0
  44. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_py.hh +0 -0
  45. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_scan.hh +0 -0
  46. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_weight.cc +0 -0
  47. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pf_weight.hh +0 -0
  48. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pyformula.cc +0 -0
  49. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/formula/pyformula.hh +0 -0
  50. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/_fileio.py +0 -0
  51. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/_utils.py +0 -0
  52. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/card.py +0 -0
  53. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/engines.py +0 -0
  54. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/integer.py +0 -0
  55. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/pb.py +0 -0
  56. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/process.py +0 -0
  57. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/pysat/solvers.py +0 -0
  58. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/SOURCES.txt +0 -0
  59. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/dependency_links.txt +0 -0
  60. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/requires.txt +0 -0
  61. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/python_sat.egg-info/top_level.txt +0 -0
  62. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/requirements.txt +0 -0
  63. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/setup.cfg +0 -0
  64. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/setup.py +0 -0
  65. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical103.tar.gz +0 -0
  66. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical153.tar.gz +0 -0
  67. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical195.tar.gz +0 -0
  68. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/cadical300.tar.gz +0 -0
  69. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose30.tar.gz +0 -0
  70. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose41.tar.gz +0 -0
  71. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/glucose421.tar.gz +0 -0
  72. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/lingeling.tar.gz +0 -0
  73. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplechrono.zip +0 -0
  74. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplecm.zip +0 -0
  75. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/maplesat.zip +0 -0
  76. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/mergesat3.tar.gz +0 -0
  77. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minicard.tar.gz +0 -0
  78. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minisat22.tar.gz +0 -0
  79. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/minisatgh.zip +0 -0
  80. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical103.patch +0 -0
  81. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical153.patch +0 -0
  82. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical195.patch +0 -0
  83. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/cadical300.patch +0 -0
  84. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose30.patch +0 -0
  85. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose41.patch +0 -0
  86. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/glucose421.patch +0 -0
  87. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/gluecard30.patch +0 -0
  88. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/gluecard41.patch +0 -0
  89. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/kissat404.patch +0 -0
  90. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/lingeling.patch +0 -0
  91. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplechrono.patch +0 -0
  92. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplecm.patch +0 -0
  93. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/maplesat.patch +0 -0
  94. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/mergesat3.patch +0 -0
  95. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minicard.patch +0 -0
  96. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisat22.patch +0 -0
  97. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisatep.patch +0 -0
  98. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/patches/minisatgh.patch +0 -0
  99. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/prepare.py +0 -0
  100. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/solvers/pysolvers.cc +0 -0
  101. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_accum_stats.py +0 -0
  102. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmost.py +0 -0
  103. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmost1.py +0 -0
  104. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_atmostk.py +0 -0
  105. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_boolengine.py +0 -0
  106. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_cnf.py +0 -0
  107. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_empty_extclause.py +0 -0
  108. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_encode_pb_conditional.py +0 -0
  109. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_equals1.py +0 -0
  110. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_formula_parsing.py +0 -0
  111. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_idpool.py +0 -0
  112. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_integer.py +0 -0
  113. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_oom.py +0 -0
  114. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_process.py +0 -0
  115. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_propagate.py +0 -0
  116. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_unique_model.py +0 -0
  117. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_unique_mus.py +0 -0
  118. {python_sat-1.9.dev7 → python_sat-1.9.dev9}/tests/test_warmstart.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-sat
3
- Version: 1.9.dev7
3
+ Version: 1.9.dev9
4
4
  Summary: A Python library for prototyping with SAT oracles
5
5
  Home-page: https://github.com/pysathq/pysat
6
6
  Author: Alexey Ignatiev, Joao Marques-Silva, Antonio Morgado
@@ -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
- iter1, iter2 = itertools.tee(self.core)
744
- self.core_sels = list(l for l in iter1 if l in self.sels_set)
745
- self.core_sums = list(l for l in iter2 if l not in self.sels_set)
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
  """
@@ -10,7 +10,7 @@
10
10
 
11
11
  # current version
12
12
  #==============================================================================
13
- VERSION = (1, 9, 'dev', 7)
13
+ VERSION = (1, 9, 'dev', 9)
14
14
 
15
15
 
16
16
  # PEP440 Format
@@ -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 a list of attributes to
887
- serve as a key associated with the formula requested by a user.
888
- The flattened result will contain string, i.e. ``repr()``,
889
- representation of all the attributes. The method is meant to be
890
- internal and not to be used by formula users.
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', repr(item[1]))]
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 ugly part:
951
- # reconstructing the key pairs, depending on the type of Formula
952
- if ftype[1] == FormulaType.ATOM:
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
- subfs[0] = ('object', repr(subfs[0][1]))
955
- elif ftype[1] == FormulaType.NEG:
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
- subfs[0] = ('subformula', id(subfs[0][1]))
958
- elif ftype[1] == FormulaType.IMPL:
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
- subfs[0] = ('left', id(subfs[0][1]))
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
- subfs[0] = ('right', id(subfs[0][1]))
1030
+ operands = [('right', subfs[0][1]), subfs[1]]
965
1031
  elif subfs[1][0] == 'right':
966
- subfs[0] = ('left', id(subfs[0][1]))
967
- elif ftype[1] == FormulaType.ITE:
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 subfs[0][0] == '' and subfs[1][0] == '' and subfs[2][0] == '':
970
- subfs[0] = ('cond', id(subfs[0][1]))
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
- if subfs[2][0] == 'cond':
975
- subfs[0] = ('cons1', id(subfs[0][1]))
976
- subfs[1] = ('cons2', id(subfs[1][1]))
977
- elif subfs[2][0] == 'cons1':
978
- subfs[0] = ('cond', id(subfs[0][1]))
979
- subfs[1] = ('cons2', id(subfs[1][1]))
980
- elif subfs[2][0] == 'cons2':
981
- subfs[0] = ('cond', id(subfs[0][1]))
982
- subfs[1] = ('cons1', id(subfs[1][1]))
983
- elif subfs[0] == '':
984
- if 'cond' not in (subfs[1][0], subfs[2][0]):
985
- subfs[0] = ('cond', id(subfs[0][0]))
986
- elif 'cons1' not in (subfs[1][0], subfs[2][0]):
987
- subfs[0] = ('cons1', id(subfs[0][0]))
988
- elif 'cons2' not in (subfs[1][0], subfs[2][0]):
989
- subfs[0] = ('cons2', id(subfs[0][0]))
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
- # these are commutative connectives; we need to sort the arguments
992
- assert len(subfs) >= 1, 'At least one subformula is required for an And/Equals/Or/XOr formula'
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
- Conjuction clausification.
1780
+ Conjunction clausification.
1712
1781
 
1713
1782
  If ``name_required`` is ``False``, the method recursively encodes
1714
- the subformulas and populates self's clauses with unit clauses,
1715
- each containing the auxiliary "name" of the corresponding
1716
- subformula, thus representing their conjunction.
1717
-
1718
- If ``name_required`` is set to ``True``, the method encodes the
1719
- conjunction using the standard logic: :math:`x \\equiv
1720
- \\bigwedge{y_i}`, if :math:`x` is the new auxiliary variable
1721
- encoding ``self`` and :math:`y_i` is the auxiliary variable
1722
- representing :math:`i`'s subformula.
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
- :param name_required: bool
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
- # first, recursively encoding subformulas
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._clausify(name_required=True)
1734
-
1735
- # adding unit clauses
1736
- self.clauses.append([sub.name])
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 new name for this formula if required
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
- if save_clauses:
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
- self.name = Formula._vpool[Formula._context].id(self)
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
- cl = [self.name] # final clause (converse implication)
1749
- for i in range(len(self.encoded)):
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
- # adding final clause
1754
- self.encoded.append(cl)
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 clauses,
1951
- each containing the auxiliary "name" of the corresponding
1952
- subformula, thus representing their conjunction.
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
- conjunction using the standard logic: :math:`x \\equiv
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
- :param name_required: bool
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
- :param name_required: bool
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 the populates self's clauses with a
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
- :param name_required: bool
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') == Atom('y') == Atom('z')
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
- :param name_required: bool
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 *equivalence* sub-operands into the list of
2656
- operands. For example, if ``XOr(XOr(x, y), z, merge=True)`` is called,
2657
- a new Formula object will be created with two operands: ``XOr(x, y)``
2658
- and ``z``, followed by merging ``x`` and ``y`` into the list of
2659
- root-level ``XOr``. This will result in a formula ``XOr(x, y, z)``.
2660
- Merging sub-operands is disabled by default if bitwise operations are
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(a1) == id(a3)
2683
- True # formulas a1 and a3 refer to the same object
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
- :param name_required: bool
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
- >>> (x >> y) & (~x >> z)
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`, respectivela, and
3075
- the populates self's clauses with two binary clauses
3076
- :math:`(\\neg{x}\\lor y)` and :math:`(x \\lor y)`.
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 y)\\right]`.
3182
+ y)\\land(x\\lor z)\\right]`.
3082
3183
 
3083
3184
  :param name_required: whether or not a Tseitin variable is needed
3084
- :param name_required: bool
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 conjuction symbol ``' & '``
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
- else: # single clause - nothing left to encode
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-sat
3
- Version: 1.9.dev7
3
+ Version: 1.9.dev9
4
4
  Summary: A Python library for prototyping with SAT oracles
5
5
  Home-page: https://github.com/pysathq/pysat
6
6
  Author: Alexey Ignatiev, Joao Marques-Silva, Antonio Morgado
@@ -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
- compare(list(g), [[1, -3], [2, -3], [3, -1, -2], [3], [4]])
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