Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chaintools"
version = "0.0.10"
version = "0.0.11"
edition = "2024"
authors = ["alejandrogzi <alejandrxgzi@gmail.com>"]
license = "MIT"
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</span>

<p align="center">
<a href="https://img.shields.io/badge/version-0.0.10-green" target="_blank">
<img alt="Version Badge" src="https://img.shields.io/badge/version-0.0.10-green">
<a href="https://img.shields.io/badge/version-0.0.11-green" target="_blank">
<img alt="Version Badge" src="https://img.shields.io/badge/version-0.0.11-green">
</a>
<a href="https://crates.io/crates/chaintools" target="_blank">
<img alt="Crates.io Version" src="https://img.shields.io/crates/v/chaintools">
Expand All @@ -29,7 +29,7 @@
<span>work with .chain files in Rust</span>
<br>
<br>
<a href="https://docs.rs/chaintools/0.0.10/chaintools/">docs</a> .
<a href="https://docs.rs/chaintools/0.0.11/chaintools/">docs</a> .
<a href="https://github.com/alejandrogzi/chaintools/tree/master/assets/usage/usage.md">usage</a> .
<a href="https://github.com/alejandrogzi/chaintools/tree/master/assets/tools">tools</a> .
<a href="https://genome.ucsc.edu/goldenpath/help/chain.html">chains</a>
Expand Down Expand Up @@ -60,8 +60,9 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
chaintools = { version = "0.0.10", features = ["mmap", "gzip", "parallel"] }
chaintools = { version = "0.0.11", features = ["mmap", "gzip", "parallel"] }
```

## Benchmarks

<div align="center">
Expand Down
24 changes: 24 additions & 0 deletions assets/changelog/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ All notable changes to **chaintools** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.11] - 2026-08-09

### Added

- **`stats` subcommand** — summarizes alignment amount, within-chain gaps,
query-strand counts, and chain fragmentation in one streaming pass.
Includes chain aligned-bp N50/L50, per-target top-1/top-3 chain
concentration and chains→50/90/95% counts, plus `--by-sequence` per-target
tables and top scored chains. Reads standard input when `--chain` is
omitted.

- **`compare` subcommand** — compares two chain files by their canonical
base-pair mappings (invariant to chain IDs, order, and harmless splitting),
reported separately from unique coverage, multi-mapped bp, continuity, and
gaps. Per-target agreement is available with `--by-sequence`. Sequence names
are interned and a fixed memory budget is checked before canonicalization,
so oversized inputs fail cleanly instead of OOMing.

### Documentation

- Tool docs for the two new subcommands in `assets/tools/`
(`stats.md`, `compare.md`).
- README "Statistics and comparison" section with both commands.

## [0.0.10] - 2026-08-08

### Added
Expand Down
155 changes: 155 additions & 0 deletions assets/tools/compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<p align="center">
<p align="center">
<img width=200 align="center" src="../logo.png" >
</p>

<span>
<h1 align="center">
chaintools
</h1>
</span>

<p align="center">
<a href="https://img.shields.io/badge/version-0.0.10-green" target="_blank">
<img alt="Version Badge" src="https://img.shields.io/badge/version-0.0.10-green">
</a>
<a href="https://crates.io/crates/chaintools" target="_blank">
<img alt="Crates.io Version" src="https://img.shields.io/crates/v/chaintools">
</a>
<a href="https://github.com/alejandrogzi/chaintools" target="_blank">
<img alt="GitHub License" src="https://img.shields.io/github/license/alejandrogzi/chaintools?color=blue">
</a>
<a href="https://crates.io/crates/chaintools" target="_blank">
<img alt="Crates.io Total Downloads" src="https://img.shields.io/crates/d/chaintools">
</a>
</p>
</p>


# chaintools compare

Compare two chain files by the exact base-pair relations they encode, separate
from how those relations are packaged into chains.

## Usage

```bash
chaintools compare --chain-a A.chain --chain-b B.chain
chaintools compare -a A.chain -b B.chain --by-sequence --top 3
```

Both inputs must be paths; there is no stdin mode.

## Options

- `--chain-a <PATH>` / `-a`: first chain file.
- `--chain-b <PATH>` / `-b`: second chain file.
- `--by-sequence`: add the per-target agreement table and top-chain lists.
- `--top <N>`: with `--by-sequence`, show the top `N` scored chains per target.
Default `3`.

## What is compared

Every aligned block is canonicalized into the base-to-base relation it
represents: a `(target, query, strand, diagonal)` key plus a target interval.
Within a key, overlapping and adjacent intervals are merged and duplicates are
removed. The result is independent of chain IDs, chain order, chain splitting,
and block splitting along the same mapping diagonal.

This means a mapping split across two chains compares equal to the same mapping
in one chain — but the two files' *continuity* differs, and `compare` reports
both facts independently. Chain counts, header overlap, and approximate
coordinate matching are never used as equivalence tests.

## Output

- **MAPPING AGREEMENT** — unique pair bp in each file, shared bp, A-only and
B-only bp, `A_retained_by_B = shared / A`, `B_retained_by_A = shared / B`,
pair Dice and pair Jaccard. The directional values are not called
precision/recall: neither file is ground truth.
- **COVERAGE / AMBIGUITY** — unique target and query bp covered by the
mappings, and bp mapped more than once on each side. This distinguishes
genuinely broader coverage from redundant multi-mapping.
- **CONTINUITY** — chains, blocks, chain aligned-bp N50/L50, largest chain,
median blocks per chain, and chromosome fragmentation (median target top-1
and top-3 share, median/max chains→90%), reusing the `stats` metrics.
- **GAPS** — target/query gap bp, gap-event counts, and largest gaps as A/B
rows with absolute and percent delta. Fewer gaps is not automatically
reported as better.
- **INTERPRETATION** — one or two lines drawn only from the measured
dimensions: mappings identical (possibly with a fragmentation difference),
or differing (Dice quoted, no winner declared); broader coverage with no
additional multi-mapping if that holds. There is no combined quality score.
- **BY TARGET** (with `--by-sequence`) — per-target continuity (chains,
top-1/top-3 share, N50, chains→90%) alongside per-target A-only/B-only/
shared bp and Dice, so divergences can be localized. Followed by the top
`N` scored chains of each file per target.

## Example: identical mappings, different fragmentation

A holds one 24-bp chain; B holds the same two 12-bp mappings as two chains:

```bash
$ chaintools compare -a frag_a.chain -b frag_b.chain
MAPPING AGREEMENT
A_unique_pair_bp 24
B_unique_pair_bp 24
shared_pair_bp 24
A_only_pair_bp 0
B_only_pair_bp 0
A_retained_by_B 100.00%
B_retained_by_A 100.00%
pair_Dice 100.00%
pair_Jaccard 100.00%

