diff --git a/CONTEXT.md b/CONTEXT.md index c718a7c3..737907ce 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -19,7 +19,7 @@ data; a query is the act of looking. **Library**: A collection of targets that also carries expected or observed fragment -intensities. Intensities are what make it a library — without them it is targets. +intensities. Intensities are what make it a library -- without them it is targets. Note this is narrower than the field's usage, where "spectral library" covers anything mapping analytes to m/z values. _Avoid_: spectral library (ambiguous), speclib @@ -37,7 +37,7 @@ determinism, so it is never caller-supplied. _Avoid_: id, library_id, row id **Source id**: -What the source file called a precursor — the JSON target payload's `id`, +What the source file called a precursor -- the JSON target payload's `id`, mzSpecLib's `` key, DIA-NN's `transition_group_id`. Opaque: carried through and echoed back, never used to address anything. Absent in some formats. _Avoid_: id, library id @@ -56,7 +56,7 @@ discovery rate. Either shipped by the library or generated as a mass shift. A target and its decoy variants, competing as a unit so exactly one survives. **Variant**: -One member of a decoy group — the target itself, or one of its mass-shifted +One member of a decoy group -- the target itself, or one of its mass-shifted decoys. A stored row expands into several scored variants, so "one row" is not "one result". @@ -68,7 +68,7 @@ contents: whether sequences are available, whether fragment labels carry ion chemistry, how isotopes are derived, how decoys are obtained. **Graceful degradation**: -Proceeding with a capability absent rather than failing — skipping FDR when there +Proceeding with a capability absent rather than failing -- skipping FDR when there are no decoys, skipping sequence-dependent scores when sequences are unavailable. The gate is per-score, not per-run; only some scores need sequences. diff --git a/Cargo.toml b/Cargo.toml index 7cd1bee9..a865fd6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ parquet = { version = "59.2" } arrow = { version = "59.2", default-features = false } # Set as the global allocator on windows and musl, whose own allocators are -# slower here — musl's serialises on one lock. Every binary that wants it repeats +# slower here -- musl's serialises on one lock. Every binary that wants it repeats # the same `cfg(any(target_os = "windows", target_env = "musl"))` gate. Only the # deployables carry it: the CLIs and the viewer. The rest are dev utilities and # may run slow on those targets. diff --git a/docs/development.md b/docs/development.md index 22cbfdbb..a04fe175 100644 --- a/docs/development.md +++ b/docs/development.md @@ -20,11 +20,11 @@ Run any with `--help` for the full flag list. | Feature | Crate | Effect | Use case | Enable | |---------|-------|--------|----------|--------| | `parallel` / `rayon` | `timsseek_cli` / `timsseek` | Rayon parallel scoring | Default; fastest wall-time | On by default | -| `instrumentation` | `timsseek_cli` / `timsseek` | `tracing-profile` perfetto spans | Perf tracing. **Requires `--no-default-features`** — the perfetto backend captures only the main thread, so rayon worker spans are dropped entirely. Run serial or traces for the hot path are empty. | `--features instrumentation --no-default-features` | +| `instrumentation` | `timsseek_cli` / `timsseek` | `tracing-profile` perfetto spans | Perf tracing. **Requires `--no-default-features`** -- the perfetto backend captures only the main thread, so rayon worker spans are dropped entirely. Run serial or traces for the hot path are empty. | `--features instrumentation --no-default-features` | | `track-alloc` | `timsseek_cli` | Global allocator tracking via `alloc_track` | Binary prints per-phase allocation deltas to stderr: `[alloc] d_bytes=... d_live=... churn=... peak=... hist=...`. Detect churn + memory regressions. Dev-only; do not ship. | `--features track-alloc` | | `dashboard` | `timsseek_cli` / `rescore_dash` | Ratatui TUI of a rescoring run: score separation, per-feature histograms, FDR and calibration curves | Interactive dev inspection. Dev-only; also needs `TIMSSEEK_RESCORE_DASHBOARD` at runtime (below). | `--features dashboard` | -| `calib-dashboard` | `timsseek_cli` | Pulls in `calib_dash`, wiring an interactive terminal dashboard into Phase 1/2 of RT calibration | Step through Phase 1 prescore batches, watch the calibration curve/DP path converge, inspect the Phase 2 fit and derived tolerances. Does nothing on its own — also requires `TIMSSEEK_CALIB_DASHBOARD=1` at runtime (see Env vars). Dev-only; do not ship. | `--features calib-dashboard` | -| `query-instr` | `timscentroid` | Per-peak atomic counters in `IndexedPeakGroup::for_each_peak` | Filter-funnel shape + pass rates. ~10× wall-time inflation — funnel counts only, not timing. | `-p timscentroid --features query-instr` | +| `calib-dashboard` | `timsseek_cli` | Pulls in `calib_dash`, wiring an interactive terminal dashboard into Phase 1/2 of RT calibration | Step through Phase 1 prescore batches, watch the calibration curve/DP path converge, inspect the Phase 2 fit and derived tolerances. Does nothing on its own -- also requires `TIMSSEEK_CALIB_DASHBOARD=1` at runtime (see Env vars). Dev-only; do not ship. | `--features calib-dashboard` | +| `query-instr` | `timscentroid` | Per-peak atomic counters in `IndexedPeakGroup::for_each_peak` | Filter-funnel shape + pass rates. ~10× wall-time inflation -- funnel counts only, not timing. | `-p timscentroid --features query-instr` | | `aws` / `gcp` / `azure` | `timscentroid` | `object_store` cloud backends | Read `.d` / speclib from cloud | `--features aws` (etc.) | ## Env vars @@ -44,13 +44,13 @@ Not shown by `--help`. Read directly via `std::env::var` / `var_os`. `task --list-all` enumerates everything. Non-obvious ones: -- `task test`, `task fmt`, `task clippy` — `task fmt` runs nightly rustfmt + ruff. Do not use `cargo fmt` (stable silently drops nightly-only opts). -- `task speclib:build -- ` — wrapper around `speclib_build`. -- `task speclib:local-koina` / `task speclib:stop-koina` — local Koina docker. First run downloads all models (~10-30 min). -- `task docker` — cross-builds linux/amd64 images. +- `task test`, `task fmt`, `task clippy` -- `task fmt` runs nightly rustfmt + ruff. Do not use `cargo fmt` (stable silently drops nightly-only opts). +- `task speclib:build -- ` -- wrapper around `speclib_build`. +- `task speclib:local-koina` / `task speclib:stop-koina` -- local Koina docker. First run downloads all models (~10-30 min). +- `task docker` -- cross-builds linux/amd64 images. - `task license_check`, `task todos`, `task bumpver`, `task build_python`. -Per-crate: `rust/timsseek/Taskfile.yml` adds a watch loop (`task timsseek`) — rebuild + test + fmt + clippy on source change. +Per-crate: `rust/timsseek/Taskfile.yml` adds a watch loop (`task timsseek`) -- rebuild + test + fmt + clippy on source change. ## S3 staging diff --git a/example_speclib_config.toml b/example_speclib_config.toml index 717afdc6..4023b535 100644 --- a/example_speclib_config.toml +++ b/example_speclib_config.toml @@ -2,7 +2,7 @@ # # Reference configuration for speclib_build. # CLI flags override any value set here. -# All fields are optional — omit a section or key to use the compiled-in default. +# All fields are optional -- omit a section or key to use the compiled-in default. # ── Output ──────────────────────────────────────────────────────────────────── # Path for the output spectral library (msgpack + zstd). @@ -41,9 +41,9 @@ max = 4 # ── Decoys ──────────────────────────────────────────────────────────────────── [decoys] # Decoy generation strategy. -# none — no decoys (target-only library) -# reverse — reverse the amino-acid sequence -# edge_mutate — mutate N- and C-terminal residues +# none -- no decoys (target-only library) +# reverse -- reverse the amino-acid sequence +# edge_mutate -- mutate N- and C-terminal residues strategy = "none" # ── Prediction ──────────────────────────────────────────────────────────────── diff --git a/python/timsquery_pyo3/README.md b/python/timsquery_pyo3/README.md index 91e3be6d..4fa2f836 100644 --- a/python/timsquery_pyo3/README.md +++ b/python/timsquery_pyo3/README.md @@ -54,7 +54,7 @@ result = index.query_chromatogram(eg, tolerance) result.fragment_intensities # shape (n_fragments, n_cycles), dtype float32 result.precursor_intensities # shape (n_precursors, n_cycles), dtype float32 -result.fragment_labels # [(label, mz), ...] — row order matches the array +result.fragment_labels # [(label, mz), ...] -- row order matches the array result.precursor_labels # [(isotope_offset, mz), ...] result.rt_range_ms # (start_ms, end_ms) result.num_cycles # number of RT points @@ -79,7 +79,7 @@ Avoid repeated allocations by reusing a `ChromatogramResult` across queries. The internal `Vec` capacity grows to the largest elution group and stays there. ```python -result = index.query_chromatogram(eg1, tolerance) # first query — allocates +result = index.query_chromatogram(eg1, tolerance) # first query -- allocates index.query_chromatogram_into(result, eg2, tolerance) # reuses allocation index.query_chromatogram_into(result, eg3, tolerance) # same allocation @@ -89,11 +89,11 @@ index.query_chromatogram_into(result, eg3, tolerance) # same allocation For large-scale workloads, stream elution groups from any Python iterator. Internally uses chunked rayon parallelism and reuses collector allocations -across chunks — after the first chunk, allocations settle and only `memcpy` +across chunks -- after the first chunk, allocations settle and only `memcpy` into numpy remains. ```python -# Any iterable works — generator, list, map, etc. +# Any iterable works -- generator, list, map, etc. eg_iter = (make_eg(row) for row in dataframe.itertuples()) # Shared tolerance @@ -113,7 +113,7 @@ for arrays in index.query_chromatograms_iter(eg_iter, tol_iter, chunk_size=256): ``` `ChromatogramArrays` is a lightweight frozen object that owns its numpy arrays. -The iterator's internal collector pool is never exposed — it just keeps reusing +The iterator's internal collector pool is never exposed -- it just keeps reusing the same Rust-side buffers across chunks. ## Spectral queries @@ -122,8 +122,8 @@ the same Rust-side buffers across chunks. ```python result = index.query_spectrum(eg, tolerance) -result.precursor_intensities # list[float] — one total intensity per precursor -result.fragment_intensities # list[float] — one total intensity per fragment +result.precursor_intensities # list[float] -- one total intensity per precursor +result.fragment_intensities # list[float] -- one total intensity per fragment result.precursor_labels # list[(isotope_offset, mz)] result.fragment_labels # list[(label, mz)] result.id # int @@ -141,9 +141,9 @@ result.id # int ``` Each stats tuple contains: -- `weight` — total accumulated intensity -- `mean_mz` — intensity-weighted mean m/z (NaN if no peaks found) -- `mean_mobility` — intensity-weighted mean ion mobility in 1/K0 (NaN if no peaks found) +- `weight` -- total accumulated intensity +- `mean_mz` -- intensity-weighted mean m/z (NaN if no peaks found) +- `mean_mobility` -- intensity-weighted mean ion mobility in 1/K0 (NaN if no peaks found) ## Tolerance reference @@ -172,10 +172,10 @@ tol = tol.with_quad(tq.PyQuadTolerance.absolute(0.2, 0.2)) ## Lazy vs eager loading ```python -# Eager (default): loads entire index into memory — faster queries +# Eager (default): loads entire index into memory -- faster queries index = tq.PyTimsIndex("experiment.d") -# Lazy: loads from cached .idx on demand — faster startup, lower memory +# Lazy: loads from cached .idx on demand -- faster startup, lower memory index = tq.PyTimsIndex("experiment.d.idx", prefer_lazy=True) index.is_lazy # bool @@ -186,7 +186,7 @@ index.is_lazy # bool ```python index.num_cycles # total MS1 cycles in the acquisition index.rt_range_ms # (start_ms, end_ms) -index.rt_values_ms # list[int] — RT in ms for every cycle index +index.rt_values_ms # list[int] -- RT in ms for every cycle index # Convert between seconds and cycle indices idx = index.rt_seconds_to_cycle_index(300.0) # nearest cycle index @@ -211,18 +211,18 @@ rt_axis = np.array(index.rt_values_ms, dtype=np.float32) / 1000.0 # seconds ## Roadmap -- [x] **Aggregator reuse** — `query_chromatogram_into` reuses a `ChromatogramCollector` +- [x] **Aggregator reuse** -- `query_chromatogram_into` reuses a `ChromatogramCollector` allocation across queries, avoiding repeated allocation. -- [x] **SpectralCollector** — `query_spectrum` (summed f32) and `query_mz_mobility` +- [x] **SpectralCollector** -- `query_spectrum` (summed f32) and `query_mz_mobility` (intensity-weighted mean m/z + mobility) per ion. -- [ ] **PointIntensityAggregator** — single scalar total intensity per elution group. -- [ ] **IonAnnot key type** — support `IonAnnot` fragment labels alongside `usize`, +- [ ] **PointIntensityAggregator** -- single scalar total intensity per elution group. +- [ ] **IonAnnot key type** -- support `IonAnnot` fragment labels alongside `usize`, enabling richer annotation round-trips between Python and Rust. -- [ ] **Zero-copy array access** — return numpy views backed by Rust-owned memory +- [ ] **Zero-copy array access** -- return numpy views backed by Rust-owned memory instead of copying, for large-scale workloads. -- [x] **CycleToRTMapping exposure** — `rt_seconds_to_cycle_index`, `cycle_index_to_rt_ms`, +- [x] **CycleToRTMapping exposure** -- `rt_seconds_to_cycle_index`, `cycle_index_to_rt_ms`, `rt_values_ms`, `num_cycles`, `rt_range_ms` on `PyTimsIndex`. -- [ ] **Library file I/O** — read DIA-NN / Spectronaut libraries directly into +- [ ] **Library file I/O** -- read DIA-NN / Spectronaut libraries directly into lists of `PyElutionGroup`, removing boilerplate on the Python side. -- [x] **Streaming queries** — `query_chromatograms_iter` streams from any Python +- [x] **Streaming queries** -- `query_chromatograms_iter` streams from any Python iterator with chunked rayon parallelism and internal collector reuse. diff --git a/python/timsquery_pyo3/examples/streaming_example.py b/python/timsquery_pyo3/examples/streaming_example.py index 3dcf446d..9c8bc715 100644 --- a/python/timsquery_pyo3/examples/streaming_example.py +++ b/python/timsquery_pyo3/examples/streaming_example.py @@ -12,9 +12,9 @@ This example demonstrates the three query modes in timsquery_pyo3: - 1. Single query — one elution group at a time - 2. Aggregator reuse — reuse allocations across sequential queries - 3. Streaming iterator — iterator-in, iterator-out with chunked parallelism + 1. Single query -- one elution group at a time + 2. Aggregator reuse -- reuse allocations across sequential queries + 3. Streaming iterator -- iterator-in, iterator-out with chunked parallelism Usage: uv run examples/streaming_example.py @@ -106,7 +106,7 @@ def main(): print(f" loaded in {time.perf_counter() - t0:.2f}s ({index})") # ------------------------------------------------------------------ - # Set up tolerances — narrow search window + # Set up tolerances -- narrow search window # ------------------------------------------------------------------ tolerance = tq.PyTolerance( mz=tq.PyMzTolerance.ppm(10.0, 10.0), diff --git a/python/timsquery_pyo3/src/chromatogram.rs b/python/timsquery_pyo3/src/chromatogram.rs index 99464447..427b1f97 100644 --- a/python/timsquery_pyo3/src/chromatogram.rs +++ b/python/timsquery_pyo3/src/chromatogram.rs @@ -63,8 +63,8 @@ impl PyChromatogramResult { } #[getter] - fn id(&self) -> u64 { - self.collector.id + fn id<'py>(&self, py: Python<'py>) -> PyResult> { + crate::source_id_to_py(py, &self.collector.id) } fn __repr__(&self) -> String { diff --git a/python/timsquery_pyo3/src/elution_group.rs b/python/timsquery_pyo3/src/elution_group.rs index 2fe68c6a..c4424a43 100644 --- a/python/timsquery_pyo3/src/elution_group.rs +++ b/python/timsquery_pyo3/src/elution_group.rs @@ -5,7 +5,7 @@ use timsquery::tinyvec::tiny_vec; /// An elution group defines a query target: one precursor and its fragments. /// /// NOTE: Fragment labels are `usize` only in this binding. This is a deliberate -/// simplification — the Rust side is generic over `T: KeyLike` but we monomorphize +/// simplification -- the Rust side is generic over `T: KeyLike` but we monomorphize /// to `usize` here for a clean Python interface. Other key types (e.g. `IonAnnot`) /// may be added in future versions. #[pyclass(skip_from_py_object)] @@ -60,8 +60,8 @@ impl PyElutionGroup { } #[getter] - fn id(&self) -> u64 { - self.inner.id() + fn id<'py>(&self, py: Python<'py>) -> PyResult> { + crate::source_id_to_py(py, &self.inner.id().to_owned_id()) } #[getter] diff --git a/python/timsquery_pyo3/src/iterator.rs b/python/timsquery_pyo3/src/iterator.rs index 44c09c36..59b362ed 100644 --- a/python/timsquery_pyo3/src/iterator.rs +++ b/python/timsquery_pyo3/src/iterator.rs @@ -23,11 +23,10 @@ pub enum ToleranceSource { /// Lightweight result yielded by the streaming iterator. /// /// Owns materialized numpy arrays and metadata. The iterator's internal -/// collector pool is never exposed — it reuses Rust-side buffers across chunks. +/// collector pool is never exposed -- it reuses Rust-side buffers across chunks. #[pyclass(frozen)] pub struct PyChromatogramArrays { - #[pyo3(get)] - id: u64, + id: timsquery::models::OwnedSourceId, precursor_intensities: Py, fragment_intensities: Py, #[pyo3(get)] @@ -42,6 +41,11 @@ pub struct PyChromatogramArrays { #[pymethods] impl PyChromatogramArrays { + #[getter] + fn id<'py>(&self, py: Python<'py>) -> PyResult> { + crate::source_id_to_py(py, &self.id) + } + #[getter] fn precursor_intensities<'py>(&self, py: Python<'py>) -> Bound<'py, PyAny> { self.precursor_intensities.clone_ref(py).into_bound(py) @@ -72,7 +76,7 @@ fn extract_arrays( let rt = collector.rt_range_milis(); Ok(PyChromatogramArrays { - id: collector.id, + id: collector.id.clone(), precursor_intensities: prec_np.into_any().unbind(), fragment_intensities: frag_np.into_any().unbind(), precursor_labels: collector diff --git a/python/timsquery_pyo3/src/lib.rs b/python/timsquery_pyo3/src/lib.rs index a74908a2..c12151b7 100644 --- a/python/timsquery_pyo3/src/lib.rs +++ b/python/timsquery_pyo3/src/lib.rs @@ -21,6 +21,21 @@ mod spectrum; mod tolerance; use pyo3::prelude::*; +use timsquery::models::OwnedSourceId; + +/// A source id keeps the shape the library gave it, so Python sees an `int` +/// for a numeric id and a `str` for a text one (DIA-NN's +/// `transition_group_id`) rather than one coerced into the other. +pub(crate) fn source_id_to_py<'py>( + py: Python<'py>, + id: &OwnedSourceId, +) -> PyResult> { + use pyo3::IntoPyObject; + match id { + OwnedSourceId::Numeric(n) => Ok(n.into_pyobject(py)?.into_any()), + OwnedSourceId::Text(s) => Ok(s.into_pyobject(py)?.into_any()), + } +} #[pymodule] fn timsquery_pyo3(m: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/python/timsquery_pyo3/src/spectrum.rs b/python/timsquery_pyo3/src/spectrum.rs index 1661ecce..0927c66d 100644 --- a/python/timsquery_pyo3/src/spectrum.rs +++ b/python/timsquery_pyo3/src/spectrum.rs @@ -4,7 +4,7 @@ use timsquery::{ SpectralCollector, }; -/// Result of a spectral query — total summed intensity per ion. +/// Result of a spectral query -- total summed intensity per ion. /// /// Each precursor/fragment gets a single f32 intensity value (summed /// across all matching peaks within the tolerance window). @@ -61,8 +61,8 @@ impl PySpectralResult { /// The elution group id. #[getter] - fn id(&self) -> u64 { - self.collector.id + fn id<'py>(&self, py: Python<'py>) -> PyResult> { + crate::source_id_to_py(py, &self.collector.id) } fn __repr__(&self) -> String { @@ -153,8 +153,8 @@ impl PyMzMobilityResult { /// The elution group id. #[getter] - fn id(&self) -> u64 { - self.collector.id + fn id<'py>(&self, py: Python<'py>) -> PyResult> { + crate::source_id_to_py(py, &self.collector.id) } fn __repr__(&self) -> String { diff --git a/rust/alloc_track/src/lib.rs b/rust/alloc_track/src/lib.rs index 495d5096..9ab6d194 100644 --- a/rust/alloc_track/src/lib.rs +++ b/rust/alloc_track/src/lib.rs @@ -91,7 +91,7 @@ mod imp { static COUNT: AtomicU64 = AtomicU64::new(0); static LIVE: AtomicU64 = AtomicU64::new(0); static PEAK: AtomicU64 = AtomicU64::new(0); - /// Number of `on_dealloc` calls whose size exceeded observed `LIVE` — + /// Number of `on_dealloc` calls whose size exceeded observed `LIVE` -- /// i.e. accounting drift (layout mismatch, custom allocator bug, etc.). /// Reported by [`report_snapshot_diff`]; nonzero means numbers are suspect. static DEALLOC_UNDERFLOWS: AtomicU64 = AtomicU64::new(0); @@ -124,7 +124,7 @@ mod imp { pub(crate) fn on_dealloc(size: usize) { let s = size as u64; // Atomic saturating sub. Drift (curr < s) means the inner allocator - // reported mismatched layouts — accounting is already wrong, so we + // reported mismatched layouts -- accounting is already wrong, so we // clamp LIVE to 0, bump DEALLOC_UNDERFLOWS, and let report_snapshot_diff // surface it. Cannot panic/log from the hook: both may allocate and // recurse into this function. The debug_assert catches drift in tests; @@ -189,7 +189,7 @@ mod imp { let drift = DEALLOC_UNDERFLOWS.load(Ordering::Relaxed); if drift > 0 { eprintln!( - "[alloc] WARNING: {drift} dealloc underflow(s) observed — live/peak numbers above are suspect." + "[alloc] WARNING: {drift} dealloc underflow(s) observed -- live/peak numbers above are suspect." ); } } diff --git a/rust/apex_sim/src/bench.rs b/rust/apex_sim/src/bench.rs index cf71ed55..71302af8 100644 --- a/rust/apex_sim/src/bench.rs +++ b/rust/apex_sim/src/bench.rs @@ -423,7 +423,7 @@ pub struct ScorePopulations { /// Score `n_seed_pairs` matched present/absent realizations (varying only the /// seed). Each pair shares a seed, so the "absent" twin has IDENTICAL noise + -/// interferents — only the real peak differs. +/// interferents -- only the real peak differs. pub fn score_populations(base: &SimParams, n_seed_pairs: usize) -> ScorePopulations { let map = base.rt_mapper(); let mut scorer = TraceScorer::new(base.n_cycles, base.real_fragments.len().max(1)); diff --git a/rust/apex_sim/src/plots.rs b/rust/apex_sim/src/plots.rs index 60f6359f..79015208 100644 --- a/rust/apex_sim/src/plots.rs +++ b/rust/apex_sim/src/plots.rs @@ -357,7 +357,7 @@ fn log10_positive(vals: &[f32]) -> Vec { /// Fraction-per-bin bars over `[lo, hi]`: heights sum to 1, so populations of /// different size stay comparable. Returns the bars and the tallest height. /// -/// `hi` must exceed `lo` — an empty range would give zero-width bins, so +/// `hi` must exceed `lo` -- an empty range would give zero-width bins, so /// callers widen degenerate (all-equal) populations first. fn hist_bars(logs: &[f64], lo: f64, hi: f64) -> (Vec, f64) { debug_assert!(hi > lo, "empty bin range [{lo}, {hi}]"); diff --git a/rust/apex_sim/src/scorer.rs b/rust/apex_sim/src/scorer.rs index 45b36979..4aa6e206 100644 --- a/rust/apex_sim/src/scorer.rs +++ b/rust/apex_sim/src/scorer.rs @@ -98,7 +98,7 @@ pub fn run( pass2, } = run_with(&mut scorer, extraction, rt_mapper)?; // Traces and the window-global apex evidence are already computed, so each - // extra `score_at` is just the cycle-local feature block — the sweep is + // extra `score_at` is just the cycle-local feature block -- the sweep is // O(cycles * window), not O(cycles^2). Cycles that fail to score give 0. let landscape = (0..n_cycles) .map(|c| { @@ -157,7 +157,7 @@ mod tests { let res = run(&data.extraction, &p.rt_mapper()).unwrap(); assert_eq!(res.landscape.len(), p.n_cycles); - // On a clean peak the reported main score IS the landscape maximum — + // On a clean peak the reported main score IS the landscape maximum -- // i.e. the scored apex is the best cycle in the window, not just a // cycle the landscape happens to agree with. let best = res diff --git a/rust/apex_sim/src/sim.rs b/rust/apex_sim/src/sim.rs index d1d1428e..4f287bd7 100644 --- a/rust/apex_sim/src/sim.rs +++ b/rust/apex_sim/src/sim.rs @@ -17,6 +17,7 @@ use rand::{ }; use rand_chacha::ChaCha8Rng; +use timsquery::models::OwnedSourceId; use timsquery::{ ChromatogramCollector, MzMajorIntensityArray, @@ -69,7 +70,7 @@ pub struct RandomPeaks { pub hit_precursors: bool, /// Interferents per cycle; when > 0 this overrides `count` as /// `round(density_per_cycle * n_cycles * hardness)`, so interferent DENSITY - /// (not absolute count) stays fixed as the window widens — a fixed count in + /// (not absolute count) stays fixed as the window widens -- a fixed count in /// a wide window is an artificially easy task. pub density_per_cycle: f32, /// Multiplier (>=1) to deliberately over-load interferents above realistic @@ -340,7 +341,7 @@ pub fn build(params: &SimParams) -> SimData { TupleRange::try_new(map(0), map(n - 1)).expect("start < end for positive period"); let chromatograms = ChromatogramCollector:: { - id: 0, + id: OwnedSourceId::placeholder(), mobility_ook0: 1.0, rt_seconds: (map((realized_apex as usize).min(n - 1)) as f32) / 1000.0, precursor_mono_mz: dummy_mz, @@ -671,8 +672,8 @@ mod tests { let (half, full) = (peak(0), peak(1)); // Both rows co-elute at the same realized (off-grid) apex with the same - // width, so every shape term — peak height scale, the gaussian, the - // sub-cycle sampling loss — is common to both and cancels in the ratio. + // width, so every shape term -- peak height scale, the gaussian, the + // sub-cycle sampling loss -- is common to both and cancels in the ratio. // What survives is exactly the obs_scale ratio, computed WITHOUT // reproducing any of the generator's own arithmetic. assert!(full > 0.0, "reference fragment must carry signal: {full}"); diff --git a/rust/array2d/src/lib.rs b/rust/array2d/src/lib.rs index 029343ab..f1b5f8b3 100644 --- a/rust/array2d/src/lib.rs +++ b/rust/array2d/src/lib.rs @@ -74,7 +74,7 @@ impl Default for Array2D { impl Array2D { /// Construct a zero-dimensional `Array2D` whose backing `Vec` has enough /// capacity for `ncols * nrows` elements. Use this for scratch buffers - /// that will be populated via [`Array2D::reset_with_value`] — the first + /// that will be populated via [`Array2D::reset_with_value`] -- the first /// call won't reallocate as long as its size fits the reserved capacity. pub fn with_capacity(ncols: usize, nrows: usize) -> Self { Self { diff --git a/rust/calib_dash/src/app.rs b/rust/calib_dash/src/app.rs index b76e517d..b32fea85 100644 --- a/rust/calib_dash/src/app.rs +++ b/rust/calib_dash/src/app.rs @@ -56,7 +56,7 @@ impl Tab { /// cycled by `m`/`M`. Exactly one is active at a time. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum Layer { - /// Bare heatmap — the raw density the fit works from. + /// Bare heatmap -- the raw density the fit works from. None, /// The DP chain and the greedily attached tails, glyph-distinguished within the /// layer (`O`/`X`): is a bad edge the DP's choice or a tail grafted on after? @@ -106,7 +106,7 @@ fn cycle( } /// Whether the batch loop driving Phase 1 scoring should keep going after a pause. -/// Only `Ctrl-C` ever produces `Abort` — a dashboard failure anywhere else is +/// Only `Ctrl-C` ever produces `Abort` -- a dashboard failure anywhere else is /// logged and treated as `Continue`, since a dev tool must never fail a search. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Flow { @@ -117,12 +117,12 @@ pub enum Flow { /// What the user asked for on leaving a pause. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum PauseAction { - /// Stay in the pause — the key changed the view, not the flow. + /// Stay in the pause -- the key changed the view, not the flow. Stay, /// Advance this many batches before pausing again. Next(u32), /// Stop showing the dashboard and let the pipeline run on. Both `r` (run to - /// end) and `q` (detach) land here — they differ only in what the user meant, + /// end) and `q` (detach) land here -- they differ only in what the user meant, /// not in what happens. Detach, Abort, @@ -156,7 +156,7 @@ impl Stepper { true } - /// Whether a prior pause resolved to `Detach` or `Abort` — the user has already + /// Whether a prior pause resolved to `Detach` or `Abort` -- the user has already /// asked to stop seeing the dashboard, and nothing should reopen it. fn is_stopped(&self) -> bool { self.stopped @@ -199,11 +199,11 @@ pub(crate) struct App { /// Bounded by `frames.retained`; the recording lives in `scrub_recording`. scrub_frame: Option, /// The original batch/chunk number of `scrub_frame`, for the Fit tab's "not live" - /// banner — "frame 2 of 5" alone means little to a user who thinks in batches. + /// banner -- "frame 2 of 5" alone means little to a user who thinks in batches. scrub_chunk: Option, /// The scrubbed frame's refit recording. `None` whenever `scrub_frame` is, and /// also momentarily after `scrub_frame` moves and before the next `sync_scrub` - /// catches up — `active_recording` falls back to the live view for that gap. + /// catches up -- `active_recording` falls back to the live view for that gap. scrub_recording: Option, /// Whether the `?` key-map overlay is open. Modal: while it shows, every keypress /// (digits and `Ctrl-C` included) only dismisses it. @@ -286,7 +286,7 @@ impl App { /// What the Fit tab should actually draw: the scrubbed frame's recording while /// `scrub_frame` is set and `sync_scrub` has caught up to it, the live `recording` - /// otherwise — never a blank tab for the gap between the two. + /// otherwise -- never a blank tab for the gap between the two. pub(crate) fn active_recording(&self) -> &FitRecording { if self.scrub_frame.is_some() && let Some(rec) = self.scrub_recording.as_ref() @@ -317,7 +317,7 @@ impl App { self.scrub_recording = Some(recording); } - /// Drops back to the live view — the user scrubbed past the last retained frame, + /// Drops back to the live view -- the user scrubbed past the last retained frame, /// or a frame could not be refit and live beats a stale or absent recording. pub(crate) fn clear_scrub(&mut self) { self.scrub_frame = None; @@ -339,7 +339,7 @@ impl App { } /// Folds one more digit into the pending count. A leading `0` does not start a - /// count — matching vim, which keeps `0` free as a motion — but extends one. + /// count -- matching vim, which keeps `0` free as a motion -- but extends one. fn push_digit(&mut self, d: u32) { if d == 0 && self.count.is_none() { return; @@ -359,7 +359,7 @@ impl App { } /// Moves the cursor forward by `n`. Past the last retained frame this returns to - /// the live view rather than clamping — "keep pressing `>` and you get to now". + /// the live view rather than clamping -- "keep pressing `>` and you get to now". fn scrub_forward(&mut self, n: u32) { let Some(i) = self.scrub_frame else { return; @@ -503,7 +503,7 @@ fn as_calibrt_tuple(p: &CalibrantPoint) -> (LibraryRT, ObservedRTSeconds, n_calibrants: usize, - /// The previous batch's points, for `churn`. Empty before the first batch — + /// The previous batch's points, for `churn`. Empty before the first batch -- /// `churn`'s own "everything admitted, nothing evicted" case. prev_points: Vec, /// The previous batch's fitted curve, for `curve_delta`. `None` before the first @@ -583,7 +583,7 @@ impl CalibDash { } /// In order: record the frame, re-fit the live curve from those same points, push - /// this batch's metrics (every batch, rendered or not — see `metrics.rs`), then + /// this batch's metrics (every batch, rendered or not -- see `metrics.rs`), then /// either continue or pause to render. /// /// `Flow::Abort` happens only because the user pressed Ctrl-C at a pause: a @@ -628,7 +628,7 @@ impl CalibDash { } /// Promotes the reserved final frame and refreshes the Convergence header's - /// decimation numbers one last time — the promotion can change them. + /// decimation numbers one last time -- the promotion can change them. pub fn finish(&mut self) { self.frames.finish(); self.sync_frame_summary(); @@ -641,7 +641,7 @@ impl CalibDash { /// Re-fits a recorded frame's points from scratch, into `refit_state`/ /// `refit_recording` rather than the live pair, through the same `fit_points` - /// sequence `refit_live` runs — so this reproduces the batch that actually ran. + /// sequence `refit_live` runs -- so this reproduces the batch that actually ran. /// `None` if the frame index doesn't exist or the re-fit was skipped. fn refit_frame(&mut self, i: usize) -> Option<(usize, &FitRecording)> { let (chunk, pts) = self.frames.frame(i)?; @@ -832,12 +832,12 @@ mod tests { .map(|i| CalibrantPoint { library_rt: i as f64 + 0.5, observed_rt: (i as f64 + 0.5) * slope, - library_id: (chunk * n + i) as u64, + identity: (chunk * n + i) as u64, }) .collect() } - /// Points at explicit `library_id`es, so a test can choose the two sets `churn` + /// Points at explicit `identity`s, so a test can choose the two sets `churn` /// diffs. On the identity line, so the fit succeeds. fn indexed_points(indices: &[usize]) -> Vec { indices @@ -846,12 +846,12 @@ mod tests { .map(|(i, &idx)| CalibrantPoint { library_rt: i as f64 + 0.5, observed_rt: i as f64 + 0.5, - library_id: idx as u64, + identity: idx as u64, }) .collect() } - /// `n` retained frames, nothing decimated — what the scrubber tests need. + /// `n` retained frames, nothing decimated -- what the scrubber tests need. fn retained(n: usize) -> FrameSummary { FrameSummary { retained: n, @@ -865,7 +865,7 @@ mod tests { rec.curve().map_or_else(Vec::new, |c| c.points().to_vec()) } - /// Pointwise curve equality — behind every "what you scrub is what ran" test. + /// Pointwise curve equality -- behind every "what you scrub is what ran" test. fn assert_same_curve(live: &[Point], refit: &[Point]) { assert_eq!(live.len(), refit.len(), "curve lengths differ"); for (a, b) in live.iter().zip(refit) { @@ -1158,7 +1158,7 @@ mod tests { /// /// The backward keys make the reduction load-bearing rather than merely fast: /// `cycle` walks back by `n - steps`, which underflows for any count past the stop - /// list's length — and a count past 5 is an ordinary keystroke. + /// list's length -- and a count past 5 is an ordinary keystroke. #[test] fn a_huge_count_before_a_cycling_key_does_not_spin_or_underflow() { let mut app = App::new(10); @@ -1273,7 +1273,7 @@ mod tests { } /// An overlong batch (more points than `n_calibrants`) must not let the live re-fit - /// see points the frame slab never recorded — `FrameStore` truncates its own copy, + /// see points the frame slab never recorded -- `FrameStore` truncates its own copy, /// and `on_batch` must clamp identically. #[test] fn an_overlong_batch_is_clamped_the_same_way_the_frame_slab_clamps_it() { diff --git a/rust/calib_dash/src/bin/calib_dash.rs b/rust/calib_dash/src/bin/calib_dash.rs index 4bf7ebf7..0fedce0a 100644 --- a/rust/calib_dash/src/bin/calib_dash.rs +++ b/rust/calib_dash/src/bin/calib_dash.rs @@ -1,9 +1,9 @@ -//! Standalone replay of a saved `calibration.json` — the RT calibration +//! Standalone replay of a saved `calibration.json` -- the RT calibration //! dashboard without a live Phase 1 search behind it. //! //! The loaded points become a single Phase-1-shaped batch (chunk 0, one frame), //! so the batch scrubber shows one frame and the Convergence tab's history is a -//! single point — a Phase 2 snapshot is one fit, not a run's sequence of batches. +//! single point -- a Phase 2 snapshot is one fit, not a run's sequence of batches. use calib_dash::{ CalibDash, @@ -43,7 +43,7 @@ fn main() { .map(|(i, p)| CalibrantPoint { library_rt: p[0], observed_rt: p[1], - library_id: i as u64, + identity: i as u64, }) .collect(); let n_calibrants = points.len(); @@ -66,8 +66,8 @@ fn main() { dash.finish(); } -/// Reads `path` and keeps only the snapshot. The residual block is left opaque — -/// nothing here reads it — and the provenance warning is dropped: there is no raw +/// Reads `path` and keeps only the snapshot. The residual block is left opaque -- +/// nothing here reads it -- and the provenance warning is dropped: there is no raw /// file to check the calibration against. fn load_snapshot(path: &Path) -> Result { let (saved, _) = calibrt::SavedCalibration::::read(path, None)?; @@ -80,11 +80,11 @@ fn load_snapshot(path: &Path) -> Result { /// needs at least 2 points to define a curve. fn validate_snapshot(snapshot: &CalibrationSnapshot) -> Result<(), String> { if snapshot.grid_size == 0 { - return Err("grid_size is 0 — a calibration grid needs at least 1 bin".to_string()); + return Err("grid_size is 0 -- a calibration grid needs at least 1 bin".to_string()); } if snapshot.points.len() < 2 { return Err(format!( - "only {} calibrant point(s) in \"calibration.points\" — at least 2 are needed to \ + "only {} calibrant point(s) in \"calibration.points\" -- at least 2 are needed to \ fit a curve", snapshot.points.len() )); @@ -104,7 +104,7 @@ mod tests { #[test] fn a_file_of_a_foreign_version_is_refused() { // The handle is held (not just its path) because dropping it is what - // deletes the file — including when the test fails. + // deletes the file -- including when the test fails. let mut f = NamedTempFile::new().expect("a writable temp dir"); f.write_all( br#"{ diff --git a/rust/calib_dash/src/frames.rs b/rust/calib_dash/src/frames.rs index 3d352c35..6c08051d 100644 --- a/rust/calib_dash/src/frames.rs +++ b/rust/calib_dash/src/frames.rs @@ -20,15 +20,16 @@ pub const DEFAULT_RUN_BUDGET_BYTES: usize = 64 * 1024 * 1024; /// has to clear one frame's worth of points. pub const REPLAY_BUDGET_BYTES: usize = 1 << 20; -/// One heap entry, flattened. `library_id` is carried because churn diffing -/// needs a stable identity for a calibrant: RT coordinates are not unique and -/// cannot distinguish "same peptide, re-scored" from "different peptide, same -/// RT". +/// One heap entry, flattened. +/// +/// `identity` tells "same calibrant, re-scored" from "different calibrant, same +/// RT" during churn diffing; RT alone is not unique. Compared, never displayed, +/// and only within one process -- the hash is not stable across runs. #[derive(Debug, Clone, Copy, PartialEq)] pub struct CalibrantPoint { pub library_rt: f64, pub observed_rt: f64, - pub library_id: u64, + pub identity: u64, } struct FrameIndex { @@ -73,7 +74,7 @@ impl FrameStore { CalibrantPoint { library_rt: 0.0, observed_rt: 0.0, - library_id: 0 + identity: 0 }; (stride_capacity + 1) * n_calibrants ]; @@ -148,7 +149,7 @@ mod tests { CalibrantPoint { library_rt: i as f64, observed_rt: i as f64 * 2.0, - library_id: i as u64, + identity: i as u64, } } @@ -156,7 +157,7 @@ mod tests { /// pass for another's. fn pt_in(chunk: usize, i: usize) -> CalibrantPoint { CalibrantPoint { - library_id: (chunk * 100 + i) as u64, + identity: (chunk * 100 + i) as u64, ..pt(i) } } @@ -184,7 +185,7 @@ mod tests { } } - /// Retained frames exceed the *stride* budget by exactly one — the reserved + /// Retained frames exceed the *stride* budget by exactly one -- the reserved /// final span, which is what makes the last chunk always replayable however /// the stride falls. Points are asserted per frame, and made /// chunk-distinguishable to do it: with the same `pt(i)` in every frame the diff --git a/rust/calib_dash/src/metrics.rs b/rust/calib_dash/src/metrics.rs index 69eceb22..d7eb224a 100644 --- a/rust/calib_dash/src/metrics.rs +++ b/rust/calib_dash/src/metrics.rs @@ -27,7 +27,7 @@ pub struct BatchMetrics { /// spaced points in `x_range`. /// /// Samples where either curve is out of bounds are skipped rather than counted -/// as zero — counting them would dilute the mean toward zero exactly when the +/// as zero -- counting them would dilute the mean toward zero exactly when the /// curves disagree most about their domain, which is the opposite of the signal /// wanted here. pub fn curve_delta( @@ -57,10 +57,10 @@ pub fn curve_delta( } } -/// `(admitted, evicted)` between two heap snapshots, by `library_id`. +/// `(admitted, evicted)` between two heap snapshots, by `identity`. pub fn churn(prev: &[CalibrantPoint], cur: &[CalibrantPoint]) -> (usize, usize) { - let prev_set: HashSet = prev.iter().map(|p| p.library_id).collect(); - let cur_set: HashSet = cur.iter().map(|p| p.library_id).collect(); + let prev_set: HashSet = prev.iter().map(|p| p.identity).collect(); + let cur_set: HashSet = cur.iter().map(|p| p.identity).collect(); let admitted = cur_set.difference(&prev_set).count(); let evicted = prev_set.difference(&cur_set).count(); (admitted, evicted) @@ -78,7 +78,7 @@ mod tests { CalibrantPoint { library_rt: 1.0, observed_rt: 1.0, - library_id: idx as u64, + identity: idx as u64, } } @@ -124,7 +124,7 @@ mod tests { } /// Both signs of the same offset. The delta is a *magnitude*, so a curve - /// that moved down by 2 must report the same 2 as one that moved up by 2 — + /// that moved down by 2 must report the same 2 as one that moved up by 2 -- /// without the `.abs()`, the downward direction reports `max = 0` (the /// running max never rises above its 0.0 seed) and a negative mean. #[test] @@ -159,7 +159,7 @@ mod tests { /// 0.0 (which would read as "the curves agree"). #[test] fn samples_outside_both_curves_are_skipped() { - // (b's x range, x range sampled, expected delta — None for NaN) + // (b's x range, x range sampled, expected delta -- None for NaN) let cases = [ ((0.0, 5.0), (0.0, 10.0), Some(3.0)), ((20.0, 25.0), (0.0, 5.0), None), diff --git a/rust/calib_dash/src/recording.rs b/rust/calib_dash/src/recording.rs index b6464a0b..ba0a3ce1 100644 --- a/rust/calib_dash/src/recording.rs +++ b/rust/calib_dash/src/recording.rs @@ -1,7 +1,7 @@ //! An owned copy of everything a finished [`calibrt::CalibrationState`] holds //! that a panel draws: the grid it fit on, and the fit's products. //! -//! `weights` are `f32` for storage and display only — every metric is computed +//! `weights` are `f32` for storage and display only -- every metric is computed //! from the `f64` values on the state, never from this downcast copy. use calibrt::{ @@ -17,7 +17,7 @@ pub struct FitRecording { weights: Vec, suppressed: Vec, /// See [`calibrt::CalibrationState::path_indices`]. The `bins` capacity is a - /// hint, not a bound — weight ties can push the survivor count past it. + /// hint, not a bound -- weight ties can push the survivor count past it. path_indices: Vec, /// See [`calibrt::CalibrationState::dp_range`]. dp_range: std::ops::Range, @@ -37,7 +37,7 @@ impl FitRecording { }, weights: vec![0.0; bins * bins], suppressed: vec![false; bins * bins], - // Capacity hint only — see the field doc comment. + // Capacity hint only -- see the field doc comment. path_indices: Vec::with_capacity(bins), dp_range: 0..0, curve: None, @@ -131,7 +131,7 @@ mod tests { // `col_of`/`row_of` would pass unnoticed without this one. Weight 0.5 // is too light to be a row/column max next to the diagonal's weight-8 // entry at row 7 and weight-3 entry at col 2, so it is suppressed and - // does not disturb the path/curve length assertions below — it only + // does not disturb the path/curve length assertions below -- it only // needs to show up in the raw weight grid. s.update(std::iter::once(( LibraryRT(2.5), @@ -159,7 +159,7 @@ mod tests { assert_eq!(rec.curve().unwrap().points().len(), 10); } - /// Nothing has been fit, so nothing is outstanding — Phase 1 draws an empty + /// Nothing has been fit, so nothing is outstanding -- Phase 1 draws an empty /// Fit tab before the first fit runs. #[test] fn a_recording_with_no_fit_reads_as_empty() { @@ -201,7 +201,7 @@ mod tests { s.fit(); assert!( !FitRecording::from_state(&s).is_suppressed(1, 1), - "A survives alone in its row/col on the second fit — a stale bit \ + "A survives alone in its row/col on the second fit -- a stale bit \ from the first fit must not linger" ); } @@ -255,7 +255,7 @@ mod tests { rec.ridge().len() ), (10, 10, 10), - "the refit's own entries only — 13 of each would mean the first \ + "the refit's own entries only -- 13 of each would mean the first \ fit's leaked through" ); } diff --git a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Curve.snap b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Curve.snap index edae2186..3b96dd1e 100644 --- a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Curve.snap +++ b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Curve.snap @@ -2,8 +2,8 @@ source: rust/calib_dash/src/ui.rs expression: out --- - ┌ Fit — observed RT (s) ↑ vs library RT (s) → ───────────────────────────────────────────── b0 ┐ - │ Showing: curve — fitted calibration │ + ┌ Fit -- observed RT (s) ↑ vs library RT (s) → ──────────────────────────────────────────── b0 ┐ + │ Showing: curve -- fitted calibration │ │ │ │ ▄▄▄▄▄▄│ │ ░░░░░░│ diff --git a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_None.snap b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_None.snap index e22b382c..38162e04 100644 --- a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_None.snap +++ b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_None.snap @@ -2,8 +2,8 @@ source: rust/calib_dash/src/ui.rs expression: out --- - ┌ Fit — observed RT (s) ↑ vs library RT (s) → ───────────────────────────────────────────── b0 ┐ - │ Showing: none — density only │ + ┌ Fit -- observed RT (s) ↑ vs library RT (s) → ──────────────────────────────────────────── b0 ┐ + │ Showing: none -- density only │ │ │ │ ▄▄▄▄▄▄│ │ ░░░░░░│ diff --git a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Path.snap b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Path.snap index 80ec39cc..045ae801 100644 --- a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Path.snap +++ b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_layer_Path.snap @@ -2,8 +2,8 @@ source: rust/calib_dash/src/ui.rs expression: out --- - ┌ Fit — observed RT (s) ↑ vs library RT (s) → ───────────────────────────────────────────── b0 ┐ - │ Showing: path — O chosen, X greedy tail │ + ┌ Fit -- observed RT (s) ↑ vs library RT (s) → ──────────────────────────────────────────── b0 ┐ + │ Showing: path -- O chosen, X greedy tail │ │ │ │ ▄▄▄▄▄▄│ │ X░░░░░│ diff --git a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_tab_shows_a_banner_and_a_different_grid_when_scrubbing.snap b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_tab_shows_a_banner_and_a_different_grid_when_scrubbing.snap index 2b766476..205bf24e 100644 --- a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_tab_shows_a_banner_and_a_different_grid_when_scrubbing.snap +++ b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__fit_tab_shows_a_banner_and_a_different_grid_when_scrubbing.snap @@ -2,9 +2,9 @@ source: rust/calib_dash/src/ui.rs expression: "render_snapshot(&mut app, 100, 30)" --- - SCRUBBED — retained frame 3/5, batch 17 (not live; `>` returns to now) - ┌ Fit — observed RT (s) ↑ vs library RT (s) → ───────────────────────────────────────────── b0 ┐ - │ Showing: none — density only │ + SCRUBBED -- retained frame 3/5, batch 17 (not live; `>` returns to now) + ┌ Fit -- observed RT (s) ↑ vs library RT (s) → ──────────────────────────────────────────── b0 ┐ + │ Showing: none -- density only │ │ ████████████│ 15┤ ████████████│ │ ▄▄▄▄▄▄▄▄▄▄▄▄████████████│ diff --git a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__the_keys_overlay_lists_every_binding_over_the_tab_beneath_it.snap b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__the_keys_overlay_lists_every_binding_over_the_tab_beneath_it.snap index 8ef621e9..48742401 100644 --- a/rust/calib_dash/src/snapshots/calib_dash__ui__tests__the_keys_overlay_lists_every_binding_over_the_tab_beneath_it.snap +++ b/rust/calib_dash/src/snapshots/calib_dash__ui__tests__the_keys_overlay_lists_every_binding_over_the_tab_beneath_it.snap @@ -3,8 +3,8 @@ source: rust/calib_dash/src/ui.rs expression: "render(&mut app, 100, 30)" --- Fit │ Convergence │ Tolerances - ┌ Fit — observed RT (s) ↑ vs library RT (s) → ───────────────────────────────────────────── b0 ┐ - │ Showing: none — density only │ + ┌ Fit -- observed RT (s) ↑ vs library RT (s) → ──────────────────────────────────────────── b0 ┐ + │ Showing: none -- density only │ │ │ │ ▄▄▄▄▄▄│ │ ░░░░░░│ diff --git a/rust/calib_dash/src/ui.rs b/rust/calib_dash/src/ui.rs index eeff25e4..fc1abc14 100644 --- a/rust/calib_dash/src/ui.rs +++ b/rust/calib_dash/src/ui.rs @@ -11,8 +11,8 @@ //! `Modifier::REVERSED` says *what mode the screen is in*: the selected tab, a //! scrubbed (not live) frame, an accumulating count, a marked heatmap cell. //! Color says *what a thing is*: the density ramp and the mark kinds. Apart, -//! neither channel has to mean three things at once, and inversion — a swap of -//! whatever colors are already there — survives a colorblind reader, a +//! neither channel has to mean three things at once, and inversion -- a swap of +//! whatever colors are already there -- survives a colorblind reader, a //! monochrome screenshot and any terminal theme. `Mark` is the only place that //! adds to this. //! @@ -21,7 +21,7 @@ //! `paint_heatmap` runs exactly one `mark_*` arm per draw (`crate::Layer`, cycled //! by `m`/`M`), and each layer is internally overlap-free: the DP chain and greedy //! tails partition the path by construction, and the other three layers only emit -//! `Mark::Region`. So no cross-layer priority exists to resolve below — `Mark`'s +//! `Mark::Region`. So no cross-layer priority exists to resolve below -- `Mark`'s //! `Ord` only arbitrates a DP node against a tail node rounded into one cell. //! //! Nothing to draw, or no room to draw it in, renders a `Paragraph` saying so @@ -151,7 +151,7 @@ const GLOBAL_KEYS: &[Binding] = &[ const TAB_KEYS_AT: usize = 2; /// `?` itself, listed apart from `GLOBAL_KEYS` because the status line pins it -/// to its own right-hand column — see `fit_status_hints`. +/// to its own right-hand column -- see `fit_status_hints`. const KEYS_OVERLAY_KEY: Binding = Binding::new("?", "keys", "this screen"); const FIT_KEYS: &[Binding] = &[ @@ -164,7 +164,7 @@ fn tab_keys(tab: Tab) -> &'static [Binding] { if tab == Tab::Fit { FIT_KEYS } else { &[] } } -/// One `key`/`action` hint per binding: the key `BOLD`, the action `DarkGray` — +/// One `key`/`action` hint per binding: the key `BOLD`, the action `DarkGray` -- /// weight is what makes the bound letter scannable. `actions` false renders the /// keys alone, which is how the degrade below sheds action words but not keys. fn binding_spans(bindings: &[Binding], actions: bool) -> Vec> { @@ -194,7 +194,7 @@ fn binding_spans(bindings: &[Binding], actions: bool) -> Vec> { /// The most detailed hint line (tab-local plus global bindings, ordered as /// `TAB_KEYS_AT` describes) that still fits `width`, degrading in stages: full /// text, keys only, global keys only, nothing. `? keys` is never part of this -/// line — it has its own pinned-right column, so it survives every stage. +/// line -- it has its own pinned-right column, so it survives every stage. fn fit_status_hints(tab_local: &[Binding], width: usize) -> Line<'static> { let (head, tail) = GLOBAL_KEYS.split_at(TAB_KEYS_AT); let full: Vec = head.iter().chain(tab_local).chain(tail).copied().collect(); @@ -212,7 +212,7 @@ fn fit_status_hints(tab_local: &[Binding], width: usize) -> Line<'static> { } /// The status line: batch/pending-count on the left, key hints in the middle -/// (degrading as `fit_status_hints` describes), `? keys` pinned to the right — +/// (degrading as `fit_status_hints` describes), `? keys` pinned to the right -- /// one `Paragraph` per column, so the right one stays right-aligned. fn draw_status_line(frame: &mut Frame, area: Rect, app: &App) { let mut state_spans = vec![Span::raw(format!(" b{} ", app.batch()))]; @@ -315,7 +315,7 @@ fn draw_scrub_banner(frame: &mut Frame, area: Rect, app: &App, frame_index: usiz .map(|c| format!(", batch {c}")) .unwrap_or_default(); let text = format!( - " SCRUBBED — retained frame {}/{}{batch_note} (not live; `>` returns to now)", + " SCRUBBED -- retained frame {}/{}{batch_note} (not live; `>` returns to now)", frame_index + 1, app.frames().retained.max(1), ); @@ -329,8 +329,8 @@ fn draw_scrub_banner(frame: &mut Frame, area: Rect, app: &App, frame_index: usiz /// What one half of a terminal cell is marked with, for whichever layer /// (`crate::Layer`) is active. /// -/// **Invariant:** a `Region` mark keeps the density glyph — `░▒▓█▀▄`, -/// half-occupancy intact — and is identified by inversion, so a mark and the +/// **Invariant:** a `Region` mark keeps the density glyph -- `░▒▓█▀▄`, +/// half-occupancy intact -- and is identified by inversion, so a mark and the /// distribution under it stay both visible; color (`region_accent`) is redundant /// reinforcement. `DpNode`/`Tail` are the exception: they *replace* the cell with /// `O`/`X`, a path node being a point estimate rather than a region. @@ -341,17 +341,17 @@ fn draw_scrub_banner(frame: &mut Frame, area: Rect, app: &App, frame_index: usiz #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum Mark { None, - /// Curve, ridge or suppressed layer — the density glyph (or, in place of a - /// space, `\u{b7}` — see `compose_marked`), reversed. + /// Curve, ridge or suppressed layer -- the density glyph (or, in place of a + /// space, `\u{b7}` -- see `compose_marked`), reversed. Region, - /// Path layer, Pass 2's greedily attached tail — `X`, the standard + /// Path layer, Pass 2's greedily attached tail -- `X`, the standard /// excluded/suspect-point convention. Tail, - /// Path layer, the DP's own chosen chain — `O`. + /// Path layer, the DP's own chosen chain -- `O`. DpNode, } -/// The active layer's accent color for a `Region` mark — reinforcement only (see +/// The active layer's accent color for a `Region` mark -- reinforcement only (see /// `Mark`), so a named ANSI constant (see `heat_color`) and never one of indices /// 9-15 (`Light*`/`White`, invisible on a light background). fn region_accent(layer: Layer) -> Color { @@ -412,7 +412,7 @@ impl Dims { } /// Which half of a terminal cell a grid row landed in. Tracked per half so two -/// grid rows sharing a terminal line stay distinct — the doubled resolution +/// grid rows sharing a terminal line stay distinct -- the doubled resolution /// `heatmap_cells` promises. #[derive(Clone, Copy)] enum Half { @@ -421,14 +421,14 @@ enum Half { } impl Half { - /// Slot offset in a cell's pair of half-rows — the layout `heatmap_cells`'s + /// Slot offset in a cell's pair of half-rows -- the layout `heatmap_cells`'s /// `out` and the mark buffer share. fn slot(self) -> usize { usize::from(matches!(self, Half::Lower)) } } -/// Flips a display-row index so grid row 0 — the *lowest* observed RT — lands at +/// Flips a display-row index so grid row 0 -- the *lowest* observed RT -- lands at /// the *bottom* of the canvas. Every y flip in this module goes through here. /// Without it grid row 0 would land at display row 0, the *top* one, and a /// monotonically increasing calibration would render as a *descending* line. @@ -457,8 +457,8 @@ fn draw_heatmap(frame: &mut Frame, area: Rect, app: &App) { return; }; - let title_left = " Fit \u{2014} observed RT (s) \u{2191} vs library RT (s) \u{2192} "; - // `batch` only — the active layer gets its own subtitle row below. + let title_left = " Fit -- observed RT (s) \u{2191} vs library RT (s) \u{2192} "; + // `batch` only -- the active layer gets its own subtitle row below. let title_right = format!(" b{} ", app.batch()); frame.render_widget( Block::bordered() @@ -499,7 +499,7 @@ fn draw_heatmap(frame: &mut Frame, area: Rect, app: &App) { } /// Where every piece of the framed heatmap goes: the block, the canvas painted -/// inside it, and the three areas — y gutter, x label row, subtitle — that are +/// inside it, and the three areas -- y gutter, x label row, subtitle -- that are /// `None` when the terminal has no room for them. `inner_width` is the canvas width /// the x ticks are spaced across, `y_target` the tick count the gutter holds. struct HeatmapLayout { @@ -657,14 +657,14 @@ fn layer_gloss(layer: Layer) -> &'static str { } } -/// The Fit heatmap's subtitle, spelled out as a label ("Showing: ridge — +/// The Fit heatmap's subtitle, spelled out as a label ("Showing: ridge -- /// tolerance band") rather than a key hint: a reader who does not know the active -/// layer cannot interpret a single inverted cell. Degrades fits-or-drops — gloss -/// first, then the label truncated — so it can never wrap or panic at `avail == 0`. +/// layer cannot interpret a single inverted cell. Degrades fits-or-drops -- gloss +/// first, then the label truncated -- so it can never wrap or panic at `avail == 0`. fn fit_subtitle(layer: Layer, avail: usize) -> String { let label = layer.label(); let gloss = layer_gloss(layer); - let full = format!(" Showing: {label} \u{2014} {gloss} "); + let full = format!(" Showing: {label} -- {gloss} "); if full.chars().count() <= avail { return full; } @@ -675,8 +675,8 @@ fn fit_subtitle(layer: Layer, avail: usize) -> String { label.chars().take(avail).collect() } -/// Paints the half-block heatmap itself — density field plus the active mark -/// layer — into `area`: the framed canvas's inner rect, or the whole Fit-tab body +/// Paints the half-block heatmap itself -- density field plus the active mark +/// layer -- into `area`: the framed canvas's inner rect, or the whole Fit-tab body /// on a terminal too small to frame. fn paint_heatmap(frame: &mut Frame, area: Rect, rec: &FitRecording, app: &App) { if area.is_empty() { @@ -731,7 +731,7 @@ fn paint_heatmap(frame: &mut Frame, area: Rect, rec: &FitRecording, app: &App) { } } -/// A "nice" axis step (1/2/5 x 10^k) close to `span / target` — 37s at a target of +/// A "nice" axis step (1/2/5 x 10^k) close to `span / target` -- 37s at a target of /// 8 ticks rounds to a step of 5s, not an unreadable 4.625. fn nice_step(span: f64, target: usize) -> f64 { let raw = span / target.max(1) as f64; @@ -780,7 +780,7 @@ fn axis_decimals(step: f64) -> usize { (-step.log10().floor()).clamp(0.0, 2.0) as usize } -/// An axis's tick step and the precision to print its labels at — paired here +/// An axis's tick step and the precision to print its labels at -- paired here /// because tick placement, labels and the gutter sized for them must agree. fn axis_scale(lo: f64, hi: f64, target: usize) -> (f64, usize) { let step = nice_step((hi - lo).abs().max(EPS), target); @@ -822,7 +822,7 @@ fn y_gutter_width(lo: f64, hi: f64, target: usize) -> usize { /// Picks one terminal cell's glyph and color from its two independent half-rows. /// "Occupied" means a half has nonzero weight or a mark, and the occupancy *shape* /// (`▀` upper only, `▄` lower only, a density glyph for both, ` ` for neither) is -/// the primary signal — the one that makes the doubled vertical resolution visible +/// the primary signal -- the one that makes the doubled vertical resolution visible /// in a `.symbol()`-only snapshot. With both halves occupied one character cannot /// show two marks, so `Mark`'s `Ord` picks the winner. fn compose_cell( @@ -849,7 +849,7 @@ fn compose_cell( /// Resolves one already-occupied half (or, from `compose_cell`'s both-occupied /// case, the winning mark for the whole cell) into a glyph/color/modifier. -/// `none_glyph` is what to draw for `Mark::None` — the asymmetric `▀`/`▄` for a +/// `none_glyph` is what to draw for `Mark::None` -- the asymmetric `▀`/`▄` for a /// single occupied half, or the combined density glyph when both are occupied and /// neither carries a mark. /// @@ -858,7 +858,7 @@ fn compose_cell( /// /// A `Region` mark on a zero-weight cell must not keep `heat_glyph`'s plain space: /// a *reversed* space is a solid block, the highest-density glyph here, and would -/// lie about the data — routine, since the curve is evaluated at every display +/// lie about the data -- routine, since the curve is evaluated at every display /// column and the ridge band often extends past the last real observation. /// `\u{b7}` reversed reads instead as "mark here, nothing under it". fn compose_marked( @@ -916,7 +916,7 @@ fn mark_suppressed(marks: &mut [Mark], dims: Dims, rec: &FitRecording) { /// Marks the two greedily attached tails (`path[..dp_range.start]` and /// `path[dp_range.end..]`, `Mark::Tail`) apart from the DP's own chain -/// (`path[dp_range]`, `Mark::DpNode`) — DP's choice, or a tail grafted on after? +/// (`path[dp_range]`, `Mark::DpNode`) -- DP's choice, or a tail grafted on after? /// The one layer that draws two mark kinds, so it relies on `Mark`'s `Ord` for the /// rare cell downsampling rounds one of each into. fn mark_path(marks: &mut [Mark], dims: Dims, rec: &FitRecording) { @@ -966,7 +966,7 @@ fn mark_curve(marks: &mut [Mark], dims: Dims, rec: &FitRecording) { /// Brackets each measured column at `curve ± half_width`. Every measurement's /// `library` is a path cell center, so it is one of the curve's own points and -/// never out of bounds — the `Err` arm is unreachable in practice. +/// never out of bounds -- the `Err` arm is unreachable in practice. fn mark_ridge(marks: &mut [Mark], dims: Dims, rec: &FitRecording) { let geom = rec.geom(); let bins = dims.bins; @@ -1036,7 +1036,7 @@ fn heatmap_cells(rec: &FitRecording, area_w: u16, area_h: u16) -> Vec { } /// The half-open range of source indices (out of `src_n`) display index `disp_i` -/// (out of `disp_n`) covers — a partition of `0..src_n` whether downsampling or +/// (out of `disp_n`) covers -- a partition of `0..src_n` whether downsampling or /// upsampling (where consecutive `disp_i` share one single-element range). fn bin_range(disp_i: usize, disp_n: usize, src_n: usize) -> Range { if disp_n == 0 || src_n == 0 { @@ -1069,7 +1069,7 @@ fn grid_to_screen(row: usize, col: usize, dims: Dims) -> (usize, usize, Half) { } /// Grid-bin index of `v` within `range`, for placing an overlay mark on a value -/// that is not itself a grid cell — a curve prediction or a ridge bound. +/// that is not itself a grid cell -- a curve prediction or a ridge bound. /// /// Zero bins, a non-finite `v` or a zero-width range map to bin 0 rather than /// panicking or producing NaN: overlay placement reads geometry straight off a @@ -1093,9 +1093,9 @@ fn bin_of(v: f64, range: (f64, f64), bins: usize) -> usize { // --------------------------------------------------------------------- /// One metric the Convergence tab reports, named once for both places it is -/// labelled — the sparkline title and the batch-table header, both on screen at +/// labelled -- the sparkline title and the batch-table header, both on screen at /// once. The short form wins: the table column is `TABLE_COL_WIDTH` wide. -/// `in_table`/`sparkline` select each view's subset — the table's bookkeeping +/// `in_table`/`sparkline` select each view's subset -- the table's bookkeeping /// columns have nothing worth plotting, and `mean_d` is plotted with no column. struct MetricColumn { label: &'static str, @@ -1170,7 +1170,7 @@ fn draw_convergence_tab(frame: &mut Frame, area: Rect, app: &App) { frame, area, "Convergence", - "No batches recorded yet — metrics appear after the first Phase 1 batch.", + "No batches recorded yet -- metrics appear after the first Phase 1 batch.", ); return; } @@ -1201,7 +1201,7 @@ fn table_columns() -> impl Iterator { fn draw_sparklines(frame: &mut Frame, area: Rect, metrics: &[BatchMetrics]) { // Every series is normalized to its own maximum and they share no unit, so no - // shape or relative height here carries a magnitude — `spark_title` puts the + // shape or relative height here carries a magnitude -- `spark_title` puts the // numbers back. `Fill(1)` spreads rounding leftovers across the panes. let columns: Vec<&MetricColumn> = METRIC_COLUMNS.iter().filter(|c| c.sparkline).collect(); let spark_rows = Layout::vertical(vec![Constraint::Fill(1); columns.len()]).split(area); @@ -1215,7 +1215,7 @@ fn draw_sparklines(frame: &mut Frame, area: Rect, metrics: &[BatchMetrics]) { } } -/// `label peak

