Skip to main content

reflex_rs/
primitives.rs

1//! Machine-native System-1 Decision Primitives: Noul, Choice, Score.
2//! 100% mathematical parity with Python reflex-core and JavaScript @reflex-ai/sdk.
3
4use std::collections::HashSet;
5use crate::encoder::{cosine_similarity, SemanticVectorEncoder, VECTOR_DIM};
6
7/// Noul: Probabilistic Boolean primitive.
8#[derive(Clone, Debug)]
9pub struct Noul {
10    pub instructions: String,
11    pub threshold: f32,
12    pub temperature: f32,
13    pub uncertainty_low: f32,
14    pub uncertainty_high: f32,
15}
16
17impl Noul {
18    pub fn new(instructions: impl Into<String>) -> Self {
19        Self {
20            instructions: instructions.into(),
21            threshold: 0.85,
22            temperature: 0.25,
23            uncertainty_low: 0.35,
24            uncertainty_high: 0.65,
25        }
26    }
27
28    pub fn with_threshold(mut self, threshold: f32) -> Self {
29        self.threshold = threshold;
30        self
31    }
32
33    pub fn with_temperature(mut self, temperature: f32) -> Self {
34        self.temperature = temperature.max(0.01);
35        self
36    }
37
38    pub fn evaluate(
39        &self,
40        state: &str,
41        state_vec: &[f32; VECTOR_DIM],
42        encoder: &SemanticVectorEncoder,
43    ) -> NoulResult {
44        let query_vec = encoder.encode(&self.instructions);
45        let sim = cosine_similarity(state_vec, &query_vec);
46
47        let state_tokens: HashSet<String> = extract_tokens(state);
48        let query_tokens: HashSet<String> = extract_tokens(&self.instructions);
49
50        // Contrast polarity adjustment
51        let neg_words = ["not", "never", "safe", "normal", "routine", "false", "ignore"];
52        let has_neg = neg_words.iter().any(|w| state_tokens.contains(*w));
53
54        // Alarm / domain boost
55        let alarm_tokens = [
56            "scam", "fraud", "wire", "urgent", "phishing", "attack", "critical", "breach",
57            "refund", "stolen", "cancel", "ransomware", "hazard", "threat", "hacked", "emergency",
58        ];
59        let alarm_overlap = alarm_tokens
60            .iter()
61            .filter(|w| state_tokens.contains(**w))
62            .count();
63
64        let query_lower = self.instructions.to_lowercase();
65        let query_is_threat = ["security", "threat", "hazard", "scam", "urgent", "refund"]
66            .iter()
67            .any(|w| query_lower.contains(w));
68
69        let keyword_overlap = query_tokens
70            .iter()
71            .filter(|w| state_tokens.contains(*w))
72            .count();
73
74        let mut effective_sim = sim + (0.30f32).min(keyword_overlap as f32 * 0.08);
75        if query_is_threat && alarm_overlap > 0 {
76            effective_sim = effective_sim.max(0.15 + alarm_overlap as f32 * 0.05);
77        }
78
79        // Calibrate similarity into probability via sigmoid
80        let adjusted_sim = effective_sim - if has_neg { 0.15 } else { 0.0 };
81        let logit = (adjusted_sim - 0.06) / self.temperature;
82        let clamped_logit = logit.clamp(-20.0, 20.0);
83        let prob = 1.0 / (1.0 + (-clamped_logit).exp());
84        let clamped = prob.clamp(0.0, 1.0);
85
86        let is_true = clamped >= self.threshold;
87        let is_false = clamped <= (1.0 - self.threshold);
88        let is_uncertain = clamped >= self.uncertainty_low && clamped <= self.uncertainty_high;
89        let confidence = clamped.max(1.0 - clamped);
90
91        NoulResult {
92            probability: clamped,
93            confidence,
94            is_true,
95            is_false,
96            is_uncertain,
97        }
98    }
99}
100
101/// Result of evaluating a Noul primitive.
102#[derive(Clone, Debug, PartialEq)]
103pub struct NoulResult {
104    pub probability: f32,
105    pub confidence: f32,
106    pub is_true: bool,
107    pub is_false: bool,
108    pub is_uncertain: bool,
109}
110
111/// Choice: Dynamic Rubric & Multi-Class Selection primitive.
112#[derive(Clone, Debug)]
113pub struct Choice {
114    pub instructions: String,
115    pub options: Vec<String>,
116    pub temperature: f32,
117}
118
119impl Choice {
120    pub fn new(instructions: impl Into<String>, options: Vec<String>) -> Self {
121        Self {
122            instructions: instructions.into(),
123            options,
124            temperature: 0.25,
125        }
126    }
127
128    pub fn with_temperature(mut self, temperature: f32) -> Self {
129        self.temperature = temperature.max(0.01);
130        self
131    }
132
133    pub fn evaluate(
134        &self,
135        _state: &str,
136        state_vec: &[f32; VECTOR_DIM],
137        encoder: &SemanticVectorEncoder,
138    ) -> ChoiceResult {
139        if self.options.is_empty() {
140            return ChoiceResult {
141                selected: String::new(),
142                confidence: 0.0,
143                distribution: Vec::new(),
144            };
145        }
146
147        let mut raw_sims: Vec<f32> = Vec::with_capacity(self.options.len());
148        for opt in &self.options {
149            let opt_text = format!("{} {}", self.instructions, opt);
150            let opt_vec = encoder.encode(&opt_text);
151            let sim = cosine_similarity(state_vec, &opt_vec);
152            raw_sims.push(sim / self.temperature);
153        }
154
155        // Softmax with numerical stability
156        let max_score = raw_sims.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
157        let exps: Vec<f32> = raw_sims.iter().map(|&s| (s - max_score).exp()).collect();
158        let sum_exp: f32 = exps.iter().sum();
159
160        let mut distribution: Vec<(String, f32)> = Vec::with_capacity(self.options.len());
161        let mut best_idx = 0;
162        let mut best_prob = -1.0f32;
163
164        for (i, opt) in self.options.iter().enumerate() {
165            let p = if sum_exp > 0.0 { exps[i] / sum_exp } else { 0.0 };
166            if p > best_prob {
167                best_prob = p;
168                best_idx = i;
169            }
170            distribution.push((opt.clone(), p));
171        }
172
173        ChoiceResult {
174            selected: self.options[best_idx].clone(),
175            confidence: best_prob,
176            distribution,
177        }
178    }
179}
180
181/// Result of evaluating a Choice primitive.
182#[derive(Clone, Debug)]
183pub struct ChoiceResult {
184    pub selected: String,
185    pub confidence: f32,
186    pub distribution: Vec<(String, f32)>,
187}
188
189impl ChoiceResult {
190    pub fn get_prob(&self, option: &str) -> f32 {
191        self.distribution
192            .iter()
193            .find(|(k, _)| k == option)
194            .map(|(_, p)| *p)
195            .unwrap_or(0.0)
196    }
197}
198
199/// Score: Continuous Scaling primitive.
200#[derive(Clone, Debug)]
201pub struct Score {
202    pub instructions: String,
203    pub min_val: f32,
204    pub max_val: f32,
205}
206
207impl Score {
208    pub fn new(instructions: impl Into<String>, min_val: f32, max_val: f32) -> Self {
209        Self {
210            instructions: instructions.into(),
211            min_val,
212            max_val,
213        }
214    }
215
216    pub fn evaluate(
217        &self,
218        _state: &str,
219        state_vec: &[f32; VECTOR_DIM],
220        encoder: &SemanticVectorEncoder,
221    ) -> ScoreResult {
222        let instr_vec = encoder.encode(&self.instructions);
223        let sim = cosine_similarity(state_vec, &instr_vec).clamp(0.0, 1.0);
224        let score = self.min_val + sim * (self.max_val - self.min_val);
225        let confidence = (0.50 + (sim - 0.50).abs()).clamp(0.0, 1.0);
226
227        ScoreResult {
228            score,
229            confidence,
230        }
231    }
232}
233
234/// Result of evaluating a Score primitive.
235#[derive(Clone, Debug, PartialEq)]
236pub struct ScoreResult {
237    pub score: f32,
238    pub confidence: f32,
239}
240
241fn extract_tokens(text: &str) -> HashSet<String> {
242    text.to_lowercase()
243        .split(|c: char| !(c.is_alphanumeric() || c == '_'))
244        .filter(|s| !s.is_empty())
245        .map(|s| s.to_string())
246        .collect()
247}