Task-oriented recipes. Every snippet here is drawn from compiled, tested code — the end-to-end recipe is
examples/cookbook.rs(run it withcargo run --release --example cookbook).
Goal: recover a hidden split/merge constant $c^\star$ online from the MSM
distances it produced. This is well-posed because MSM distance is monotone
non-decreasing in $c$.
use adaptive_msm::{AdaptiveMsm, AdaptiveMsmConfig, MsmConfig};
fn learn_c(teacher_c: f64, pairs: &[(Vec<f64>, Vec<f64>)]) -> f64 {
let teacher = MsmConfig::new(teacher_c);
let targets: Vec<f64> = pairs.iter().map(|(q, t)| teacher.distance(q, t)).collect();
let mut learner = AdaptiveMsm::new(
AdaptiveMsmConfig::new()
.initial_c(1.0)
.epsilon(1.0) // scale = 1/epsilon near the data spacing
.c_bounds(0.05, 8.0)
.window_size(16)
.seed(42),
);
const LOSS_SCALE: f64 = 0.002; // keep epsilon * gradient gentle (see known-issues B1)
for _ in 0..300 {
for ((q, t), &d_star) in pairs.iter().zip(&targets) {
let c = learner.explore_c();
let predicted = MsmConfig::new(c).distance(q, t);
learner.observe(LOSS_SCALE * (predicted - d_star).powi(2));
}
}
learner.current_c()
}With unequal-length pairs (so alignments must split/merge), this recovers
$c^\star = 2.0$ as $c \approx 2.07$. Key point: the observed cost must
depend on the explored $c$ — feeding a constant unrelated to $c$ gives no
gradient and $c$ drifts to a bound. Scale the loss so the step stays gentle
(../engineering/known-issues.md §B1).
Goal: order a candidate set by MSM distance to a query.
use adaptive_msm::MsmConfig;
let cfg = MsmConfig::new(2.07); // e.g. the learned c
let query = [1.0, 2.0, 3.0, 4.0];
let candidates = [
("near", vec![1.0, 2.0, 3.0, 4.0]),
("shifted", vec![2.0, 3.0, 4.0, 5.0]),
("far", vec![10.0, 0.0, 10.0, 0.0]),
];
let mut ranked: Vec<(&str, f64)> = candidates
.iter()
.map(|(name, s)| (*name, cfg.distance(&query, s)))
.collect();
ranked.sort_by(|a, b| a.1.partial_cmp(&b.1).expect("finite distances"));
// -> near (0.0), shifted (4.0), far (22.0)Goal: compute the MSM distance as a tropical shortest path over the lattice,
and confirm it equals the exact DP. The full wfst_distance helper (a
relax-to-fixpoint shortest distance over MsmStateSource) is in
examples/cookbook.rs; the essential contract is:
tropical shortest path (start → final) over MsmStateSource
== MsmConfig::new(c).distance(query, target) (exact, to 1e-9)
The example asserts this for every candidate — see the embedding-equivalence
proof in ../formal/msm-wfst-embedding.md.
Goal: one update from a (positive, negative) pair scored at the same $c$.
use adaptive_msm::{AdaptiveMsm, AdaptiveMsmConfig, MsmConfig};
let mut learner = AdaptiveMsm::new(AdaptiveMsmConfig::new().epsilon(1.0).seed(7));
let (anchor, positive, negative) = ([1.0, 2.0, 3.0], [1.0, 2.1, 3.0], [7.0, 1.0, 7.0]);
let c = learner.explore_c(); // ONE draw for the whole round
let cfg = MsmConfig::new(c);
let margin = 1.0;
let loss = (cfg.distance(&anchor, &positive)
- cfg.distance(&anchor, &negative) + margin).max(0.0);
learner.observe(loss); // ONE updateDo not use two predict calls here — each perturbs $c$ independently, so
the positive and negative would be scored at different $c$ values
(learner-guide.md §3).
Goal: depend on the crate without the lling-llang / WFST stack.
[dependencies]
adaptive-msm = { version = "0.1", default-features = false }Everything in Recipes 1, 2 and 4 works under default-features = false (they use
only AdaptiveMsm and MsmConfig); the WFST recipes need the default wfst
feature.
Running cargo run --release --example cookbook:
teacher c* = 2.00 -> learned c = 2.0724
ranking (nearest first):
near d = 0.0000
noisy d = 1.5000
shifted d = 4.0000
far d = 22.0000
WFST embedding matches MsmConfig::distance for all candidates ✓