cdclkit 0.1.5__tar.gz → 0.1.6__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 (45) hide show
  1. {cdclkit-0.1.5 → cdclkit-0.1.6}/PKG-INFO +2 -2
  2. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/__init__.py +1 -1
  3. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/cli.py +29 -2
  4. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/native.py +8 -6
  5. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/pyeq.py +10 -5
  6. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/PKG-INFO +2 -2
  7. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/requires.txt +1 -1
  8. {cdclkit-0.1.5 → cdclkit-0.1.6}/pyproject.toml +2 -2
  9. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_adaptive_proof.py +45 -1
  10. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_portfolio.py +6 -0
  11. {cdclkit-0.1.5 → cdclkit-0.1.6}/LICENSE +0 -0
  12. {cdclkit-0.1.5 → cdclkit-0.1.6}/README.md +0 -0
  13. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/__main__.py +0 -0
  14. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/brute.py +0 -0
  15. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/encodings.py +0 -0
  16. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/heap.py +0 -0
  17. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/model.py +0 -0
  18. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/mus.py +0 -0
  19. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/pipeline.py +0 -0
  20. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/portfolio.py +0 -0
  21. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/preprocess.py +0 -0
  22. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/py.typed +0 -0
  23. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit/solver.py +0 -0
  24. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/SOURCES.txt +0 -0
  25. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/dependency_links.txt +0 -0
  26. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/entry_points.txt +0 -0
  27. {cdclkit-0.1.5 → cdclkit-0.1.6}/cdclkit.egg-info/top_level.txt +0 -0
  28. {cdclkit-0.1.5 → cdclkit-0.1.6}/setup.cfg +0 -0
  29. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_agent_docs.py +0 -0
  30. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_brute.py +0 -0
  31. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_cli_defaults.py +0 -0
  32. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_core.py +0 -0
  33. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_encodings.py +0 -0
  34. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_integration.py +0 -0
  35. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_mus.py +0 -0
  36. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_native.py +0 -0
  37. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_native_solver.py +0 -0
  38. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_packaging.py +0 -0
  39. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_pipeline.py +0 -0
  40. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_preprocess.py +0 -0
  41. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_proof.py +0 -0
  42. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_pyeq.py +0 -0
  43. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_readme.py +0 -0
  44. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_solver.py +0 -0
  45. {cdclkit-0.1.5 → cdclkit-0.1.6}/tests/test_target_scaling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A CDCL SAT solver, preprocessor, encoding library and modelling layer, written from scratch in readable Python. Every answer comes with a certificate.
5
5
  Author: Carlo Perassi
6
6
  License-Expression: Apache-2.0
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dratify>=0.1.7
27
27
  Provides-Extra: native
28
- Requires-Dist: cdclkit-native==0.1.5; extra == "native"
28
+ Requires-Dist: cdclkit-native==0.1.6; extra == "native"
29
29
  Dynamic: license-file
30
30
 
31
31
  # cdclkit
@@ -52,7 +52,7 @@ from .preprocess import Preprocessor, preprocess
52
52
  from dratify.proof import DRATChecker, MemoryProof, ProofWriter, check_proof
53
53
  from .solver import Config, SAT, Solver, Stats, UNKNOWN, UNSAT
54
54
 
55
- __version__ = "0.1.5"
55
+ __version__ = "0.1.6"
56
56
 
57
57
  __all__ = [
58
58
  "CNF",
@@ -47,6 +47,26 @@ def _native_ok() -> bool:
47
47
  return native.available()
48
48
 
49
49
 
50
+ def _pick_engine() -> str | None:
51
+ """The engine for `--adaptive` and `-j`; None after printing an error.
52
+
53
+ Native when it is installed: it is bit-exact with Python, so this changes
54
+ the speed and not the answer. CDCLKIT_ENGINE overrides it, which is how to
55
+ rule the native engine in or out of a suspect result. The plain sequential
56
+ path is always Python.
57
+ """
58
+ from . import native
59
+
60
+ try:
61
+ engine = native.engine_requested(
62
+ default="native" if _native_ok() else "python")
63
+ except RuntimeError as e:
64
+ print(f"c error: {e}", file=sys.stderr)
65
+ return None
66
+ print(f"c engine: {engine}")
67
+ return engine
68
+
69
+
50
70
  EXIT_SAT = 10
51
71
  EXIT_UNSAT = 20
52
72
  EXIT_UNKNOWN = 0
@@ -115,9 +135,12 @@ def cmd_solve(args) -> int:
115
135
  "(workers race and their steps interleave); drop -j or drop "
116
136
  "--proof/--self-check")
117
137
  return EXIT_ERROR
