Vectorize compute-properties: replace per-row BioPython with array math (~10x at scale)#17
Conversation
…ibuted diffs, mode-gated aa_fraction)
…ction, aa fractions)
Replace row-by-row BioPython compute with vectorized array math (build_counts substrate + per-property numpy/bisection). Add signed-zero canonicalization to the quantization boundary (determinism hardening; numpy reduction order can flip the sign of a sub-ULP zero). Regenerate the characterization golden under the canonicalized contract — only signed-zero cells changed (-0.0 -> 0.0), verified.
There was a problem hiding this comment.
Code Review
This pull request vectorizes sequence property computations by replacing per-row BioPython calculations with single-threaded NumPy and Polars array operations, significantly improving performance. It also introduces comprehensive quantization rounding across all float families to maintain determinism and absorb floating-point drift. Feedback on the changes suggests reordering the quantization prefix list to prevent incorrect prefix matching of 'chargeShift_' with 'charge_', and using safe dictionary retrieval with fallbacks when accessing chain substrates for Fv properties to avoid potential KeyErrors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…ents.txt + CI sync (per titeseq)
…ero canonicalization, sync-guard + signed-zero tests, comment/log-token fixes
… E402 with ruff per-file-ignore
…p dead retention)
…rop vestigial constants, exclude perf from CI gate - Add pipeline<->oracle composition parity test (catches pKa/include_cys/chain-pairing wiring regressions the self-generated golden can't) - Add vectorized-engine invariant tests (invariants hold on the shipping code, not only the oracle) - Add edge tests: Fv AND-gating with a present-but-invalid chain; zero-row full-chain+Fv; non-ASCII single-byte cleaning divergence - Remove vestigial CID_QUANTIZE_PREFIXES/DECIMALS; tests derive decimals from the live _decimals_for - Exclude the @slow perf benchmark from the pytest CI gate (wall-clock budget flakes on shared runners)
~4.85 GB/1M optimized footprint; 12GiB covers ~2M clones with headroom (was 16GiB).
…t/integration/data)
…specific RSS figures
What
Replaces the row-by-row BioPython computation in
compute-propertieswith a single-threaded vectorized engine (software/src/vectorized.py): a per-residue count matrix feeds numpy/polars array math, reusing BioPython's exact constant tables (kd,DIWV,protein_weights) so the numbers are unchanged. The oldsoftware/src/properties.pyis retained unchanged as the test oracle.Why
The block's "calculation" step was the wall-clock bottleneck — not p-frame manipulation, as first suspected. The Python step iterated clones one at a time, constructing BioPython
ProteinAnalysis/IsoelectricPointobjects and running a pI bisection per clone — O(N) serial Python.Result
Baseline (pre-change per-row code) vs this PR, measured back-to-back on identical full-paired antibody input:
~10× at production scale, where it plateaus (~120 µs/clone baseline vs ~12 µs/clone vectorized). Best case at small N (~50k, isolated) is ~14×. Single-threaded (
POLARS_MAX_THREADS=1), so behavior is identical on a 2-core laptop and a 64-core server — no cpu()/core-count dependency.Memory & resources
The vectorized engine materializes full-N count matrices, so it uses more peak memory than the old per-row code:
The compute step's
mem()is raised 4 → 12 GiB to cover ~2M clones;cpu(1)stays (single-threaded by design).Correctness
Output is unchanged within a quantized-equal contract (every emitted float rounded below display precision at the pipeline boundary). Verified against the retained scalar oracle:
include_cys/ chain-pairing wiring regressions the self-generated golden can't.@slowperf benchmark runs separately, excluded from the CI gate where wall-clock budgets flake).One refinement: signed-zero canonicalization (
-0.0 → +0.0) at the quantization boundary — numpy's reduction order can flip the sign of a numerically-zero residual. It keeps the CID stable and fixes a pre-existing-0.000display wart; the golden changed only on 3 verified-benign zero cells.Upgrade impact
Upgrading from the published block recomputes this block (the engine changed) and dirties downstream consumers of
gravy_*,mw_*,instability_*,aliphatic_*,aromaticity_*, andaaFraction— their bytes change (the published block stored them at full BioPython precision; the new ones are numpy-computed and rounded below display).charge_*,chargeShift_*,pi_*,eox_*,ered_*are byte-stable. The values are scientifically identical (≤1e-13); the downstream recompute is one-time and lands on the same answer. Forward-stable from here — the round-below-display contract holds the CID steady across future numpy/libm upgrades.Live validation (in-platform, real data)
Ran the full pipeline Samples & Data → Import V(D)J Data → Sequence Properties against a backend on a real MiXCR bulk BCR export (280 IGHeavy clonotypes) — once with the published block and once with this branch, both fed from the same
Import V(D)JIGHeavy output. Both produced the same mode/coverage (antibody_tcr_legacy_bulk/full_chain/ IG), no errors.Comparing every emitted column across all 280 clonotypes (order-independent hash of
clonotypeKey=value, at both stored precision and raw float bytes):chargeShift,charge(CDR-H3 + VH),eox,ered,pi.gravy×2,mw,instability,aliphatic,aromaticity) — published emits full BioPython precision (e.g.-0.30640000000000006), this branch rounds below display (-0.3064).This confirms the Upgrade impact section empirically: the predicted byte-stable set matched byte-for-byte, the predicted recompute set differs only below display precision (values unchanged). The quantized-equal contract — previously checked only against the in-repo oracle — now also holds against the actually-published block.
Notes
pyproject.toml[project.dependencies]the single source of truth and generatessrc/requirements.txtfrom it, gated by a CIrequirements-synccheck (matching titeseq-analysis)..softwareand.workflow(patch).Greptile Summary
This PR replaces the row-by-row BioPython computation in
compute-propertieswith a vectorized numpy/polars engine (vectorized.py) that builds a per-residue count matrix once and derives all properties with array math, reusing BioPython's exact constant tables so numeric output is unchanged. Correctness is pinned by 259 tests including a new oracle-parity integration test that validates every emitted cell against the scalar BioPython path.vectorized.py(new): single-pass cleaning via latin-1 byte gather → count matrix scatter; lockstep fixed-iteration bisection for pI (bit-identical to scalar); order-preserving dipeptide instability vianp.add.at; signed-zero canonicalization at the output boundary.pipeline.py: per-row loops removed; full-chain path shares a single_Cleanedobject across the count substrate and the instability index;run_peptidecallsbuild_counts+instability_indexseparately (two clean passes — see inline comment).mem()raised 4 → 12 GiB;POLARS_MAX_THREADS=1pinned before polars import;pyproject.tomlis now the single source of truth for runtime deps with a new CIrequirements-syncjob.Confidence Score: 5/5
Safe to merge. The vectorized engine is extensively tested against the scalar BioPython oracle on every emitted cell, and the single remaining suboptimal pattern (peptide path double-cleans) is a pure performance concern with no correctness impact.
The vectorized math is backed by property-level Hypothesis parity tests, a frozen whole-frame golden snapshot (7/7 byte-equal), and a new pipeline-vs-oracle integration test covering every column's pKa-set/include_cys/chain-pairing wiring. The only finding is a redundant cleaning pass in the peptide path with no effect on correctness or output values. No data-affecting bugs were found.
software/src/pipeline.py (run_peptide double-cleans sequences)
Important Files Changed
Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "chore(workflow): trim compute-properties..." | Re-trigger Greptile