use amari_holographic::optical::SymbolId;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct OrderedFloat<T>(pub T);
impl OrderedFloat<f32> {
#[inline]
pub fn new(value: f32) -> Self {
Self(value)
}
#[inline]
pub fn into_inner(self) -> f32 {
self.0
}
}
impl PartialEq for OrderedFloat<f32> {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl Eq for OrderedFloat<f32> {}
impl Hash for OrderedFloat<f32> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl PartialOrd for OrderedFloat<f32> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedFloat<f32> {
#[allow(clippy::cast_possible_wrap)]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let a = self.0.to_bits() as i32;
let b = other.0.to_bits() as i32;
let a_adj = if a < 0 { !a } else { a ^ (1 << 31) };
let b_adj = if b < 0 { !b } else { b ^ (1 << 31) };
a_adj.cmp(&b_adj)
}
}
impl From<f32> for OrderedFloat<f32> {
fn from(value: f32) -> Self {
Self(value)
}
}
impl From<OrderedFloat<f32>> for f32 {
fn from(value: OrderedFloat<f32>) -> Self {
value.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SymbolicExpression {
Symbol(SymbolId),
Bind(Box<SymbolicExpression>, Box<SymbolicExpression>),
Bundle(Vec<(OrderedFloat<f32>, SymbolicExpression)>),
}
impl SymbolicExpression {
pub fn symbol(name: impl Into<String>) -> Self {
Self::Symbol(SymbolId::new(name))
}
pub fn bind(a: Self, b: Self) -> Self {
Self::Bind(Box::new(a), Box::new(b))
}
pub fn bundle(elements: Vec<(f32, Self)>) -> Self {
Self::Bundle(
elements
.into_iter()
.map(|(w, e)| (OrderedFloat(w), e))
.collect(),
)
}
pub fn bundle_uniform(elements: Vec<Self>) -> Self {
Self::Bundle(
elements
.into_iter()
.map(|e| (OrderedFloat(1.0), e))
.collect(),
)
}
pub fn role_filler(role: impl Into<String>, filler: impl Into<String>) -> Self {
Self::bind(Self::symbol(role), Self::symbol(filler))
}
pub fn referenced_symbols(&self) -> Vec<&SymbolId> {
match self {
Self::Symbol(id) => vec![id],
Self::Bind(a, b) => {
let mut syms = a.referenced_symbols();
syms.extend(b.referenced_symbols());
syms
}
Self::Bundle(elements) => elements
.iter()
.flat_map(|(_, e)| e.referenced_symbols())
.collect(),
}
}
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
pub fn is_bind(&self) -> bool {
matches!(self, Self::Bind(_, _))
}
pub fn is_bundle(&self) -> bool {
matches!(self, Self::Bundle(_))
}
pub fn as_symbol(&self) -> Option<&SymbolId> {
match self {
Self::Symbol(id) => Some(id),
_ => None,
}
}
pub fn node_count(&self) -> usize {
match self {
Self::Symbol(_) => 1,
Self::Bind(a, b) => 1 + a.node_count() + b.node_count(),
Self::Bundle(elements) => {
1 + elements.iter().map(|(_, e)| e.node_count()).sum::<usize>()
}
}
}
pub fn depth(&self) -> usize {
match self {
Self::Symbol(_) => 1,
Self::Bind(a, b) => 1 + a.depth().max(b.depth()),
Self::Bundle(elements) => {
1 + elements.iter().map(|(_, e)| e.depth()).max().unwrap_or(0)
}
}
}
}
impl std::fmt::Display for SymbolicExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Symbol(id) => write!(f, "{id}"),
Self::Bind(a, b) => write!(f, "({a} ⊗ {b})"),
Self::Bundle(elements) => {
write!(f, "[")?;
for (i, (w, e)) in elements.iter().enumerate() {
if i > 0 {
write!(f, " + ")?;
}
let weight = w.0;
if (weight - 1.0).abs() > 0.001 {
write!(f, "{weight:.2}·{e}")?;
} else {
write!(f, "{e}")?;
}
}
write!(f, "]")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ordered_float_equality() {
let a = OrderedFloat(1.0f32);
let b = OrderedFloat(1.0f32);
let c = OrderedFloat(2.0f32);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_ordered_float_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(OrderedFloat(1.0f32));
set.insert(OrderedFloat(1.0f32)); set.insert(OrderedFloat(2.0f32));
assert_eq!(set.len(), 2);
}
#[test]
fn test_symbolic_expression_symbol() {
let expr = SymbolicExpression::symbol("test");
assert!(expr.is_symbol());
assert_eq!(expr.as_symbol().unwrap().as_str(), "test");
}
#[test]
fn test_symbolic_expression_bind() {
let expr = SymbolicExpression::bind(
SymbolicExpression::symbol("a"),
SymbolicExpression::symbol("b"),
);
assert!(expr.is_bind());
let symbols = expr.referenced_symbols();
assert_eq!(symbols.len(), 2);
}
#[test]
fn test_symbolic_expression_bundle() {
let expr = SymbolicExpression::bundle(vec![
(1.0, SymbolicExpression::symbol("a")),
(0.5, SymbolicExpression::symbol("b")),
]);
assert!(expr.is_bundle());
let symbols = expr.referenced_symbols();
assert_eq!(symbols.len(), 2);
}
#[test]
fn test_role_filler() {
let a = SymbolicExpression::role_filler("AGENT", "John");
let b = SymbolicExpression::bind(
SymbolicExpression::symbol("AGENT"),
SymbolicExpression::symbol("John"),
);
assert_eq!(a, b);
}
#[test]
fn test_node_count_and_depth() {
let simple = SymbolicExpression::symbol("x");
assert_eq!(simple.node_count(), 1);
assert_eq!(simple.depth(), 1);
let nested = SymbolicExpression::bind(
SymbolicExpression::bind(
SymbolicExpression::symbol("a"),
SymbolicExpression::symbol("b"),
),
SymbolicExpression::symbol("c"),
);
assert_eq!(nested.node_count(), 5); assert_eq!(nested.depth(), 3);
}
#[test]
fn test_display() {
let expr = SymbolicExpression::role_filler("AGENT", "John");
let s = format!("{}", expr);
assert!(s.contains("AGENT"));
assert!(s.contains("John"));
}
#[test]
fn test_serde_roundtrip() {
let expr = SymbolicExpression::bind(
SymbolicExpression::symbol("AGENT"),
SymbolicExpression::symbol("John"),
);
let json = serde_json::to_string(&expr).unwrap();
let restored: SymbolicExpression = serde_json::from_str(&json).unwrap();
assert_eq!(expr, restored);
}
}