lazy-scs 1.0.0__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.
lazy_scs-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Krushna Dere
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: lazy-scs
3
+ Version: 1.0.0
4
+ Classifier: License :: OSI Approved :: MIT License
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
8
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
9
+ License-File: LICENSE
10
+ Summary: Scalable factor-2 approximation for the shortest common superstring, with a certified lower bound
11
+ Keywords: shortest common superstring,approximation algorithm,genome assembly,suffix automaton,strings
12
+ Author: Krushna Dere, Svyatoblood
13
+ License: MIT
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
16
+ Project-URL: Paper, https://github.com/nurric-ai/lazy-scs/blob/main/paper/paper.pdf
17
+ Project-URL: Repository, https://github.com/nurric-ai/lazy-scs
18
+
19
+ # lazy-scs
20
+
21
+ Fast, memory-lean **factor-2 approximation for the shortest common superstring** (SCS), with a
22
+ **certified lower bound** on the optimum. It implements the first polynomial-time factor-2
23
+ algorithm ([openai/math result 128](https://github.com/openai/math)) using lazily enabled periodic
24
+ rules and suffix-automaton counts, so memory grows linearly with the input (about 115 bytes per
25
+ input character), not quadratically.
26
+
27
+ ```bash
28
+ pip install lazy-scs
29
+ ```
30
+
31
+ ```python
32
+ import lazy_scs
33
+
34
+ lazy_scs.shortest_superstring(["GATTACA", "TACAGG", "AGGCT"])
35
+ # 'GATTACAGGCT'
36
+
37
+ r = lazy_scs.solve(reads) # list of str or bytes
38
+ r.superstring # contains every read
39
+ r.lower_bound # certified: lower_bound <= OPT
40
+ r.ratio # len / lower_bound; 1.0 = provably optimal
41
+ ```
42
+
43
+ The output always satisfies `len(superstring) <= 2 * lower_bound <= 2 * OPT`. On large, realistic
44
+ inputs it is usually within a fraction of a percent of the bound; on tiny adversarial inputs it can
45
+ be further from optimal (still within the factor 2). `solve(..., verify=True)`
46
+ re-justifies every count of the lower bound with an independent checker.
47
+
48
+ Command line (one string per line, or FASTA):
49
+
50
+ ```bash
51
+ lazy-scs reads.txt -o superstring.txt --verify
52
+ ```
53
+
54
+ On error-free 150 bp reads at 10x coverage of the *E. coli* K-12 genome (46.4 million input
55
+ characters) it runs in about two minutes on a laptop and returns a superstring within 0.021% of the
56
+ certified lower bound.
57
+
58
+ Paper: *Lazy Periodic Rules: A Scalable Implementation of the Factor-2 Approximation for Shortest
59
+ Common Superstring*, Krushna Dere and Svyatoblood, independent researchers, October 2026 —
60
+ [paper.pdf](https://github.com/nurric-ai/lazy-scs/blob/main/paper/paper.pdf). Source, the Lean 4
61
+ formalisation of the lower-bound argument, and benchmarks: <https://github.com/nurric-ai/lazy-scs>.
62
+
63
+ MIT licence.
64
+
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.7,<2"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "lazy-scs"
7
+ version = "1.0.0"
8
+ description = "Scalable factor-2 approximation for the shortest common superstring, with a certified lower bound"
9
+ readme = "python/README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Krushna Dere" }, { name = "Svyatoblood" }]
12
+ requires-python = ">=3.9"
13
+ keywords = ["shortest common superstring", "approximation algorithm", "genome assembly", "suffix automaton", "strings"]
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Rust",
18
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
19
+ "Topic :: Scientific/Engineering :: Mathematics",
20
+ ]
21
+
22
+ [project.urls]
23
+ Repository = "https://github.com/nurric-ai/lazy-scs"
24
+ Paper = "https://github.com/nurric-ai/lazy-scs/blob/main/paper/paper.pdf"
25
+
26
+ [project.scripts]
27
+ lazy-scs = "lazy_scs.__main__:main"
28
+
29
+ [tool.maturin]
30
+ manifest-path = "scs/scs-rs/Cargo.toml"
31
+ python-source = "python"
32
+ module-name = "lazy_scs._core"
33
+ features = ["python"]
34
+ bindings = "pyo3"
35
+ include = [{ path = "LICENSE", format = ["sdist", "wheel"] }]
@@ -0,0 +1,45 @@
1
+ # lazy-scs
2
+
3
+ Fast, memory-lean **factor-2 approximation for the shortest common superstring** (SCS), with a
4
+ **certified lower bound** on the optimum. It implements the first polynomial-time factor-2
5
+ algorithm ([openai/math result 128](https://github.com/openai/math)) using lazily enabled periodic
6
+ rules and suffix-automaton counts, so memory grows linearly with the input (about 115 bytes per
7
+ input character), not quadratically.
8
+
9
+ ```bash
10
+ pip install lazy-scs
11
+ ```
12
+
13
+ ```python
14
+ import lazy_scs
15
+
16
+ lazy_scs.shortest_superstring(["GATTACA", "TACAGG", "AGGCT"])
17
+ # 'GATTACAGGCT'
18
+
19
+ r = lazy_scs.solve(reads) # list of str or bytes
20
+ r.superstring # contains every read
21
+ r.lower_bound # certified: lower_bound <= OPT
22
+ r.ratio # len / lower_bound; 1.0 = provably optimal
23
+ ```
24
+
25
+ The output always satisfies `len(superstring) <= 2 * lower_bound <= 2 * OPT`. On large, realistic
26
+ inputs it is usually within a fraction of a percent of the bound; on tiny adversarial inputs it can
27
+ be further from optimal (still within the factor 2). `solve(..., verify=True)`
28
+ re-justifies every count of the lower bound with an independent checker.
29
+
30
+ Command line (one string per line, or FASTA):
31
+
32
+ ```bash
33
+ lazy-scs reads.txt -o superstring.txt --verify
34
+ ```
35
+
36
+ On error-free 150 bp reads at 10x coverage of the *E. coli* K-12 genome (46.4 million input
37
+ characters) it runs in about two minutes on a laptop and returns a superstring within 0.021% of the
38
+ certified lower bound.
39
+
40
+ Paper: *Lazy Periodic Rules: A Scalable Implementation of the Factor-2 Approximation for Shortest
41
+ Common Superstring*, Krushna Dere and Svyatoblood, independent researchers, October 2026 —
42
+ [paper.pdf](https://github.com/nurric-ai/lazy-scs/blob/main/paper/paper.pdf). Source, the Lean 4
43
+ formalisation of the lower-bound argument, and benchmarks: <https://github.com/nurric-ai/lazy-scs>.
44
+
45
+ MIT licence.
@@ -0,0 +1,83 @@
1
+ """lazy-scs: scalable factor-2 approximation for the shortest common superstring.
2
+
3
+ >>> import lazy_scs
4
+ >>> lazy_scs.shortest_superstring(["GATTACA", "TACAGG", "AGGCT"])
5
+ 'GATTACAGGCT'
6
+ >>> r = lazy_scs.solve(["GATTACA", "TACAGG", "AGGCT"])
7
+ >>> r.length, r.lower_bound, r.ratio
8
+ (11, 11, 1.0)
9
+
10
+ The superstring always has length <= 2 * lower_bound <= 2 * OPT, and `ratio` = length / lower_bound
11
+ is a per-instance certificate: ratio 1.0 means the output is provably optimal.
12
+
13
+ Strings may be `str` (encoded as UTF-8) or `bytes`. The result has the same type as the inputs.
14
+ The core is written in Rust and releases the GIL while it runs.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import Iterable, Sequence, Union
20
+
21
+ from ._core import __version__
22
+ from ._core import solve as _solve
23
+
24
+ __all__ = ["Result", "solve", "shortest_superstring", "__version__"]
25
+
26
+ StrOrBytes = Union[str, bytes]
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Result:
31
+ superstring: StrOrBytes
32
+ """A superstring of every input (same type as the inputs)."""
33
+ lower_bound: int
34
+ """Certified lower bound W' on the optimal length: lower_bound <= OPT <= len(superstring)."""
35
+ rounds: int
36
+ """Solver rounds (1 + number of times a lazy periodic rule had to be enabled)."""
37
+ stats: dict = field(default_factory=dict)
38
+ """Internal counters (and verify_* entries when solve(..., verify=True))."""
39
+
40
+ @property
41
+ def length(self) -> int:
42
+ return len(self.superstring)
43
+
44
+ @property
45
+ def ratio(self) -> float:
46
+ """length / lower_bound >= length / OPT: how far from optimal the output can be, at most."""
47
+ return self.length / self.lower_bound if self.lower_bound else 1.0
48
+
49
+
50
+ def _encode(strings: Iterable[StrOrBytes]) -> tuple[list[bytes], bool]:
51
+ items = list(strings)
52
+ if not items:
53
+ return [], True
54
+ kinds = {type(s) for s in items}
55
+ if kinds <= {bytes, bytearray, memoryview}:
56
+ return [bytes(s) for s in items], False
57
+ if kinds <= {str}:
58
+ return [s.encode("utf-8") for s in items], True
59
+ raise TypeError("strings must be all str or all bytes, got " + ", ".join(sorted(k.__name__ for k in kinds)))
60
+
61
+
62
+ def solve(strings: Sequence[StrOrBytes], *, verify: bool = False, check: bool = False) -> Result:
63
+ """Run the factor-2 approximation and return the superstring with its certified lower bound.
64
+
65
+ verify: re-justify every count of the lower bound from scratch (independent checker; slower).
66
+ check: extra internal consistency checks while building (for debugging).
67
+ """
68
+ data, is_text = _encode(strings)
69
+ sup, w, _w_ext, rounds, stats = _solve(data, check=check, verify=verify)
70
+ if verify and (stats.get("verify_unjustified") or stats.get("verify_ext_violations")):
71
+ raise RuntimeError(f"lower-bound verification failed: {stats}")
72
+ if is_text:
73
+ try:
74
+ sup = sup.decode("utf-8")
75
+ except UnicodeDecodeError as e: # overlaps are found byte-wise; only possible with non-ASCII text
76
+ raise ValueError("the superstring of the UTF-8 encoded inputs is not valid UTF-8; pass bytes "
77
+ "instead (e.g. [s.encode() for s in strings])") from e
78
+ return Result(superstring=sup, lower_bound=w, rounds=rounds, stats=dict(stats))
79
+
80
+
81
+ def shortest_superstring(strings: Sequence[StrOrBytes]) -> StrOrBytes:
82
+ """A superstring of all `strings` with length at most twice the optimum."""
83
+ return solve(strings).superstring
@@ -0,0 +1,63 @@
1
+ """Command line: lazy-scs INPUT [-o OUTPUT] [--verify] [--json]
2
+
3
+ INPUT has one string per line, or FASTA records (any line starting with '>'); '-' reads stdin.
4
+ Prints the superstring (or writes it to OUTPUT) and a summary line on stderr.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import sys
11
+ import time
12
+
13
+ from . import __version__, solve
14
+
15
+
16
+ def read_strings(text: bytes) -> list[bytes]:
17
+ lines = text.splitlines()
18
+ if any(l.startswith(b">") for l in lines):
19
+ out, cur = [], []
20
+ for l in lines:
21
+ if l.startswith(b">"):
22
+ if cur:
23
+ out.append(b"".join(cur))
24
+ cur = []
25
+ else:
26
+ cur.append(l.strip())
27
+ if cur:
28
+ out.append(b"".join(cur))
29
+ return [s for s in out if s]
30
+ return [l.rstrip(b"\r\n") for l in lines if l.rstrip(b"\r\n")]
31
+
32
+
33
+ def main(argv=None) -> int:
34
+ ap = argparse.ArgumentParser(prog="lazy-scs", description="Factor-2 approximate shortest common superstring.")
35
+ ap.add_argument("input", help="file with one string per line or FASTA; '-' for stdin")
36
+ ap.add_argument("-o", "--output", help="write the superstring here instead of stdout")
37
+ ap.add_argument("--verify", action="store_true", help="independently re-check the lower bound")
38
+ ap.add_argument("--json", action="store_true", help="print the summary as JSON on stdout (no superstring)")
39
+ ap.add_argument("--version", action="version", version=f"lazy-scs {__version__}")
40
+ a = ap.parse_args(argv)
41
+
42
+ raw = sys.stdin.buffer.read() if a.input == "-" else open(a.input, "rb").read()
43
+ strings = read_strings(raw)
44
+ t0 = time.perf_counter()
45
+ r = solve(strings, verify=a.verify)
46
+ summary = {"strings": len(strings), "input_chars": sum(map(len, strings)), "seconds": round(time.perf_counter() - t0, 3),
47
+ "length": r.length, "lower_bound": r.lower_bound, "ratio": round(r.ratio, 6), "rounds": r.rounds}
48
+ if a.verify:
49
+ summary["verified"] = True
50
+ if a.output:
51
+ with open(a.output, "wb") as f:
52
+ f.write(r.superstring)
53
+ if a.json:
54
+ print(json.dumps(summary))
55
+ else:
56
+ if not a.output:
57
+ sys.stdout.buffer.write(r.superstring + b"\n")
58
+ print(json.dumps(summary), file=sys.stderr)
59
+ return 0
60
+
61
+
62
+ if __name__ == "__main__":
63
+ sys.exit(main())
@@ -0,0 +1,180 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.5"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
16
+
17
+ [[package]]
18
+ name = "heck"
19
+ version = "0.5.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
22
+
23
+ [[package]]
24
+ name = "indoc"
25
+ version = "2.0.8"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "a37b2691796cffeb8a8cd305ac66e65841559f147f4e63231d0eafa4db5384d1"
28
+ dependencies = [
29
+ "rustversion",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "libc"
34
+ version = "0.2.190"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
37
+
38
+ [[package]]
39
+ name = "memoffset"
40
+ version = "0.9.1"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
43
+ dependencies = [
44
+ "autocfg",
45
+ ]
46
+
47
+ [[package]]
48
+ name = "once_cell"
49
+ version = "1.21.4"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
52
+
53
+ [[package]]
54
+ name = "portable-atomic"
55
+ version = "1.15.0"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
58
+
59
+ [[package]]
60
+ name = "proc-macro2"
61
+ version = "1.0.107"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
64
+ dependencies = [
65
+ "unicode-ident",
66
+ ]
67
+
68
+ [[package]]
69
+ name = "pyo3"
70
+ version = "0.23.5"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
73
+ dependencies = [
74
+ "cfg-if",
75
+ "indoc",
76
+ "libc",
77
+ "memoffset",
78
+ "once_cell",
79
+ "portable-atomic",
80
+ "pyo3-build-config",
81
+ "pyo3-ffi",
82
+ "pyo3-macros",
83
+ "unindent",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "pyo3-build-config"
88
+ version = "0.23.5"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
91
+ dependencies = [
92
+ "once_cell",
93
+ "target-lexicon",
94
+ ]
95
+
96
+ [[package]]
97
+ name = "pyo3-ffi"
98
+ version = "0.23.5"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
101
+ dependencies = [
102
+ "libc",
103
+ "pyo3-build-config",
104
+ ]
105
+
106
+ [[package]]
107
+ name = "pyo3-macros"
108
+ version = "0.23.5"
109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
110
+ checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
111
+ dependencies = [
112
+ "proc-macro2",
113
+ "pyo3-macros-backend",
114
+ "quote",
115
+ "syn",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "pyo3-macros-backend"
120
+ version = "0.23.5"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
123
+ dependencies = [
124
+ "heck",
125
+ "proc-macro2",
126
+ "pyo3-build-config",
127
+ "quote",
128
+ "syn",
129
+ ]
130
+
131
+ [[package]]
132
+ name = "quote"
133
+ version = "1.0.47"
134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
135
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
136
+ dependencies = [
137
+ "proc-macro2",
138
+ ]
139
+
140
+ [[package]]
141
+ name = "rustversion"
142
+ version = "1.0.23"
143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
144
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
145
+
146
+ [[package]]
147
+ name = "scs-rs"
148
+ version = "1.0.0"
149
+ dependencies = [
150
+ "pyo3",
151
+ ]
152
+
153
+ [[package]]
154
+ name = "syn"
155
+ version = "2.0.119"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
158
+ dependencies = [
159
+ "proc-macro2",
160
+ "quote",
161
+ "unicode-ident",
162
+ ]
163
+
164
+ [[package]]
165
+ name = "target-lexicon"
166
+ version = "0.12.16"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
169
+
170
+ [[package]]
171
+ name = "unicode-ident"
172
+ version = "1.0.27"
173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
174
+ checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
175
+
176
+ [[package]]
177
+ name = "unindent"
178
+ version = "0.2.4"
179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
180
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "scs-rs"
3
+ version = "1.0.0"
4
+ edition = "2024"
5
+ description = "Scalable factor-2 approximation for shortest common superstring (lazy periodic rules)"
6
+ license = "MIT"
7
+ repository = "https://github.com/nurric-ai/lazy-scs"
8
+
9
+ [lib]
10
+ name = "lazy_scs_core"
11
+ crate-type = ["cdylib", "rlib"]
12
+
13
+ [[bin]]
14
+ name = "scs-rs"
15
+ path = "src/main.rs"
16
+
17
+ [features]
18
+ # `python` builds the lazy_scs._core extension module (maturin, see pyproject.toml at the repo root)
19
+ python = ["dep:pyo3"]
20
+
21
+ [dependencies]
22
+ pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"], optional = true }