seqpro 0.13.0__tar.gz → 0.14.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.
Files changed (110) hide show
  1. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/release.yaml +2 -9
  2. {seqpro-0.13.0 → seqpro-0.14.0}/CHANGELOG.md +22 -0
  3. {seqpro-0.13.0 → seqpro-0.14.0}/PKG-INFO +1 -1
  4. seqpro-0.14.0/benchmarks/bench_to_packed.py +187 -0
  5. seqpro-0.14.0/docs/superpowers/plans/2026-05-31-rag-to-packed.md +815 -0
  6. seqpro-0.14.0/docs/superpowers/specs/2026-05-31-rag-to-packed-design.md +134 -0
  7. {seqpro-0.13.0 → seqpro-0.14.0}/pyproject.toml +1 -1
  8. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_encoders.py +4 -5
  9. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/alphabets/_alphabets.py +1 -2
  10. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/__init__.py +2 -1
  11. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/_array.py +22 -11
  12. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/_ops.py +148 -5
  13. {seqpro-0.13.0 → seqpro-0.14.0}/skills/seqpro/SKILL.md +3 -2
  14. seqpro-0.14.0/tests/test_rag_to_packed.py +130 -0
  15. {seqpro-0.13.0 → seqpro-0.14.0}/.claude/skills/zensical-setup/.skillfish.json +0 -0
  16. {seqpro-0.13.0 → seqpro-0.14.0}/.claude/skills/zensical-setup/SKILL.md +0 -0
  17. {seqpro-0.13.0 → seqpro-0.14.0}/.gitattributes +0 -0
  18. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/bump.yaml +0 -0
  19. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/docs.yml +0 -0
  20. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/lint.yaml +0 -0
  21. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/merge.yaml +0 -0
  22. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/publish.yaml +0 -0
  23. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/release-pipeline.yaml +0 -0
  24. {seqpro-0.13.0 → seqpro-0.14.0}/.github/workflows/test.yaml +0 -0
  25. {seqpro-0.13.0 → seqpro-0.14.0}/.gitignore +0 -0
  26. {seqpro-0.13.0 → seqpro-0.14.0}/.pre-commit-config.yaml +0 -0
  27. {seqpro-0.13.0 → seqpro-0.14.0}/CLAUDE.md +0 -0
  28. {seqpro-0.13.0 → seqpro-0.14.0}/Cargo.lock +0 -0
  29. {seqpro-0.13.0 → seqpro-0.14.0}/Cargo.toml +0 -0
  30. {seqpro-0.13.0 → seqpro-0.14.0}/LICENSE +0 -0
  31. {seqpro-0.13.0 → seqpro-0.14.0}/README.md +0 -0
  32. {seqpro-0.13.0 → seqpro-0.14.0}/benches/kshuffle.rs +0 -0
  33. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/alphabets.md +0 -0
  34. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/bed.md +0 -0
  35. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/gtf.md +0 -0
  36. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/index.md +0 -0
  37. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/ragged.md +0 -0
  38. {seqpro-0.13.0 → seqpro-0.14.0}/docs/api/types.md +0 -0
  39. {seqpro-0.13.0 → seqpro-0.14.0}/docs/index.md +0 -0
  40. {seqpro-0.13.0 → seqpro-0.14.0}/docs/ragged.md +0 -0
  41. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-04-ragged-record-array.md +0 -0
  42. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-05-documentation-site.md +0 -0
  43. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-05-narwhals-coord-schema.md +0 -0
  44. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-05-ragged-zip-and-record-introspection.md +0 -0
  45. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-20-kshuffle-optimization.md +0 -0
  46. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-20-kshuffle-pooled-buffers-and-k2-fast-path.md +0 -0
  47. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-20-kshuffle-wilson-single-pass.md +0 -0
  48. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-20-release-pipeline.md +0 -0
  49. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-28-translate-lut-validation.md +0 -0
  50. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/plans/2026-05-31-flat-buffer-to-padded.md +0 -0
  51. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-04-ragged-record-array-design.md +0 -0
  52. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-05-documentation-site-design.md +0 -0
  53. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-05-narwhals-coord-schema-design.md +0 -0
  54. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-05-ragged-zip-and-record-introspection-design.md +0 -0
  55. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-20-kshuffle-optimization-design.md +0 -0
  56. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-20-kshuffle-pooled-buffers-and-k2-fast-path-design.md +0 -0
  57. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-20-kshuffle-wilson-single-pass-design.md +0 -0
  58. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-20-release-pipeline-design.md +0 -0
  59. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-28-translate-lut-and-validation-design.md +0 -0
  60. {seqpro-0.13.0 → seqpro-0.14.0}/docs/superpowers/specs/2026-05-31-flat-buffer-to-padded-design.md +0 -0
  61. {seqpro-0.13.0 → seqpro-0.14.0}/meta.yaml +0 -0
  62. {seqpro-0.13.0 → seqpro-0.14.0}/pixi.lock +0 -0
  63. {seqpro-0.13.0 → seqpro-0.14.0}/pixi.toml +0 -0
  64. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/__init__.py +0 -0
  65. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_analyzers.py +0 -0
  66. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_cleaners.py +0 -0
  67. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_coords.py +0 -0
  68. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_modifiers.py +0 -0
  69. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_numba.py +0 -0
  70. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_types.py +0 -0
  71. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/_utils.py +0 -0
  72. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/alphabets/__init__.py +0 -0
  73. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/bed.py +0 -0
  74. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/experimental/_experimental.py +0 -0
  75. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/experimental/_visualizers.py +0 -0
  76. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/gtf.py +0 -0
  77. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/py.typed +0 -0
  78. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/_gufuncs.py +0 -0
  79. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/_types.py +0 -0
  80. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/rag/_utils.py +0 -0
  81. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/transforms/__init__.py +0 -0
  82. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/transforms/augmentation.py +0 -0
  83. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/transforms/tmm.py +0 -0
  84. {seqpro-0.13.0 → seqpro-0.14.0}/python/seqpro/xr/__init__.py +0 -0
  85. {seqpro-0.13.0 → seqpro-0.14.0}/scratch_bench_rc.py +0 -0
  86. {seqpro-0.13.0 → seqpro-0.14.0}/scratch_bench_to_padded.py +0 -0
  87. {seqpro-0.13.0 → seqpro-0.14.0}/src/kmer_encode.rs +0 -0
  88. {seqpro-0.13.0 → seqpro-0.14.0}/src/kshuffle.rs +0 -0
  89. {seqpro-0.13.0 → seqpro-0.14.0}/src/kshuffle_ref.rs +0 -0
  90. {seqpro-0.13.0 → seqpro-0.14.0}/src/lib.rs +0 -0
  91. {seqpro-0.13.0 → seqpro-0.14.0}/tests/_shape_fixtures.py +0 -0
  92. {seqpro-0.13.0 → seqpro-0.14.0}/tests/bed/test_pyranges.py +0 -0
  93. {seqpro-0.13.0 → seqpro-0.14.0}/tests/bed/test_read.py +0 -0
  94. {seqpro-0.13.0 → seqpro-0.14.0}/tests/bed/test_sort.py +0 -0
  95. {seqpro-0.13.0 → seqpro-0.14.0}/tests/bed/test_with_length.py +0 -0
  96. {seqpro-0.13.0 → seqpro-0.14.0}/tests/bench_translate_lut.py +0 -0
  97. {seqpro-0.13.0 → seqpro-0.14.0}/tests/conftest.py +0 -0
  98. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_analyzers.py +0 -0
  99. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_coords.py +0 -0
  100. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_encoders.py +0 -0
  101. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_modifiers.py +0 -0
  102. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_ohe.py +0 -0
  103. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_ragged.py +0 -0
  104. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_ragged_rc.py +0 -0
  105. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_ragged_to_padded.py +0 -0
  106. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_shape_matrix.py +0 -0
  107. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_tokenize.py +0 -0
  108. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_transforms.py +0 -0
  109. {seqpro-0.13.0 → seqpro-0.14.0}/tests/test_translate.py +0 -0
  110. {seqpro-0.13.0 → seqpro-0.14.0}/zensical.toml +0 -0
