Skip to main content

Module trellis

Module trellis 

Source
Expand description

The exact solver forced alignment is built on, over any trellis of the same shape.

align is one instance of something more general. Strip the phones out of it and what is left is T frames against N + 1 positions, where every transition consumes exactly one frame and advances the position by a bounded number of places, and the path starts at position 0 and ends at position N. Nothing else about the chain matters to the search.

That shape permits exact rather than pruned search. This module provides the reachable-cell band, packed traceback, reusable score rows and a numerically careful ⊕ in the log semiring. A Trellis implementation supplies the transition topology and costs.

fixedfree
one frame per transitionhow many transitions there are
position never goes backwardswhat each of them costs
an advance of at most Trellis::REACHwhat it reads, and what it means
starts at 0, ends at Nwhether the cost depends on the frame

§Writing one

Trellis answers “what enters this cell, and what does it cost”. The cost is the whole cost, meaning a structural penalty and whatever the frame charges for what the transition reads, already multiplied together, so a penalty that varies by position, by frame, or by both needs no extra machinery.

use sicada_decode::trellis::{Step, Trellis, best_path};

/// A reference that must be sounded in order, one frame at a time, with no
/// silence and nothing skippable: the smallest trellis there is.
struct Rigid<'a> {
    scores: &'a [f32],
    num_symbols: usize,
    phones: &'a [u32],
}

impl Trellis<2> for Rigid<'_> {
    type Frame<'a> = &'a [f32] where Self: 'a;

    fn num_frames(&self) -> usize {
        self.scores.len() / self.num_symbols
    }
    fn num_positions(&self) -> usize {
        self.phones.len()
    }
    fn frame(&self, frame: usize) -> &[f32] {
        &self.scores[frame * self.num_symbols..(frame + 1) * self.num_symbols]
    }

    fn steps_into(&self, frame: &[f32], position: usize) -> [Step; 2] {
        if position == 0 {
            // Nothing reaches `s_0` after the start: the reference has to
            // begin in the first frame.
            return [Step::ABSENT; 2];
        }
        let sounding = frame[self.phones[position - 1] as usize];
        // Hold this phone, or arrive at it. Listed best-first, which is the
        // tie-break: a phone is held rather than started again.
        [Step::new(0, sounding), Step::new(1, sounding)]
    }
}

let scores = [
    9.0, 0.0, 9.0, // phone 1
    9.0, 0.0, 9.0, // still phone 1
    9.0, 9.0, 0.0, // phone 2
];
let path = best_path(&Rigid { scores: &scores, num_symbols: 3, phones: &[1, 2] })?
    .expect("the reference fits");
assert_eq!(path.positions(), [1, 1, 2]);
assert_eq!(path.codes(), [1, 0, 1]); // arrive, hold, arrive

ReversibleTrellis adds the same transitions read backwards, which is all posteriors needs. It has nothing to implement, because the backward reading is derived from the forward one. Write it out only to make the backward pass faster, and then put axioms::check in a test: a forward-backward over two graphs that differ does not fail, it returns numbers that look entirely reasonable.

§What it will not do

There is no beam here and no place to put one. The band is the set of cells a complete path can stand in at all, so leaving out the rest costs nothing; narrowing it further would make the result approximate.

Modules§

axioms
Checks that a trellis obeys the contract the solvers rely on.

Structs§

Path
The best path through a trellis: which transition each frame took.
Step
One transition of a Trellis: how far it moves, and what it costs.
Transition
One transition of a trellis, as posteriors visits it.

Constants§

NEGLIGIBLE
Where a term stops being able to change an f32.

Traits§

ReversibleTrellis
A Trellis that can also be read backwards, as a forward-backward requires.
Trellis
T frames against N + 1 positions, one frame consumed per transition.

Functions§

band
The cells a complete path can stand in after frame frames.
best_path
The best path through trellis, exactly.
derive_steps_out_of
The transitions leaving position, read off the ones entering the cells they could reach.
posteriors
Forward-backward over trellis in the log semiring: the posterior of every transition, and the total.