cdclkit 0.1.0__tar.gz → 0.1.1__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 (39) hide show
  1. {cdclkit-0.1.0 → cdclkit-0.1.1}/PKG-INFO +4 -3
  2. {cdclkit-0.1.0 → cdclkit-0.1.1}/README.md +1 -2
  3. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/__init__.py +1 -1
  4. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/pipeline.py +1 -0
  5. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/portfolio.py +3 -1
  6. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit.egg-info/PKG-INFO +4 -3
  7. cdclkit-0.1.1/cdclkit.egg-info/requires.txt +4 -0
  8. {cdclkit-0.1.0 → cdclkit-0.1.1}/pyproject.toml +7 -1
  9. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_native_solver.py +79 -0
  10. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_packaging.py +33 -0
  11. cdclkit-0.1.0/cdclkit.egg-info/requires.txt +0 -1
  12. {cdclkit-0.1.0 → cdclkit-0.1.1}/LICENSE +0 -0
  13. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/__main__.py +0 -0
  14. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/brute.py +0 -0
  15. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/cli.py +0 -0
  16. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/encodings.py +0 -0
  17. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/heap.py +0 -0
  18. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/model.py +0 -0
  19. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/mus.py +0 -0
  20. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/native.py +0 -0
  21. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/preprocess.py +0 -0
  22. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/pyeq.py +0 -0
  23. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit/solver.py +0 -0
  24. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit.egg-info/SOURCES.txt +0 -0
  25. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit.egg-info/dependency_links.txt +0 -0
  26. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit.egg-info/entry_points.txt +0 -0
  27. {cdclkit-0.1.0 → cdclkit-0.1.1}/cdclkit.egg-info/top_level.txt +0 -0
  28. {cdclkit-0.1.0 → cdclkit-0.1.1}/setup.cfg +0 -0
  29. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_core.py +0 -0
  30. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_encodings.py +0 -0
  31. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_integration.py +0 -0
  32. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_mus.py +0 -0
  33. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_native.py +0 -0
  34. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_pipeline.py +0 -0
  35. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_portfolio.py +0 -0
  36. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_preprocess.py +0 -0
  37. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_proof.py +0 -0
  38. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_pyeq.py +0 -0
  39. {cdclkit-0.1.0 → cdclkit-0.1.1}/tests/test_solver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -23,6 +23,8 @@ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: dratify>=0.1.1
26
+ Provides-Extra: native
27
+ Requires-Dist: cdclkit-native==0.1.1; extra == "native"
26
28
  Dynamic: license-file
27
29
 
28
30
  # cdclkit
@@ -77,8 +79,7 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
77
79
  | `portfolio` | parallel configurations |
78
80
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
79
81
 
80
- An optional Rust engine (built from source with `make native` -- it is not
81
- on PyPI yet) is roughly 18x faster
82
+ An optional Rust engine (`pip install cdclkit[native]`) is roughly 18x faster
82
83
  and **bit-exact** with the Python one: identical conflicts, decisions and
83
84
  propagations on every instance. The pure-Python path has zero third-party
84
85
  dependencies and is the one that must never break.
@@ -50,8 +50,7 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
50
50
  | `portfolio` | parallel configurations |
51
51
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
52
52
 
53
- An optional Rust engine (built from source with `make native` -- it is not
54
- on PyPI yet) is roughly 18x faster
53
+ An optional Rust engine (`pip install cdclkit[native]`) is roughly 18x faster
55
54
  and **bit-exact** with the Python one: identical conflicts, decisions and
56
55
  propagations on every instance. The pure-Python path has zero third-party
57
56
  dependencies and is the one that must never break.
@@ -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.0"
55
+ __version__ = "0.1.1"
56
56
 
57
57
  __all__ = [
58
58
  "CNF",
@@ -92,6 +92,7 @@ def _solve(f: CNF, cfg: Config, budget: int | None, engine: str,
92
92
  cla_decay=cfg.cla_decay, luby_base=float(cfg.luby_base),
93
93
  first_reduce=cfg.first_reduce, reduce_inc=cfg.reduce_inc,
94
94
  glue_keep=cfg.glue_keep, block_restart=cfg.block_restart,
95
+ rnd_freq=cfg.rnd_freq, rnd_seed=cfg.rnd_seed,
95
96
  )
96
97
  for c in f.clauses:
97
98
  if not s.add_clause(list(c)):
@@ -235,6 +235,7 @@ def _worker_native(index, nvars, clauses, cfg_kwargs, want_proof):
235
235
  cla_decay=cfg.cla_decay, luby_base=float(cfg.luby_base),
236
236
  first_reduce=cfg.first_reduce, reduce_inc=cfg.reduce_inc,
237
237
  glue_keep=cfg.glue_keep, block_restart=cfg.block_restart,
238
+ rnd_freq=cfg.rnd_freq, rnd_seed=cfg.rnd_seed,
238
239
  )