@@ -1,9 +1,6 @@
1
1
  name: Create release
2
2
 
3
3
  on:
4
- push:
5
- tags:
6
- - '[0-9]+.[0-9]+.[0-9]+'
7
4
  workflow_dispatch:
8
5
  inputs:
9
6
  tag:
@@ -30,15 +27,11 @@ jobs:
30
27
  - uses: actions/checkout@v4
31
28
  with:
32
29
  fetch-depth: 0
30
+ ref: ${{ inputs.tag }}
33
31
 
34
32
  - name: Resolve tag
35
33
  id: tag
36
- run: |
37
- case "${{ github.event_name }}" in
38
- push) echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT ;;
39
- workflow_dispatch) echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT ;;
40
- workflow_call) echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT ;;
41
- esac
34
+ run: echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT
42
35
 
43
36
  - name: Extract changelog section
44
37
  run: |
@@ -1,3 +1,25 @@
1
+ ## 0.14.0 (2026-06-01)
2
+
3
+ ### Feat
4
+
5
+ - **rag**: add Ragged.to_packed method and export to_packed
6
+ - **rag**: add Numba-parallelized to_packed for flat layouts
7
+
8
+ ### Fix
9
+
10
+ - **ci**: check out release tag and drop duplicate release trigger
11
+ - **rag**: produce canonical list-of-records layout in to_packed
12
+
13
+ ### Refactor
14
+
15
+ - **rag**: make Ragged.to_packed copy keyword-only; add multibyte-trailing test
16
+ - **rag**: drop unused as_contiguous packing path in unbox
17
+
18
+ ### Perf
19
+
20
+ - **rag**: microbenchmark to_packed throughput vs ak.to_packed
21
+ - **rag**: use to_packed at internal ak.to_packed call sites
22
+
1
23
  ## 0.13.0 (2026-06-01)
