Skip to main content

evaluate

Function evaluate 

Source
pub fn evaluate(frame: &DecisionFrame) -> Result<DecisionResult>
Examples found in repository?
examples/bench_kernel.rs (line 312)
242fn decisions(cases: usize, seed: u64) -> Result<Value> {
243    if cases < 6 {
244        return Err("at least 6 cases required to exercise every family".into());
245    }
246    let mut rng = Rng(seed);
247    let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
248    let mut failures = Vec::new();
249    let mut violations = 0;
250    let start = Instant::now();
251    for i in 0..cases {
252        let mut a = option("a", rng.unit(), rng.unit());
253        let mut b = option("b", rng.unit(), rng.unit());
254        let family;
255        let mut expected: Option<Option<String>> = None;
256        match i % 6 {
257            0 => {
258                family = "high_score_hard_block";
259                a.scores.values_mut().for_each(|s| *s = 1.0);
260                a.hard_blocks.push("no authorization".into());
261                expected = Some(Some("b".into()));
262            }
263            1 => {
264                family = "all_blocked";
265                a.hard_blocks.push("privacy".into());
266                b.hard_blocks.push("consent".into());
267                expected = Some(None);
268            }
269            2 => {
270                family = "complete_score_oracle";
271                let sum_a: f64 = a.scores.values().sum();
272                let sum_b: f64 = b.scores.values().sum();
273                expected = Some(Some(if sum_a >= sum_b { "a" } else { "b" }.into()));
274            }
275            3 => {
276                family = "unknowns_and_model_estimates";
277                a.scores.clear();
278                a.model_scores.insert("a".into(), rng.unit());
279            }
280            4 => {
281                family = "reversibility_band";
282                let top = 0.5 + rng.unit() * 0.4;
283                let gap = if rng.next().is_multiple_of(2) {
284                    0.01
285                } else {
286                    0.03
287                };
288                a.scores.values_mut().for_each(|s| *s = top);
289                a.evidence_confidence = 0.1;
290                b.scores.values_mut().for_each(|s| *s = top - gap);
291                b.reversible = true;
292                expected = Some(Some(if gap < 0.02 { "b" } else { "a" }.into()));
293            }
294            _ => {
295                family = "invalid_numeric_input";
296                a.scores.insert("a".into(), 1.01 + rng.unit());
297            }
298        }
299        *counts.entry(family).or_default() += 1;
300        let frame = DecisionFrame {
301            question: "synthetic decision".into(),
302            criteria: ["a", "b"]
303                .into_iter()
304                .map(|id| Criterion {
305                    id: id.into(),
306                    weight: 1.0,
307                    description: id.into(),
308                })
309                .collect(),
310            options: vec![a, b],
311        };
312        let actual = evaluate(&frame);
313        let mut reasons = Vec::new();
314        if family == "invalid_numeric_input" {
315            if actual.is_ok() {
316                reasons.push("invalid input admitted");
317            }
318        } else if let Ok(answer) = &actual {
319            if let Some(expected) = expected {
320                if answer.recommended != expected {
321                    reasons.push("wrong recommendation");
322                }
323            }
324            if let Some(name) = &answer.recommended {
325                if frame
326                    .options
327                    .iter()
328                    .any(|o| &o.name == name && !o.hard_blocks.is_empty())
329                {
330                    reasons.push("hard-blocked option selected");
331                }
332            }
333            if family == "unknowns_and_model_estimates" {
334                let ranked = answer.ranking.iter().find(|o| o.name == "a").unwrap();
335                if ranked.coverage != 0.0
336                    || ranked.evidence_interval != [0.0, 1.0]
337                    || ranked.missing_criteria != ["b"]
338                    || answer.decisive
339                {
340                    reasons.push("unknown evidence hidden or model guess counted as evidence");
341                }
342            }
343            let mut permuted = frame.clone();
344            permuted.options.reverse();
345            permuted.question = "irrelevant wording changed".into();
346            let other = evaluate(&permuted)?;
347            if answer.recommended != other.recommended
348                || serde_json::to_value(&answer.ranking)? != serde_json::to_value(&other.ranking)?
349            {
350                reasons.push("order or irrelevant text changed result");
351            }
352        } else {
353            reasons.push("valid frame rejected");
354        }
355        if !reasons.is_empty() {
356            violations += 1;
357            if failures.len() < 20 {
358                failures.push(
359                    json!({"case_index": i, "family": family, "reasons": reasons,
360                    "frame": frame, "actual": actual.as_ref().ok()}),
361                );
362            }
363        }
364    }
365    Ok(
366        json!({"kind": "synthetic_decision_invariants", "seed": seed, "rng": "splitmix64",
367        "cases": cases, "families": counts, "violations": violations,
368        "violation_rate": violations as f64 / cases as f64, "ok": violations == 0,
369        "elapsed_ms": ms(start), "counterexamples": failures,
370        "limits": ["Generated families are not real decision-quality labels or a proof over all inputs.",
371          "Metamorphic checks keep criteria fixed; weight perturbations require separate expectations."]}),
372    )
373}