use crate::merge::Merge;
use crate::counter::ConstraintGCounter;
use crate::orset::ConstraintORSet;
use crate::eisenstein::EisensteinRegister;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintState {
pub node_id: String,
pub constraints: ConstraintORSet,
pub metrics: ConstraintGCounter,
pub position: EisensteinRegister,
pub version: u64,
}
impl ConstraintState {
pub fn new(node_id: &str) -> Self {
Self {
node_id: node_id.to_string(),
constraints: ConstraintORSet::new(),
metrics: ConstraintGCounter::new(),
position: EisensteinRegister::new((0, 0), node_id),
version: 0,
}
}
pub fn add_constraint(&mut self, id: &str) {
self.constraints.add(id, &self.node_id);
self.version += 1;
}
pub fn remove_constraint(&mut self, id: &str) {
self.constraints.remove(id);
self.version += 1;
}
pub fn record_satisfied(&mut self, count: u64) {
self.metrics.record_satisfied(&self.node_id, count);
self.version += 1;
}
pub fn record_violations(&mut self, count: u64) {
self.metrics.record_violations(&self.node_id, count);
self.version += 1;
}
pub fn update_position(&mut self, pos: (i32, i32)) {
self.position.update(pos, &self.node_id);
self.version += 1;
}
pub fn satisfaction_rate(&self) -> f64 {
self.metrics.satisfaction_rate()
}
pub fn active_constraint_count(&self) -> usize {
self.constraints.len()
}
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
}
impl Merge for ConstraintState {
fn merge(&mut self, other: &Self) {
self.constraints.merge(&other.constraints);
self.metrics.merge(&other.metrics);
self.position.merge(&other.position);
self.version = self.version.max(other.version);
}
}
impl fmt::Display for ConstraintState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ConstraintState(node={}, v={}, {} active, {:.1}% satisfied, pos={})",
self.node_id, self.version,
self.active_constraint_count(),
self.satisfaction_rate() * 100.0,
self.position)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merge::laws;
#[test]
fn test_state_creation() {
let s = ConstraintState::new("forgemaster");
assert_eq!(s.node_id, "forgemaster");
assert_eq!(s.version, 0);
assert_eq!(s.active_constraint_count(), 0);
}
#[test]
fn test_add_remove_constraints() {
let mut s = ConstraintState::new("a");
s.add_constraint("bounds");
s.add_constraint("norm");
assert_eq!(s.active_constraint_count(), 2);
s.remove_constraint("bounds");
assert_eq!(s.active_constraint_count(), 1);
assert!(s.constraints.contains("norm"));
}
#[test]
fn test_merge_two_nodes() {
let mut a = ConstraintState::new("forgemaster");
a.add_constraint("bounds");
a.add_constraint("norm");
a.record_satisfied(1000);
a.record_violations(5);
let mut b = ConstraintState::new("oracle1");
b.add_constraint("holonomy");
b.record_satisfied(2000);
b.record_violations(10);
let merged = a.merged(&b);
assert!(merged.constraints.contains("bounds"));
assert!(merged.constraints.contains("norm"));
assert!(merged.constraints.contains("holonomy"));
assert_eq!(merged.metrics.total_satisfied(), 3000);
assert_eq!(merged.metrics.total_violations(), 15);
assert!(merged.version >= a.version);
}
#[test]
fn test_merge_commutative() {
let mut a = ConstraintState::new("a");
a.add_constraint("c1");
a.record_satisfied(100);
let mut b = ConstraintState::new("b");
b.add_constraint("c2");
b.record_satisfied(200);
assert!(laws::check_commutative(&a, &b));
}
#[test]
fn test_merge_associative() {
let mut a = ConstraintState::new("a");
a.add_constraint("c1");
let mut b = ConstraintState::new("b");
b.add_constraint("c2");
let mut c = ConstraintState::new("c");
c.add_constraint("c3");
assert!(laws::check_associative(&a, &b, &c));
}
#[test]
fn test_merge_idempotent() {
let mut a = ConstraintState::new("a");
a.add_constraint("c1");
a.record_satisfied(100);
assert!(laws::check_idempotent(&a));
}
#[test]
fn test_satisfaction_rate() {
let mut s = ConstraintState::new("a");
s.record_satisfied(950);
s.record_violations(50);
assert!((s.satisfaction_rate() - 0.95).abs() < 0.01);
}
#[test]
fn test_json_roundtrip() {
let mut s = ConstraintState::new("test");
s.add_constraint("c1");
s.record_satisfied(100);
let json = s.to_json();
assert!(json.contains("test"));
assert!(json.contains("c1"));
}
}
impl PartialEq for ConstraintState {
fn eq(&self, other: &Self) -> bool {
self.constraints == other.constraints
&& self.metrics == other.metrics
&& self.position == other.position
&& self.version == other.version
}
}