138
+ engine = _pick_engine()
139
+ if engine is None:
140
+ return EXIT_ERROR
118
141
  pr = solve_adaptive(
119
142
  f,
120
- engine="native" if _native_ok() else "python",
143
+ engine=engine,
121
144
  config=cfg,
122
145
  jobs=jobs,
123
146
  proof=proof_sink,
@@ -146,7 +169,11 @@ def cmd_solve(args) -> int:
146
169
  # Parallel portfolio. Only the winning worker's proof exists, and
147
170
  # because workers share no clauses it is a complete refutation on its
148
171
  # own -- see cdclkit/portfolio.py.
149
- pr = solve_portfolio(f, jobs=jobs, want_proof=proof_sink is not None)
172
+ engine = _pick_engine()
173
+ if engine is None:
174
+ return EXIT_ERROR
175
+ pr = solve_portfolio(f, jobs=jobs, engine=engine,
176
+ want_proof=proof_sink is not None)
150
177
  if not pr.finished:
151
178
  print("s UNKNOWN")
152
179
  return EXIT_UNKNOWN
@@ -13,10 +13,13 @@ The contract, in both directions:
13
13
  full under a plain `python3` with no Rust toolchain anywhere. That property
14
14
  is tested, not assumed -- it is what lets the port proceed in small commits
15
15
  without the repo ever being in a broken state.
16
- * **If it is present, it is opt-in.** Nothing selects the native path on its
17
- own. A caller asks for it explicitly (`--engine native`, or
18
- `CDCLKIT_ENGINE=native`), because a silent switch between two implementations
19
- is how differential bugs hide.
16
+ * **If it is present, the plain solver still does not use it.** `Solver`,
17
+ `solve()` and `cdclkit solve` are Python. `solve_adaptive()`,
18
+ `solve_portfolio()` and `cdclkit solve --adaptive` / `-j N` use it when it is
19
+ installed. That is safe only because the two engines are bit-exact and tested
20
+ to stay that way. A silent switch between two implementations is how
21
+ differential bugs hide, so the CLI prints which engine ran and
22
+ `CDCLKIT_ENGINE` (read by `engine_requested()`) overrides the choice.
20
23
 
21
24
  Building it::
22
25
 
@@ -54,8 +57,7 @@ else: # pragma: no cover - depends on whether the module was built
54
57
  #
55
58
  # This is not the silent engine switch the rest of this module warns about.
56
59
  # Two checkers must agree by construction -- if they ever disagree that is
57
- # a bug worth surfacing, not a difference worth preserving. The solver is
58
- # the thing that stays explicitly opt-in.
60
+ # a bug worth surfacing, not a difference worth preserving.
59
61
  try:
60
62
  import dratify as _dratify
61
63
  _dratify.register_native(_native)
@@ -198,8 +198,11 @@ class BitVec:
198
198
  return self.enc.and_gate(same)
199
199
 
200
200
  def slt(self, other: "BitVec") -> int:
201
- """Signed less-than, via the sign of the difference with overflow
202
- correction: a < b iff (a-b) is negative, XOR the signed-overflow flag."""
201
+ """Signed less-than: a < b iff a - b is negative.
202
+
203
+ Both operands are sign-extended by one bit before subtracting, so the
204
+ difference of two w-bit values always fits in w+1 bits and cannot
205
+ overflow. Its top bit is then the answer, with no overflow flag."""
203
206
  enc = self.enc
204
207
  w = max(self.width, other.width) + 1 # one extra bit kills the overflow case
205
208
  a, b = self.extend(w), other.extend(w)
@@ -451,10 +454,12 @@ class _Compiler(ast.NodeVisitor):
451
454
  self._fail(node, type(node).__name__)
452
455
 
453
456
  def assign(self, name: str, value: BitVec) -> None:
454
- """Assign under the current path condition.
457
+ """Bind `name` to `value` in the current environment, unconditionally.
455
458
 
456
- Inside a branch the assignment is conditional, so the variable becomes
457
- a select between the new value and whatever it held before.
459
+ Branches stay correct without a select here: an `if` runs each arm on
460
+ its own copy of the environment and merges them afterwards, with one
461
+ select per variable that differs between the arms (see the `ast.If`
462
+ case of `stmt`).
458
463
  """
459
464
  self.env[name] = value
460
465
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A CDCL SAT solver, preprocessor, encoding library and modelling layer, written from scratch in readable Python. Every answer comes with a certificate.
5
5
  Author: Carlo Perassi
6
6
  License-Expression: Apache-2.0
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dratify>=0.1.7
27
27
  Provides-Extra: native
28
- Requires-Dist: cdclkit-native==0.1.5; extra == "native"
28
+ Requires-Dist: cdclkit-native==0.1.6; extra == "native"
29
29
  Dynamic: license-file
30
30
 
31
31
  # cdclkit
@@ -1,4 +1,4 @@
1
1
  dratify>=0.1.7
2
2
 
3
3
  [native]
4
- cdclkit-native==0.1.5
4
+ cdclkit-native==0.1.6
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdclkit"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "A CDCL SAT solver, preprocessor, encoding library and modelling layer, written from scratch in readable Python. Every answer comes with a certificate."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -36,7 +36,7 @@ dependencies = ["dratify>=0.1.7"]
36
36
  # The Rust accelerator, as abi3 wheels for Linux and macOS. Pinned exactly:
37
37
  # the two are bit-exact by construction and a mismatched pair is a divergence
38
38
  # nobody would look for. tests/test_packaging.py enforces the pin.
39
- native = ["cdclkit-native==0.1.5"]
39
+ native = ["cdclkit-native==0.1.6"]
40
40
 
41
41
  [project.scripts]
42
42
  cdclkit = "cdclkit.__main__:main"
@@ -113,11 +113,14 @@ class TestAdaptiveEmitsACheckableProof(unittest.TestCase):
113
113
  class TestAdaptiveCLI(unittest.TestCase):
114
114
  """End to end, because the bug was in the wiring rather than the pipeline."""
115
115
 
116
- def _cli(self, *args):
116
+ def _cli(self, *args, engine=None):
117
117
  root = pathlib.Path(__file__).resolve().parent.parent
118
118
  import os
119
119
 
120
120
  env = dict(os.environ)
121
+ env.pop("CDCLKIT_ENGINE", None)
122
+ if engine is not None:
123
+ env["CDCLKIT_ENGINE"] = engine
121
124
  # prepend, do not replace: a pure-Python checkout finds dratify on the
122
125
  # inherited path, and clobbering it made these fail for the wrong reason
123
126
  env["PYTHONPATH"] = os.pathsep.join(
@@ -156,6 +159,47 @@ class TestAdaptiveCLI(unittest.TestCase):
156
159
  self.assertEqual(r.returncode, 1)
157
160
  self.assertIn("interleave", r.stdout)
158
161
 
162
+ # AGENTS.md said CDCLKIT_ENGINE selected the engine. Nothing read it, and
163
+ # `--adaptive` picked native on its own whenever it was installed.
164
+
165
+ def _php(self, engine=None):
166
+ with tempfile.TemporaryDirectory() as d:
167
+ cnf = pathlib.Path(d) / "php.cnf"
168
+ cnf.write_text(PHP)
169
+ return self._cli("solve", str(cnf), "--adaptive", engine=engine)
170
+
171
+ def test_adaptive_uses_native_when_installed(self):
172
+ r = self._php()
173
+ self.assertEqual(r.returncode, 20, r.stdout + r.stderr)
174
+ want = "native" if native.available() else "python"
175
+ self.assertIn(f"c engine: {want}", r.stdout)
176
+
177
+ def test_cdclkit_engine_python_overrides_adaptive(self):
178
+ r = self._php(engine="python")
179
+ self.assertEqual(r.returncode, 20, r.stdout + r.stderr)
180
+ self.assertIn("c engine: python", r.stdout)
181
+
182
+ def test_cdclkit_engine_native_without_the_build_is_an_error(self):
183
+ if native.available():
184
+ self.skipTest("native engine is built here")
185
+ r = self._php(engine="native")
186
+ self.assertEqual(r.returncode, 1, r.stdout + r.stderr)
187
+ self.assertIn("CDCLKIT_ENGINE=native", r.stderr)
188
+ self.assertNotIn("Traceback", r.stderr)
189
+
190
+ def test_portfolio_honours_cdclkit_engine_too(self):
191
+ with tempfile.TemporaryDirectory() as d:
192
+ cnf = pathlib.Path(d) / "php.cnf"
193
+ cnf.write_text(PHP)
194
+ r = self._cli("solve", str(cnf), "-j", "2", engine="python")
195
+ self.assertEqual(r.returncode, 20, r.stdout + r.stderr)
196
+ self.assertIn("c engine: python", r.stdout)
197
+
198
+ def test_unknown_cdclkit_engine_is_an_error(self):
199
+ r = self._php(engine="quantum")
200
+ self.assertEqual(r.returncode, 1, r.stdout + r.stderr)
201
+ self.assertIn("unknown CDCLKIT_ENGINE", r.stderr)
202
+
159
203
 
160
204
  if __name__ == "__main__":
161
205
  unittest.main()
@@ -151,6 +151,12 @@ class TestEngineSelection(unittest.TestCase):
151
151
  """The portfolio can run native workers; correctness must not depend on it."""
152
152
 
153
153
  def test_python_and_native_engines_agree(self):
154
+ from cdclkit import native
155
+
156
+ if not native.available():
157
+ # engine="native" falls back to Python, so this would compare
158
+ # Python against Python and pass
159
+ self.skipTest("native engine not built")
154
160
  rng = random.Random(fuzz_seed(13))
155
161
  for _ in range(15):
156
162
  f = random_cnf(rng, max_vars=9, ratio=4.8)
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