cdclkit 0.1.0__tar.gz → 0.1.2__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.2}/PKG-INFO +22 -7
  2. {cdclkit-0.1.0 → cdclkit-0.1.2}/README.md +19 -6
  3. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/__init__.py +1 -1
  4. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/pipeline.py +1 -0
  5. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/portfolio.py +3 -1
  6. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit.egg-info/PKG-INFO +22 -7
  7. cdclkit-0.1.2/cdclkit.egg-info/requires.txt +4 -0
  8. {cdclkit-0.1.0 → cdclkit-0.1.2}/pyproject.toml +7 -1
  9. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_native_solver.py +79 -0
  10. {cdclkit-0.1.0 → cdclkit-0.1.2}/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.2}/LICENSE +0 -0
  13. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/__main__.py +0 -0
  14. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/brute.py +0 -0
  15. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/cli.py +0 -0
  16. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/encodings.py +0 -0
  17. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/heap.py +0 -0
  18. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/model.py +0 -0
  19. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/mus.py +0 -0
  20. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/native.py +0 -0
  21. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/preprocess.py +0 -0
  22. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/pyeq.py +0 -0
  23. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit/solver.py +0 -0
  24. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit.egg-info/SOURCES.txt +0 -0
  25. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit.egg-info/dependency_links.txt +0 -0
  26. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit.egg-info/entry_points.txt +0 -0
  27. {cdclkit-0.1.0 → cdclkit-0.1.2}/cdclkit.egg-info/top_level.txt +0 -0
  28. {cdclkit-0.1.0 → cdclkit-0.1.2}/setup.cfg +0 -0
  29. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_core.py +0 -0
  30. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_encodings.py +0 -0
  31. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_integration.py +0 -0
  32. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_mus.py +0 -0
  33. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_native.py +0 -0
  34. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_pipeline.py +0 -0
  35. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_portfolio.py +0 -0
  36. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_preprocess.py +0 -0
  37. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_proof.py +0 -0
  38. {cdclkit-0.1.0 → cdclkit-0.1.2}/tests/test_pyeq.py +0 -0
  39. {cdclkit-0.1.0 → cdclkit-0.1.2}/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.2
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.2; extra == "native"
26
28
  Dynamic: license-file
27
29
 
28
30
  # cdclkit
@@ -33,9 +35,17 @@ library and a modelling layer — written from scratch in readable Python.**
33
35
  Every answer comes with a certificate, and the certificate gets checked.
34
36
 
35
37
  ```bash
36
- pip install cdclkit
38
+ pip install cdclkit # pure Python
39
+ pip install "cdclkit[native]" # plus the Rust engine, ~18x faster
37
40
  ```
38
41
 
