use scirs2_core::ndarray::ArrayD;
use std::collections::HashMap;
use crate::error::{PgmError, Result};
use crate::factor::Factor;
use crate::graph::FactorGraph;
pub struct BayesianNetwork {
graph: FactorGraph,
structure: HashMap<String, Vec<String>>, }
impl BayesianNetwork {
pub fn new() -> Self {
Self {
graph: FactorGraph::new(),
structure: HashMap::new(),
}
}
pub fn add_variable(&mut self, name: String, cardinality: usize) -> &mut Self {
self.graph
.add_variable_with_card(name.clone(), "Discrete".to_string(), cardinality);
self.structure.insert(name, Vec::new());
self
}
pub fn add_cpd(
&mut self,
child: String,
parents: Vec<String>,
cpd: ArrayD<f64>,
) -> Result<&mut Self> {
if self.graph.get_variable(&child).is_none() {
return Err(PgmError::VariableNotFound(child));
}
for parent in &parents {
if self.graph.get_variable(parent).is_none() {
return Err(PgmError::VariableNotFound(parent.clone()));
}
}
self.structure.insert(child.clone(), parents.clone());
let mut factor_vars = parents.clone();
factor_vars.push(child.clone());
let factor = Factor::new(format!("P({}|{:?})", child, parents), factor_vars, cpd)?;
self.graph.add_factor(factor)?;
Ok(self)
}
pub fn add_prior(&mut self, variable: String, prior: ArrayD<f64>) -> Result<&mut Self> {
let factor = Factor::new(format!("P({})", variable), vec![variable.clone()], prior)?;
self.graph.add_factor(factor)?;
self.structure.insert(variable, Vec::new());
Ok(self)
}
pub fn graph(&self) -> &FactorGraph {
&self.graph
}
pub fn is_acyclic(&self) -> bool {
let mut visited = HashMap::new();
let mut rec_stack = HashMap::new();
for node in self.structure.keys() {
if !visited.contains_key(node) && self.has_cycle(node, &mut visited, &mut rec_stack) {
return false;
}
}
true
}
fn has_cycle(
&self,
node: &str,
visited: &mut HashMap<String, bool>,
rec_stack: &mut HashMap<String, bool>,
) -> bool {
visited.insert(node.to_string(), true);
rec_stack.insert(node.to_string(), true);
if let Some(parents) = self.structure.get(node) {
for parent in parents {
if !visited.contains_key(parent) {
if self.has_cycle(parent, visited, rec_stack) {
return true;
}
} else if rec_stack.get(parent) == Some(&true) {
return true;
}
}
}
rec_stack.insert(node.to_string(), false);
false
}
pub fn topological_order(&self) -> Result<Vec<String>> {
if !self.is_acyclic() {
return Err(PgmError::InvalidGraph(
"Network contains cycles".to_string(),
));
}
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut children: HashMap<String, Vec<String>> = HashMap::new();
for (child, parents) in &self.structure {
in_degree.insert(child.clone(), parents.len());
for parent in parents {
children
.entry(parent.clone())
.or_default()
.push(child.clone());
}
}
let mut queue: Vec<String> = in_degree
.iter()
.filter(|(_, °)| deg == 0)
.map(|(v, _)| v.clone())
.collect();
let mut result = Vec::new();
while let Some(node) = queue.pop() {
result.push(node.clone());
if let Some(child_nodes) = children.get(&node) {
for child in child_nodes {
if let Some(deg) = in_degree.get_mut(child) {
*deg -= 1;
if *deg == 0 {
queue.push(child.clone());
}
}
}
}
}
if result.len() != self.structure.len() {
return Err(PgmError::InvalidGraph(
"Could not compute topological order".to_string(),
));
}
Ok(result)
}
}
impl Default for BayesianNetwork {
fn default() -> Self {
Self::new()
}
}
pub struct HiddenMarkovModel {
graph: FactorGraph,
#[allow(dead_code)]
num_states: usize,
#[allow(dead_code)]
num_observations: usize,
time_steps: usize,
}
impl HiddenMarkovModel {
pub fn new(num_states: usize, num_observations: usize, time_steps: usize) -> Self {
let mut graph = FactorGraph::new();
for t in 0..time_steps {
graph.add_variable_with_card(
format!("state_{}", t),
"HiddenState".to_string(),
num_states,
);
}
for t in 0..time_steps {
graph.add_variable_with_card(
format!("obs_{}", t),
"Observation".to_string(),
num_observations,
);
}
Self {
graph,
num_states,
num_observations,
time_steps,
}
}
pub fn set_initial_distribution(&mut self, initial: ArrayD<f64>) -> Result<&mut Self> {
let factor = Factor::new(
"P(state_0)".to_string(),
vec!["state_0".to_string()],
initial,
)?;
self.graph.add_factor(factor)?;
Ok(self)
}
pub fn set_transition_matrix(&mut self, transition: ArrayD<f64>) -> Result<&mut Self> {
for t in 1..self.time_steps {
let factor = Factor::new(
format!("P(state_{}|state_{})", t, t - 1),
vec![format!("state_{}", t - 1), format!("state_{}", t)],
transition.clone(),
)?;
self.graph.add_factor(factor)?;
}
Ok(self)
}
pub fn set_emission_matrix(&mut self, emission: ArrayD<f64>) -> Result<&mut Self> {
for t in 0..self.time_steps {
let factor = Factor::new(
format!("P(obs_{}|state_{})", t, t),
vec![format!("state_{}", t), format!("obs_{}", t)],
emission.clone(),
)?;
self.graph.add_factor(factor)?;
}
Ok(self)
}
pub fn graph(&self) -> &FactorGraph {
&self.graph
}
pub fn filter(&self, observations: &[usize], t: usize) -> Result<ArrayD<f64>> {
if t >= self.time_steps {
return Err(PgmError::InvalidDistribution(format!(
"Time step {} exceeds sequence length {}",
t, self.time_steps
)));
}
if t >= observations.len() {
return Err(PgmError::InvalidDistribution(format!(
"Not enough observations: need {} but got {}",
t + 1,
observations.len()
)));
}
let mut evidence_graph = self.graph.clone();
for (time, &obs_value) in observations.iter().enumerate().take(t + 1) {
let obs_var = format!("obs_{}", time);
let mut evidence_values = vec![0.0; self.num_observations];
evidence_values[obs_value] = 1.0;
let evidence_factor = Factor::new(
format!("evidence_{}", time),
vec![obs_var.clone()],
ArrayD::from_shape_vec(vec![self.num_observations], evidence_values)?,
)?;
evidence_graph.add_factor(evidence_factor)?;
}
use crate::variable_elimination::VariableElimination;
let ve = VariableElimination::new();
let state_var = format!("state_{}", t);
ve.marginalize(&evidence_graph, &state_var)
}
pub fn smooth(&self, observations: &[usize], t: usize) -> Result<ArrayD<f64>> {
if t >= self.time_steps {
return Err(PgmError::InvalidDistribution(format!(
"Time step {} exceeds sequence length {}",
t, self.time_steps
)));
}
if observations.len() != self.time_steps {
return Err(PgmError::InvalidDistribution(format!(
"Expected {} observations but got {}",
self.time_steps,
observations.len()
)));
}
let mut evidence_graph = self.graph.clone();
for (time, &obs_value) in observations.iter().enumerate().take(self.time_steps) {
let obs_var = format!("obs_{}", time);
let mut evidence_values = vec![0.0; self.num_observations];
evidence_values[obs_value] = 1.0;
let evidence_factor = Factor::new(
format!("evidence_{}", time),
vec![obs_var.clone()],
ArrayD::from_shape_vec(vec![self.num_observations], evidence_values)?,
)?;
evidence_graph.add_factor(evidence_factor)?;
}
use crate::variable_elimination::VariableElimination;
let ve = VariableElimination::new();
let state_var = format!("state_{}", t);
ve.marginalize(&evidence_graph, &state_var)
}
pub fn viterbi(&self, observations: &[usize]) -> Result<Vec<usize>> {
if observations.len() != self.time_steps {
return Err(PgmError::InvalidDistribution(format!(
"Observations length {} does not match time steps {}",
observations.len(),
self.time_steps
)));
}
let mut evidence_graph = self.graph.clone();
for (time, &obs_value) in observations.iter().enumerate().take(self.time_steps) {
let obs_var = format!("obs_{}", time);
let mut evidence_values = vec![0.0; self.num_observations];
evidence_values[obs_value] = 1.0;
let evidence_factor = Factor::new(
format!("evidence_{}", time),
vec![obs_var.clone()],
ArrayD::from_shape_vec(vec![self.num_observations], evidence_values)?,
)?;
evidence_graph.add_factor(evidence_factor)?;
}
use crate::variable_elimination::VariableElimination;
let ve = VariableElimination::new();
let assignment = ve.map(&evidence_graph)?;
let mut sequence = Vec::new();
for t in 0..self.time_steps {
let state_var = format!("state_{}", t);
if let Some(&state) = assignment.get(&state_var) {
sequence.push(state);
} else {
return Err(PgmError::VariableNotFound(state_var));
}
}
Ok(sequence)
}
}
pub struct MarkovRandomField {
graph: FactorGraph,
}
impl MarkovRandomField {
pub fn new() -> Self {
Self {
graph: FactorGraph::new(),
}
}
pub fn add_variable(&mut self, name: String, cardinality: usize) -> &mut Self {
self.graph
.add_variable_with_card(name, "Discrete".to_string(), cardinality);
self
}
pub fn add_pairwise_potential(
&mut self,
var1: String,
var2: String,
potential: ArrayD<f64>,
) -> Result<&mut Self> {
let factor = Factor::new(
format!("φ({},{})", var1, var2),
vec![var1.clone(), var2.clone()],
potential,
)?;
self.graph.add_factor(factor)?;
Ok(self)
}
pub fn add_unary_potential(
&mut self,
var: String,
potential: ArrayD<f64>,
) -> Result<&mut Self> {
let factor = Factor::new(format!("φ({})", var), vec![var.clone()], potential)?;
self.graph.add_factor(factor)?;
Ok(self)
}
pub fn graph(&self) -> &FactorGraph {
&self.graph
}
}
impl Default for MarkovRandomField {
fn default() -> Self {
Self::new()
}
}
pub struct ConditionalRandomField {
graph: FactorGraph,
input_vars: Vec<String>,
output_vars: Vec<String>,
}
impl ConditionalRandomField {
pub fn new() -> Self {
Self {
graph: FactorGraph::new(),
input_vars: Vec::new(),
output_vars: Vec::new(),
}
}
pub fn add_input_variable(&mut self, name: String, cardinality: usize) -> &mut Self {
self.graph
.add_variable_with_card(name.clone(), "Input".to_string(), cardinality);
self.input_vars.push(name);
self
}
pub fn add_output_variable(&mut self, name: String, cardinality: usize) -> &mut Self {
self.graph
.add_variable_with_card(name.clone(), "Output".to_string(), cardinality);
self.output_vars.push(name);
self
}
pub fn add_feature(
&mut self,
name: String,
variables: Vec<String>,
potential: ArrayD<f64>,
) -> Result<&mut Self> {
let factor = Factor::new(format!("feature_{}", name), variables, potential)?;
self.graph.add_factor(factor)?;
Ok(self)
}
pub fn graph(&self) -> &FactorGraph {
&self.graph
}
}
impl Default for ConditionalRandomField {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array;
#[test]
fn test_bayesian_network_creation() {
let mut bn = BayesianNetwork::new();
bn.add_variable("x".to_string(), 2);
bn.add_variable("y".to_string(), 2);
assert!(bn.graph().get_variable("x").is_some());
assert!(bn.graph().get_variable("y").is_some());
}
#[test]
fn test_bayesian_network_cpd() {
let mut bn = BayesianNetwork::new();
bn.add_variable("x".to_string(), 2);
bn.add_variable("y".to_string(), 2);
let prior = Array::from_shape_vec(vec![2], vec![0.6, 0.4])
.expect("unwrap")
.into_dyn();
bn.add_prior("x".to_string(), prior).expect("unwrap");
let cpd = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
bn.add_cpd("y".to_string(), vec!["x".to_string()], cpd)
.expect("unwrap");
assert_eq!(bn.graph().num_factors(), 2);
}
#[test]
fn test_bayesian_network_acyclic() {
let mut bn = BayesianNetwork::new();
bn.add_variable("x".to_string(), 2);
bn.add_variable("y".to_string(), 2);
let cpd = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
bn.add_cpd("y".to_string(), vec!["x".to_string()], cpd)
.expect("unwrap");
assert!(bn.is_acyclic());
}
#[test]
fn test_bayesian_network_topological_order() {
let mut bn = BayesianNetwork::new();
bn.add_variable("x".to_string(), 2);
bn.add_variable("y".to_string(), 2);
bn.add_variable("z".to_string(), 2);
let cpd_y = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
bn.add_cpd("y".to_string(), vec!["x".to_string()], cpd_y)
.expect("unwrap");
let cpd_z = Array::from_shape_vec(vec![2, 2], vec![0.8, 0.2, 0.3, 0.7])
.expect("unwrap")
.into_dyn();
bn.add_cpd("z".to_string(), vec!["y".to_string()], cpd_z)
.expect("unwrap");
let order = bn.topological_order().expect("unwrap");
assert_eq!(order.len(), 3);
}
#[test]
fn test_hmm_creation() {
let hmm = HiddenMarkovModel::new(3, 2, 5);
assert_eq!(hmm.graph().num_variables(), 10); }
#[test]
fn test_hmm_parameters() {
let mut hmm = HiddenMarkovModel::new(2, 2, 3);
let initial = Array::from_shape_vec(vec![2], vec![0.6, 0.4])
.expect("unwrap")
.into_dyn();
hmm.set_initial_distribution(initial).expect("unwrap");
let transition = Array::from_shape_vec(vec![2, 2], vec![0.7, 0.3, 0.4, 0.6])
.expect("unwrap")
.into_dyn();
hmm.set_transition_matrix(transition).expect("unwrap");
let emission = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
hmm.set_emission_matrix(emission).expect("unwrap");
assert!(hmm.graph().num_factors() > 0);
}
#[test]
fn test_hmm_filtering() {
let mut hmm = HiddenMarkovModel::new(2, 2, 3);
let initial = Array::from_shape_vec(vec![2], vec![0.6, 0.4])
.expect("unwrap")
.into_dyn();
hmm.set_initial_distribution(initial).expect("unwrap");
let transition = Array::from_shape_vec(vec![2, 2], vec![0.7, 0.3, 0.4, 0.6])
.expect("unwrap")
.into_dyn();
hmm.set_transition_matrix(transition).expect("unwrap");
let emission = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
hmm.set_emission_matrix(emission).expect("unwrap");
let observations = vec![0, 1, 0];
let result = hmm.filter(&observations, 1);
assert!(result.is_ok());
let marginal = result.expect("unwrap");
assert_eq!(marginal.len(), 2);
let sum: f64 = marginal.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_hmm_smoothing() {
let mut hmm = HiddenMarkovModel::new(2, 2, 3);
let initial = Array::from_shape_vec(vec![2], vec![0.6, 0.4])
.expect("unwrap")
.into_dyn();
hmm.set_initial_distribution(initial).expect("unwrap");
let transition = Array::from_shape_vec(vec![2, 2], vec![0.7, 0.3, 0.4, 0.6])
.expect("unwrap")
.into_dyn();
hmm.set_transition_matrix(transition).expect("unwrap");
let emission = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
hmm.set_emission_matrix(emission).expect("unwrap");
let observations = vec![0, 1, 0];
let result = hmm.smooth(&observations, 1);
assert!(result.is_ok());
let marginal = result.expect("unwrap");
assert_eq!(marginal.len(), 2);
let sum: f64 = marginal.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_hmm_viterbi() {
let mut hmm = HiddenMarkovModel::new(2, 2, 3);
let initial = Array::from_shape_vec(vec![2], vec![0.6, 0.4])
.expect("unwrap")
.into_dyn();
hmm.set_initial_distribution(initial).expect("unwrap");
let transition = Array::from_shape_vec(vec![2, 2], vec![0.7, 0.3, 0.4, 0.6])
.expect("unwrap")
.into_dyn();
hmm.set_transition_matrix(transition).expect("unwrap");
let emission = Array::from_shape_vec(vec![2, 2], vec![0.9, 0.1, 0.2, 0.8])
.expect("unwrap")
.into_dyn();
hmm.set_emission_matrix(emission).expect("unwrap");
let observations = vec![0, 1, 0];
let result = hmm.viterbi(&observations);
assert!(result.is_ok());
let sequence = result.expect("unwrap");
assert_eq!(sequence.len(), 3);
for state in sequence {
assert!(state < 2);
}
}
#[test]
fn test_mrf_creation() {
let mut mrf = MarkovRandomField::new();
mrf.add_variable("x".to_string(), 2);
mrf.add_variable("y".to_string(), 2);
let potential = Array::from_shape_vec(vec![2, 2], vec![1.0, 0.5, 0.5, 1.0])
.expect("unwrap")
.into_dyn();
mrf.add_pairwise_potential("x".to_string(), "y".to_string(), potential)
.expect("unwrap");
assert_eq!(mrf.graph().num_factors(), 1);
}
#[test]
fn test_crf_creation() {
let mut crf = ConditionalRandomField::new();
crf.add_input_variable("x".to_string(), 3);
crf.add_output_variable("y".to_string(), 2);
let feature = Array::from_shape_vec(vec![3, 2], vec![1.0, 0.5, 0.8, 0.2, 0.6, 0.4])
.expect("unwrap")
.into_dyn();
crf.add_feature(
"f1".to_string(),
vec!["x".to_string(), "y".to_string()],
feature,
)
.expect("unwrap");
assert_eq!(crf.graph().num_factors(), 1);
}
}