Skip to main content

converge_analytics/packs/classification/
solver.rs

1use super::types::*;
2use converge_pack::gate::GateResult as Result;
3use converge_pack::gate::{ProblemSpec, ReplayEnvelope, SolverReport};
4
5pub struct LogisticClassifier;
6
7impl LogisticClassifier {
8    pub fn solve(
9        &self,
10        input: &ClassificationInput,
11        spec: &ProblemSpec,
12    ) -> Result<(ClassificationOutput, SolverReport)> {
13        let (pos_label, neg_label) = input
14            .labels
15            .clone()
16            .unwrap_or_else(|| ("positive".to_string(), "negative".to_string()));
17
18        let mut positive_count = 0usize;
19        let mut negative_count = 0usize;
20
21        let predictions: Vec<ClassifiedRecord> = input
22            .records
23            .iter()
24            .enumerate()
25            .map(|(i, record)| {
26                let logit: f64 = record
27                    .iter()
28                    .zip(&input.weights)
29                    .map(|(x, w)| x * w)
30                    .sum::<f64>()
31                    + input.bias;
32
33                let probability = sigmoid(logit);
34                let label = if probability >= input.threshold {
35                    positive_count += 1;
36                    pos_label.clone()
37                } else {
38                    negative_count += 1;
39                    neg_label.clone()
40                };
41
42                ClassifiedRecord {
43                    index: i,
44                    probability,
45                    label,
46                }
47            })
48            .collect();
49
50        let total = predictions.len();
51        let output = ClassificationOutput {
52            predictions,
53            positive_count,
54            negative_count,
55            total,
56        };
57
58        // Confidence based on how decisive the classifications are
59        let avg_decisiveness: f64 = output
60            .predictions
61            .iter()
62            .map(|p| (p.probability - 0.5).abs() * 2.0)
63            .sum::<f64>()
64            / total as f64;
65
66        let confidence = avg_decisiveness.clamp(0.3, 0.95);
67
68        let replay = ReplayEnvelope::minimal(spec.seed());
69        let report = SolverReport::optimal("logistic-classifier-v1", confidence, replay);
70
71        Ok((output, report))
72    }
73}
74
75fn sigmoid(x: f64) -> f64 {
76    1.0 / (1.0 + (-x).exp())
77}