#![allow(clippy::cast_precision_loss)]
use crate::Uncertain;
use crate::traits::Shareable;
pub trait LogicalOps {
#[must_use]
fn and(&self, other: &Self) -> Self;
#[must_use]
fn or(&self, other: &Self) -> Self;
#[must_use]
fn not(&self) -> Self;
#[must_use]
fn xor(&self, other: &Self) -> Self;
#[must_use]
fn nand(&self, other: &Self) -> Self;
#[must_use]
fn nor(&self, other: &Self) -> Self;
}
impl LogicalOps for Uncertain<bool> {
fn and(&self, other: &Self) -> Self {
let sample_fn1 = self.sample_fn.clone();
let sample_fn2 = other.sample_fn.clone();
Uncertain::new(move || sample_fn1() && sample_fn2())
}
fn or(&self, other: &Self) -> Self {
let sample_fn1 = self.sample_fn.clone();
let sample_fn2 = other.sample_fn.clone();
Uncertain::new(move || sample_fn1() || sample_fn2())
}
fn not(&self) -> Self {
let sample_fn = self.sample_fn.clone();
Uncertain::new(move || !sample_fn())
}
fn xor(&self, other: &Self) -> Self {
let sample_fn1 = self.sample_fn.clone();
let sample_fn2 = other.sample_fn.clone();
Uncertain::new(move || sample_fn1() ^ sample_fn2())
}
fn nand(&self, other: &Self) -> Self {
self.and(other).not()
}
fn nor(&self, other: &Self) -> Self {
self.or(other).not()
}
}
impl Uncertain<bool> {
#[must_use]
pub fn if_then_else<T, F1, F2>(&self, if_true: F1, if_false: F2) -> Uncertain<T>
where
T: Shareable,
F1: Fn() -> Uncertain<T> + Send + Sync + 'static,
F2: Fn() -> Uncertain<T> + Send + Sync + 'static,
{
let sample_fn = self.sample_fn.clone();
Uncertain::new(move || {
if sample_fn() {
if_true().sample()
} else {
if_false().sample()
}
})
}
#[must_use]
pub fn implies(&self, consequent: &Self) -> Uncertain<bool> {
self.not().or(consequent)
}
#[must_use]
pub fn if_and_only_if(&self, other: &Self) -> Uncertain<bool> {
let both_true = self.and(other);
let both_false = self.not().and(&other.not());
both_true.or(&both_false)
}
#[must_use]
pub fn probability(&self, sample_count: usize) -> f64 {
let samples: Vec<bool> = self.take_samples(sample_count);
samples.iter().filter(|&&x| x).count() as f64 / samples.len() as f64
}
}
use std::ops::{BitAnd, BitOr, Not};
impl BitAnd for Uncertain<bool> {
type Output = Uncertain<bool>;
fn bitand(self, rhs: Self) -> Self::Output {
self.and(&rhs)
}
}
impl BitOr for Uncertain<bool> {
type Output = Uncertain<bool>;
fn bitor(self, rhs: Self) -> Self::Output {
self.or(&rhs)
}
}
impl Not for Uncertain<bool> {
type Output = Uncertain<bool>;
fn not(self) -> Self::Output {
LogicalOps::not(&self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operations::Comparison;
#[test]
fn test_logical_and() {
let always_true = Uncertain::point(true);
let always_false = Uncertain::point(false);
assert!(always_true.and(&always_true).sample());
assert!(!always_true.and(&always_false).sample());
assert!(!always_false.and(&always_false).sample());
}
#[test]
fn test_logical_or() {
let always_true = Uncertain::point(true);
let always_false = Uncertain::point(false);
assert!(always_true.or(&always_true).sample());
assert!(always_true.or(&always_false).sample());
assert!(!always_false.or(&always_false).sample());
}
#[test]
fn test_logical_not() {
let always_true = Uncertain::point(true);
let always_false = Uncertain::point(false);
assert!(!always_true.not().sample());
assert!(always_false.not().sample());
}
#[test]
fn test_operator_overloading() {
let a = Uncertain::point(true);
let b = Uncertain::point(false);
assert!(!((a.clone() & b.clone()).sample()));
assert!((a.clone() | b.clone()).sample());
assert!(!(!a).sample());
}
#[test]
fn test_complex_logical_expression() {
let temp = Uncertain::normal(22.0, 2.0);
let humidity = Uncertain::normal(50.0, 5.0);
let temp_ok = temp.within_range(20.0, 25.0);
let humidity_ok = humidity.within_range(40.0, 60.0);
let comfortable = temp_ok.and(&humidity_ok);
let uncomfortable = temp_ok.not().or(&humidity_ok.not());
let comfortable_prob = comfortable.probability(1000);
let uncomfortable_prob = uncomfortable.probability(1000);
assert!((comfortable_prob + uncomfortable_prob - 1.0).abs() < 0.1);
}
#[test]
#[allow(clippy::float_cmp)]
fn test_if_then_else() {
let condition = Uncertain::bernoulli(0.8);
let result = condition.if_then_else(|| Uncertain::point(10.0), || Uncertain::point(5.0));
let samples: Vec<f64> = result.take_samples(1000);
let ten_count = samples.iter().filter(|&&x| x == 10.0).count();
let ten_ratio = ten_count as f64 / samples.len() as f64;
assert!((ten_ratio - 0.8).abs() < 0.1);
}
#[test]
fn test_implication() {
let raining = Uncertain::bernoulli(0.3);
let umbrella = Uncertain::bernoulli(0.9);
let implication = raining.implies(&umbrella);
let prob = implication.probability(1000);
assert!(prob > 0.9);
}
#[test]
fn test_shared_variable_semantics() {
let x = Uncertain::normal(0.0, 1.0);
let above = Comparison::gt(&x, 0.0);
let below = Comparison::lt(&x, 0.0);
let both = above.and(&below);
let prob_both = both.probability(1000);
assert!((0.0..=1.0).contains(&prob_both));
}
}