use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", derive(PartialEq))]
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
pub enum RelationalOperator {
EqualTo,
NotEqualTo,
LessOrEqualTo,
LessThan,
GreaterOrEqualTo,
GreaterThan,
}
#[cfg(feature = "python")]
#[pymethods]
impl RelationalOperator {
pub fn __repr__(&self) -> PyResult<String> {
Ok(format!("RelationalOperator({:?})", self))
}
pub fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass(get_all))]
pub struct Constraint {
name: String,
operator: RelationalOperator,
target: f64,
}
impl Constraint {
pub fn new(name: &str, operator: RelationalOperator, target: f64) -> Self {
Self {
name: name.to_owned(),
operator,
target,
}
}
pub fn new_with_modifiers(
name: &str,
operator: RelationalOperator,
target: f64,
scale: f64,
offset: f64,
) -> Self {
Self {
name: name.to_owned(),
operator,
target: target * scale + offset,
}
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn is_met(&self, value: f64) -> bool {
match self.operator {
RelationalOperator::EqualTo => value == self.target,
RelationalOperator::NotEqualTo => value != self.target,
RelationalOperator::LessOrEqualTo => value <= self.target,
RelationalOperator::LessThan => value < self.target,
RelationalOperator::GreaterOrEqualTo => value >= self.target,
RelationalOperator::GreaterThan => value > self.target,
}
}
pub fn constraint_violation(&self, value: f64) -> f64 {
if self.is_met(value) {
0.0
} else {
match self.operator {
RelationalOperator::EqualTo => f64::abs(self.target - value),
RelationalOperator::NotEqualTo => 1.0,
RelationalOperator::LessOrEqualTo | RelationalOperator::GreaterOrEqualTo => {
f64::abs(self.target - value)
}
RelationalOperator::LessThan | RelationalOperator::GreaterThan => {
f64::abs(self.target - value) + 0.0001
}
}
}
}
pub fn target(&self) -> f64 {
self.target
}
pub fn operator(&self) -> RelationalOperator {
self.operator.clone()
}
}
impl Display for Constraint {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let sign = match self.operator {
RelationalOperator::EqualTo => "==",
RelationalOperator::NotEqualTo => "!=",
RelationalOperator::LessOrEqualTo => "<=",
RelationalOperator::LessThan => "<",
RelationalOperator::GreaterOrEqualTo => ">=",
RelationalOperator::GreaterThan => ">",
};
f.write_fmt(format_args!("{} {} {}", self.name, sign, self.target))
}
}
#[cfg(feature = "python")]
#[pymethods]
impl Constraint {
pub fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"Constraint(name='{}', operator='{:?}', target={})",
self.name, self.operator, self.target
))
}
pub fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
#[cfg(test)]
mod test {
use float_cmp::assert_approx_eq;
use crate::core::{Constraint, RelationalOperator};
#[test]
fn test_is_met() {
let c = Constraint::new("test", RelationalOperator::EqualTo, 5.2);
assert!(c.is_met(5.2));
assert!(!c.is_met(15.0));
let c = Constraint::new("test", RelationalOperator::NotEqualTo, 5.2);
assert!(!c.is_met(5.2));
assert!(c.is_met(15.0));
let c = Constraint::new("test", RelationalOperator::GreaterThan, 5.2);
assert!(!c.is_met(5.2));
assert!(c.is_met(15.0));
assert!(!c.is_met(1.0));
let c = Constraint::new("test", RelationalOperator::GreaterOrEqualTo, 5.2);
assert!(c.is_met(5.2));
assert!(c.is_met(15.0));
assert!(!c.is_met(1.0));
let c = Constraint::new("test", RelationalOperator::LessThan, 5.2);
assert!(!c.is_met(5.2));
assert!(c.is_met(1.0));
assert!(!c.is_met(15.0));
let c = Constraint::new("test", RelationalOperator::LessOrEqualTo, 5.2);
assert!(c.is_met(5.2));
assert!(!c.is_met(15.0));
assert!(c.is_met(1.0));
let c = Constraint::new_with_modifiers("test", RelationalOperator::EqualTo, 5.2, 1.0, -1.0);
assert!(c.is_met(4.2));
let c = Constraint::new_with_modifiers("test", RelationalOperator::EqualTo, 5.0, 0.5, 1.0);
assert!(c.is_met(3.5));
}
#[test]
fn test_constraint_violation() {
let c = Constraint::new("test", RelationalOperator::EqualTo, 5.2);
assert_eq!(c.constraint_violation(5.2), 0.0);
assert_eq!(c.constraint_violation(1.2), 4.0);
assert_eq!(c.constraint_violation(-1.2), 6.4);
let c = Constraint::new("test", RelationalOperator::NotEqualTo, 5.2);
assert_eq!(c.constraint_violation(5.2), 1.0);
assert_eq!(c.constraint_violation(1.0), 0.0);
let c = Constraint::new("test", RelationalOperator::LessThan, 5.2);
assert_eq!(c.constraint_violation(0.0), 0.0);
assert_approx_eq!(f64, c.constraint_violation(9.2), 4.0, epsilon = 0.001);
let c = Constraint::new("test", RelationalOperator::GreaterThan, 5.2);
assert_eq!(c.constraint_violation(10.0), 0.0);
assert_approx_eq!(f64, c.constraint_violation(2.2), 3.0, epsilon = 0.001);
let c = Constraint::new("test", RelationalOperator::LessOrEqualTo, 5.2);
assert_eq!(c.constraint_violation(0.0), 0.0);
assert_eq!(c.constraint_violation(5.2), 0.0);
assert_approx_eq!(f64, c.constraint_violation(9.2), 4.0, epsilon = 0.001);
let c = Constraint::new("test", RelationalOperator::GreaterOrEqualTo, 5.2);
assert_eq!(c.constraint_violation(10.0), 0.0);
assert_eq!(c.constraint_violation(5.2), 0.0);
assert_approx_eq!(f64, c.constraint_violation(2.2), 3.0, epsilon = 0.001);
}
}