2
24
 
3
25
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seqpro
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -0,0 +1,187 @@
1
+ """Microbenchmark: seqpro.rag.to_packed vs ak.to_packed vs NumPy gather.
2
+
3
+ Run in the bench env:
4
+ pixi run -e bench python benchmarks/bench_to_packed.py --out bench_to_packed
5
+
6
+ Outputs <out>.csv and <out>_*.png. Sweeps n_rows, mean length, length
7
+ distribution, source (RAM vs memmap), dtype itemsize, and thread count, and
8
+ prints an effect-size ranking of which axes most affect the speedup ratio.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import os
15
+ import time
16
+ from pathlib import Path
17
+
18
+ import numpy as np
19
+
20
+
21
+ def make_lengths(rng, n_rows, mean_len, distribution):
22
+ if distribution == "uniform":
23
+ lo, hi = max(1, mean_len // 2), mean_len * 2
24
+ return rng.integers(lo, hi + 1, size=n_rows)
25
+ # long-tailed: most rows short, a few very long (load imbalance)
26
+ lengths = rng.integers(1, max(2, mean_len // 4), size=n_rows)
27
+ n_big = max(1, n_rows // 100)
28
+ lengths[rng.choice(n_rows, n_big, replace=False)] = mean_len * 50
29
+ return lengths
30
+
31
+
32
+ def build(rng, n_rows, mean_len, distribution, dtype, source, tmpdir):
33
+ import seqpro.rag as spr
34
+
35
+ lengths = make_lengths(rng, n_rows, mean_len, distribution).astype(np.int64)
36
+ total = int(lengths.sum())
37
+ if source == "memmap":
38
+ path = Path(tmpdir) / "data.dat"
39
+ data = np.memmap(path, dtype=dtype, mode="w+", shape=(total,))
40
+ data[:] = rng.integers(0, 255, size=total).astype(dtype)
41
+ else:
42
+ data = rng.integers(0, 255, size=total).astype(dtype)
43
+ rag = spr.Ragged.from_lengths(data, lengths)
44
+ # reorder so the layout is an (unpacked) ListArray, forcing a real gather
45
+ return rag[rng.permutation(n_rows)], total * np.dtype(dtype).itemsize
46
+
47
+
48
+ def numpy_gather(rag):
49
+ import seqpro.rag as spr
50
+
51
+ parts = rag._parts
52
+ offs = parts.offsets
53
+ starts, stops = (offs[0], offs[1]) if offs.ndim == 2 else (offs[:-1], offs[1:])
54
+ lengths = stops - starts
55
+ out_off = np.empty(lengths.size + 1, dtype=np.int64)
56
+ out_off[0] = 0
57
+ np.cumsum(lengths, out=out_off[1:])
58
+ idx = np.repeat(starts - out_off[:-1], lengths) + np.arange(int(out_off[-1]))
59
+ return spr.Ragged.from_offsets(parts.data[idx], parts.shape, out_off)
60
+
61
+
62
+ def timeit(fn, arg, repeats):
63
+ fn(arg) # warm up (JIT, caches)
64
+ times = []
65
+ for _ in range(repeats):
66
+ t0 = time.perf_counter()
67
+ fn(arg)
68
+ times.append(time.perf_counter() - t0)
69
+ return float(np.median(times))
70
+
71
+
72
+ def main():
73
+ p = argparse.ArgumentParser()
74
+ p.add_argument("--out", default="bench_to_packed")
75
+ p.add_argument("--repeats", type=int, default=5)
76
+ p.add_argument(
77
+ "--n-rows", type=int, nargs="+", default=[1000, 10000, 100000, 1000000]
78
+ )
79
+ p.add_argument("--mean-len", type=int, nargs="+", default=[16, 256, 4096])
80
+ p.add_argument("--distributions", nargs="+", default=["uniform", "longtail"])
81
+ p.add_argument("--sources", nargs="+", default=["ram", "memmap"])
82
+ p.add_argument("--dtypes", nargs="+", default=["uint8", "float64"])
83
+ p.add_argument("--threads", type=int, nargs="+", default=[int(os.cpu_count() or 1)])
84
+ args = p.parse_args()
85
+
86
+ import awkward as ak
87
+ import pandas as pd
88
+ import seqpro.rag as spr # noqa: F401 (ensures kernel import)
89
+ import tempfile
90
+
91
+ rng = np.random.default_rng(0)
92
+ rows = []
93
+ with tempfile.TemporaryDirectory() as tmp:
94
+ for nthreads in args.threads:
95
+ import numba
96
+
97
+ numba.set_num_threads(nthreads)
98
+ impls = {
99
+ "seqpro": lambda r: r.to_packed(),
100
+ "ak": lambda r: spr.Ragged(ak.to_packed(r)),
101
+ "numpy": numpy_gather,
102
+ }
103
+ for n_rows in args.n_rows:
104
+ for mean_len in args.mean_len:
105
+ for dist in args.distributions:
106
+ for src in args.sources:
107
+ for dt in args.dtypes:
108
+ rag, nbytes = build(
109
+ rng, n_rows, mean_len, dist, dt, src, tmp
110
+ )
111
+ for name, fn in impls.items():
112
+ t = timeit(fn, rag, args.repeats)
113
+ rows.append(
114
+ dict(
115
+ impl=name,
116
+ n_rows=n_rows,
117
+ mean_len=mean_len,
118
+ distribution=dist,
119
+ source=src,
120
+ dtype=dt,
121
+ threads=nthreads,
122
+ seconds=t,
123
+ gbps=nbytes / t / 1e9,
124
+ )
125
+ )
126
+
127
+ df = pd.DataFrame(rows)
128
+ df.to_csv(f"{args.out}.csv", index=False)
129
+ _plots_and_summary(df, args.out)
130
+
131
+
132
+ def _plots_and_summary(df, out):
133
+ import matplotlib
134
+
135
+ matplotlib.use("Agg")
136
+ import matplotlib.pyplot as plt
137
+ import seaborn as sns
138
+
139
+ # throughput vs n_rows, faceted by mean_len x dtype, hue impl
140
+ g = sns.relplot(
141
+ data=df,
142
+ x="n_rows",
143
+ y="gbps",
144
+ hue="impl",
145
+ style="source",
146
+ col="mean_len",
147
+ row="dtype",
148
+ kind="line",
149
+ marker="o",
150
+ facet_kws=dict(sharey=False),
151
+ )
152
+ g.set(xscale="log")
153
+ g.savefig(f"{out}_throughput.png", dpi=120)
154
+
155
+ # thread scaling for seqpro
156
+ if df["threads"].nunique() > 1:
157
+ sub = df[df.impl == "seqpro"]
158
+ gt = sns.relplot(
159
+ data=sub, x="threads", y="gbps", hue="mean_len", kind="line", marker="o"
160
+ )
161
+ gt.savefig(f"{out}_threads.png", dpi=120)
162
+ plt.close("all")
163
+
164
+ # effect-size ranking on speedup (seqpro / ak) — goal 2
165
+ wide = df.pivot_table(
166
+ index=["n_rows", "mean_len", "distribution", "source", "dtype", "threads"],
167
+ columns="impl",
168
+ values="gbps",
169
+ ).reset_index()
170
+ wide["speedup"] = wide["seqpro"] / wide["ak"]
171
+ print("\n=== Speedup (seqpro / ak) summary ===")
172
+ print(wide["speedup"].describe())
173
+ print("\n=== Mean speedup by axis (effect size) ===")
174
+ axes = ["n_rows", "mean_len", "distribution", "source", "dtype", "threads"]
175
+ spreads = {}
176
+ for ax in axes:
177
+ means = wide.groupby(ax)["speedup"].mean()
178
+ spreads[ax] = means.max() - means.min()
179
+ print(f"\n{ax}:")
180
+ print(means)
181
+ print("\n=== Axes ranked by speedup spread (most influential first) ===")
182
+ for ax, s in sorted(spreads.items(), key=lambda kv: -kv[1]):
183
+ print(f" {ax:14s} spread={s:.2f}")
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()