239
240
  if want_proof:
240
241
  s.enable_proof() # must precede the first clause
@@ -416,7 +417,8 @@ def _config_tuples(configs):
416
417
  var_decay=c.var_decay, var_decay_max=c.var_decay_max,
417
418
  cla_decay=c.cla_decay, luby_base=float(c.luby_base),
418
419
  first_reduce=c.first_reduce, reduce_inc=c.reduce_inc,
419
- glue_keep=c.glue_keep, block_restart=c.block_restart)
420
+ glue_keep=c.glue_keep, block_restart=c.block_restart,
421
+ rnd_freq=c.rnd_freq, rnd_seed=c.rnd_seed)
420
422
  for c in configs
421
423
  ]
422
424
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -23,6 +23,8 @@ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: dratify>=0.1.1
26
+ Provides-Extra: native
27
+ Requires-Dist: cdclkit-native==0.1.1; extra == "native"
26
28
  Dynamic: license-file
27
29
 
28
30
  # cdclkit
@@ -77,8 +79,7 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
77
79
  | `portfolio` | parallel configurations |
78
80
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
79
81
 
80
- An optional Rust engine (built from source with `make native` -- it is not
81
- on PyPI yet) is roughly 18x faster
82
+ An optional Rust engine (`pip install cdclkit[native]`) is roughly 18x faster
82
83
  and **bit-exact** with the Python one: identical conflicts, decisions and
83
84
  propagations on every instance. The pure-Python path has zero third-party
84
85
  dependencies and is the one that must never break.