42
+ Three packages, and no third-party code:
43
+ [`cdclkit`](https://pypi.org/project/cdclkit/),
44
+ [`dratify`](https://pypi.org/project/dratify/) (the proof checker, zero
45
+ dependencies), and optionally
46
+ [`cdclkit-native`](https://pypi.org/project/cdclkit-native/) (abi3 wheels for
47
+ Linux and macOS).
48
+
39
49
  ```bash
40
50
  python3 -m cdclkit solve instance.cnf --self-check --check-model
41
51
  ```
@@ -77,16 +87,19 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
77
87
  | `portfolio` | parallel configurations |
78
88
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
79
89
 
80
- An optional Rust engine (built from source with `make native` -- it is not
81
- on PyPI yet) is roughly 18x faster
90
+ An optional Rust engine ([`cdclkit-native`](https://pypi.org/project/cdclkit-native/))
91
+ is roughly 18x faster
82
92
  and **bit-exact** with the Python one: identical conflicts, decisions and
83
93
  propagations on every instance. The pure-Python path has zero third-party
84
94
  dependencies and is the one that must never break.
85
95
 
86
96
  ## Relationship to dratify
87
97
 
88
- Proof checking lives in a separate package, [`dratify`](https://github.com/carlok/dratify),
89
- which `cdclkit` depends on. That split is deliberate:
98
+ Proof checking lives in a separate package,
99
+ [`dratify`](https://pypi.org/project/dratify/)
100
+ ([source](https://github.com/carlok/dratify), also a
101
+ [Rust crate](https://crates.io/crates/dratify)), which `cdclkit` depends on.
102
+ That split is deliberate:
90
103
 
91
104
  - You should not have to install a SAT solver to verify a proof someone else
92
105
  produced.
@@ -95,7 +108,9 @@ which `cdclkit` depends on. That split is deliberate:
95
108
  solver rather than only by its own suite.
96
109
 
97
110
  `dratify` has no dependencies of its own, so installing `cdclkit` pulls in no
98
- third-party code.
111
+ third-party code. With `[native]`, `cdclkit-native` hands its compiled checker
112
+ to `dratify` through `register_native()`, so proof checking gets the Rust
113
+ implementation too rather than only the solver.
99
114
 
100
115
  ## Performance
101
116
 
@@ -6,9 +6,17 @@ library and a modelling layer — written from scratch in readable Python.**
6
6
  Every answer comes with a certificate, and the certificate gets checked.
7
7
 
8
8
  ```bash
9
- pip install cdclkit
9
+ pip install cdclkit # pure Python
10
+ pip install "cdclkit[native]" # plus the Rust engine, ~18x faster
10
11
  ```
11
12
 
13
+ Three packages, and no third-party code:
14
+ [`cdclkit`](https://pypi.org/project/cdclkit/),
15
+ [`dratify`](https://pypi.org/project/dratify/) (the proof checker, zero
16
+ dependencies), and optionally
17
+ [`cdclkit-native`](https://pypi.org/project/cdclkit-native/) (abi3 wheels for
18
+ Linux and macOS).
19
+
12
20
  ```bash
13
21
  python3 -m cdclkit solve instance.cnf --self-check --check-model
14
22
  ```
@@ -50,16 +58,19 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
50
58
  | `portfolio` | parallel configurations |
51
59
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
52
60
 
53
- An optional Rust engine (built from source with `make native` -- it is not
54
- on PyPI yet) is roughly 18x faster
61
+ An optional Rust engine ([`cdclkit-native`](https://pypi.org/project/cdclkit-native/))
62
+ is roughly 18x faster
55
63
  and **bit-exact** with the Python one: identical conflicts, decisions and
56
64
  propagations on every instance. The pure-Python path has zero third-party
57
65
  dependencies and is the one that must never break.
58
66
 
59
67
  ## Relationship to dratify
60
68
 
61
- Proof checking lives in a separate package, [`dratify`](https://github.com/carlok/dratify),
62
- which `cdclkit` depends on. That split is deliberate:
69
+ Proof checking lives in a separate package,
70
+ [`dratify`](https://pypi.org/project/dratify/)
71
+ ([source](https://github.com/carlok/dratify), also a
72
+ [Rust crate](https://crates.io/crates/dratify)), which `cdclkit` depends on.
73
+ That split is deliberate:
63
74
 
64
75
  - You should not have to install a SAT solver to verify a proof someone else
65
76
  produced.
@@ -68,7 +79,9 @@ which `cdclkit` depends on. That split is deliberate:
68
79
  solver rather than only by its own suite.
69
80
 
70
81
  `dratify` has no dependencies of its own, so installing `cdclkit` pulls in no
71
- third-party code.
82
+ third-party code. With `[native]`, `cdclkit-native` hands its compiled checker
83
+ to `dratify` through `register_native()`, so proof checking gets the Rust
84
+ implementation too rather than only the solver.
72
85
 
73
86
  ## Performance
74
87
 
@@ -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.2"
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.2
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.2; extra == "native"
26
28
  Dynamic: license-file
27
29
 
28
30
  # cdclkit
@@ -33,9 +35,17 @@ library and a modelling layer — written from scratch in readable Python.**
33
35
  Every answer comes with a certificate, and the certificate gets checked.
34
36
 
35
37
  ```bash
36
- pip install cdclkit
38
+ pip install cdclkit # pure Python
39
+ pip install "cdclkit[native]" # plus the Rust engine, ~18x faster
37
40
  ```
38
41
 
42
+ Three packages, and no third-party code:
43
+ [`cdclkit`](https://pypi.org/project/cdclkit/),
44
+ [`dratify`](https://pypi.org/project/dratify/) (the proof checker, zero
45
+ dependencies), and optionally
46
+ [`cdclkit-native`](https://pypi.org/project/cdclkit-native/) (abi3 wheels for
47
+ Linux and macOS).
48
+
39
49
  ```bash
40
50
  python3 -m cdclkit solve instance.cnf --self-check --check-model
41
51
  ```
@@ -77,16 +87,19 @@ puzzle, graph colouring, bounded model checking, circuit equivalence.
77
87
  | `portfolio` | parallel configurations |
78
88
  | `pyeq` | **experimental** — bounded equivalence of two Python integer functions |
79
89
 
80
- An optional Rust engine (built from source with `make native` -- it is not
81
- on PyPI yet) is roughly 18x faster
90
+ An optional Rust engine ([`cdclkit-native`](https://pypi.org/project/cdclkit-native/))
91
+ is roughly 18x faster
82
92
  and **bit-exact** with the Python one: identical conflicts, decisions and
83
93
  propagations on every instance. The pure-Python path has zero third-party
84
94
  dependencies and is the one that must never break.
85
95
 
86
96
  ## Relationship to dratify
87
97
 
88
- Proof checking lives in a separate package, [`dratify`](https://github.com/carlok/dratify),
89
- which `cdclkit` depends on. That split is deliberate:
98
+ Proof checking lives in a separate package,
99
+ [`dratify`](https://pypi.org/project/dratify/)
100
+ ([source](https://github.com/carlok/dratify), also a
101
+ [Rust crate](https://crates.io/crates/dratify)), which `cdclkit` depends on.
102
+ That split is deliberate:
90
103
 
91
104
  - You should not have to install a SAT solver to verify a proof someone else
92
105
  produced.
@@ -95,7 +108,9 @@ which `cdclkit` depends on. That split is deliberate:
95
108
  solver rather than only by its own suite.
96
109
 
97
110
  `dratify` has no dependencies of its own, so installing `cdclkit` pulls in no
98
- third-party code.
111
+ third-party code. With `[native]`, `cdclkit-native` hands its compiled checker
112
+ to `dratify` through `register_native()`, so proof checking gets the Rust
113
+ implementation too rather than only the solver.
99
114
 
100
115
  ## Performance
101
116
 
@@ -0,0 +1,4 @@
1
+ dratify>=0.1.1
2
+
3
+ [native]
4
+ cdclkit-native==0.1.2
@@ -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.2"
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.2"]
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