Skip to main content

ringkernel_procint/kernels/
conformance.rs

1//! Conformance checking kernel.
2//!
3//! Validates traces against reference process models using GPU acceleration.
4
5use crate::cuda::{
6    generate_conformance_kernel, CpuFallbackExecutor, ExecutionResult, GpuStats, GpuStatus,
7    KernelExecutor,
8};
9use crate::models::{ComplianceLevel, ConformanceResult, GpuObjectEvent, ProcessModel};
10
11/// Conformance checking kernel.
12pub struct ConformanceKernel {
13    /// Reference model.
14    model: ProcessModel,
15    /// Kernel executor.
16    executor: KernelExecutor,
17    /// Use GPU if available.
18    use_gpu: bool,
19    /// Whether kernel is compiled.
20    kernel_compiled: bool,
21}
22
23impl ConformanceKernel {
24    /// Create a new conformance kernel with reference model.
25    pub fn new(model: ProcessModel) -> Self {
26        let mut kernel = Self {
27            model,
28            executor: KernelExecutor::new(),
29            use_gpu: true,
30            kernel_compiled: false,
31        };
32
33        // Try to compile the CUDA kernel at creation time
34        kernel.try_compile_kernel();
35        kernel
36    }
37
38    /// Try to compile the CUDA kernel.
39    fn try_compile_kernel(&mut self) {
40        if self.executor.is_cuda_available() && !self.kernel_compiled {
41            let source = generate_conformance_kernel();
42            match self.executor.compile(&source) {
43                Ok(_) => {
44                    log::info!("Conformance CUDA kernel compiled successfully");
45                    self.kernel_compiled = true;
46                }
47                Err(e) => {
48                    log::warn!("Conformance CUDA kernel compilation failed: {}", e);
49                    self.kernel_compiled = false;
50                }
51            }
52        }
53    }
54
55    /// Disable GPU (use CPU fallback).
56    pub fn with_cpu_only(mut self) -> Self {
57        self.use_gpu = false;
58        self
59    }
60
61    /// Get GPU status.
62    pub fn gpu_status(&self) -> GpuStatus {
63        self.executor.gpu_status()
64    }
65
66    /// Get GPU stats.
67    pub fn gpu_stats(&self) -> &GpuStats {
68        &self.executor.stats
69    }
70
71    /// Check if GPU is being used.
72    pub fn is_using_gpu(&self) -> bool {
73        self.use_gpu && self.kernel_compiled && self.executor.is_cuda_available()
74    }
75
76    /// Get the reference model.
77    pub fn model(&self) -> &ProcessModel {
78        &self.model
79    }
80
81    /// Check conformance for a batch of events.
82    pub fn check(&mut self, events: &[GpuObjectEvent]) -> ConformanceCheckResult {
83        let start = std::time::Instant::now();
84
85        // Try GPU path first if available and compiled
86        #[cfg(feature = "cuda")]
87        let (gpu_results, exec_result) = if self.is_using_gpu() {
88            match self.executor.execute_conformance_gpu(events, &self.model) {
89                Ok((results, result)) => {
90                    log::debug!(
91                        "Conformance GPU execution: {} events -> {} results in {}µs",
92                        events.len(),
93                        results.len(),
94                        result.execution_time_us
95                    );
96                    (Some(results), result)
97                }
98                Err(e) => {
99                    log::warn!(
100                        "Conformance GPU execution failed, falling back to CPU: {}",
101                        e
102                    );
103                    (None, ExecutionResult::default())
104                }
105            }
106        } else {
107            (None, ExecutionResult::default())
108        };
109
110        #[cfg(not(feature = "cuda"))]
111        let gpu_results: Option<Vec<ConformanceResult>> = None;
112        #[cfg(not(feature = "cuda"))]
113        let exec_result = ExecutionResult::default();
114
115        // Use GPU results or fall back to CPU
116        let (results, exec_result) = if let Some(gpu_results) = gpu_results {
117            (gpu_results, exec_result)
118        } else {
119            // CPU fallback path
120            let (results, result) = CpuFallbackExecutor::execute_conformance(events, &self.model);
121            (results, result)
122        };
123
124        let total_time = start.elapsed().as_micros() as u64;
125
126        ConformanceCheckResult {
127            results,
128            execution_result: exec_result,
129            total_time_us: total_time,
130        }
131    }
132}
133
134/// Result of conformance checking.
135#[derive(Debug, Clone)]
136pub struct ConformanceCheckResult {
137    /// Individual conformance results.
138    pub results: Vec<ConformanceResult>,
139    /// Kernel execution result.
140    pub execution_result: ExecutionResult,
141    /// Total processing time in microseconds.
142    pub total_time_us: u64,
143}
144
145impl ConformanceCheckResult {
146    /// Get number of conformant traces.
147    pub fn conformant_count(&self) -> usize {
148        self.results.iter().filter(|r| r.is_conformant()).count()
149    }
150
151    /// Get number of non-conformant traces.
152    pub fn non_conformant_count(&self) -> usize {
153        self.results.len() - self.conformant_count()
154    }
155
156    /// Get average fitness.
157    pub fn avg_fitness(&self) -> f32 {
158        if self.results.is_empty() {
159            return 1.0;
160        }
161        let total: f32 = self.results.iter().map(|r| r.fitness).sum();
162        total / self.results.len() as f32
163    }
164
165    /// Get results by compliance level.
166    pub fn by_compliance(&self, level: ComplianceLevel) -> Vec<&ConformanceResult> {
167        self.results
168            .iter()
169            .filter(|r| r.get_compliance_level() == level)
170            .collect()
171    }
172
173    /// Get conformance distribution.
174    pub fn distribution(&self) -> ConformanceDistribution {
175        let mut dist = ConformanceDistribution::default();
176        for result in &self.results {
177            match result.get_compliance_level() {
178                ComplianceLevel::FullyCompliant => dist.fully_compliant += 1,
179                ComplianceLevel::MostlyCompliant => dist.mostly_compliant += 1,
180                ComplianceLevel::PartiallyCompliant => dist.partially_compliant += 1,
181                ComplianceLevel::NonCompliant => dist.non_compliant += 1,
182            }
183        }
184        dist
185    }
186}
187
188/// Distribution of conformance levels.
189#[derive(Debug, Clone, Default)]
190pub struct ConformanceDistribution {
191    /// Fully compliant (fitness >= 0.95).
192    pub fully_compliant: usize,
193    /// Mostly compliant (fitness >= 0.8).
194    pub mostly_compliant: usize,
195    /// Partially compliant (fitness >= 0.5).
196    pub partially_compliant: usize,
197    /// Non-compliant (fitness < 0.5).
198    pub non_compliant: usize,
199}
200
201impl ConformanceDistribution {
202    /// Get total traces.
203    pub fn total(&self) -> usize {
204        self.fully_compliant + self.mostly_compliant + self.partially_compliant + self.non_compliant
205    }
206
207    /// Get percentages.
208    pub fn percentages(&self) -> [f32; 4] {
209        let total = self.total() as f32;
210        if total == 0.0 {
211            return [0.0; 4];
212        }
213        [
214            self.fully_compliant as f32 / total * 100.0,
215            self.mostly_compliant as f32 / total * 100.0,
216            self.partially_compliant as f32 / total * 100.0,
217            self.non_compliant as f32 / total * 100.0,
218        ]
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::models::{HybridTimestamp, ProcessModelType};
226
227    fn create_test_model() -> ProcessModel {
228        let mut model = ProcessModel::new(1, "Test", ProcessModelType::DFG);
229        model.start_activities = vec![1];
230        model.end_activities = vec![3];
231        model.add_transition(1, 2); // A -> B
232        model.add_transition(2, 3); // B -> C
233        model
234    }
235
236    fn create_conformant_events() -> Vec<GpuObjectEvent> {
237        // A -> B -> C (conformant)
238        vec![
239            GpuObjectEvent {
240                event_id: 1,
241                object_id: 100,
242                activity_id: 1,
243                timestamp: HybridTimestamp::new(0, 0),
244                ..Default::default()
245            },
246            GpuObjectEvent {
247                event_id: 2,
248                object_id: 100,
249                activity_id: 2,
250                timestamp: HybridTimestamp::new(100, 0),
251                ..Default::default()
252            },
253            GpuObjectEvent {
254                event_id: 3,
255                object_id: 100,
256                activity_id: 3,
257                timestamp: HybridTimestamp::new(200, 0),
258                ..Default::default()
259            },
260        ]
261    }
262
263    #[test]
264    fn test_conformant_trace() {
265        let model = create_test_model();
266        let mut kernel = ConformanceKernel::new(model).with_cpu_only();
267        let events = create_conformant_events();
268        let result = kernel.check(&events);
269
270        assert_eq!(result.results.len(), 1);
271        assert!(result.results[0].is_conformant());
272        assert_eq!(result.results[0].fitness, 1.0);
273    }
274
275    #[test]
276    fn test_non_conformant_trace() {
277        let model = create_test_model();
278        let mut kernel = ConformanceKernel::new(model).with_cpu_only();
279
280        // A -> C (missing B)
281        let events = vec![
282            GpuObjectEvent {
283                event_id: 1,
284                object_id: 100,
285                activity_id: 1,
286                timestamp: HybridTimestamp::new(0, 0),
287                ..Default::default()
288            },
289            GpuObjectEvent {
290                event_id: 2,
291                object_id: 100,
292                activity_id: 3,
293                timestamp: HybridTimestamp::new(100, 0),
294                ..Default::default()
295            },
296        ];
297
298        let result = kernel.check(&events);
299        assert!(!result.results[0].is_conformant());
300    }
301
302    #[test]
303    fn test_conformance_distribution() {
304        let model = create_test_model();
305        let mut kernel = ConformanceKernel::new(model).with_cpu_only();
306        let events = create_conformant_events();
307        let result = kernel.check(&events);
308
309        let dist = result.distribution();
310        assert_eq!(dist.fully_compliant, 1);
311    }
312}