143 lines
5.8 KiB
C
143 lines
5.8 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file parity_gate_criteria.c
|
|
* @brief Implementation of the shared parity-gate verdict declared in parity_gate_criteria.h.
|
|
*
|
|
* @status Working
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#include "parity_gate_criteria.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
|
|
/** One criterion's verdict, so the report can NAME what failed instead of just failing. */
|
|
typedef struct {
|
|
const char *name;
|
|
double value;
|
|
double limit;
|
|
int failed;
|
|
} parity_gate_criterion;
|
|
|
|
/**
|
|
* Fill one criterion and return whether it failed. `higher_is_worse` throughout.
|
|
*
|
|
* **THE TEST IS `!(value <= limit)` AND NOT `value > limit`, AND THAT IS THE WHOLE POINT.**
|
|
* Every comparison against a NaN is false, so `value > limit` reads PASS on a figure that is
|
|
* not a number at all — the same defect that let a broken chunkwise arm score 7.775e-15 and
|
|
* read green (`if (difference > worst)` never accepting a NaN as the new worst). Negating the
|
|
* ordered form makes a NaN fail instead of vanish, on every criterion at once.
|
|
*/
|
|
static int parity_gate_check(parity_gate_criterion *c, const char *name, double value,
|
|
double limit)
|
|
{
|
|
c->name = name;
|
|
c->value = value;
|
|
c->limit = limit;
|
|
c->failed = !(value <= limit);
|
|
return c->failed;
|
|
}
|
|
|
|
/**
|
|
* How many of the summary's figures are not finite.
|
|
*
|
|
* The criteria above are NaN-safe individually, but a run whose arithmetic produced a NaN
|
|
* anywhere should say SO rather than fail one threshold for an unexplained reason. This
|
|
* counts them so the report names the condition. `parity_double_is_finite` asks the BITS —
|
|
* `isfinite()` is foldable to 1 under this tree's `-ffast-math`.
|
|
*/
|
|
static int parity_gate_non_finite_figures(const parity_summary *m)
|
|
{
|
|
const double figures[] = {m->accel_ppl, m->cpu_ppl, m->ratio, m->dnll_mean,
|
|
m->dnll_sd, m->t, m->kl_mean};
|
|
int count = 0;
|
|
|
|
for (size_t i = 0; i < sizeof(figures) / sizeof(figures[0]); i++)
|
|
if (!parity_double_is_finite(figures[i]))
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
double parity_gate_worst_document_ratio(const parity_score *per_doc, int docs, int *worst_doc)
|
|
{
|
|
double worst = 0.0;
|
|
|
|
if (!worst_doc)
|
|
return 0.0;
|
|
*worst_doc = -1;
|
|
if (!per_doc)
|
|
return 0.0;
|
|
for (int i = 0; i < docs; i++) {
|
|
parity_summary m;
|
|
|
|
parity_score_summarise(&per_doc[i], &m);
|
|
if (m.n < PARITY_GATE_MIN_DOC_SCORED)
|
|
continue;
|
|
if (*worst_doc < 0 || m.ratio > worst) {
|
|
worst = m.ratio;
|
|
*worst_doc = i;
|
|
}
|
|
}
|
|
return worst;
|
|
}
|
|
|
|
int parity_gate_verdict(const parity_summary *m, const parity_score *per_doc, int docs,
|
|
int identity, double noise_sigma, const char *framing)
|
|
{
|
|
parity_gate_criterion criteria[6];
|
|
int worst_doc = -1;
|
|
double worst_ratio = parity_gate_worst_document_ratio(per_doc, docs, &worst_doc);
|
|
int failed = 0;
|
|
|
|
failed |= parity_gate_check(&criteria[0], "ppl_ratio", m->ratio, PARITY_GATE_MAX_PPL_RATIO);
|
|
failed |= parity_gate_check(&criteria[1], "paired_t", m->t, PARITY_GATE_MAX_PAIRED_T);
|
|
failed |= parity_gate_check(&criteria[2], "disagree_rate", m->disagree_rate,
|
|
PARITY_GATE_MAX_DISAGREE_RATE);
|
|
/* Inverted on purpose: too FEW scored positions is the failure, so the criterion is
|
|
* expressed as a shortfall against the minimum and read the same way as the others. */
|
|
failed |= parity_gate_check(&criteria[3], "scored_shortfall",
|
|
(double)(PARITY_GATE_MIN_SCORED - m->n), 0.0);
|
|
failed |= parity_gate_check(&criteria[4], "worst_doc_ppl_ratio", worst_ratio,
|
|
PARITY_GATE_MAX_DOC_PPL_RATIO);
|
|
/* Named rather than inferred. A gate that merely happened to fail a threshold because one
|
|
* of its inputs was a NaN would send the reader to the wrong question. */
|
|
failed |= parity_gate_check(&criteria[5], "non_finite_figures",
|
|
(double)parity_gate_non_finite_figures(m), 0.0);
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
printf("GATE criterion %-19s value=%-12.6g limit=%-10.6g %s\n", criteria[i].name,
|
|
criteria[i].value, criteria[i].limit, criteria[i].failed ? "FAIL" : "pass");
|
|
printf("GATE worst_document index=%d ratio=%.6f (documents below %d scored positions are "
|
|
"reported but not thresholded)\n", worst_doc, worst_ratio,
|
|
PARITY_GATE_MIN_DOC_SCORED);
|
|
|
|
/* Under injected noise the two arms differ BY CONSTRUCTION, so the exact-zero assertion is
|
|
* not merely relaxed — it would be the wrong question. What the run is asking is whether
|
|
* the criteria move at all, and a PASS at a sigma the thresholds were calibrated to catch
|
|
* is the finding. */
|
|
if (identity && noise_sigma > 0.0) {
|
|
printf("GATE identity_control skipped (--noise %.4g is injected divergence; a "
|
|
"non-zero result is what this arm is for)\n", noise_sigma);
|
|
identity = 0;
|
|
}
|
|
if (identity) {
|
|
int exact = m->ratio == 1.0 && m->dnll_mean == 0.0 && m->kl_mean == 0.0 &&
|
|
m->n_disagree == 0;
|
|
|
|
printf("GATE identity_control exact_zero=%s\n", exact ? "yes" : "NO");
|
|
if (!exact) {
|
|
printf("GATE VERDICT framing=%s FAIL (identity control is not exactly zero — this "
|
|
"run measured the harness and the harness is broken)\n", framing);
|
|
return 1;
|
|
}
|
|
}
|
|
printf("GATE VERDICT framing=%s noise=%.4g %s (n=%ld ratio=%.6f t=%.3f disagree=%.4f "
|
|
"kl=%.6g)\n", framing, noise_sigma, failed ? "FAIL" : "PASS", m->n, m->ratio, m->t,
|
|
m->disagree_rate, m->kl_mean);
|
|
fflush(stdout);
|
|
return failed ? 1 : 0;
|
|
}
|