use matrices::{Vector, Matrix};
type LabelId = usize;
type StateId = usize;
pub struct HiddenMarkov {
labels_count: usize,
init_states: Vector,
state_transitions: Matrix,
observation_model: Matrix,
}
impl HiddenMarkov {
pub fn new(initials: Vec<f64>, transitions: Vec<Vec<f64>>,
observation_model: Vec<Vec<f64>>) -> Option<HiddenMarkov>
{
let num_states = initials.len();
let is = Vector::new(initials);
let ts = Matrix::new(transitions);
let os = Matrix::new(observation_model);
if num_states < 2 { return None }
if !is.is_positive() { return None }
if ts.is_none() { return None }
let trans = ts.unwrap();
if !trans.is_positive() { return None }
if os.is_none() { return None }
let obs = os.unwrap();
if !obs.is_positive() { return None }
let num_outcomes = obs.cols();
let is_log: Vector = is.minus_log();
let trans_log: Matrix = trans.minus_log();
let obs_log: Matrix = obs.minus_log();
Some(
HiddenMarkov {
labels_count: num_outcomes,
init_states: is_log,
state_transitions: trans_log,
observation_model: obs_log
}
)
}
pub fn map_estimate(&self, observations: Vec<LabelId>) -> Vec<StateId> {
let obs_len = observations.len();
if obs_len == 0 {return vec![]}
if observations.iter().any(|&x| x >= self.labels_count) { return vec![]; }
let mut last_msg: Vector = self.init_states.clone();
let mut tracebacks: Vec<Vec<StateId>> = Vec::with_capacity(obs_len);
for i in 0..observations.len() {
let phi = last_msg.add_vector(&self.state_from_observation(observations[i]));
let (msg, t) = self.next_msg_and_traceback(&phi);
last_msg = msg;
tracebacks.push(t);
}
let mut states: Vec<StateId> = vec![0; obs_len];
let mut last_state = last_msg.argmin();
for i in (0..obs_len).rev() {
let state: StateId = tracebacks[i][last_state];
last_state = state;
states[i] = state;
}
states
}
fn state_from_observation(&self, obs: LabelId) -> Vector {
self.observation_model.column(obs).unwrap()
}
fn next_msg_and_traceback(&self, phi: &Vector) -> (Vector, Vec<StateId>) {
let mat = self.state_transitions.add_to_columns(phi);
(mat.min_by_column(), mat.argmin_by_column())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let initials: Vec<f64> = vec![0.5, 0.5];
let st = vec![ vec![0.75, 0.25],
vec![0.25, 0.75]];
let obs = vec![ vec![0.5, 0.5],
vec![0.25, 0.75]];
assert!(HiddenMarkov::new(initials, st, obs).is_some());
}
#[test]
fn test_new_none1() {
let initials: Vec<f64> = vec![0.5, -0.5];
let st = vec![ vec![0.75, 0.25],
vec![0.25, 0.75]];
let obs = vec![ vec![0.5, 0.5],
vec![0.25, 0.75]];
assert!(HiddenMarkov::new(initials, st, obs).is_none());
}
#[test]
fn test_map_estimation() {
let initials: Vec<f64> = vec![0.5, 0.5];
let st = vec![ vec![0.75, 0.25],
vec![0.25, 0.75]];
let obs = vec![ vec![0.5, 0.5],
vec![0.25, 0.75]];
let hmm = HiddenMarkov::new(initials, st, obs).unwrap();
let estimate = hmm.map_estimate(vec![0, 0, 1, 1, 1]);
assert!(estimate == vec![0, 0, 1, 1, 1])
}
}