Skip to main content

qec_code/
family_verifier.rs

1use std::collections::BTreeMap;
2use std::fs;
3use std::path::PathBuf;
4
5use serde::Deserialize;
6use serde_json::Value;
7
8use crate::error::{CssMatrixReadSource, QecError};
9use crate::family_contract::{
10    CssCodeStats, CssConstructionResult, RequestedFamilyId, construct_css,
11    parse_css_construction_json, verify_css_orthogonality,
12};
13
14const MANIFEST_REL_PATH: &str = "tests/fixtures/family_manifest/manifest.v1.json";
15const EXPECTED_FAMILY_IDS: [&str; 14] = [
16    "directional",
17    "quantum_tanner",
18    "generalized_bicycle",
19    "la_cross",
20    "random_hgp",
21    "lifted_product",
22    "hyperbolic_5_5",
23    "coprime_bb",
24    "toric_3d",
25    "color_666",
26    "surface",
27    "shor_like",
28    "random_two_block",
29    "perturbed_hgp",
30];
31const EXPECTED_SUPPORTED_FAMILIES: usize = 12;
32const EXPECTED_DEFERRED_FAMILIES: usize = 2;
33
34#[derive(Debug, Clone, Copy)]
35struct DeferredFamilyBoundary {
36    tracking_issue: usize,
37    contract_path: &'static str,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct FamilyVerificationReport {
42    pub output: String,
43    pub failed: usize,
44}
45
46pub fn verify_checked_in_family_manifest() -> Result<FamilyVerificationReport, QecError> {
47    let path = checked_in_manifest_path();
48    let text = fs::read_to_string(&path).map_err(|error| QecError::CssMatrixReadFailed {
49        path: path.display().to_string(),
50        source: CssMatrixReadSource(error.to_string()),
51    })?;
52    Ok(verify_family_manifest_text(&text))
53}
54
55pub fn verify_family_manifest_text(text: &str) -> FamilyVerificationReport {
56    match serde_json::from_str::<FamilyCatalog>(text) {
57        Ok(catalog) => verify_catalog(&catalog),
58        Err(error) => failure_report(format!("FAIL manifest invalid_json={error}"), 0, 0),
59    }
60}
61
62fn checked_in_manifest_path() -> PathBuf {
63    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(MANIFEST_REL_PATH)
64}
65
66#[derive(Debug, Deserialize)]
67struct FamilyCatalog {
68    families: Vec<FamilyCatalogEntry>,
69}
70
71#[derive(Debug, Deserialize)]
72struct FamilyCatalogEntry {
73    family_id: String,
74    disposition: FamilyDisposition,
75    availability: RuntimeAvailability,
76    #[serde(default)]
77    research_contracts: Vec<String>,
78    callable_constructor: Option<CallableConstructorRef>,
79    expected: Option<ExpectedStats>,
80    row_weight_summary: Option<RowWeightSummary>,
81    #[serde(default)]
82    executable_cases: Vec<ExecutableCase>,
83}
84
85#[derive(Debug, Deserialize)]
86struct CallableConstructorRef {
87    rust_path: String,
88}
89
90#[derive(Debug, PartialEq, Eq, Deserialize)]
91struct ExpectedStats {
92    n: usize,
93    m_x: usize,
94    m_z: usize,
95    rank_x: usize,
96    rank_z: usize,
97    k: usize,
98    d_x: Option<usize>,
99    d_z: Option<usize>,
100}
101
102#[derive(Debug, PartialEq, Eq, Deserialize)]
103struct RowWeightSummary {
104    h_x: Vec<RowWeightBucket>,
105    h_z: Vec<RowWeightBucket>,
106}
107
108#[derive(Debug, PartialEq, Eq, Deserialize)]
109struct RowWeightBucket {
110    weight: usize,
111    count: usize,
112}
113
114#[derive(Debug, Deserialize)]
115struct ExecutableCase {
116    case_kind: ExecutableCaseKind,
117    expected_outcome: ExpectedOutcome,
118    request: Option<Value>,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
122#[serde(rename_all = "snake_case")]
123enum FamilyDisposition {
124    Supported,
125    Deferred,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
129#[serde(rename_all = "snake_case")]
130enum RuntimeAvailability {
131    Planned,
132    Available,
133    NotApplicable,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
137#[serde(rename_all = "snake_case")]
138enum ExecutableCaseKind {
139    Positive,
140    Negative,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
144#[serde(rename_all = "snake_case")]
145enum ExpectedOutcome {
146    Success,
147    Rejection,
148}
149
150enum EntryVerification {
151    Line(String),
152    Failure(String),
153}
154
155fn verify_catalog(catalog: &FamilyCatalog) -> FamilyVerificationReport {
156    let supported = catalog
157        .families
158        .iter()
159        .filter(|entry| entry.disposition == FamilyDisposition::Supported)
160        .count();
161    let deferred = catalog
162        .families
163        .iter()
164        .filter(|entry| entry.disposition == FamilyDisposition::Deferred)
165        .count();
166    let mut lines = manifest_contract_failures(catalog, supported, deferred);
167    let mut failed = lines.len();
168
169    for entry in &catalog.families {
170        match verify_entry(entry) {
171            EntryVerification::Line(line) => lines.push(line),
172            EntryVerification::Failure(line) => {
173                failed += 1;
174                lines.push(line);
175            }
176        }
177    }
178
179    let status = if failed == 0 { "PASS" } else { "FAIL" };
180    lines.push(format!(
181        "SUMMARY {status} supported={supported} deferred={deferred} failed={failed}"
182    ));
183    FamilyVerificationReport {
184        output: lines.join("\n"),
185        failed,
186    }
187}
188
189fn manifest_contract_failures(
190    catalog: &FamilyCatalog,
191    supported: usize,
192    deferred: usize,
193) -> Vec<String> {
194    let mut failures = Vec::new();
195
196    for family_id in EXPECTED_FAMILY_IDS {
197        let occurrences = catalog
198            .families
199            .iter()
200            .filter(|entry| entry.family_id == family_id)
201            .count();
202        match occurrences {
203            0 => failures.push(format!("FAIL manifest missing family_id={family_id}")),
204            1 => {}
205            _ => failures.push(format!("FAIL manifest duplicate family_id={family_id}")),
206        }
207    }
208
209    for entry in &catalog.families {
210        if !EXPECTED_FAMILY_IDS.contains(&entry.family_id.as_str()) {
211            failures.push(format!(
212                "FAIL manifest unexpected family_id={}",
213                entry.family_id
214            ));
215        }
216    }
217
218    for (index, entry) in catalog.families.iter().enumerate() {
219        let expected = EXPECTED_FAMILY_IDS.get(index).copied().unwrap_or("none");
220        if entry.family_id != expected {
221            failures.push(format!(
222                "FAIL manifest family_id_order index={index} expected={expected} actual={}",
223                entry.family_id
224            ));
225        }
226    }
227
228    if supported != EXPECTED_SUPPORTED_FAMILIES {
229        failures.push(format!(
230            "FAIL manifest expected supported={EXPECTED_SUPPORTED_FAMILIES} actual supported={supported}"
231        ));
232    }
233    if deferred != EXPECTED_DEFERRED_FAMILIES {
234        failures.push(format!(
235            "FAIL manifest expected deferred={EXPECTED_DEFERRED_FAMILIES} actual deferred={deferred}"
236        ));
237    }
238
239    failures
240}
241
242fn verify_entry(entry: &FamilyCatalogEntry) -> EntryVerification {
243    match entry.disposition {
244        FamilyDisposition::Supported => verify_supported_entry(entry),
245        FamilyDisposition::Deferred => verify_deferred_entry(entry),
246    }
247}
248
249fn verify_supported_entry(entry: &FamilyCatalogEntry) -> EntryVerification {
250    if entry.availability != RuntimeAvailability::Available {
251        return failure(format!(
252            "FAIL {} disposition=supported availability={} expected=available",
253            entry.family_id,
254            availability_name(entry.availability)
255        ));
256    }
257
258    let Some(case) = entry.executable_cases.iter().find(|case| {
259        case.case_kind == ExecutableCaseKind::Positive
260            && case.expected_outcome == ExpectedOutcome::Success
261    }) else {
262        return failure(format!(
263            "FAIL {} missing positive success case",
264            entry.family_id
265        ));
266    };
267    let Some(request) = case.request.as_ref() else {
268        return failure(format!(
269            "FAIL {} positive success case missing request",
270            entry.family_id
271        ));
272    };
273    let request_text = match serde_json::to_string(request) {
274        Ok(text) => text,
275        Err(error) => return failure(format!("FAIL {} request_json={error}", entry.family_id)),
276    };
277    let result = match parse_css_construction_json(&request_text).and_then(construct_css) {
278        Ok(result) => result,
279        Err(error) => return failure(format!("FAIL {} construction={error}", entry.family_id)),
280    };
281
282    if let Err(error) =
283        verify_css_orthogonality(result.stats.n, &result.checks.h_x, &result.checks.h_z)
284    {
285        return failure(format!("FAIL {} orthogonality={error}", entry.family_id));
286    }
287
288    if let Some(line) = verify_result_metadata(entry, &result) {
289        return failure(line);
290    }
291
292    EntryVerification::Line(format_pass_line(&entry.family_id, &result))
293}
294
295fn verify_result_metadata(
296    entry: &FamilyCatalogEntry,
297    result: &CssConstructionResult,
298) -> Option<String> {
299    if result.requested_family_id.map(RequestedFamilyId::as_str) != Some(entry.family_id.as_str()) {
300        return Some(format!(
301            "FAIL {} expected requested_family_id={} actual requested_family_id={}",
302            entry.family_id,
303            entry.family_id,
304            result
305                .requested_family_id
306                .map(RequestedFamilyId::as_str)
307                .unwrap_or("none")
308        ));
309    }
310
311    let Some(callable) = entry.callable_constructor.as_ref() else {
312        return Some(format!(
313            "FAIL {} missing callable_constructor",
314            entry.family_id
315        ));
316    };
317    if result.provenance.source != callable.rust_path {
318        return Some(format!(
319            "FAIL {} expected provenance={} actual provenance={}",
320            entry.family_id, callable.rust_path, result.provenance.source
321        ));
322    }
323
324    let Some(expected) = entry.expected.as_ref() else {
325        return Some(format!("FAIL {} missing expected stats", entry.family_id));
326    };
327    if let Some(line) = stats_mismatch(&entry.family_id, expected, &result.stats) {
328        return Some(line);
329    }
330
331    let Some(expected_weights) = entry.row_weight_summary.as_ref() else {
332        return Some(format!(
333            "FAIL {} missing row_weight_summary",
334            entry.family_id
335        ));
336    };
337    let actual_h_x = row_weight_summary(&result.checks.h_x);
338    if actual_h_x != expected_weights.h_x {
339        return Some(format!(
340            "FAIL {} expected row_weights_h_x={} actual row_weights_h_x={}",
341            entry.family_id,
342            format_row_weights(&expected_weights.h_x),
343            format_row_weights(&actual_h_x)
344        ));
345    }
346    let actual_h_z = row_weight_summary(&result.checks.h_z);
347    if actual_h_z != expected_weights.h_z {
348        return Some(format!(
349            "FAIL {} expected row_weights_h_z={} actual row_weights_h_z={}",
350            entry.family_id,
351            format_row_weights(&expected_weights.h_z),
352            format_row_weights(&actual_h_z)
353        ));
354    }
355
356    None
357}
358
359fn stats_mismatch(
360    family_id: &str,
361    expected: &ExpectedStats,
362    actual: &CssCodeStats,
363) -> Option<String> {
364    macro_rules! compare_stat {
365        ($field:ident) => {
366            if expected.$field != actual.$field {
367                return Some(format!(
368                    "FAIL {family_id} expected {}={} actual {}={}",
369                    stringify!($field),
370                    expected.$field,
371                    stringify!($field),
372                    actual.$field
373                ));
374            }
375        };
376    }
377
378    compare_stat!(n);
379    compare_stat!(m_x);
380    compare_stat!(m_z);
381    compare_stat!(rank_x);
382    compare_stat!(rank_z);
383    compare_stat!(k);
384    if expected.d_x != actual.d_x {
385        return Some(format!(
386            "FAIL {family_id} expected d_x={:?} actual d_x={:?}",
387            expected.d_x, actual.d_x
388        ));
389    }
390    if expected.d_z != actual.d_z {
391        return Some(format!(
392            "FAIL {family_id} expected d_z={:?} actual d_z={:?}",
393            expected.d_z, actual.d_z
394        ));
395    }
396    None
397}
398
399fn verify_deferred_entry(entry: &FamilyCatalogEntry) -> EntryVerification {
400    if entry.availability != RuntimeAvailability::NotApplicable {
401        return failure(format!(
402            "FAIL {} disposition=deferred availability={} expected=not_applicable",
403            entry.family_id,
404            availability_name(entry.availability)
405        ));
406    }
407    if entry.research_contracts.len() != 1 {
408        return failure(format!(
409            "FAIL {} expected exactly one research_contract",
410            entry.family_id
411        ));
412    }
413    let Some(boundary) = deferred_family_boundary(&entry.family_id) else {
414        return failure(format!("FAIL {} unknown deferred family", entry.family_id));
415    };
416    if entry.research_contracts[0] != boundary.contract_path {
417        return failure(format!(
418            "FAIL {} expected contract={} actual contract={}",
419            entry.family_id, boundary.contract_path, entry.research_contracts[0]
420        ));
421    }
422    EntryVerification::Line(format!(
423        "DEFERRED {} tracking_issue=#{} contract={}",
424        entry.family_id, boundary.tracking_issue, boundary.contract_path
425    ))
426}
427
428fn deferred_family_boundary(family_id: &str) -> Option<DeferredFamilyBoundary> {
429    match family_id {
430        "hyperbolic_5_5" => Some(DeferredFamilyBoundary {
431            tracking_issue: 571,
432            contract_path: "qec-code/doc/hyperbolic_5_5_contract.md",
433        }),
434        "perturbed_hgp" => Some(DeferredFamilyBoundary {
435            tracking_issue: 572,
436            contract_path: "qec-code/doc/perturbed_hgp_contract.md",
437        }),
438        _ => None,
439    }
440}
441
442fn format_pass_line(family_id: &str, result: &CssConstructionResult) -> String {
443    let params = serde_json::to_string(&result.normalized_parameters)
444        .expect("normalized CSS construction parameters should serialize");
445    format!(
446        "PASS {family_id} params={params} n={} checks=h_x:{},h_z:{} ranks=rank_x:{},rank_z:{} k={} row_weights=h_x:{},h_z:{} orthogonal=true provenance={}@{}",
447        result.stats.n,
448        result.stats.m_x,
449        result.stats.m_z,
450        result.stats.rank_x,
451        result.stats.rank_z,
452        result.stats.k,
453        format_row_weights(&row_weight_summary(&result.checks.h_x)),
454        format_row_weights(&row_weight_summary(&result.checks.h_z)),
455        result.provenance.source,
456        result.provenance.normalized_input_digest,
457    )
458}
459
460fn row_weight_summary(rows: &[Vec<usize>]) -> Vec<RowWeightBucket> {
461    let mut counts = BTreeMap::new();
462    for row in rows {
463        *counts.entry(row.len()).or_insert(0usize) += 1;
464    }
465    counts
466        .into_iter()
467        .map(|(weight, count)| RowWeightBucket { weight, count })
468        .collect()
469}
470
471fn format_row_weights(buckets: &[RowWeightBucket]) -> String {
472    let values = buckets
473        .iter()
474        .map(|bucket| format!("w{}={}", bucket.weight, bucket.count))
475        .collect::<Vec<_>>();
476    format!("[{}]", values.join(","))
477}
478
479fn availability_name(availability: RuntimeAvailability) -> &'static str {
480    match availability {
481        RuntimeAvailability::Planned => "planned",
482        RuntimeAvailability::Available => "available",
483        RuntimeAvailability::NotApplicable => "not_applicable",
484    }
485}
486
487fn failure(line: String) -> EntryVerification {
488    EntryVerification::Failure(line)
489}
490
491fn failure_report(line: String, supported: usize, deferred: usize) -> FamilyVerificationReport {
492    FamilyVerificationReport {
493        output: format!("{line}\nSUMMARY FAIL supported={supported} deferred={deferred} failed=1"),
494        failed: 1,
495    }
496}