Two binaries for slicing GFA unitig graphs into manageable pieces and
extracting two haplotype-style walks per piece. Pure Rust, no external
dependencies, builds with cargo build --release.
cargo build --release
# binaries land in ./target/release/{gfa_split, gfa_haps}
gfa_split <input.gfa> [--out-prefix PREFIX] [--min-len BP]
- Decomposes the graph into connected components by undirected link membership (orientation ignored for connectivity).
- Components with unique sequence length ≥
--min-len(default 100,000) each get their own file<prefix>.component_NN.gfa, indexed by descending size socomponent_01is the biggest. - Smaller components are concatenated into
<prefix>.small_components.gfa. - "Unique sequence length" = Σ segment lengths − Σ link overlap CIGAR lengths. Exact for tree-shaped components; over-subtracts by one overlap per cycle-closing link in cyclic components.
- Routes
S,L,C,P,Wrecords into the right output by segment membership.Hrecords are replicated to every output.Arecords are dropped. Each output is a self-contained valid GFA.
gfa_haps <input.gfa> [--endpoints tip|any] [--candidates K]
[--seed S] [--sample NAME] [--keep-sequences]
[--repair-max-revisits N] [--no-repair] [--verbose]
For each connected component, generates a diverse pool of candidate walks and selects the best pair under a lexicographic objective:
- Maximize union coverage (bp covered by either walk).
- Maximize symmetric difference (bp covered by exactly one walk) — this is what drives the two walks to take opposite sides of bubbles.
- Minimize total revisits across the two walks.
After pair selection, a coverage repair pass tries to splice any
uncovered nodes into one of the walks via shortest bidirected detours.
Splices are only accepted if they strictly increase joint coverage,
preventing oscillation. Use --no-repair to disable, or
--repair-max-revisits N (default 4) to cap the allowed revisits added
per splice.
tip(default): walks start and end at graph tips — sides of nodes with no outgoing edges. If a component has no tips, falls back toany.any: walks can start and end anywhere.
Number of candidate walks generated per starting policy (default 64).
The actual pool is ~2K because the algorithm also generates K complementary
walks that strongly avoid the leader candidate's nodes (haplotype-divergence
pressure). All ~(2K)²/2 pairs are scored — cheap at typical K values.
Increase for hairier components if you suspect the best pair was missed.
A walk respects unitig orientation: leaving a node via its right end forces
the next node to be entered on the side joined by the link. Walks emit GFA
W-line format with >name / <name for + / - orientations.
<basename>.with_paths.gfa— the input GFA with twoPlines added per component:P-lines (not W-lines) are used because the GFA v1.1 spec restricts W-lines to graphs without overlaps between segments. Hifiasm unitig graphs have nonzero overlaps on every link, so P-lines are the correct format. CIGAR strings in the Overlaps field are preserved verbatim from the original L-lines.P <sample>_h1_component_NN <seg1>+,<seg2>+,...,<segN>+ <cigar1>,<cigar2>,... P <sample>_h2_component_NN ...<basename>.haps.fa(only if--keep-sequences) — FASTA with one record per (component, haplotype), built by concatenating unitig sequences with overlaps trimmed from the prefix of each non-first step.
In a bidirected unitig graph, walks must traverse nodes coherently —
entering one side and exiting the other. This means dead-end side branches
(unitigs with no return path through their other side) cannot be visited
by a walk that also reaches a distal tip. With only two walks per
component, such branches can be unreachable. The tool reports
nodes_covered / nodes_total per component so you can spot this.
gfa_split input.gfa --min-len 100000
for comp in input.component_*.gfa; do
gfa_haps "$comp" --keep-sequences --candidates 128
done
Per-component summary lines from gfa_haps are written to stdout as TSV,
suitable for piping into downstream analysis.