Tutorial 22: Momentum Fork Resolution
This tutorial demonstrates how to use the MomentumForkResolver API to accumulate read evidence at assembly forks and make an SPRT-based branch decision.
From the Mycelia base directory, convert this tutorial to a notebook:
julia --project=. -e 'import Literate; Literate.notebook("tutorials/22_momentum_fork_resolution.jl", "tutorials/notebooks", execute=false)'import Pkg
if isinteractive()
Pkg.activate("..")
end
import Mycelia
println("=== Momentum Fork Resolution Tutorial ===")1. Define a fork and initialize the active-read state
The reference branch is the null hypothesis (H0). Competing branches become the alternative hypothesis (H1) whenever they accumulate more evidence than the reference.
state = Mycelia.Rhizomorph.MomentumForkResolver.ActiveReadState(
"read_001",
[:reference_path, :variant_path];
reference_branch = :reference_path
)
println("Initial state:")
println(" reference branch: $(state.reference_branch)")
println(" current branch: $(state.current_branch)")
println(" initial LLR: $(state.log_likelihood_ratio)")2. Compare the available weighting functions
linear = Mycelia.Rhizomorph.MomentumForkResolver.linear_weight(6; slope = 1.0)
saturating = Mycelia.Rhizomorph.MomentumForkResolver.saturating_weight(
6; max_weight = 3.0, half_saturation = 2.0)
llr = Mycelia.Rhizomorph.MomentumForkResolver.llr_weight(12, 3)
println("\nExample weights:")
println(" linear weight for support=6: $linear")
println(" saturating weight for support=6: $(round(saturating, digits=3))")
println(" direct LLR for support 12 vs 3: $(round(llr, digits=3))")3. Accumulate sequential evidence with a saturating response
Saturating weights are useful when repeated support should still help, but each extra read should contribute a little less than the previous one.
for support in (2, 3, 4)
decision = Mycelia.Rhizomorph.MomentumForkResolver.observe_fork_event!(
state,
:variant_path,
support;
weight_fn = Mycelia.Rhizomorph.MomentumForkResolver.saturating_weight,
max_weight = 2.5,
half_saturation = 1.5,
alpha = 0.05,
beta = 0.05
)
println(
" update with support=$(support): branch=$(state.current_branch), " *
"llr=$(round(state.log_likelihood_ratio, digits=3)), decision=$(decision)"
)
end4. Inject a direct log-likelihood-ratio update
When evidence already arrives as a branch-vs-branch comparison, apply it directly instead of forcing it through a univariate weight curve.
decision = Mycelia.Rhizomorph.MomentumForkResolver.observe_fork_event!(
state,
:variant_path,
:reference_path,
50,
2;
alpha = 0.05,
beta = 0.05
)
resolution = Mycelia.Rhizomorph.MomentumForkResolver.fork_resolution(state)
println("\nDirect LLR update:")
println(" decision: $(decision)")
println(" resolved branch: $(resolution.resolved_branch)")
println(" current branch: $(resolution.current_branch)")
println(" cumulative LLR: $(round(resolution.log_likelihood_ratio, digits=3))")
println(" accept alternative at: $(round(resolution.thresholds.accept_alternative, digits=3))")
println(" accept reference at: $(round(resolution.thresholds.accept_reference, digits=3))")5. Interpretation
A positive LLR favors the strongest competing branch over the reference. Once the upper SPRT threshold is crossed, the fork can be resolved in favor of the competing branch. Strong negative evidence would instead drive the state below the lower threshold and resolve back to the reference branch.