now ` — the y-scale and the latest sample, which is what +/// `label peak

now ` -- the y-scale and the latest sample, which is what /// turns a self-normalized shape back into a measurement: a `wrmse` that fell /// 5.0 → 0.08 otherwise draws the same descent as one that fell 5.0 → 4.9. `now` /// is the last *finite* sample, matching what `scaled_u64`'s hold actually draws. @@ -1234,13 +1234,13 @@ fn spark_title(label: &str, values: &[f64], nan_holds: bool) -> String { ) } -/// A metric as a short decimal: `—` when non-finite, no fractional part for a +/// A metric as a short decimal: `--` when non-finite, no fractional part for a /// whole number (`path` is a count), scientific notation only outside `FIXED`. fn fmt_metric(v: f64) -> String { // Outside this range four decimals print `0.0000` or run past the width. const FIXED: Range = 0.001..100_000.0; if !v.is_finite() { - return "—".to_string(); + return "--".to_string(); } let mag = v.abs(); if v == 0.0 || v.fract() == 0.0 && mag < FIXED.end { @@ -1258,8 +1258,8 @@ const SPARK_SCALE: f64 = 1000.0; /// Scales a metric series into `0..=SPARK_SCALE` for `Sparkline`, which takes /// `u64` and so cannot mark a sample as "no data" distinctly from `0`. -/// Non-finite samples are routine here — batch 0 has no prior curve to diff -/// against, and a failed fit produces NaN throughout — and mapping them to `0` would +/// Non-finite samples are routine here -- batch 0 has no prior curve to diff +/// against, and a failed fit produces NaN throughout -- and mapping them to `0` would /// draw identically to "the curve stopped moving". So each holds the last finite /// value, and only a leading run with no finite value yet reports as `0`. fn scaled_u64(values: &[f64]) -> Vec { @@ -1415,7 +1415,7 @@ mod tests { /// The height thresholds: two chrome rows come off before the body sees anything, /// then `area.height >= 4` (the x-tick row), `block_h < 3` and `inner.height >= 2` - /// (the subtitle row) — one row later again with the banner. + /// (the subtitle row) -- one row later again with the banner. const SWEEP_HEIGHTS: [u16; 7] = [0, 1, 2, 3, 4, 5, 6]; /// Both dimensions inside their smallest thresholds, then where two thresholds @@ -1435,7 +1435,7 @@ mod tests { app.handle_key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)); } - /// Cycles to `tab` with the real `l` binding — `App` has no jump-to-tab setter. + /// Cycles to `tab` with the real `l` binding -- `App` has no jump-to-tab setter. fn goto_tab(app: &mut App, tab: Tab) { while app.tab() != tab { press(app, 'l'); @@ -1460,8 +1460,8 @@ mod tests { glyph_grid(&body_buffer(app, w, h)) } - /// The tab body alone — `draw`'s middle rows, without the tab bar or status line - /// — rebased so row 0 is the body's first row. A tab-content snapshot has no + /// The tab body alone -- `draw`'s middle rows, without the tab bar or status line + /// -- rebased so row 0 is the body's first row. A tab-content snapshot has no /// business regenerating because a key hint was reworded. fn body_buffer(app: &mut App, w: u16, h: u16) -> Buffer { let buf = draw_to_buffer(app, w, h); @@ -1493,7 +1493,7 @@ mod tests { } /// The tab body's glyph grid plus a trailing section naming every `REVERSED` - /// cell — for the snapshots where inversion *is* the payload: a region mark keeps + /// cell -- for the snapshots where inversion *is* the payload: a region mark keeps /// the density glyph under it (`Mark`), so in a `.symbol()`-only grid a layer /// marking the wrong cells is byte-identical to one marking the right ones. /// Column spans keep it to one line per affected row. @@ -1583,7 +1583,7 @@ mod tests { } /// A plain diagonal ridge sized to `bins`, asymmetric in range only (`x` spans - /// `(0, bins)`, `y` spans `(0, 2*bins)`) — only exercises `heatmap_cells`'s + /// `(0, bins)`, `y` spans `(0, 2*bins)`) -- only exercises `heatmap_cells`'s /// down/upsampling at arbitrary bin counts. fn fixture_recording(bins: usize) -> FitRecording { let bins = bins.max(1); @@ -1651,7 +1651,7 @@ mod tests { } } - /// The active layer's name must survive on screen — without it a single inverted + /// The active layer's name must survive on screen -- without it a single inverted /// mark is uninterpretable. Checked on the two layers no snapshot pins, so a /// change to the degrade order cannot drop it for only some of them. #[test] @@ -1668,8 +1668,8 @@ mod tests { } /// `App::set_scrub_recording` is what `CalibDash::sync_scrub` calls once `<`/`>` - /// have moved `scrub_frame`. Pins that the Fit tab switches to that recording — - /// a visibly different grid — and draws the "not live" banner. + /// have moved `scrub_frame`. Pins that the Fit tab switches to that recording -- + /// a visibly different grid -- and draws the "not live" banner. #[test] fn fit_tab_shows_a_banner_and_a_different_grid_when_scrubbing() { let mut app = fixture_app_with_ridge(); @@ -1684,7 +1684,7 @@ mod tests { /// A Fit-tab body exactly one row tall must still show the banner rather than /// spend that row on a heatmap too short to read: without it the replayed grid is /// indistinguishable from live. And clearing the scrub must take the banner back - /// off — a banner that survives the clear is the same lie inverted. + /// off -- a banner that survives the clear is the same lie inverted. #[test] fn the_scrub_banner_wins_the_only_body_row_and_goes_away_when_the_scrub_clears() { let mut app = fixture_app_with_ridge(); @@ -1733,7 +1733,7 @@ mod tests { /// A `contains`, not a snapshot: the panel is literal prose with no computed /// content, so a picture of it pins four wrapped lines and rows of border padding - /// to catch one thing — whether the explanation reaches the screen. + /// to catch one thing -- whether the explanation reaches the screen. #[test] fn tolerances_tab_explains_itself_during_phase_one() { let mut app = fixture_app_with_ridge(); // real_fit is None @@ -1772,8 +1772,8 @@ mod tests { } /// The half-block parity, end to end and independently derived. - /// `fixture_recording(4)`'s four points land one per grid cell on the diagonal — - /// `(row, col)` = `(0,0) (1,1) (2,2) (3,3)` with weights 1..4 — so a 4x2 canvas + /// `fixture_recording(4)`'s four points land one per grid cell on the diagonal -- + /// `(row, col)` = `(0,0) (1,1) (2,2) (3,3)` with weights 1..4 -- so a 4x2 canvas /// is exactly two grid rows per terminal line. /// /// Row 0 (the *lowest* observed RT) must land in the bottom line's *lower* half @@ -1851,7 +1851,7 @@ mod tests { } /// A `Mark::Region` on a weighted cell changes no glyph, so neither function's - /// output is visible in a `.symbol()`-only snapshot — this pins what they mark, at + /// output is visible in a `.symbol()`-only snapshot -- this pins what they mark, at /// the mark-buffer level. `Layer::Ridge`'s whole payload is the sparse /// `center ± half_width` pair per measured column, straddling the curve, rather /// than the filled band between them; asserted structurally because a snapshot @@ -2018,7 +2018,7 @@ mod tests { #[test] fn scaled_u64_carries_the_last_finite_value_across_a_nan() { - // Batch 0's own delta and any failed batch's metrics are NaN — this must not + // Batch 0's own delta and any failed batch's metrics are NaN -- this must not // draw as the scale's `0`, which looks identical to "the curve stopped". let scaled = scaled_u64(&[10.0, f64::NAN, 10.0]); assert_eq!( @@ -2027,7 +2027,7 @@ mod tests { ); assert_eq!(scaled[2], scaled[0]); - // A *leading* NaN run has nothing to carry forward — the one case where `0` + // A *leading* NaN run has nothing to carry forward -- the one case where `0` // means "nothing to show yet" rather than "converged". let scaled = scaled_u64(&[f64::NAN, f64::NAN, 10.0]); assert_eq!(scaled[0], 0, "{scaled:?}"); @@ -2061,7 +2061,7 @@ mod tests { line.spans.iter().map(|s| s.content.as_ref()).collect() } - /// Inversion, not a color, is what makes a pending count unmistakable — and the + /// Inversion, not a color, is what makes a pending count unmistakable -- and the /// glyph-only `render` harness cannot see style, so this reads the buffer. #[test] fn status_line_pending_count_is_reversed() { @@ -2173,15 +2173,15 @@ mod tests { } /// Reporting `-8.5 .. +9.5` as `±9.5` understates one side by two ppm and - /// `±8.5` overstates the other — the reason `mz_ppm` is a pair at all. + /// `±8.5` overstates the other -- the reason `mz_ppm` is a pair at all. #[test] fn fmt_interval_collapses_only_the_symmetric_case() { assert_eq!(fmt_interval((-3.0, 3.0)), "±3.0"); assert_eq!(fmt_interval((-8.5, 9.5)), "-8.5 .. +9.5"); } - /// `now` must report what the right edge draws — the last *finite* sample under - /// `scaled_u64`'s hold. Reporting `—` beside a held bar contradicts the plot. + /// `now` must report what the right edge draws -- the last *finite* sample under + /// `scaled_u64`'s hold. Reporting `--` beside a held bar contradicts the plot. #[test] fn spark_title_reports_the_value_the_right_edge_actually_draws() { let title = spark_title("wrmse", &[2.0, 0.5, f64::NAN], true); @@ -2191,7 +2191,7 @@ mod tests { // Batch 0's `max_delta` is NaN, so the plot is flat at the floor, where // `peak 0 now 0` would read as "converged" rather than "not measured". let title = spark_title("max_delta", &[f64::NAN], true); - assert_eq!(title, "max_delta peak — now — (NaN holds)"); + assert_eq!(title, "max_delta peak -- now -- (NaN holds)"); } /// Every tab, every mark layer, a scrubbed frame, an empty app and the keys @@ -2241,7 +2241,7 @@ mod tests { sweep_painted(&mut fixture_app_with_metrics()); - // A scrubbed frame is `draw_fit_tab`'s `Length(1), Min(0)` split — the one + // A scrubbed frame is `draw_fit_tab`'s `Length(1), Min(0)` split -- the one // path that deliberately hands the heatmap zero rows. let mut scrubbed = fixture_app_with_ridge(); scrubbed.set_frame_summary(RETAINED_5); @@ -2249,7 +2249,7 @@ mod tests { sweep_painted(&mut scrubbed); // A mark layer changes the glyphs, never the layout, so it needs the size - // sweep once rather than once per layout combination above — and only on the + // sweep once rather than once per layout combination above -- and only on the // Fit tab, the only tab that reads `app.layer()`. for layer in Layer::ALL.into_iter().filter(|l| *l != Layer::None) { let mut app = fixture_app_with_metrics(); diff --git a/rust/calibrt/src/grid.rs b/rust/calibrt/src/grid.rs index 993c1d7b..b9cbe242 100644 --- a/rust/calibrt/src/grid.rs +++ b/rust/calibrt/src/grid.rs @@ -246,7 +246,7 @@ impl Grid { pub struct Node { pub center: Point, pub suppressed: bool, - // Internal accumulators — not exposed beyond crate + // Internal accumulators -- not exposed beyond crate pub(crate) sum_wx: f64, pub(crate) sum_wy: f64, pub(crate) sum_w: f64, diff --git a/rust/calibrt/src/lib.rs b/rust/calibrt/src/lib.rs index ee48222f..0c1faf98 100644 --- a/rust/calibrt/src/lib.rs +++ b/rust/calibrt/src/lib.rs @@ -119,7 +119,7 @@ impl CalibrationCurve { // Find the partition point; first element >= x_val. let i = self.points.partition_point(|p| p.library < x_val); - // Clamp to [1, slopes.len()] — partition_point can return 0 when x_val == first_x + // Clamp to [1, slopes.len()] -- partition_point can return 0 when x_val == first_x let i = i.max(1).min(self.slopes.len()); Ok(ObservedRTSeconds(self.predict_with_index(x_val, i))) } @@ -173,7 +173,7 @@ pub struct RidgeMeasurement { } /// Everything a consumer reports about a fit's [`RidgeMeasurement`]s, folded in -/// the one place the arithmetic is written — the dashboard, the search's derived +/// the one place the arithmetic is written -- the dashboard, the search's derived /// tolerances and the CLI's log line all read this rather than each summing the /// slice their own way. #[derive(Debug, Clone, Copy, PartialEq)] @@ -220,7 +220,7 @@ impl RidgeSummary { } } -/// Serializable snapshot of calibration data — points + config. +/// Serializable snapshot of calibration data -- points + config. /// Used for save/load. Does not include the fitted curve (reconstructed on load). #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CalibrationSnapshot { @@ -233,7 +233,7 @@ pub struct CalibrationSnapshot { /// writer and the reader's gate cannot disagree. pub const CALIBRATION_FORMAT_VERSION: &str = "v3"; -/// JSON v3 calibration file format — shared between CLI and viewer. +/// JSON v3 calibration file format -- shared between CLI and viewer. /// /// `calibration` is the grid's own snapshot and the only record of the fit: the /// curve and the ridge widths are recomputed by refitting it, so the file cannot @@ -246,11 +246,11 @@ pub struct SavedCalibration { pub version: String, pub rt_range_seconds: [f64; 2], pub calibration: CalibrationSnapshot, - /// The uniform RT tolerance. Every writer has one — it is what a query falls + /// The uniform RT tolerance. Every writer has one -- it is what a query falls /// back to where the grid measured no ridge. pub rt_tolerance_minutes: f32, /// What a search measured beyond the curve. `None` for a writer that measures - /// no residuals — zeros there would read as "measured, and tight". + /// no residuals -- zeros there would read as "measured, and tight". /// /// The path is named because a bare `default` would demand `R: Default`. #[serde(default = "Option::default")] @@ -288,8 +288,8 @@ impl SavedCalibration { /// Parse a calibration file and check its provenance. The `Option` /// is a reason to distrust the file, not an error: a calibration is only - /// valid for the run it was fit on, and `raw_rt_range` — the RT span of the - /// run it is about to be used on — is the one cheap way to catch the wrong + /// valid for the run it was fit on, and `raw_rt_range` -- the RT span of the + /// run it is about to be used on -- is the one cheap way to catch the wrong /// file. `None` there means nothing verifies it, which also warns. pub fn read( path: &std::path::Path, @@ -318,7 +318,7 @@ impl SavedCalibration { fn provenance_warning(&self, raw_rt_range: Option<[f64; 2]>) -> Option { let Some(raw) = raw_rt_range else { return Some( - "No raw RT range to check the calibration against — nothing verifies it was \ + "No raw RT range to check the calibration against -- nothing verifies it was \ fit on this run" .to_string(), ); @@ -332,7 +332,7 @@ impl SavedCalibration { } Some(format!( "Calibration RT range [{:.1}, {:.1}]s overlaps the raw file's [{:.1}, {:.1}]s by \ - {:.0}% — it may have been fit on a different run", + {:.0}% -- it may have been fit on a different run", self.rt_range_seconds[0], self.rt_range_seconds[1], raw[0], @@ -357,7 +357,7 @@ pub struct GridGeom { pub struct CalibrationState { grid: grid::Grid, path_indices: Vec, - /// The DP-chosen segment within [`Self::path_indices`] — see + /// The DP-chosen segment within [`Self::path_indices`] -- see /// [`Self::dp_range`]. dp_range: std::ops::Range, /// Survivors of suppression, refilled per fit. Sized at `bins`, which is a @@ -409,7 +409,7 @@ impl CalibrationState { /// /// The geometry is derived *here*, by `point_ranges`, rather than passed in: /// a caller that supplies the acquisition RT range instead would clamp an - /// iRT-scaled library — whose RTs fall entirely outside it — into one edge + /// iRT-scaled library -- whose RTs fall entirely outside it -- into one edge /// column. Every calibrant weighs [`CALIBRANT_WEIGHT`]. /// /// On `Err` the previous fit is left alone: a later, larger point set may well @@ -497,7 +497,7 @@ impl CalibrationState { self.clear_fit(); } - /// Re-point `self` at a new geometry and clear the previous fit — see + /// Re-point `self` at a new geometry and clear the previous fit -- see /// `grid::Grid::reconfigure` for what stays allocated. pub fn reconfigure( &mut self, @@ -534,7 +534,7 @@ impl CalibrationState { } /// The assembled path as row-major grid indices, one per point, from the - /// grid's own arithmetic — so an overlay never has to re-derive them and risk + /// grid's own arithmetic -- so an overlay never has to re-derive them and risk /// landing in a different cell. pub fn path_indices(&self) -> &[usize] { &self.path_indices @@ -561,7 +561,7 @@ impl CalibrationState { /// Everything needed to rebuild an equal state: the points, and the grid /// geometry they were binned under. Refitting a snapshot under its own /// `(grid_size, lookback)` reproduces the curve, so this is the state's - /// persistent form — see [`Self::from_snapshot`]. + /// persistent form -- see [`Self::from_snapshot`]. pub fn snapshot(&self) -> CalibrationSnapshot { CalibrationSnapshot { points: self @@ -828,7 +828,7 @@ mod ridge_summary_tests { } /// A total ridge weight of 0.25 must report the half-width it measured, not - /// a fraction of it. And a weightless column keeps its count and bounds — + /// a fraction of it. And a weightless column keeps its count and bounds -- /// only the mean goes NaN, where a 0.0 would read as a perfectly tight /// ridge. #[test] @@ -846,7 +846,7 @@ mod ridge_summary_tests { /// The two axes are bounded independently, a non-finite point drops out of /// both, and the three refusals are distinguishable: nothing left to bound - /// is `NoPoints`, while a single collapsed axis is `ZeroRange` — which is + /// is `NoPoints`, while a single collapsed axis is `ZeroRange` -- which is /// what a grid built from these ranges would have said anyway. #[test] fn point_ranges_bounds_both_axes_and_names_each_refusal() { @@ -924,7 +924,7 @@ mod calibration_state_tests { s.update(pts1.iter().copied()).unwrap(); s.fit(); - // Same bins, a completely different (shifted, wider) range — the case + // Same bins, a completely different (shifted, wider) range -- the case // `reconfigure` exists to keep allocation-free. s.reconfigure(10, (100.0, 200.0), (100.0, 200.0)).unwrap(); let pts2: Vec<_> = (0..10) diff --git a/rust/calibrt/src/pathfinding.rs b/rust/calibrt/src/pathfinding.rs index 3a7c2960..5a3b0991 100644 --- a/rust/calibrt/src/pathfinding.rs +++ b/rust/calibrt/src/pathfinding.rs @@ -243,7 +243,7 @@ mod tests { /// declining one takes a node with no admissible in-window predecessor: with /// `lookback == 1` the dip at `(3.5, 0.1)` fails the monotonic edge back to /// the core chain, and the stray at `(4.5, 4.5)` can only look back one rank - /// — at the dip — so it accumulates the dip's 0.4 rather than the chain's, + /// -- at the dip -- so it accumulates the dip's 0.4 rather than the chain's, /// and the DP's best path ends at the chain. Pass 2's forward walk re-checks /// monotonicity against the DP's chosen *endpoint* instead, skips the dip /// (its observed RT is below the chain) and grafts the stray on as a suffix. diff --git a/rust/calibrt/src/types.rs b/rust/calibrt/src/types.rs index 3c76c816..2e108417 100644 --- a/rust/calibrt/src/types.rs +++ b/rust/calibrt/src/types.rs @@ -4,7 +4,7 @@ use serde::{ }; use std::fmt; -/// Library reference retention time. Unit-agnostic — could be iRT, minutes, +/// Library reference retention time. Unit-agnostic -- could be iRT, minutes, /// or arbitrary units depending on the spectral library. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(transparent)] diff --git a/rust/calibrt/tests/tests.rs b/rust/calibrt/tests/tests.rs index 1df120a5..7dabd81a 100644 --- a/rust/calibrt/tests/tests.rs +++ b/rust/calibrt/tests/tests.rs @@ -5,7 +5,7 @@ use calibrt::{ }; /// A degenerate range on *either* axis is an error. Both, because the two are -/// separate checks in `grid::spans` — dropping the y one leaves the x case +/// separate checks in `grid::spans` -- dropping the y one leaves the x case /// green. #[test] fn a_zero_range_on_either_axis_is_rejected() { diff --git a/rust/rescore_dash/src/app.rs b/rust/rescore_dash/src/app.rs index e4a5aec8..85468857 100644 --- a/rust/rescore_dash/src/app.rs +++ b/rust/rescore_dash/src/app.rs @@ -1,8 +1,8 @@ //! Dashboard state: which tab, which feature, which transforms, and the key //! bindings that move between them. //! -//! [`App`] is a [`Dashboard`] — everything materialized, see -//! [`crate::precompute`] — plus the handful of fields a keystroke can change. +//! [`App`] is a [`Dashboard`] -- everything materialized, see +//! [`crate::precompute`] -- plus the handful of fields a keystroke can change. //! Nothing here computes over the data; a key press moves an index. //! Rendering lives in [`crate::ui`]. @@ -156,8 +156,8 @@ impl App { } pub(crate) fn handle_key(&mut self, key: KeyEvent) -> Flow { - // Handled before anything else — including the filter-editing dispatch - // below — so Ctrl-C always quits rather than being typed into the filter + // Handled before anything else -- including the filter-editing dispatch + // below -- so Ctrl-C always quits rather than being typed into the filter // box or toggling clip. if is_ctrl_c(key) { return Flow::Quit; @@ -260,7 +260,7 @@ impl App { } else { // `sort_desc` picks the finite-value order; NaN sorts last either // way. That rule has to live in the comparator itself, not in a - // value substitution followed by a blanket `idx.reverse()` — a + // value substitution followed by a blanket `idx.reverse()` -- a // later reverse of the whole vector would undo a NaN-last // placement exactly when `sort_desc` is false. let desc = self.sort_desc; @@ -291,7 +291,7 @@ fn available() -> bool { /// Never fails the caller and never panics on setup: a non-terminal stdout or /// a failed `try_init` warns, restores the terminal and returns `Ok`. This is /// deliberately unlike `ratatui::init`, which is `try_init().expect(..)` and -/// would unwind past the caller's warn-only `if let Err` — after the results +/// would unwind past the caller's warn-only `if let Err` -- after the results /// have already been written to disk. /// /// The terminal is restored on every returning path. [`catch_panics`] covers @@ -576,7 +576,7 @@ pub(crate) mod tests { assert_eq!(app.visible, &[1, 0]); } - /// A wholly NaN column gets a NaN AUC — a real, naturally occurring case, + /// A wholly NaN column gets a NaN AUC -- a real, naturally occurring case, /// not a synthetic key. It must sort last regardless of direction. #[test] fn nan_sort_keys_sort_last_in_both_directions() { diff --git a/rust/rescore_dash/src/curves.rs b/rust/rescore_dash/src/curves.rs index 4d669578..a9b6bc39 100644 --- a/rust/rescore_dash/src/curves.rs +++ b/rust/rescore_dash/src/curves.rs @@ -44,7 +44,7 @@ pub(crate) fn qvalue_curves( /// already covers both classes, so the curve is `(1,1)` at every point rather /// than starting at `(0,0)`. /// -/// Empty when either class is absent — a PP plot of one class says nothing. +/// Empty when either class is absent -- a PP plot of one class says nothing. pub(crate) fn pp_curve(score: &[f32], is_target: &[bool], n_points: usize) -> Vec<(f64, f64)> { let mut targets: Vec = Vec::new(); let mut decoys: Vec = Vec::new(); @@ -92,7 +92,7 @@ mod tests { } /// A zoomed curve must spend all of its points inside its own range and - /// agree with the wide curve wherever they overlap — a zoom that merely + /// agree with the wide curve wherever they overlap -- a zoom that merely /// rescaled the axis without re-gridding would be the bug this guards. #[test] fn zoomed_curves_resolve_the_low_q_region() { @@ -157,7 +157,7 @@ mod tests { /// Axes are `(decoy CDF, target CDF)`; the panel draws the `y = x` /// reference line, so a curve point where `d > tt` sits BELOW that /// diagonal. When targets score higher, the decoy CDF races ahead of the - /// target CDF in the shared middle range — the curve dips below y = x, + /// target CDF in the shared middle range -- the curve dips below y = x, /// which is what good target/decoy separation looks like on this plot. #[test] fn pp_curve_dips_below_the_diagonal_when_targets_score_higher() { diff --git a/rust/rescore_dash/src/labels.rs b/rust/rescore_dash/src/labels.rs index a6fd4395..bd68feb5 100644 --- a/rust/rescore_dash/src/labels.rs +++ b/rust/rescore_dash/src/labels.rs @@ -109,7 +109,7 @@ impl Labels { } } -/// One title per `(column, transform)`, the discriminant score last — it is +/// One title per `(column, transform)`, the discriminant score last -- it is /// titled like a feature because it is stored and queried like one. fn build_titles(view: &RescoreView<'_>) -> Vec { view.feature_names @@ -124,7 +124,7 @@ fn build_titles(view: &RescoreView<'_>) -> Vec { /// /// Two exclusions at two steps, neither a subset of the other: `dropped` is what /// the transform refused, `outside range` what survived it and then fell outside -/// the axis. Kept short — the panel is under half the terminal width and its +/// the axis. Kept short -- the panel is under half the terminal width and its /// border truncates the rest; run-level facts go to [`Labels::basis`]. fn build_subtitles(slots: &[Slot], n_sampled: usize) -> Vec { let pct = |n: u32| { @@ -270,7 +270,7 @@ mod tests { /// The subtitle has to fit the histogram panel, which is under half of a /// terminal's width; anything longer is silently cut off by the border it is /// drawn on. `log10` rejects the non-positive half, and the subtitle is the - /// only place that says so — a histogram that silently shrank would read as + /// only place that says so -- a histogram that silently shrank would read as /// "those rows do not exist". #[test] fn subtitles_fit_a_narrow_panel_and_report_what_a_transform_refused() { diff --git a/rust/rescore_dash/src/lib.rs b/rust/rescore_dash/src/lib.rs index 2e657479..e90fed8c 100644 --- a/rust/rescore_dash/src/lib.rs +++ b/rust/rescore_dash/src/lib.rs @@ -2,8 +2,8 @@ //! target/decoy separation, FDR curve and decoy calibration. //! //! Two steps, deliberately separate. [`Dashboard::build`] materializes -//! everything on screen from a [`RescoreView`] — see [`precompute`] for what is -//! exact and what is sampled — and [`run`] opens the TUI over the result. +//! everything on screen from a [`RescoreView`] -- see [`precompute`] for what is +//! exact and what is sampled -- and [`run`] opens the TUI over the result. //! Splitting them lets the caller drop the feature matrix, gigabytes at a //! realistic library size, before the TUI blocks for as long as the user leaves //! it open. diff --git a/rust/rescore_dash/src/precompute.rs b/rust/rescore_dash/src/precompute.rs index ccb6588f..d5e2d324 100644 --- a/rust/rescore_dash/src/precompute.rs +++ b/rust/rescore_dash/src/precompute.rs @@ -41,7 +41,7 @@ use std::sync::Arc; /// /// Approximate, and not pinned by a test. A clipped axis bound is `log10` of a /// discrete order statistic, so on a spiky column the sample and the whole data -/// can pick neighbouring values and slide every bin — a swing far wider than +/// can pick neighbouring values and slide every bin -- a swing far wider than /// the sampling error itself, and one that moves with the draw. Any bound /// asserted here would be measuring the seed. pub const DEFAULT_SAMPLE: usize = 250_000; @@ -96,9 +96,9 @@ const PP_CURVE_POINTS: usize = 200; /// Upper q-value bounds the FDR curve can be drawn over, widest first. /// -/// Every one of these gets its own `Q_CURVE_POINTS` grid — see +/// Every one of these gets its own `Q_CURVE_POINTS` grid -- see /// [`curves::qvalue_curves`] for why a zoom cannot just be a slice of the wide -/// curve — which is affordable because they share one sort. +/// curve -- which is affordable because they share one sort. const Q_ZOOMS: [f64; 4] = [1.0, 0.1, 0.05, 0.01]; /// Which of [`Q_ZOOMS`] the FDR tab opens on. @@ -140,7 +140,7 @@ impl Slot { /// One stored histogram, borrowed from the dashboard's count store. /// -/// The panel never owns or builds one of these — [`precompute_column`] fills +/// The panel never owns or builds one of these -- [`precompute_column`] fills /// the counts at init and [`Dashboard::hist`] slices into them. #[derive(Debug, Clone, Copy)] pub(crate) struct HistView<'a> { @@ -234,7 +234,7 @@ pub struct Dashboard { impl Dashboard { /// Materialize everything the TUI shows. /// - /// `sample` is the pass-B row count — [`DEFAULT_SAMPLE`] unless the caller + /// `sample` is the pass-B row count -- [`DEFAULT_SAMPLE`] unless the caller /// has a reason. At or above `view.n_rows()` every row is taken in order, /// which makes the histograms, clip ranges and per-feature AUCs exact /// rather than sampled. @@ -320,7 +320,7 @@ impl Dashboard { column * SLOTS_PER_COLUMN + local_slot(t.index(), usize::from(clip)) } - /// The stored histogram for a column. Pure indexing — this is the whole of + /// The stored histogram for a column. Pure indexing -- this is the whole of /// what a redraw does. pub(crate) fn hist(&self, column: usize, t: Axis, clip: bool) -> HistView<'_> { let slot = self.slot_index(column, t, clip); @@ -420,8 +420,8 @@ fn all_rows_tables(view: &RescoreView<'_>) -> AllRows { } /// Deterministic mixed LCG. Two runs over the same data must show the same -/// histograms — a sample that moved between runs would be indistinguishable -/// from a bug — and this crate carries no `rand` dependency. +/// histograms -- a sample that moved between runs would be indistinguishable +/// from a bug -- and this crate carries no `rand` dependency. struct Lcg(u64); impl Lcg { @@ -685,7 +685,7 @@ pub(crate) mod tests { use super::*; use crate::view::ThresholdRow; - /// A synthetic run. THE definition of what test rows look like — the + /// A synthetic run. THE definition of what test rows look like -- the /// alternating labels, the perfectly-separating score and the /// target/decoy q-values are asserted against from two modules, so they /// get one home rather than a copy in each. @@ -805,7 +805,7 @@ pub(crate) mod tests { /// Zooming has to re-grid, not re-slice. Every zoom keeps a full /// `Q_CURVE_POINTS` grid over its own range, and the y axis rescales with - /// it — a `q <= 0.01` panel still scaled to the q = 1 target count would + /// it -- a `q <= 0.01` panel still scaled to the q = 1 target count would /// draw the part being zoomed into flat against the axis. #[test] fn zooming_the_fdr_curve_regrids_and_rescales() { @@ -898,7 +898,7 @@ pub(crate) mod tests { /// Both sampling regimes, on a column whose value *is* its row index. /// /// At or above the run size every row is taken in order, which is what makes - /// small runs — and most of these tests — exact rather than approximate. + /// small runs -- and most of these tests -- exact rather than approximate. #[test] fn gather_sample_takes_every_row_or_a_random_draw_across_the_whole_run() { let f = fixture(10_000, &[("a", &|i, _| i as f64)]); @@ -1085,7 +1085,7 @@ pub(crate) mod tests { /// The unclipped axis comes from pass A's exact all-rows values, and the /// sample is a subset of those rows, so nothing sampled can fall outside - /// it — for any transform, including the two that are not monotone. + /// it -- for any transform, including the two that are not monotone. #[test] fn nothing_sampled_ever_falls_outside_the_unclipped_axis() { let f = fixture( diff --git a/rust/rescore_dash/src/stats.rs b/rust/rescore_dash/src/stats.rs index 4e8698eb..08584d02 100644 --- a/rust/rescore_dash/src/stats.rs +++ b/rust/rescore_dash/src/stats.rs @@ -123,7 +123,7 @@ impl ColumnStats { self.hi >= self.lo } - /// `|Cohen's d|` — pooled-SD standardized mean difference. NaN when either + /// `|Cohen's d|` -- pooled-SD standardized mean difference. NaN when either /// class contributed no finite value; `0.0` for a constant column, where /// there is no difference to standardize. pub(crate) fn cohens_d(&self) -> f64 { @@ -405,7 +405,7 @@ mod tests { } /// Ties share the mid-rank of their run, so an all-tied column collapses to - /// 50 regardless of length — not to 0, and not spread across the axis. + /// 50 regardless of length -- not to 0, and not spread across the axis. #[test] fn mid_rank_percentiles_map_an_all_tied_column_entirely_to_fifty() { for len in [1, 2, 3, 4, 10, 11] { diff --git a/rust/rescore_dash/src/transform.rs b/rust/rescore_dash/src/transform.rs index 1c8f5f42..8a8eb509 100644 --- a/rust/rescore_dash/src/transform.rs +++ b/rust/rescore_dash/src/transform.rs @@ -2,7 +2,7 @@ //! //! A transform never errors. Values it cannot map (non-positives under a log, //! negatives under a square root, anything non-finite) are dropped, and the -//! caller reports the drop count in the panel subtitle — a silently shrinking +//! caller reports the drop count in the panel subtitle -- a silently shrinking //! histogram would misread as "these rows do not exist". //! //! [`XTransform`] is applied once, at init, over a sorted sample. (`YTransform` @@ -96,7 +96,7 @@ impl XTransform { } } - /// Whether a *finite* `v` is inside this transform's domain — exactly the + /// Whether a *finite* `v` is inside this transform's domain -- exactly the /// condition [`Self::apply`] tests. pub fn accepts(self, v: f64) -> bool { match self { diff --git a/rust/rescore_dash/src/ui.rs b/rust/rescore_dash/src/ui.rs index 63c055e1..3b03e8ab 100644 --- a/rust/rescore_dash/src/ui.rs +++ b/rust/rescore_dash/src/ui.rs @@ -3,7 +3,7 @@ //! Every panel is a lookup into the precomputed [`crate::precompute::Dashboard`]: //! histogram bins, axis ranges, curves, threshold tables, panel titles and //! table cells were all computed before the TUI opened. A frame indexes arrays -//! and draws — a keystroke costs a redraw and not a re-scan. +//! and draws -- a keystroke costs a redraw and not a re-scan. use crate::app::{ App, @@ -77,7 +77,7 @@ pub fn draw(frame: &mut Frame, app: &mut App) { let selected = app.tab().index(); // Full width, which is why the run-level basis goes here and not into a // histogram subtitle that the panel border would truncate. - let banner = format!("rescore — {}", app.dash.labels.basis); + let banner = format!("rescore -- {}", app.dash.labels.basis); frame.render_widget( Tabs::new(titles) .select(selected) @@ -368,7 +368,7 @@ mod tests { )) } - /// A smoke test over the whole lookup surface — every axis and both clip + /// A smoke test over the whole lookup surface -- every axis and both clip /// settings on every tab. It asserts only that a frame draws, and that the /// tab that drew it says which one it is: a slot the precompute left /// unplottable or an axis range it could not derive panics on index here @@ -406,7 +406,7 @@ mod tests { } /// The features tab is the only one with a sort, and `S` only flips the - /// direction — which has to appear on screen, or the key silently changes + /// direction -- which has to appear on screen, or the key silently changes /// what the table means. #[test] fn the_features_tab_names_its_sort_key_and_direction() { diff --git a/rust/speclib_build_cli/src/config.rs b/rust/speclib_build_cli/src/config.rs index 0c24bff1..b399c92c 100644 --- a/rust/speclib_build_cli/src/config.rs +++ b/rust/speclib_build_cli/src/config.rs @@ -196,7 +196,7 @@ impl Default for FiltersConfig { #[derive(Debug, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct SpeclibBuildConfig { - // Inputs — not directly deserialised from TOML but set after merging CLI args. + // Inputs -- not directly deserialised from TOML but set after merging CLI args. // URIs: either local paths or remote (s3://...). #[serde(skip)] pub fasta: Option, diff --git a/rust/speclib_build_cli/src/dedup.rs b/rust/speclib_build_cli/src/dedup.rs index ddc2e2b8..424a5f6b 100644 --- a/rust/speclib_build_cli/src/dedup.rs +++ b/rust/speclib_build_cli/src/dedup.rs @@ -21,7 +21,7 @@ impl PeptideDedup { bloom.set(seq); buckets.entry(key).or_default().push(slice); } else { - // Maybe seen — check bucket + // Maybe seen -- check bucket let bucket = buckets.entry(key).or_default(); if !bucket .iter() diff --git a/rust/speclib_build_cli/src/entry.rs b/rust/speclib_build_cli/src/entry.rs index e5686aa6..68a7668e 100644 --- a/rust/speclib_build_cli/src/entry.rs +++ b/rust/speclib_build_cli/src/entry.rs @@ -247,7 +247,7 @@ mod tests { #[test] fn test_build_entry_skipped_too_few_ions() { - // Only 1 ion — min_ions = 3 → should return None. + // Only 1 ion -- min_ions = 3 → should return None. let fragment = FragmentPrediction { annotations: vec!["y3^1".to_string()], mzs: vec![400.0], diff --git a/rust/speclib_build_cli/src/koina/mod.rs b/rust/speclib_build_cli/src/koina/mod.rs index 6e4ff811..8fc44fd6 100644 --- a/rust/speclib_build_cli/src/koina/mod.rs +++ b/rust/speclib_build_cli/src/koina/mod.rs @@ -91,7 +91,7 @@ impl KoinaClient { let status = resp.status(); - // Client errors (4xx) — no point retrying. + // Client errors (4xx) -- no point retrying. if status.is_client_error() { let body = resp.text().await.unwrap_or_default(); return Err(format!( @@ -111,7 +111,7 @@ impl KoinaClient { }); } - // 5xx or other non-success — record and retry. + // 5xx or other non-success -- record and retry. last_err = format!("Koina request failed with status {status}"); } diff --git a/rust/speclib_build_cli/src/mods.rs b/rust/speclib_build_cli/src/mods.rs index 0be51883..a1c7d7cb 100644 --- a/rust/speclib_build_cli/src/mods.rs +++ b/rust/speclib_build_cli/src/mods.rs @@ -315,7 +315,7 @@ mod tests { #[test] fn test_expand_variable_mods_respects_fixed_mods() { - // Sequence already has a fixed C[U:4] — variable M should still work. + // Sequence already has a fixed C[U:4] -- variable M should still work. let var_m = Modification::parse("M[U:35]").unwrap(); let sequence = "PEPTMC[U:4]IDMEK"; let results = expand_variable_mods(sequence, &[var_m], 1); diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs index 01537c50..b82f5674 100644 --- a/rust/speclib_build_cli/src/pipeline.rs +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -63,7 +63,7 @@ async fn flush_batch( return Ok(()); } - // Koina/Prosit accepts UNIMOD notation — convert our short form [U:N] → [UNIMOD:N] + // Koina/Prosit accepts UNIMOD notation -- convert our short form [U:N] → [UNIMOD:N] let inputs: Vec = batch .iter() .map(|item| PredictionInput { @@ -159,7 +159,7 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box Result<(), Box` does not extend the /// borrow beyond the handle's lifetime. Do NOT store `&Path` derived from /// `as_ref()` past the end of the scope that holds the handle. @@ -139,7 +139,7 @@ fn sweep_stale(root: &Path, age_hours: u64) -> Result<(), StageError> { let mtime = meta.modified().ok(); if has_lock { // Live session. Only force-remove if it's very stale (2x threshold) - // AND still claims to be locked — best-effort for crashed runs. + // AND still claims to be locked -- best-effort for crashed runs. if let Some(mt) = mtime { let hard_threshold = std::time::SystemTime::now() .checked_sub(std::time::Duration::from_secs(age_hours * 2 * 3600)) diff --git a/rust/tims_stage/src/download.rs b/rust/tims_stage/src/download.rs index a2beb784..07683541 100644 --- a/rust/tims_stage/src/download.rs +++ b/rust/tims_stage/src/download.rs @@ -1,7 +1,7 @@ //! Stream a URI to a local file. //! //! For URIs that may exceed RAM (speclib parquet, large FASTAs), this is the -//! right primitive — it streams via `StorageProvider::get_to_file` for remote +//! right primitive -- it streams via `StorageProvider::get_to_file` for remote //! and is a plain file copy for local. Unlike [`crate::open_reader`] it does //! not buffer the whole payload in memory, so no size cap is imposed. diff --git a/rust/tims_stage/src/error.rs b/rust/tims_stage/src/error.rs index 8ff69b0f..895e70a5 100644 --- a/rust/tims_stage/src/error.rs +++ b/rust/tims_stage/src/error.rs @@ -44,7 +44,7 @@ pub enum StageError { PayloadTooLarge { uri: String, size: u64, cap: u64 }, } -/// Strip query strings from a URI before embedding in an error message — +/// Strip query strings from a URI before embedding in an error message -- /// presigned-style URLs carry credentials in the query. pub(crate) fn redact_uri(uri: &str) -> String { match uri.split_once('?') { diff --git a/rust/tims_stage/src/load.rs b/rust/tims_stage/src/load.rs index 4f32815a..07864436 100644 --- a/rust/tims_stage/src/load.rs +++ b/rust/tims_stage/src/load.rs @@ -2,8 +2,8 @@ //! //! `load_raw` is the one place raw dispatch happens. It sniffs the URI via the //! reader registry, asks the chosen reader for its [`Manifest`] (the files it -//! needs), materializes those — in place for local inputs, or by fetching -//! exactly the declared files for remote ones — and calls `read`. Transport +//! needs), materializes those -- in place for local inputs, or by fetching +//! exactly the declared files for remote ones -- and calls `read`. Transport //! never guesses vendor shape: it fetches what the reader declared, by name. //! //! Lives in `tims_stage` (one crate above `timscentroid`, where the registry @@ -84,7 +84,7 @@ pub fn load_raw( }); } - // Local: canonicalize first so RELATIVE paths resolve — `local_uri` + // Local: canonicalize first so RELATIVE paths resolve -- `local_uri` // (sniff) and `local_in_place` (read) both require an absolute path. This // is the single place all entry points funnel through, so relative inputs // work uniformly (`read_index`, `load_index_auto`, the pyo3 binding, …). @@ -257,7 +257,7 @@ mod tests { let dotd = src.path().join("sample.d"); std::fs::create_dir(&dotd).unwrap(); std::fs::write(dotd.join("analysis.tdf"), b"tdf").unwrap(); - // analysis.tdf_bin intentionally absent — must error, not silently skip. + // analysis.tdf_bin intentionally absent -- must error, not silently skip. let backend = PerRunTempdir::new(StagingConfig::default()).unwrap(); let err = stage_manifest(&backend, &dotd_manifest(&dotd)).unwrap_err(); diff --git a/rust/tims_stage/src/open.rs b/rust/tims_stage/src/open.rs index 9f691039..03caa146 100644 --- a/rust/tims_stage/src/open.rs +++ b/rust/tims_stage/src/open.rs @@ -8,7 +8,7 @@ //! single buffer. //! //! **This path is only appropriate for small inputs** (speclibs, FASTAs, -//! peptide lists — typically MBs to low-GB). The default cap is +//! peptide lists -- typically MBs to low-GB). The default cap is //! [`DEFAULT_IN_MEMORY_CAP`] (4 GiB); remote payloads that exceed this abort //! with [`StageError::PayloadTooLarge`] *before* the full GET. For streaming //! reads of larger objects (e.g. raw `.d` bundles), use the tar / prefix @@ -44,7 +44,7 @@ pub fn open_reader(uri: &str) -> Result, StageError> { /// Cheap existence probe for a URI. Remote URIs dispatch a HEAD through the /// provider; local paths call `Path::exists`. Network failures surface as -/// [`StageError::Transport`] — only `NotFound` translates to `Ok(false)`. +/// [`StageError::Transport`] -- only `NotFound` translates to `Ok(false)`. pub fn uri_exists(uri: &str) -> Result { if is_remote_uri(uri) { let (loc, key) = split_uri(uri)?; diff --git a/rust/tims_stage/src/resolve.rs b/rust/tims_stage/src/resolve.rs index 77f947f0..8b240c99 100644 --- a/rust/tims_stage/src/resolve.rs +++ b/rust/tims_stage/src/resolve.rs @@ -21,13 +21,13 @@ use timscentroid::{ #[derive(Debug)] pub enum Resolved { - /// A prebuilt `.idx` (local or remote) — load directly, no reader dispatch. + /// A prebuilt `.idx` (local or remote) -- load directly, no reader dispatch. Idx { loc: StorageLocation }, /// A raw vendor artifact (local path or `s3://…`). Handed to /// [`crate::load::load_raw`], which sniffs the reader and manifest-stages /// remote inputs. Carries the canonical URI verbatim. Raw { uri: String }, - /// A tarred `.d` container — extracted to a tempdir first, then read as a + /// A tarred `.d` container -- extracted to a tempdir first, then read as a /// local `.d`. (Bruker-specific transport.) Tar { spec: SourceSpec }, } @@ -62,7 +62,7 @@ pub fn resolve(uri: &str) -> Result { } // No sidecar. `.tar` is a container (extract first); everything else is a - // raw artifact handed to the reader registry via `load_raw` — no vendor + // raw artifact handed to the reader registry via `load_raw` -- no vendor // shape decided here. match (shape.loc, shape.name) { (_, NameKind::Raw) => Ok(Resolved::Raw { diff --git a/rust/tims_stage/src/tar.rs b/rust/tims_stage/src/tar.rs index c4fdb40a..825acab7 100644 --- a/rust/tims_stage/src/tar.rs +++ b/rust/tims_stage/src/tar.rs @@ -28,7 +28,7 @@ const PREFETCH: usize = 64 * 1024; /// Abstraction over "something we can pull byte ranges out of". /// /// `read_range` is for small header reads (up to a few KiB). For large -/// payload copies (often GBs), use `copy_range_to_file` which streams — the +/// payload copies (often GBs), use `copy_range_to_file` which streams -- the /// S3 impl routes that through one range-GET whose response body writes /// directly to disk. Never loop `read_range` for big payloads: each call /// is a separate HTTP GET on S3. @@ -234,10 +234,10 @@ fn basename_of(path: &str) -> &str { /// Returns a map `basename -> (payload_offset, payload_size)`. /// /// Typeflag handling: -/// - `'0'` / `0` : regular file — record if basename is required. -/// - `'x'` / `'g'`: pax extended header — skip payload. -/// - `'L'` / `'K'`: GNU long-name record — hard error. -/// - anything else: unknown — skip payload, don't record. +/// - `'0'` / `0` : regular file -- record if basename is required. +/// - `'x'` / `'g'`: pax extended header -- skip payload. +/// - `'L'` / `'K'`: GNU long-name record -- hard error. +/// - anything else: unknown -- skip payload, don't record. /// /// Permissive-by-default; the corpus-inventory step will tell us which /// branches can be pruned later. @@ -267,9 +267,9 @@ fn walk_tar_index(reader: &mut dyn TarReader) -> Result { /* pax extended header — skip payload */ } + b'x' | b'g' => { /* pax extended header -- skip payload */ } b'L' | b'K' => return Err(StageError::UnsupportedTarFeature), - _ => { /* unknown typeflag — skip payload */ } + _ => { /* unknown typeflag -- skip payload */ } } offset = payload_offset + padded; if REQUIRED.iter().all(|r| out.contains_key(*r)) { diff --git a/rust/tims_stage/src/upload.rs b/rust/tims_stage/src/upload.rs index 41033fe4..8a03195b 100644 --- a/rust/tims_stage/src/upload.rs +++ b/rust/tims_stage/src/upload.rs @@ -4,7 +4,7 @@ //! small outputs** (result JSONs, parquet shards, run reports). For the //! default cap see [`DEFAULT_UPLOAD_CAP`]. To stream a large `.d` bundle //! instead, bundle it into a tar and use the staging pipeline in reverse -//! (not yet implemented — avoid pointing `upload_file` at multi-GB payloads). +//! (not yet implemented -- avoid pointing `upload_file` at multi-GB payloads). use crate::common::transport_err; use crate::error::StageError; diff --git a/rust/tims_stage/src/uri.rs b/rust/tims_stage/src/uri.rs index def6e5f3..14176b8a 100644 --- a/rust/tims_stage/src/uri.rs +++ b/rust/tims_stage/src/uri.rs @@ -4,7 +4,7 @@ use crate::error::StageError; use std::path::Path; use timscentroid::StorageLocation; -/// Where an URI points — local filesystem or remote object store. +/// Where an URI points -- local filesystem or remote object store. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum LocKind { Local, @@ -14,9 +14,9 @@ pub(crate) enum LocKind { /// What kind of artifact the URI names, by suffix. /// /// Only two categories matter to staging/resolution: a prebuilt `.idx`, a -/// `.tar` container, or `Raw` — any vendor artifact. Vendor SHAPE (`.d`, +/// `.tar` container, or `Raw` -- any vendor artifact. Vendor SHAPE (`.d`, /// `.mzML`, `.wiff`) is NOT classified here; that lives in each reader's -/// `sniff`. `Raw` never errors on an unfamiliar suffix — the reader registry +/// `sniff`. `Raw` never errors on an unfamiliar suffix -- the reader registry /// rejects genuinely-unknown formats at dispatch time (loudly), not the parser. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum NameKind { diff --git a/rust/tims_stage/tests/minio_smoke.rs b/rust/tims_stage/tests/minio_smoke.rs index 5ffbe4a1..769d105b 100644 --- a/rust/tims_stage/tests/minio_smoke.rs +++ b/rust/tims_stage/tests/minio_smoke.rs @@ -13,7 +13,7 @@ use common::minio; /// /// Fixtures are seeded by the test itself (two tiny objects) so no external /// bucket state is assumed. A missing `MINIO_TEST_ENDPOINT` is a hard failure -/// when the test runs — silent passes would mask CI misconfiguration. +/// when the test runs -- silent passes would mask CI misconfiguration. #[test] #[ignore = "requires MinIO endpoint + aws feature; run explicitly with --ignored"] fn stage_manifest_against_minio() { diff --git a/rust/timscentroid/src/centroiding.rs b/rust/timscentroid/src/centroiding.rs index 1c1e4731..ffdc9954 100644 --- a/rust/timscentroid/src/centroiding.rs +++ b/rust/timscentroid/src/centroiding.rs @@ -44,7 +44,7 @@ pub struct CentroidingConfig { /// after which the centroiding will stop early (instead of going /// through all peaks, which will very likely be noise). /// A number ~200 seems to work well in practice. **`0` disables - /// early-stop** — the whole frame is clustered and only `max_peaks` + /// early-stop** -- the whole frame is clustered and only `max_peaks` /// can truncate it. Disabling is the right choice under a tight /// `mz_ppm_tol`, where most peaks are singletons and early-stop would /// otherwise clip real signal (see `IndexingCentroidingConfig`). @@ -67,7 +67,7 @@ impl Default for CentroidingConfig { /// MS1 and MS2 are centroided independently: MS1 favors precursor m/z /// precision (peak counts are small, so a tight merge is cheap), while MS2 /// keeps a tight m/z merge with early-stop disabled and a moderate -/// `max_peaks` cap — the setting that preserves fragment signal without the +/// `max_peaks` cap -- the setting that preserves fragment signal without the /// peak-count explosion of a full raw pass. /// /// Use [`IndexingCentroidingConfig::uniform`] to apply one config to both @@ -100,7 +100,7 @@ impl Default for IndexingCentroidingConfig { max_peaks: 50_000, mz_ppm_tol: 1.0, im_pct_tol: 3.0, - early_stop_iterations: 0, // disabled — see field doc + early_stop_iterations: 0, // disabled -- see field doc }, } } diff --git a/rust/timscentroid/src/dimension.rs b/rust/timscentroid/src/dimension.rs index 9ae9fdf4..e9073b73 100644 --- a/rust/timscentroid/src/dimension.rs +++ b/rust/timscentroid/src/dimension.rs @@ -13,7 +13,7 @@ /// and take the mobility-less fallback path. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum MobilityKind { - /// TIMS 1/K0 — the only searchable kind (current behavior). + /// TIMS 1/K0 -- the only searchable kind (current behavior). Ook0, /// No IM axis (mzML, no-IM library); the stored scalar is a sentinel. Absent, diff --git a/rust/timscentroid/src/geometry.rs b/rust/timscentroid/src/geometry.rs index 779a6405..08f4c967 100644 --- a/rust/timscentroid/src/geometry.rs +++ b/rust/timscentroid/src/geometry.rs @@ -53,7 +53,7 @@ pub enum RingShape { mz_lo: (f64, f64), mz_hi: (f64, f64), }, - /// Generic polygon fallback. Not expected on current instruments — + /// Generic polygon fallback. Not expected on current instruments -- /// loading one fires a single-shot `tracing::warn!` asking the user /// to report the ring shape so we can extend the classifier. Polygon(Polygon), diff --git a/rust/timscentroid/src/indexing.rs b/rust/timscentroid/src/indexing.rs index 4c9644d0..b742789d 100644 --- a/rust/timscentroid/src/indexing.rs +++ b/rust/timscentroid/src/indexing.rs @@ -420,7 +420,7 @@ impl IndexedTimstofPeaks { /// /// Four length-aligned columns carried together as a single value. Fields /// are private so the "all columns same length" invariant can't be broken -/// by a direct push to one column — callers go through `push`, which +/// by a direct push to one column -- callers go through `push`, which /// advances all four in lockstep. #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct PeakColumns { @@ -456,7 +456,7 @@ impl PeakColumns { self.cycle_index.reserve(n); } - /// Atomic row append — all four columns advance together. + /// Atomic row append -- all four columns advance together. #[inline] pub fn push(&mut self, mz: f32, intensity: f32, mobility: MobInt, cycle_index: T) { self.mz.push(mz); @@ -475,7 +475,7 @@ impl PeakColumns { } } - /// Consume into raw column Vecs. Private — drops the bundled invariant; + /// Consume into raw column Vecs. Private -- drops the bundled invariant; /// used only by the AoS sort round-trip inside this module. fn into_parts(self) -> (Vec, Vec, Vec, Vec) { (self.mz, self.intensity, self.mobility, self.cycle_index) @@ -892,7 +892,7 @@ pub fn dump_for_each_peak_funnel(label: &str) { } } -/// Zero the funnel counters — call between phases so each phase's dump +/// Zero the funnel counters -- call between phases so each phase's dump /// shows its own contribution, not cumulative. No-op when `query-instr` /// is disabled. pub fn reset_for_each_peak_funnel() { @@ -971,7 +971,7 @@ fn check_bucket_sorted_heuristic_aos( ) -> bool { // 1. max mz of each bucket <= min mz of the next bucket // 2. each bucket is sorted by cycle_index (fully checked on bucket 0, - // first/last only on later buckets — cheap heuristic) + // first/last only on later buckets -- cheap heuristic) let mut last_max = f32::MIN; let buckets_ordered = peaks.chunks(bucket_size).all(|bucket| { let curr_min = bucket @@ -1046,7 +1046,7 @@ fn apply_mob_mask( /// Per-bucket inner scan for `for_each_peak`. `mz_filter = Unrestricted` means /// "the whole bucket is known to be inside the query mz range, skip the per-peak /// mz compare". With `#[inline(always)]` + the `OptionallyRestricted` split at -/// each call site, LLVM specializes two code paths — one that branches on mz per +/// each call site, LLVM specializes two code paths -- one that branches on mz per /// peak, one that doesn't. /// /// Each filter reads only the column it needs (mz or mobility). The full @@ -1122,7 +1122,7 @@ fn scan_bucket_slice( impl IndexedPeakGroup { /// Query peaks based on m/z, rt, and im ranges. /// - /// Returns an iterator of owned `IndexedPeak` — peaks are materialized + /// Returns an iterator of owned `IndexedPeak` -- peaks are materialized /// from the SoA columns on demand. pub fn query_peaks( &self, @@ -1134,7 +1134,7 @@ impl IndexedPeakGroup { } /// Callback-style peak scan. Mirrors `query_peaks` but fuses the - /// consumer body into the inner bucket loop — no + /// consumer body into the inner bucket loop -- no /// `Iterator::next` call boundary per peak. Per the flamegraph, /// `QueryPeaksIterator::next` takes ~63% of wall at /// `RAYON_NUM_THREADS=1`; inlining the consumer via `#[inline]` @@ -1213,7 +1213,7 @@ impl IndexedPeakGroup { bucket_size ); } - // Cheap but worth it — <40ms even on Hela. + // Cheap but worth it -- <40ms even on Hela. assert!(peaks.iter().all(|x| x.intensity >= 0.0)); let max_cycle = T::new(cycle_to_rt_ms.len() as u32 - 1); assert!(peaks.iter().all(|x| x.cycle_index <= max_cycle)); @@ -1769,7 +1769,7 @@ mod tests { } /// The matched-peak set returned by `for_each_peak` (the production query - /// path) must NOT depend on `bucket_size` — bucketing is an index-layout + /// path) must NOT depend on `bucket_size` -- bucketing is an index-layout /// detail, not a filter. Regression guard for a bucket-boundary bug where /// `query_bucket_range` used `end() <= mz_range.start()` and dropped peaks /// at exactly the query's (inclusive) lower m/z bound. Stresses duplicated diff --git a/rust/timscentroid/src/reader/mod.rs b/rust/timscentroid/src/reader/mod.rs index d75e339a..c09eac82 100644 --- a/rust/timscentroid/src/reader/mod.rs +++ b/rust/timscentroid/src/reader/mod.rs @@ -4,7 +4,7 @@ //! claims ([`RawReader::sniff`]), which artifacts belong together //! ([`RawReader::manifest`]), and how to build the in-memory index //! ([`RawReader::read`]). The [`ReaderRegistry`] picks a backend for a URI by -//! sniffing — vendor suffix/scheme knowledge lives ONLY in each reader, never +//! sniffing -- vendor suffix/scheme knowledge lives ONLY in each reader, never //! in the staging/resolve layer. Adding a format is one `impl RawReader` + one //! registry push. @@ -65,7 +65,7 @@ impl ResolvedSource { Self { dir, entry } } - /// Borrow a local artifact in place — no staging. `dir` is the artifact's + /// Borrow a local artifact in place -- no staging. `dir` is the artifact's /// parent, `entry` its own name (a `.d` dir or an `.mzML` file). pub fn local_in_place(path: &Path) -> Result { let dir = path @@ -132,7 +132,7 @@ pub trait RawReader: Send + Sync { } /// The exact artifact set (entry + required + optional). Vendor "which - /// files belong together" knowledge lives here, nowhere else — the staging + /// files belong together" knowledge lives here, nowhere else -- the staging /// layer fetches exactly these, by name, so transport never guesses shape. fn manifest(&self, uri: &Uri) -> Manifest; @@ -144,7 +144,7 @@ pub trait RawReader: Send + Sync { /// Extensions this reader claims for single-FILE inputs, for building /// file-dialog / help filters (no leading dot, e.g. `["mzML", "mzml"]`). - /// Directory formats (`.d`) return `&[]` — they are picked as folders. + /// Directory formats (`.d`) return `&[]` -- they are picked as folders. fn file_extensions(&self) -> &'static [&'static str] { &[] } @@ -172,7 +172,7 @@ impl ReaderRegistry { Self(v) } - /// The union of single-file extensions claimed by all registered readers — + /// The union of single-file extensions claimed by all registered readers -- /// the single source of truth for "what raw files can we open" (dialog /// filters, help text). Directory formats (`.d`) are excluded. pub fn file_extensions(&self) -> Vec<&'static str> { @@ -249,7 +249,7 @@ pub fn local_uri(path: &Path) -> Result { }) } -/// Bruker TDF `.d` reader — wraps [`IndexedTimstofPeaks::from_timstof_file`]. +/// Bruker TDF `.d` reader -- wraps [`IndexedTimstofPeaks::from_timstof_file`]. /// /// MVP regression-safety: the manifest declares the minimal read set /// (`analysis.tdf` + `analysis.tdf_bin`) for documentation and future diff --git a/rust/timscentroid/src/reader/mzdata.rs b/rust/timscentroid/src/reader/mzdata.rs index cda5ba47..5f91a966 100644 --- a/rust/timscentroid/src/reader/mzdata.rs +++ b/rust/timscentroid/src/reader/mzdata.rs @@ -43,20 +43,20 @@ use crate::rt_mapping::{ }; /// CV accessions for the ion-mobility-type scan param. -const ACC_INVERSE_REDUCED_IM: u32 = 1002815; // 1/K0 — searchable +const ACC_INVERSE_REDUCED_IM: u32 = 1002815; // 1/K0 -- searchable const ACC_FAIMS_CV: u32 = 1001581; // FAIMS compensation voltage const ACC_DRIFT_TIME: u32 = 1002476; // drift time (not 1/K0) /// Bucket size for peak groups, matching the TDF path. const BUCKET_SIZE: usize = 4096; -/// The `1.0` mobility placeholder stored on every peak of a non-`Ook0` run — +/// The `1.0` mobility placeholder stored on every peak of a non-`Ook0` run -- /// present only to satisfy `IndexedPeakGroup::new` (non-NaN, ≥0); never used /// once the mobility filter is unrestricted. const MOBILITY_SENTINEL: f32 = 1.0; /// Reader for open mzML. (Native Thermo `.raw` via mzdata's `thermo` feature is -/// deferred — it needs `MZReader::open_path`, not the mzML-only `MzMLReader` +/// deferred -- it needs `MZReader::open_path`, not the mzML-only `MzMLReader` /// used here, and would extend `sniff` to claim `.raw`.) pub struct MzdataReader; @@ -103,7 +103,7 @@ impl RawReader for MzdataReader { /// Detect the mobility axis from a scan's `ion_mobility_type` param accession. /// Keys on the ACCESSION, not `has_ion_mobility()`: a real Astral file carries /// `FAIMS compensation voltage` (value 0), so `has_ion_mobility()` returns true -/// and `ion_mobility()` returns 0.0 — both would mislead. +/// and `ion_mobility()` returns 0.0 -- both would mislead. /// /// Returns `None` if the spectrum carries no scan yet (defer to a later /// spectrum); `Some(Absent)` when a scan exists but declares no IM-type param. @@ -206,7 +206,7 @@ pub fn from_mzml_file( } continue; }; - // Dedup windows by their bounds at millidalton (1e-3 m/z) precision — + // Dedup windows by their bounds at millidalton (1e-3 m/z) precision -- // fine enough to keep genuinely distinct DIA windows apart, coarse // enough that float jitter maps a recurring window to one bin. let key = ( @@ -310,7 +310,7 @@ fn push_peaks(spec: &impl SpectrumLike, cycle_index: T, out: &mut Ve } /// First strictly-overlapping window pair, if any. Half-open: adjacent DIA -/// windows SHARE an edge (`upper == next.lower`), which is NOT an overlap — only +/// windows SHARE an edge (`upper == next.lower`), which is NOT an overlap -- only /// a strict interior intersection (`a.lo < b.hi && b.lo < a.hi`) counts, so a /// normal contiguous DIA scheme does not trip it. fn first_overlapping_pair(windows: &[WindowAccum]) -> Option<(usize, usize)> { @@ -329,7 +329,7 @@ fn warn_on_overlapping_windows(windows: &[WindowAccum]) { let (a, b) = (&windows[i], &windows[j]); warn!( "mzML: overlapping isolation windows ([{:.3},{:.3}] vs [{:.3},{:.3}]); \ - staggered/demux acquisition is out of scope — results may be approximate", + staggered/demux acquisition is out of scope -- results may be approximate", a.mz_start, a.mz_end, b.mz_start, b.mz_end ); } diff --git a/rust/timscentroid/src/reader/uri_util.rs b/rust/timscentroid/src/reader/uri_util.rs index 1c45f051..d39113a3 100644 --- a/rust/timscentroid/src/reader/uri_util.rs +++ b/rust/timscentroid/src/reader/uri_util.rs @@ -13,7 +13,7 @@ use http::uri::PathAndQuery; /// /// The `parse`/`from_parts` panics are effectively unreachable: `base` already /// parsed as a `Uri`, and callers only ever append literal path segments -/// (`analysis.tdf`, `.scan`) to an already-valid, already-encoded path — so the +/// (`analysis.tdf`, `.scan`) to an already-valid, already-encoded path -- so the /// result is always a valid URI path. A panic here means a caller passed an /// unencoded segment, which is a bug, not runtime input. fn with_path(base: &Uri, new_path: &str) -> Uri { @@ -26,7 +26,7 @@ fn with_path(base: &Uri, new_path: &str) -> Uri { Uri::from_parts(parts).expect("reassembled URI is valid") } -/// Append `/segment` to the URI path — a child within a directory URI. +/// Append `/segment` to the URI path -- a child within a directory URI. /// `s3://bkt/sample.d` + `analysis.tdf` → `s3://bkt/sample.d/analysis.tdf`. pub fn child(base: &Uri, segment: &str) -> Uri { let path = base.path().trim_end_matches('/'); @@ -40,7 +40,7 @@ pub fn with_suffix(base: &Uri, suffix: &str) -> Uri { with_path(base, &format!("{path}{suffix}")) } -/// Replace the last path segment with `name` — a co-located sibling artifact. +/// Replace the last path segment with `name` -- a co-located sibling artifact. /// `.../a/foo.wiff` + `bar.data` → `.../a/bar.data`. pub fn sibling(base: &Uri, name: &str) -> Uri { let path = base.path().trim_end_matches('/'); diff --git a/rust/timscentroid/src/serialization/mod.rs b/rust/timscentroid/src/serialization/mod.rs index f5a2351b..632343ab 100644 --- a/rust/timscentroid/src/serialization/mod.rs +++ b/rust/timscentroid/src/serialization/mod.rs @@ -536,7 +536,7 @@ impl IndexedTimstofPeaks { storage: StorageProvider, meta: TimscentroidMetadata, ) -> Result { - // Always parallel — measured 144s serial vs ( cols: PeakColumnsView<'_, T>, @@ -658,7 +658,7 @@ fn write_peaks_to_parquet_bytes( /// Extend SoA column buffers from a single Arrow RecordBatch. /// /// Validates schema + mobility invariants (non-negative, non-NaN) at the -/// boundary — the inner scan loop assumes this. +/// boundary -- the inner scan loop assumes this. pub(crate) fn extend_soa_from_batch( dst: &mut PeakColumns, batch: &RecordBatch, diff --git a/rust/timscentroid/src/storage.rs b/rust/timscentroid/src/storage.rs index 7bf34b2b..87d4de12 100644 --- a/rust/timscentroid/src/storage.rs +++ b/rust/timscentroid/src/storage.rs @@ -173,7 +173,7 @@ impl StorageProvider { } /// Non-creating constructor for read-only callers. Does NOT `create_dir_all` - /// on a local path — use `new` when you want writes (which may implicitly + /// on a local path -- use `new` when you want writes (which may implicitly /// create the parent directory). pub fn open(location: StorageLocation) -> Result { let (store, is_local, prefix): (Arc, bool, String) = match location { @@ -225,7 +225,7 @@ impl StorageProvider { }) } - /// Fetch a specific byte range. Errors on short read — S3 returns 416 on + /// Fetch a specific byte range. Errors on short read -- S3 returns 416 on /// out-of-bounds, but `LocalFileSystem` silently truncates to EOF, so we /// post-check the returned length against the requested length. No /// pre-HEAD; the tar walker issues many small range GETs and doubling diff --git a/rust/timscentroid/src/utils.rs b/rust/timscentroid/src/utils.rs index 3986abb7..31a64611 100644 --- a/rust/timscentroid/src/utils.rs +++ b/rust/timscentroid/src/utils.rs @@ -5,7 +5,7 @@ use thiserror::Error; /// Non-negative non-NaN f16 mobility, stored as raw bits. /// /// Invariant: `bits` encodes a non-negative, non-NaN f16 value. -/// Enforced at construction via `try_new` / `from_f16` — every `MobInt` +/// Enforced at construction via `try_new` / `from_f16` -- every `MobInt` /// in flight has already passed the check. /// /// For values in the valid range, `u16` bit comparison matches `f16` @@ -14,7 +14,7 @@ use thiserror::Error; /// hot path SIMD-trivial. /// /// `#[repr(transparent)]` ensures `Vec` is byte-identical to -/// `Vec` — zero-cost reinterpretation in either direction when +/// `Vec` -- zero-cost reinterpretation in either direction when /// needed (though construction must still go through `try_new`). #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, @@ -55,7 +55,7 @@ impl MobInt { Self::try_new(m.to_bits()) } - /// Free reinterpret — same bits, same register. + /// Free reinterpret -- same bits, same register. #[inline(always)] pub fn to_f16(self) -> f16 { f16::from_bits(self.0) diff --git a/rust/timsquery/Cargo.toml b/rust/timsquery/Cargo.toml index 01ff008a..e4c50ca4 100644 --- a/rust/timsquery/Cargo.toml +++ b/rust/timsquery/Cargo.toml @@ -31,6 +31,9 @@ thiserror = { workspace = true } # Forward mzML/mzdata ingest support down to timscentroid, where the reader lives. # Not defaulted here (see timsseek_cli) so `--no-default-features` stays lean. mzdata = ["timscentroid/mzdata"] +# Constructors for the opaque arena handles (`models::test_handles`). Enabled +# only through a `dev-dependency`, so a shipped build still cannot mint one. +test-support = [] [dev-dependencies] tempfile = { workspace = true } diff --git a/rust/timsquery/src/models/aggregators/chromatogram_agg.rs b/rust/timsquery/src/models/aggregators/chromatogram_agg.rs index 80b186aa..7948e70c 100644 --- a/rust/timsquery/src/models/aggregators/chromatogram_agg.rs +++ b/rust/timsquery/src/models/aggregators/chromatogram_agg.rs @@ -19,30 +19,30 @@ use timscentroid::rt_mapping::{ }; use timscentroid::utils::TupleRange; -// TODO: rename to `ChromatogramAccumulator` — struct carries query scalars +// TODO: rename to `ChromatogramAccumulator` -- struct carries query scalars // (id, mobility_ook0, etc.) alongside the accumulated chromatograms, but the // "Collector" name dates from when it owned a full Target. The query // and accumulator roles are now structurally separated; a rename would match. #[derive(Debug, Clone, Serialize)] pub struct ChromatogramCollector { // Query scalars carried from the eg at reset time. - pub id: u64, + pub id: crate::models::OwnedSourceId, pub mobility_ook0: f32, pub rt_seconds: f32, pub precursor_mono_mz: f64, pub precursor_charge: u8, /// Cached from `Target::precursor_mz_limits()` at reset - /// (skips negative-isotope labels — do not derive from mono_mz + charge alone). + /// (skips negative-isotope labels -- do not derive from mono_mz + charge alone). pub precursor_mz_limits: (f64, f64), - // mz_order inside each array IS the (key, mz) list we need for iteration — + // mz_order inside each array IS the (key, mz) list we need for iteration -- // labels/mzs are NOT duplicated on the collector. pub precursors: MzMajorIntensityArray, pub fragments: MzMajorIntensityArray, pub rt_range_ms: TupleRange, /// MS1 peaks written into any precursor chromatogram cell during the - /// most recent `add_query`. Informational only — downstream fast-path + /// most recent `add_query`. Informational only -- downstream fast-path /// decisions key off `n_fragment_peaks_added`. pub n_precursor_peaks_added: u64, @@ -90,7 +90,7 @@ impl ChromatogramCollector { let fragments = MzMajorIntensityArray::try_new_empty(fragment_order, num_cycles, start.index())?; Ok(Self { - id: eg.output_id(), + id: eg.output_id().to_owned_id(), mobility_ook0: eg.mobility_ook0(), rt_seconds: eg.rt_seconds(), precursor_mono_mz: eg.mono_precursor_mz(), @@ -115,7 +115,7 @@ impl ChromatogramCollector { } /// Like `try_reset_with` but lets callers override `rt_seconds` / `mobility_ook0` - /// without rebuilding the source eg — replaces the `eg.clone().with_rt_seconds(..)` + /// without rebuilding the source eg -- replaces the `eg.clone().with_rt_seconds(..)` /// and `eg.clone().with_mobility(..)` clone-then-mutate pattern. pub fn try_reset_with_overrides( &mut self, @@ -136,7 +136,7 @@ impl ChromatogramCollector { return Err(DataProcessingError::ExpectedNonEmptyData); } - self.id = eg.output_id(); + self.id.set_from(eg.output_id()); self.mobility_ook0 = mobility_override.unwrap_or_else(|| eg.mobility_ook0()); self.rt_seconds = rt_override.unwrap_or_else(|| eg.rt_seconds()); self.precursor_mono_mz = eg.mono_precursor_mz(); @@ -219,10 +219,6 @@ impl ChromatogramCollector { } impl HasQueryData for ChromatogramCollector { - fn id(&self) -> u64 { - self.id - } - fn precursor_mz_limits(&self) -> (f64, f64) { self.precursor_mz_limits } diff --git a/rust/timsquery/src/models/aggregators/point_agg.rs b/rust/timsquery/src/models/aggregators/point_agg.rs index 672c55e4..17caac9f 100644 --- a/rust/timsquery/src/models/aggregators/point_agg.rs +++ b/rust/timsquery/src/models/aggregators/point_agg.rs @@ -10,7 +10,7 @@ const POINT_INLINE_CAP: usize = 13; #[derive(Debug, Clone, Serialize)] pub struct PointIntensityAggregator { - pub id: u64, + pub id: crate::models::OwnedSourceId, pub mobility_ook0: f32, pub rt_seconds: f32, pub precursor_mono_mz: f64, @@ -42,7 +42,7 @@ impl PointIntensityAggregator { fragment_mzs.push(mz); } Self { - id: eg.output_id(), + id: eg.output_id().to_owned_id(), mobility_ook0: eg.mobility_ook0(), rt_seconds: eg.rt_seconds(), precursor_mono_mz: eg.mono_precursor_mz(), @@ -58,10 +58,6 @@ impl PointIntensityAggregator { } impl HasQueryData for PointIntensityAggregator { - fn id(&self) -> u64 { - self.id - } - fn precursor_mz_limits(&self) -> (f64, f64) { self.precursor_mz_limits } diff --git a/rust/timsquery/src/models/aggregators/spectrum_agg.rs b/rust/timsquery/src/models/aggregators/spectrum_agg.rs index b30514df..15f5df9f 100644 --- a/rust/timsquery/src/models/aggregators/spectrum_agg.rs +++ b/rust/timsquery/src/models/aggregators/spectrum_agg.rs @@ -23,22 +23,22 @@ use std::ops::{ }; /// Inline-capacity target matching `Target`'s precursor/fragment -/// label TinyVecs — typical peptide ≤13 fragments / ≤3 precursors stays +/// label TinyVecs -- typical peptide ≤13 fragments / ≤3 precursors stays /// stack-resident. const SPEC_INLINE_CAP: usize = 13; -// TODO: rename to `SpectralAccumulator` — struct holds query scalars + +// TODO: rename to `SpectralAccumulator` -- struct holds query scalars + // label/mz lists alongside the accumulated intensities. "Collector" name // predates the Query/Accumulator split. #[derive(Debug, Clone, Serialize)] pub struct SpectralCollector { // Query scalars carried from eg at construction / reset. - pub id: u64, + pub id: crate::models::OwnedSourceId, pub mobility_ook0: f32, pub rt_seconds: f32, pub precursor_mono_mz: f64, pub precursor_charge: u8, - /// Cached from `Target::precursor_mz_limits()` — skips + /// Cached from `Target::precursor_mz_limits()` -- skips /// negative-isotope labels, do NOT recompute from mono_mz + charge. pub precursor_mz_limits: (f64, f64), // Labels + mzs: arrays carry only intensities, so we need separate storage. @@ -53,7 +53,7 @@ pub struct SpectralCollector { impl SpectralCollector { pub fn new(eg: &impl QueryGeom