cdclkit 0.1.4__tar.gz → 0.1.5__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.4 → cdclkit-0.1.5}/PKG-INFO +3 -2
  2. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/__init__.py +1 -1
  3. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/solver.py +20 -4
  4. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/PKG-INFO +3 -2
  5. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/SOURCES.txt +2 -1
  6. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/requires.txt +1 -1
  7. {cdclkit-0.1.4 → cdclkit-0.1.5}/pyproject.toml +3 -2
  8. cdclkit-0.1.5/tests/test_target_scaling.py +78 -0
  9. {cdclkit-0.1.4 → cdclkit-0.1.5}/LICENSE +0 -0
  10. {cdclkit-0.1.4 → cdclkit-0.1.5}/README.md +0 -0
  11. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/__main__.py +0 -0
  12. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/brute.py +0 -0
  13. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/cli.py +0 -0
  14. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/encodings.py +0 -0
  15. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/heap.py +0 -0
  16. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/model.py +0 -0
  17. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/mus.py +0 -0
  18. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/native.py +0 -0
  19. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/pipeline.py +0 -0
  20. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/portfolio.py +0 -0
  21. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/preprocess.py +0 -0
  22. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/py.typed +0 -0
  23. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit/pyeq.py +0 -0
  24. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/dependency_links.txt +0 -0
  25. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/entry_points.txt +0 -0
  26. {cdclkit-0.1.4 → cdclkit-0.1.5}/cdclkit.egg-info/top_level.txt +0 -0
  27. {cdclkit-0.1.4 → cdclkit-0.1.5}/setup.cfg +0 -0
  28. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_adaptive_proof.py +0 -0
  29. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_agent_docs.py +0 -0
  30. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_brute.py +0 -0
  31. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_cli_defaults.py +0 -0
  32. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_core.py +0 -0
  33. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_encodings.py +0 -0
  34. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_integration.py +0 -0
  35. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_mus.py +0 -0
  36. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_native.py +0 -0
  37. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_native_solver.py +0 -0
  38. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_packaging.py +0 -0
  39. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_pipeline.py +0 -0
  40. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_portfolio.py +0 -0
  41. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_preprocess.py +0 -0
  42. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_proof.py +0 -0
  43. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_pyeq.py +0 -0
  44. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_readme.py +0 -0
  45. {cdclkit-0.1.4 → cdclkit-0.1.5}/tests/test_solver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.4
3
+ Version: 0.1.5
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Classifier: Programming Language :: Rust
21
22
  Classifier: Topic :: Scientific/Engineering :: Mathematics
22
23
  Requires-Python: >=3.10
@@ -24,7 +25,7 @@ Description-Content-Type: text/markdown
24
25
  License-File: LICENSE
25
26
  Requires-Dist: dratify>=0.1.7
26
27
  Provides-Extra: native
27
- Requires-Dist: cdclkit-native==0.1.4; extra == "native"
28
+ Requires-Dist: cdclkit-native==0.1.5; extra == "native"
28
29
  Dynamic: license-file
29
30
 
30
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.4"
55
+ __version__ = "0.1.5"
56
56
 
57
57
  __all__ = [
58
58
  "CNF",
@@ -392,6 +392,11 @@ class Solver:
392
392
  #: depth. Only meaningful when cfg.target_phase is on.
393
393
  self._target = bytearray()
394
394
  self._target_size = 0
395
+ #: how much of the current trail the target already records: for every
396
+ #: i < _target_synced, _target holds trail[i]'s value. The trail only
397
+ #: grows by appending and only shrinks in _cancel_until, so a prefix
398
+ #: that has been copied stays copied until a backtrack cuts into it.
399
+ self._target_synced = 0
395
400
  self._walk_best = 1 << 30
396
401
  self._walk_stale = 0
397
402
  self.frozen = bytearray() # variables excluded from decisions
@@ -605,6 +610,8 @@ class Solver:
605
610
  del trail[bound:]
606
611
  del self.trail_lim[level:]
607
612
  self.qhead = len(trail)
613
+ if self._target_synced > bound:
614
+ self._target_synced = bound
608
615
 
609
616
  # ------------------------------------------------------------ propagation
610
617
 
@@ -1154,6 +1161,7 @@ class Solver:
1154
1161
  self._target[:] = best
1155
1162
  self.polarity[:] = best
1156
1163
  self._target_size = 0 # the target now describes the walk, not a trail
1164
+ self._target_synced = 0
1157
1165
 
1158
1166
  # -------------------------------------------------------------- restarts
1159
1167
 
@@ -1229,13 +1237,21 @@ class Solver:
1229
1237
  self._reduce_db()
1230
1238
  else:
1231
1239
  # A new deepest conflict-free trail: remember the assignment
1232
- # that reached it. Copying costs O(|trail|), but only on a
1233
- # strict improvement, and improvements become rare quickly.
1240
+ # that reached it -- copying only what the target does not
1241
+ # already hold. This used to copy the whole trail on every
1242
+ # improvement, on the theory that improvements become rare
1243
+ # quickly. They do once conflicts start; a search that makes
1244
+ # many decisions without one improves on every decision, and
1245
+ # n of them cost O(n^2) (tests/test_target_scaling.py). The
1246
+ # target ends up byte-for-byte what the full copy produced.
1234
1247
  if self.cfg.target_phase and len(self.trail) > self._target_size:
1235
- self._target_size = len(self.trail)
1248
+ trail = self.trail
1249
+ self._target_size = len(trail)
1236
1250
  target = self._target
1237
- for t in self.trail:
1251
+ for i in range(self._target_synced, len(trail)):
1252
+ t = trail[i]
1238
1253
  target[t >> 1] = 0 if (t & 1) else 1
1254
+ self._target_synced = len(trail)
1239
1255
 
1240
1256
  if max_conflicts is not None and conflicts >= max_conflicts:
1241
1257
  self._cancel_until(len(self.assumptions_applied))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdclkit
3
- Version: 0.1.4
3
+ Version: 0.1.5
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Classifier: Programming Language :: Rust
21
22
  Classifier: Topic :: Scientific/Engineering :: Mathematics
22
23
  Requires-Python: >=3.10
@@ -24,7 +25,7 @@ Description-Content-Type: text/markdown
24
25
  License-File: LICENSE
25
26
  Requires-Dist: dratify>=0.1.7
26
27
  Provides-Extra: native
27
- Requires-Dist: cdclkit-native==0.1.4; extra == "native"
28
+ Requires-Dist: cdclkit-native==0.1.5; extra == "native"
28
29
  Dynamic: license-file
29
30
 
30
31
  # cdclkit
@@ -39,4 +39,5 @@ tests/test_preprocess.py
39
39
  tests/test_proof.py
40
40
  tests/test_pyeq.py
41
41
  tests/test_readme.py
42
- tests/test_solver.py
42
+ tests/test_solver.py
43
+ tests/test_target_scaling.py
@@ -1,4 +1,4 @@
1
1
  dratify>=0.1.7
2
2
 
3
3
  [native]
4
- cdclkit-native==0.1.4
4
+ cdclkit-native==0.1.5
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdclkit"
7
- version = "0.1.4"
7
+ version = "0.1.5"
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"
@@ -22,6 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
24
  "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: 3.15",
25
26
  "Programming Language :: Rust",
26
27
  "Topic :: Scientific/Engineering :: Mathematics",
27
28
  ]