COVERAGE / AMBIGUITY
metric A B
unique_target_covered_bp 24 24
unique_query_covered_bp 24 24
target_bp_mapped_more_than_once 0 0
query_bp_mapped_more_than_once 0 0

CONTINUITY
metric A B
chains 1 2
blocks 2 2
chain_aligned_bp_n50 24 12
chain_aligned_bp_l50 1 1
largest_chain_aligned_bp 24 12
median_blocks_per_chain 2.0 1.0
median_target_top1_share 100.00% 50.00%
median_target_top3_share 100.00% 100.00%
median_target_chains_to_90pct 1.0 2.0
max_target_chains_to_90pct 1 2

GAPS
metric A B absolute_delta percent_delta
target_gap_bp 0 0 +0 0.00%
query_gap_bp 0 0 +0 0.00%
target_only_gap_events 0 0 +0 0.00%
query_only_gap_events 0 0 +0 0.00%
dual_gap_events 0 0 +0 0.00%
largest_target_gap 0 0 +0 0.00%
largest_query_gap 0 0 +0 0.00%

INTERPRETATION
Canonical mappings are identical, but B is more fragmented.
```

100% mapping agreement and worse continuity in the same report: that is the
point of the tool.

## Compatibility

Before comparing, both files are fully validated (same checks as `stats`) and
sequence sizes are cross-checked. The same target or query name assigned
conflicting sizes in the two files is fatal. A sequence present in only one
file is not an error — that may be a real coverage difference.

## Memory

Canonical mappings are held in memory (one ~32-byte segment per block, both
files). The estimate is checked against a fixed 8 GiB ceiling before
canonicalization; past it the command fails with a clear error instead of
OOMing. Per-key external sorting is the planned upgrade path for larger inputs
and is not implemented.
140 changes: 140 additions & 0 deletions assets/tools/stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<p align="center">
<p align="center">
<img width=200 align="center" src="../logo.png" >
</p>

<span>
<h1 align="center">
chaintools
</h1>
</span>

<p align="center">
<a href="https://img.shields.io/badge/version-0.0.10-green" target="_blank">
<img alt="Version Badge" src="https://img.shields.io/badge/version-0.0.10-green">
</a>
<a href="https://crates.io/crates/chaintools" target="_blank">
<img alt="Crates.io Version" src="https://img.shields.io/crates/v/chaintools">
</a>
<a href="https://github.com/alejandrogzi/chaintools" target="_blank">
<img alt="GitHub License" src="https://img.shields.io/github/license/alejandrogzi/chaintools?color=blue">
</a>
<a href="https://crates.io/crates/chaintools" target="_blank">
<img alt="Crates.io Total Downloads" src="https://img.shields.io/crates/d/chaintools">
</a>
</p>
</p>


# chaintools stats

Summarize alignment amount, gaps, strands, and chain fragmentation in one
streaming pass over a chain file.

## Usage

```bash
chaintools stats --chain file.chain
chaintools stats --chain file.chain --by-sequence --top 3
chaintools stats < file.chain
```

If `--chain` is omitted, chain data is read from standard input. `.gz` input
paths are supported when the `gzip` feature is enabled.

## Options

- `--chain <PATH>`: input `.chain` file. Omit to read standard input.
- `--by-sequence`: add the per-target fragmentation table and top-chain lists.
- `--top <N>`: with `--by-sequence`, show the top `N` scored chains per target.
Default `3`.

## Output

Output is tab-separated, grouped into sections:

- **GENERAL** — chain and block counts, distinct target/query sequence counts.
- **ALIGNMENT** — `aligned_pair_bp` (sum of block sizes), `target_span_bp`
and `query_span_bp` (sum of header spans). `aligned_pair_bp` is never called
genome coverage.
- **CONTINUITY** — largest chain, chain aligned-bp N50/L50, median chain
aligned bp, median blocks per chain, and global chromosome fragmentation
(median/max target chains→90%).
- **GAPS** — within-chain gap bp on each side, gap-event types
(target-only `dt>0,dq==0`, query-only `dt==0,dq>0`, dual `dt>0,dq>0`), and
largest gaps.
- **STRAND** — plus/minus chain and aligned-bp counts (query strand).
- **SCORE** — sum of header scores and mean score per chain.
- **BY TARGET** (with `--by-sequence`) — one row per target: chain count,
total aligned bp, top-1 and top-3 aligned-bp share, chains required for
50/90/95% of the target's aligned bp, and per-target chain N50.
- **TOP CHAINS** (with `--by-sequence`) — for each target, the top `N` chains
by score with aligned bp, spans, block count, alignment density
(`aligned_bp / target_span`), gaps, and largest gap.

### Definitions

- **top-1 share** = largest chain aligned bp / total chain aligned bp on the
target. **top-3 share** is the same with the largest 3 chains. These are
chain-concentration metrics, not unique genome coverage: one 100-Mb chain
scores 100%, two 50-Mb chains score 50%, even with identical base-pair
mappings.
- **chains→N%** = smallest `k` such that the `k` largest chains cover ≥ N% of
the target's aligned bp.
- **N50** uses chain aligned bp, not header span.
- Score alone is never used as a continuity metric; the top-chain table reports
density and spans alongside it.

## Example

```bash
$ chaintools stats --chain a.chain
GENERAL
chains 4
blocks 7
target_sequences 2
query_sequences 3

ALIGNMENT
aligned_pair_bp 70
target_span_bp 76
query_span_bp 78

CONTINUITY
largest_chain_aligned_bp 40
chain_aligned_bp_n50 40
chain_aligned_bp_l50 1
median_chain_aligned_bp 10.0
median_blocks_per_chain 1.0
median_target_top1_share 65.00%
median_target_top3_share 100.00%
median_target_chains_to_90pct 2.0
max_target_chains_to_90pct 2

GAPS
target_gap_bp 6
query_gap_bp 8
target_only_gap_events 1
query_only_gap_events 1
dual_gap_events 1
largest_target_gap 4
largest_query_gap 5

STRAND
plus_chains 2
minus_chains 2
plus_aligned_bp 50
minus_aligned_bp 20

SCORE
score_sum 200
mean_score_per_chain 50.00
```

## Validation

Every chain is validated before it is counted: the target strand must be `+`,
spans must be non-empty and fit inside their sequence sizes, no block may be
empty, and block sizes plus gaps must account for both header spans exactly.
Conflicting sizes for the same sequence name anywhere in the input are fatal.
A malformed chain aborts with a `format error` naming the byte offset.
Loading
Loading