Skip to content

Latest commit

 

History

History
131 lines (102 loc) · 4.53 KB

File metadata and controls

131 lines (102 loc) · 4.53 KB

Cookbook

Task-oriented recipes. Every snippet here is drawn from compiled, tested code — the end-to-end recipe is examples/cookbook.rs (run it with cargo run --release --example cookbook).

Recipe 1 — Learn c from data (teacher recovery)

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).

Recipe 2 — Rank candidates with the learned c

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)

Recipe 3 — Distance through the WFST embedding

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.

Recipe 4 — Contrastive / triplet rounds

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 update

Do 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).

Recipe 5 — Learner-only build (no WFST)

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.

Reference output

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 ✓