@@ -35,7 +36,7 @@ dependencies = ["dratify>=0.1.7"]
35
36
  # The Rust accelerator, as abi3 wheels for Linux and macOS. Pinned exactly:
36
37
  # the two are bit-exact by construction and a mismatched pair is a divergence
37
38
  # nobody would look for. tests/test_packaging.py enforces the pin.
38
- native = ["cdclkit-native==0.1.4"]
39
+ native = ["cdclkit-native==0.1.5"]
39
40
 
40
41
  [project.scripts]
41
42
  cdclkit = "cdclkit.__main__:main"
@@ -0,0 +1,78 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Carlo Perassi. Licensed under the Apache License 2.0.
3
+ """Target phases must not make the search quadratic in the number of variables.
4
+
5
+ With target phases on (the default), each time the trail reached a new depth
6
+ the solver copied the whole trail into the target. The comment beside it said
7
+ improvements "become rare quickly". That holds only once conflicts happen. A
8
+ search that makes many decisions without a conflict improves on every one, so
9
+ n decisions copied 1, 2, ..., n entries.
10
+
11
+ Measured on the Python engine, solving one unit clause in a formula that
12
+ declares n variables: 4,096 took 0.8 s, 16,384 took 10.6 s and 65,536 took
13
+ 144 s -- each 4x costing about 13x. Turning target phases off made 16,384
14
+ take 0.11 s. Since dratify 0.1.7 a header may declare up to 2^20 variables
15
+ from a file of any length, so a 22-byte file cost hours of CPU.
16
+
17
+ The test compares scaling rather than absolute time, which shared CI runners
18
+ cannot be trusted with: quadrupling the variables should cost about 4x when
19
+ the search is linear and about 16x when it is quadratic. The threshold sits
20
+ between them, and each size takes the best of three runs to damp noise.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import time
26
+ import unittest
27
+
28
+ from cdclkit import native
29
+ from cdclkit.solver import Solver
30
+
31
+
32
+ def _solve(nvars: int, engine: str) -> bool:
33
+ """One unit clause, `nvars` declared: every other variable is a decision."""
34
+ if engine == "native":
35
+ s = native.require().Solver(nvars) # defaults match Config
36
+ else:
37
+ s = Solver(nvars)
38
+ s.add_clause([0])
39
+ return s.solve()
40
+
41
+
42
+ def _seconds(nvars: int, engine: str) -> float:
43
+ best = float("inf")
44
+ for _ in range(3):
45
+ t0 = time.perf_counter()
46
+ sat = _solve(nvars, engine)
47
+ best = min(best, time.perf_counter() - t0)
48
+ assert sat
49
+ return best
50
+
51
+
52
+ class TestUnusedVariablesCostLinearTime(unittest.TestCase):
53
+ #: quadrupling: ~4x if linear, ~16x if quadratic
54
+ LIMIT = 8.0
55
+
56
+ def _check(self, engine: str, small: int):
57
+ t_small = _seconds(small, engine)
58
+ t_large = _seconds(4 * small, engine)
59
+ ratio = t_large / max(t_small, 1e-4)
60
+ self.assertLess(
61
+ ratio, self.LIMIT,
62
+ f"{engine}: {small:,} vars took {t_small:.3f}s and "
63
+ f"{4 * small:,} took {t_large:.3f}s, a {ratio:.1f}x increase for "
64
+ f"4x the variables. Linear is ~4x; this is closer to quadratic.")
65
+
66
+ def test_python_engine(self):
67
+ self._check("python", 4096)
68
+
69
+ @unittest.skipUnless(native.available(),
70
+ "native engine not built for this interpreter")
71
+ def test_native_engine(self):
72
+ # Rust is fast enough that the quadratic term only dominates at
73
+ # larger sizes.
74
+ self._check("native", 32768)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ unittest.main()
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