@@ -0,0 +1,4 @@
1
+ dratify>=0.1.1
2
+
3
+ [native]
4
+ cdclkit-native==0.1.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdclkit"
7
- version = "0.1.0"
7
+ version = "0.1.1"
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"
@@ -31,6 +31,12 @@ classifiers = [
31
31
  # test run here as well as by its own suite.
32
32
  dependencies = ["dratify>=0.1.1"]
33
33
 
34
+ [project.optional-dependencies]
35
+ # The Rust accelerator, as abi3 wheels for Linux and macOS. Pinned exactly:
36
+ # the two are bit-exact by construction and a mismatched pair is a divergence
37
+ # nobody would look for. tests/test_packaging.py enforces the pin.
38
+ native = ["cdclkit-native==0.1.1"]
39
+
34
40
  [project.scripts]
35
41
  cdclkit = "cdclkit.__main__:main"
36
42
 
@@ -160,6 +160,85 @@ class TestDefaultsAgree(unittest.TestCase):
160
160
  self.assertEqual(py.stats.propagations, rs.propagations)
161
161
 
162
162
 
163
+ @requires_native
164
+ class TestRandomBranchingAgrees(unittest.TestCase):
165
+ """`rnd_freq` and `rnd_seed` must reach the native engine and match.
166
+
167
+ These two knobs had no Rust counterpart at all: the native side accepted
168
+ the rest of the configuration and silently dropped these, so a Python run
169
+ at rnd_freq=0.02 took 4,987 conflicts where the native one took 3,171.
170
+
171
+ That was not a cosmetic gap. `portfolio.py` builds worker diversity from
172
+ exactly these two fields, and sets a per-worker seed specifically "so
173
+ duplicated recipes still diverge" -- which on the native path they did not,
174
+ because every worker got the same search. Duplicate recipes became
175
+ bit-identical duplicate workers doing the same work in parallel.
176
+
177
+ The existing bit-exactness tests could not have caught this: none of them
178
+ set either knob.
179
+ """
180
+
181
+ def _native(self, f, rnd_freq, rnd_seed):
182
+ n = native.require()
183
+ d = Config()
184
+ rs = n.Solver(
185
+ f.nvars, restart=d.restart, ccmin=d.ccmin,
186
+ phase_saving=d.phase_saving, init_phase=d.init_phase,
187
+ target_phase=d.target_phase, target_reset=d.target_reset,
188
+ walk_flips=d.walk_flips, walk_interval=d.walk_interval,
189
+ walk_patience=d.walk_patience,
190
+ walk_min_conflicts=d.walk_min_conflicts,
191
+ var_decay=d.var_decay, var_decay_max=d.var_decay_max,
192
+ cla_decay=d.cla_decay, luby_base=float(d.luby_base),
193
+ first_reduce=d.first_reduce, reduce_inc=d.reduce_inc,
194
+ glue_keep=d.glue_keep, block_restart=d.block_restart,
195
+ rnd_freq=rnd_freq, rnd_seed=rnd_seed)
196
+ for c in f.clauses:
197
+ rs.add_clause(list(c))
198
+ rs.solve(None)
199
+ return rs.conflicts, rs.decisions, rs.propagations
200
+
201
+ def _python(self, f, rnd_freq, rnd_seed):
202
+ from cdclkit.solver import Solver as PySolver
203
+
204
+ cfg = Config()
205
+ cfg.rnd_freq = rnd_freq
206
+ cfg.rnd_seed = rnd_seed
207
+ py = PySolver(f.nvars, config=cfg)
208
+ py.add_cnf(f)
209
+ py.solve()
210
+ return py.stats.conflicts, py.stats.decisions, py.stats.propagations
211
+
212
+ def test_engines_agree_across_frequencies_and_seeds(self):
213
+ f = php(6)
214
+ for freq, seed in [(0.0, 91648253), (0.02, 91648253), (0.02, 12345),
215
+ (0.10, 7), (0.05, 999983)]:
216
+ with self.subTest(rnd_freq=freq, rnd_seed=seed):
217
+ self.assertEqual(
218
+ self._python(f, freq, seed), self._native(f, freq, seed),
219
+ f"engines diverge at rnd_freq={freq}, rnd_seed={seed}")
220
+
221
+ def test_a_zero_frequency_does_not_touch_the_random_stream(self):
222
+ """The short-circuit is load-bearing, not an optimisation.
223
+
224
+ Python tests `rnd_freq > 0.0` before drawing, so the default
225
+ configuration never advances the PRNG. If the native side drew first,
226
+ the walk -- which shares the stream -- would desynchronise and the two
227
+ engines would diverge on instances long enough to reach it.
228
+ """
229
+ f = php(6)
230
+ self.assertEqual(self._python(f, 0.0, 91648253),
231
+ self._native(f, 0.0, 91648253))
232
+
233
+ def test_the_seed_actually_changes_the_search(self):
234
+ """A knob that is wired up but inert would pass the tests above."""
235
+ f = php(6)
236
+ runs = {self._native(f, 0.05, seed) for seed in (1, 7, 12345, 999983)}
237
+ self.assertGreater(len(runs), 1,
238
+ "every seed produced an identical search, so the "
239
+ "seed is not reaching the engine")
240
+
241
+
163
242
  @requires_native
164
243
  class TestTier2Faithfulness(unittest.TestCase):
165
244
  """Bit-exact agreement with the Python solver."""
@@ -157,6 +157,39 @@ class TestPublicAPISurface(unittest.TestCase):
157
157
  self.assertNotIn(internal, cdclkit.__all__)
158
158
 
159
159
 
160
+ class TestWorkflowsAreWellFormed(unittest.TestCase):
161
+ """Every top-level line in a workflow must be a known key or a comment.
162
+
163
+ A comment in release.yml lost its leading "# " during an edit. The result
164
+ was still valid YAML -- it simply became a mapping key -- so a
165
+ `yaml.safe_load` check passed it, and GitHub then refused the whole file
166
+ with "this run likely failed because of a workflow file issue". Zero jobs
167
+ ran, and the tag had to be deleted and re-cut.
168
+
169
+ Deliberately does not import yaml: the dependency-free test path is the one
170
+ that must never break, and this failure mode does not need a parser.
171
+ """
172
+
173
+ #: https://docs.github.com/actions/reference/workflow-syntax-for-github-actions
174
+ ALLOWED = {"name", "on", "permissions", "env", "defaults", "concurrency",
175
+ "jobs", "run-name"}
176
+
177
+ def test_no_stray_top_level_keys(self):
178
+ workflows = sorted((ROOT / ".github" / "workflows").glob("*.yml"))
179
+ self.assertTrue(workflows, "no workflows found")
180
+ for wf in workflows:
181
+ with self.subTest(workflow=wf.name):
182
+ for n, line in enumerate(wf.read_text().splitlines(), 1):
183
+ if not line or line[0] in " \t#":
184
+ continue
185
+ key = line.split(":", 1)[0].strip()
186
+ self.assertIn(
187
+ key, self.ALLOWED,
188
+ f"{wf.name}:{n} starts a top-level key {key!r} that "
189
+ f"GitHub does not recognise -- most likely a comment "
190
+ f"that lost its '# '")
191
+
192
+
160
193
  class TestLicenceConsistency(unittest.TestCase):
161
194
  """The tree is Apache-2.0; every place that states a licence must agree.
162
195
 
@@ -1 +0,0 @@
1
- dratify>=0.1.1
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