1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub struct TokenReplayResult {
6 pub fitness: f64,
7 pub produced_tokens: usize,
8 pub consumed_tokens: usize,
9 pub missing_tokens: usize,
10 pub remaining_tokens: usize,
11}
12
13impl TokenReplayResult {
14 pub fn new(
15 fitness: f64,
16 produced_tokens: usize,
17 consumed_tokens: usize,
18 missing_tokens: usize,
19 remaining_tokens: usize,
20 ) -> Self {
21 TokenReplayResult {
22 fitness,
23 produced_tokens,
24 consumed_tokens,
25 missing_tokens,
26 remaining_tokens,
27 }
28 }
29
30 pub fn calculate_fitness(
31 produced: usize,
32 consumed: usize,
33 missing: usize,
34 remaining: usize,
35 ) -> f64 {
36 let denom = (produced + remaining).max(1) as f64;
37 let num = consumed.saturating_sub(missing) as f64;
38 clamp_finite(num / denom, 0.0, 1.0)
41 }
42}
43
44fn clamp_finite(x: f64, lo: f64, hi: f64) -> f64 {
52 if x.is_nan() || x < lo {
53 lo
54 } else if x > hi {
55 hi
56 } else {
57 x
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63pub struct ConformanceResult {
64 pub fitness: f64,
65 pub precision: Option<f64>,
66 pub generalization: Option<f64>,
67 pub simplicity: Option<f64>,
68 pub total_traces: usize,
69 pub fitting_traces: usize,
70 pub deviating_traces: usize,
71}
72
73impl ConformanceResult {
74 pub fn new(
75 fitness: f64,
76 total_traces: usize,
77 fitting_traces: usize,
78 deviating_traces: usize,
79 ) -> Self {
80 ConformanceResult {
81 fitness,
82 precision: None,
83 generalization: None,
84 simplicity: None,
85 total_traces,
86 fitting_traces,
87 deviating_traces,
88 }
89 }
90
91 pub fn with_precision(mut self, precision: f64) -> Self {
92 self.precision = Some(clamp_finite(precision, 0.0, 1.0));
94 self
95 }
96
97 pub fn with_generalization(mut self, generalization: f64) -> Self {
98 self.generalization = Some(clamp_finite(generalization, 0.0, 1.0));
99 self
100 }
101
102 pub fn with_simplicity(mut self, simplicity: f64) -> Self {
103 self.simplicity = Some(clamp_finite(simplicity, 0.0, 1.0));
104 self
105 }
106
107 pub fn conformance_rate(&self) -> f64 {
108 if self.total_traces == 0 {
109 0.0
110 } else {
111 self.fitting_traces as f64 / self.total_traces as f64
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_token_replay_fitness() {
122 let fitness = TokenReplayResult::calculate_fitness(100, 95, 5, 10);
123 assert!((fitness - 0.8181818).abs() < 0.001); }
125
126 #[test]
127 fn test_conformance_result() {
128 let result = ConformanceResult::new(0.95, 100, 95, 5);
129 assert_eq!(result.conformance_rate(), 0.95);
130 assert_eq!(result.fitting_traces, 95);
131 }
132
133 #[test]
139 fn clamp_finite_handles_nan_and_inf() {
140 assert_eq!(clamp_finite(f64::NAN, 0.0, 1.0), 0.0);
141 assert_eq!(clamp_finite(f64::INFINITY, 0.0, 1.0), 1.0);
142 assert_eq!(clamp_finite(f64::NEG_INFINITY, 0.0, 1.0), 0.0);
143 assert_eq!(clamp_finite(0.5, 0.0, 1.0), 0.5);
144 assert_eq!(clamp_finite(-1.0, 0.0, 1.0), 0.0);
145 assert_eq!(clamp_finite(2.0, 0.0, 1.0), 1.0);
146 }
147
148 #[test]
151 fn conformance_builders_do_not_panic_on_nan() {
152 let r = ConformanceResult::new(0.5, 10, 5, 5)
153 .with_precision(f64::NAN)
154 .with_generalization(f64::NAN)
155 .with_simplicity(f64::NAN);
156 assert_eq!(r.precision, Some(0.0));
157 assert_eq!(r.generalization, Some(0.0));
158 assert_eq!(r.simplicity, Some(0.0));
159 }
160}