use itertools::Itertools;
use ndarray::prelude::*;
use pest::{Parser, iterators::Pair};
use pest_derive::Parser;
use crate::{
models::{BN, CPD, CatBN, CatCPD, DiGraph, Graph, Labelled},
types::{Map, States},
};
#[derive(Debug)]
struct Network {
pub name: String,
pub properties: Vec<Property>,
pub variables: Vec<Variable>,
pub probabilities: Vec<Probability>,
}
#[derive(Debug)]
struct Property {
pub key: String,
pub value: String,
}
#[derive(Debug)]
struct Variable {
pub label: String,
pub states: Vec<String>,
pub _properties: Vec<Property>,
}
#[derive(Debug)]
struct Probability {
pub label: String,
pub parents: Vec<String>,
pub table: Option<Vec<f64>>, pub entries: Option<Vec<(Vec<String>, Vec<f64>)>>, }
#[derive(Parser)]
#[grammar = "src/io/bif/bif.pest"]
pub struct BifParser;
impl BifParser {
pub fn parse_str(bif: &str) -> CatBN {
let network = Self::parse(Rule::file, bif)
.expect("Failed to parse BIF file.")
.map(build_ast)
.next()
.expect("Failed to parse BIF file.");
let properties: Map<_, _> = network
.properties
.into_iter()
.map(|p| (p.key, p.value))
.collect();
let name = Some(network.name);
let description = properties.get("description").cloned();
let states: States = network
.variables
.into_iter()
.map(|v| (v.label, v.states.into_iter().collect()))
.collect();
let cpds: Vec<_> = network
.probabilities
.into_iter()
.map(|p| {
let variable = States::from_iter([(
p.label.clone(),
states
.get(&p.label)
.expect("Failed to get variable states.")
.clone(),
)]);
let conditioning_variables: States = p
.parents
.iter()
.map(|x| {
let states = states.get(x).expect("Failed to get variable states.");
(x.to_string(), states.iter().cloned().collect())
})
.collect();
let parameters = match (p.table, p.entries) {
(Some(table), None) => Array1::from_vec(table).insert_axis(Axis(0)),
(None, Some(entries)) => {
let entries: Map<_, _> = entries.into_iter().collect();
let entries: Vec<_> = conditioning_variables
.iter()
.map(|(_, states)| states)
.cloned()
.multi_cartesian_product()
.map(|states| &entries[&states])
.collect();
let shape = (entries.len(), entries[0].len());
let parameters: Array1<_> =
entries.into_iter().flatten().copied().collect();
parameters
.into_shape_with_order(shape)
.expect("Failed to reshape parameters.")
}
_ => unreachable!(),
};
let parameters = ¶meters / parameters.sum_axis(Axis(1)).insert_axis(Axis(1));
CatCPD::new(variable, conditioning_variables, parameters)
})
.collect();
let mut graph = DiGraph::empty(states.keys());
cpds.iter().for_each(|p| {
assert_eq!(p.labels().len(), 1);
let x = &p.labels()[0];
let x = graph
.labels()
.get_index_of(x)
.unwrap_or_else(|| panic!("Failed to get index of label '{x}'."));
p.conditioning_labels().into_iter().for_each(|z| {
let z = graph
.labels()
.get_index_of(z)
.unwrap_or_else(|| panic!("Failed to get index of label '{z}'."));
graph.add_edge(z, x);
});
});
CatBN::with_optionals(name, description, graph, cpds)
}
}
fn build_ast(pair: Pair<Rule>) -> Network {
assert_eq!(pair.as_rule(), Rule::file);
let mut name = String::new();
let mut properties = vec![];
let mut variables = vec![];
let mut probabilities = vec![];
for item in pair.into_inner() {
match item.as_rule() {
Rule::network => {
let mut inner = item.into_inner();
name = inner.next().unwrap().as_str().to_string();
for p in inner {
if p.as_rule() == Rule::property {
properties.push(parse_property(p));
}
}
}
Rule::variable => variables.push(parse_variable(item)),
Rule::probability => probabilities.push(parse_probability(item)),
_ => {}
}
}
Network {
name,
properties,
variables,
probabilities,
}
}
fn parse_property(pair: Pair<Rule>) -> Property {
let mut inner = pair.into_inner();
let key = inner.next().unwrap().as_str().to_string();
let value = inner.next().unwrap().as_str().to_string();
Property { key, value }
}
fn parse_variable(pair: Pair<Rule>) -> Variable {
let mut inner = pair.into_inner();
let label = inner.next().unwrap().as_str().to_string();
inner.next(); let values_pair = inner.next().unwrap(); let states = values_pair
.into_inner()
.map(|v| v.as_str().to_string())
.collect();
inner.next();
let properties = inner
.filter(|p| p.as_rule() == Rule::property)
.map(parse_property)
.collect();
Variable {
label,
states,
_properties: properties,
}
}
fn parse_probability(pair: Pair<Rule>) -> Probability {
let mut inner = pair.into_inner();
let label = inner.next().unwrap().as_str().to_string();
let mut parents = vec![];
let mut table = None;
let mut entries = vec![];
let mut next = inner.next().unwrap();
if next.as_rule() == Rule::parents {
parents = next
.into_inner()
.next()
.unwrap()
.into_inner()
.map(|p| p.as_str().to_string())
.collect();
next = inner.next().unwrap(); }
match next.as_rule() {
Rule::number_list => {
table = Some(parse_number_list(next));
}
Rule::entry => {
entries.push(parse_entry(next));
for entry in inner {
if entry.as_rule() == Rule::entry {
entries.push(parse_entry(entry));
}
}
}
_ => {}
}
let entries = if entries.is_empty() {
None
} else {
Some(entries)
};
Probability {
label,
parents,
table,
entries,
}
}
fn parse_entry(pair: Pair<Rule>) -> (Vec<String>, Vec<f64>) {
let mut inner = pair.into_inner();
let values = inner
.next()
.unwrap()
.into_inner()
.map(|v| v.as_str().to_string())
.collect();
let probs = parse_number_list(inner.next().unwrap());
(values, probs)
}
fn parse_number_list(pair: Pair<Rule>) -> Vec<f64> {
pair.into_inner()
.map(|n| n.as_str().parse::<f64>().unwrap())
.collect()
}