Skip to main content

Sharing

Struct Sharing 

Source
pub struct Sharing {
    pub node_occurrences: usize,
    pub distinct_nodes: usize,
    pub result_occurrences: usize,
    pub distinct_results: usize,
    pub support_occurrences: usize,
    pub distinct_support: usize,
    pub term_occurrences: usize,
    pub distinct_terms: usize,
}
Expand description

How often the interned entries of a run repeat.

Counted over the evidence alone — the statements — because that is the part that grows with the corpus. The catalog is a fixed per-run cost and would flatter the ratio: it is interned into the same tables, so folding it in adds distinct entries without adding evidence occurrences.

Tagged nodes are split into two families, because they answer different questions and the mixture is dominated by the wrong one. Result nodes are Witness and SatTrace — one validation judgment each. Support nodes are PathSupport certificates, which say how a value was reached and are not judgments about anything. Both serialize as {"type", "details"} and the encoder interns both, so the combined node_redundancy is a statement about encoding cost only. Sharing between validation results is result_redundancy, and on the Brick corpus the two differ by several fold because support nodes are the large majority of occurrences.

Fields§

§node_occurrences: usize

Tagged nodes of either family as written out by the full encoding.

§distinct_nodes: usize

Distinct such nodes, i.e. the size of the node table.

§result_occurrences: usize

Occurrences of tagged nodes that carry a validation judgment.

§distinct_results: usize

Distinct such nodes.

§support_occurrences: usize

Occurrences of path-support certificates.

§distinct_support: usize

Distinct such certificates.

§term_occurrences: usize

RDF terms as written out by the full encoding.

§distinct_terms: usize

Distinct such terms, i.e. the size of the term table.

Implementations§

Source§

impl Sharing

Source

pub fn node_redundancy(&self) -> f64

Occurrences per distinct node over both families; 1.0 when nothing repeats.

What the compact encoding collapses. Not a measure of sharing between validation results — see result_redundancy.

Examples found in repository?
examples/bench_evidence.rs (line 533)
132fn main() {
133    let args = parse_args();
134
135    eprintln!("loading shapes {}…", args.shapes.display());
136    let shapes_bytes = fs::read(&args.shapes).expect("cannot read --shapes");
137    let shapes = shifty_parse::load_turtle(&shapes_bytes, None).expect("cannot parse --shapes");
138    let compiled = CompiledShapes::compile(shapes).expect("cannot compile shapes");
139    drop(shapes_bytes);
140
141    if args.header {
142        println!("{COLUMNS}");
143    }
144
145    for model in models_in(&args.models) {
146        let name = model
147            .file_name()
148            .and_then(|name| name.to_str())
149            .unwrap_or("?")
150            .to_string();
151        eprint!("{name}… ");
152
153        // ---- untimed setup: everything common to all workflows --------------
154        let data_bytes = fs::read(&model).expect("cannot read model");
155        let data = shifty_parse::load_turtle(&data_bytes, None).expect("cannot parse model");
156        drop(data_bytes);
157        let data_graph = if args.no_infer {
158            data.graph.clone()
159        } else {
160            compiled
161                .session(
162                    SessionData::Separate(data.graph.clone()),
163                    SessionOptions {
164                        inference: true,
165                        ..SessionOptions::default()
166                    },
167                )
168                .unwrap_or_else(|error| panic!("{name}: inference failed: {error}"))
169                .data()
170                .clone()
171        };
172        let data_triples = data_graph.len();
173
174        let prepared = PreparedEvidenceValidator::with_graphs(
175            &data_graph,
176            &compiled.source().graph,
177            compiled.authored_schema(),
178            ValidationGraphMode::Union,
179        )
180        .unwrap_or_else(|error| panic!("{name}: schema is non-stratifiable: {error}"));
181        let options = ValidationOptions::default();
182        let scan_options = ConformanceOptions::default();
183
184        // ---- timed: validation only -----------------------------------------
185        // One warm-up round, discarded: it faults in the caches all arms share.
186        // Its duration is only consulted when `--budget-ms` is in play.
187        let probe = Instant::now();
188        let _ = prepared.validate_conformance(&scan_options);
189        let _ = prepared.validate_canonical(&options);
190        let (_, warm_failures) = prepared.find_failures(&scan_options);
191        for pair in &warm_failures {
192            std::hint::black_box(prepared.explain_canonical(pair));
193        }
194        let probe_ms = probe.elapsed().as_secs_f64() * 1e3;
195        // Every model gets the same iteration count. `--budget-ms` only caps the
196        // count for a corpus slow enough that a fixed count is impractical; it
197        // is off by default, and the emitted `iters` column records what was used.
198        let iters = if args.budget_ms > 0.0 {
199            ((args.budget_ms / probe_ms.max(f64::MIN_POSITIVE)) as usize)
200                .clamp(args.min_iters.min(args.iters), args.iters)
201        } else {
202            args.iters
203        };
204
205        let mut conformance_samples = Vec::with_capacity(iters);
206        let mut evidence_samples = Vec::with_capacity(iters);
207        let mut discovery_samples = Vec::with_capacity(iters);
208        let mut explanation_samples = Vec::with_capacity(iters);
209        let mut on_demand_samples = Vec::with_capacity(iters);
210        let mut last_run = None;
211        let mut last_conformance = None;
212        let mut last_on_demand_conformance = None;
213        let mut last_failures = Vec::new();
214        let mut last_explanations = Vec::new();
215
216        // Rotate A/B/C so no arm systematically inherits the cache, allocator,
217        // scheduler, or thermal state left by another arm.
218        for iteration in 0..iters {
219            for arm in ARM_ORDERS[iteration % ARM_ORDERS.len()] {
220                match arm {
221                    Arm::Conformance => {
222                        let start = Instant::now();
223                        let conformance_run = prepared.validate_conformance(&scan_options);
224                        conformance_samples.push(start.elapsed());
225                        std::hint::black_box(conformance_run.selected_pairs);
226                        last_conformance = Some(conformance_run);
227                    }
228                    Arm::FullEvidence => {
229                        let start = Instant::now();
230                        let run = prepared.validate_canonical(&options);
231                        evidence_samples.push(start.elapsed());
232                        std::hint::black_box(run.statements.len());
233                        last_run = Some(run);
234                    }
235                    Arm::OnDemand => {
236                        let start = Instant::now();
237                        let (on_demand_conformance, failures) =
238                            prepared.find_failures(&scan_options);
239                        let discovery = start.elapsed();
240
241                        let start = Instant::now();
242                        let explanations: Vec<_> = failures
243                            .iter()
244                            .flat_map(|pair| prepared.explain_canonical(pair))
245                            .collect();
246                        let explanation = start.elapsed();
247
248                        discovery_samples.push(discovery);
249                        explanation_samples.push(explanation);
250                        on_demand_samples.push(discovery + explanation);
251                        std::hint::black_box(explanations.len());
252                        last_on_demand_conformance = Some(on_demand_conformance);
253                        last_failures = failures;
254                        last_explanations = explanations;
255                    }
256                }
257            }
258        }
259
260        // ---- untimed: shape of the evidence produced -------------------------
261        // Reuse the final timed results rather than paying for another run.
262        let conformance = last_conformance.expect("at least one iteration runs");
263        let on_demand_conformance =
264            last_on_demand_conformance.expect("at least one iteration runs");
265        let run = last_run.expect("at least one iteration runs");
266        let authored_pairs: usize = run
267            .statements
268            .iter()
269            .map(|statement| statement.selected_foci.len())
270            .sum();
271        let authored_fail_pairs: usize = run
272            .statements
273            .iter()
274            .flat_map(|statement| &statement.selected_foci)
275            .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
276            .count();
277
278        // The three interfaces must describe the same normalized selection and
279        // polarity partition. Compare exact failed identities, not just the
280        // report-wide conformance bit.
281        assert_eq!(
282            conformance, on_demand_conformance,
283            "{name}: on-demand counts"
284        );
285        assert_eq!(
286            conformance.conforms, run.conforms,
287            "{name}: conformance bit"
288        );
289        let full_selected: HashSet<_> = run
290            .statements
291            .iter()
292            .flat_map(|statement| {
293                statement.selected_foci.iter().filter_map(|focus| {
294                    statement
295                        .normalized_statement_id
296                        .map(|id| (id, focus.focus.clone()))
297                })
298            })
299            .collect();
300        let full_failures: HashSet<_> = run
301            .statements
302            .iter()
303            .flat_map(|statement| {
304                statement
305                    .selected_foci
306                    .iter()
307                    .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
308                    .filter_map(|focus| {
309                        statement
310                            .normalized_statement_id
311                            .map(|id| (id, focus.focus.clone()))
312                    })
313            })
314            .collect();
315        let discovered_failures: HashSet<_> = last_failures
316            .iter()
317            .map(|pair| (pair.normalized_statement(), pair.focus().clone()))
318            .collect();
319        assert_eq!(
320            full_selected.len(),
321            conformance.selected_pairs,
322            "{name}: selected pairs"
323        );
324        assert_eq!(
325            full_failures.len(),
326            conformance.failed,
327            "{name}: failed pairs"
328        );
329        assert_eq!(
330            full_selected.len() - full_failures.len(),
331            conformance.passed,
332            "{name}: passed pairs"
333        );
334        assert_eq!(
335            full_failures, discovered_failures,
336            "{name}: failed identities"
337        );
338
339        let full_by_authored: HashMap<_, _> = run
340            .statements
341            .iter()
342            .flat_map(|statement| {
343                statement.selected_foci.iter().map(|focus| {
344                    (
345                        (statement.source_statement_id, focus.focus.clone()),
346                        &focus.evidence,
347                    )
348                })
349            })
350            .collect();
351        let mut explained_foci = 0;
352        for statement in &last_explanations {
353            for focus in &statement.selected_foci {
354                explained_foci += 1;
355                assert_eq!(
356                    focus.status(),
357                    shifty_engine::EvaluationStatus::Fail,
358                    "{name}: on-demand explanation polarity"
359                );
360                let key = (statement.source_statement_id, focus.focus.clone());
361                assert_eq!(
362                    &focus.evidence,
363                    *full_by_authored
364                        .get(&key)
365                        .unwrap_or_else(|| panic!("{name}: unexplained full-run pair {key:?}")),
366                    "{name}: full and on-demand evidence differ for {key:?}"
367                );
368            }
369        }
370        assert_eq!(
371            explained_foci, authored_fail_pairs,
372            "{name}: authored explanations"
373        );
374
375        let evidence_nodes = run.walk().len();
376        let evidence_bytes: usize = run
377            .statements
378            .iter()
379            .flat_map(|statement| &statement.selected_foci)
380            .map(|focus| focus.evidence.to_json().expect("evidence serializes").len())
381            .sum();
382        let full_json = run.to_json().expect("evidence run serializes");
383        let full_run_bytes_with_catalog = full_json.len();
384        let mut full_value_no_catalog =
385            serde_json::to_value(&run).expect("evidence run converts to JSON");
386        full_value_no_catalog
387            .as_object_mut()
388            .expect("evidence run is a JSON object")
389            .remove("constraints");
390        let full_run_bytes_no_catalog = serde_json::to_string(&full_value_no_catalog)
391            .expect("catalog-free evidence run serializes")
392            .len();
393
394        // Compare complete runs with the same catalog policy on each side, and
395        // verify both encodings reconstruct the exact typed result.
396        let encoded_with_catalog =
397            shifty_engine::compact(&run, true).expect("compact encoding succeeds");
398        let compact_run_bytes_with_catalog = serde_json::to_string(&encoded_with_catalog)
399            .expect("compact encoding serializes")
400            .len();
401        assert_eq!(
402            shifty_engine::expand(&encoded_with_catalog).expect("compact run expands"),
403            run,
404            "{name}: compact run with catalog is not lossless"
405        );
406        let encoded_no_catalog =
407            shifty_engine::compact(&run, false).expect("compact encoding succeeds");
408        let compact_run_bytes_no_catalog = serde_json::to_string(&encoded_no_catalog)
409            .expect("catalog-free compact encoding serializes")
410            .len();
411        let catalog = serde_json::to_value(&run.constraints).expect("catalog serializes");
412        assert_eq!(
413            shifty_engine::expand_with_catalog(&encoded_no_catalog, catalog)
414                .expect("catalog-free compact run expands"),
415            run,
416            "{name}: compact run without catalog is not lossless"
417        );
418        // Sharing is reported over the evidence alone. The node and term tables
419        // of `encoded` also hold catalog entries, which would understate the
420        // redundancy by adding distinct entries with no evidence occurrences.
421        let sharing = shifty_engine::sharing(&run).expect("sharing measures");
422        let results = shifty_engine::result_sharing(&run);
423
424        // The two passes reach the same judgment nodes by routes that share no
425        // code: `sharing` interns serialized JSON, `result_sharing` walks the
426        // typed evidence. Agreement on both counts is what makes either number
427        // reportable; a mismatch means the node families are misclassified or a
428        // traversal is missing a variant.
429        assert_eq!(
430            sharing.result_occurrences + sharing.support_occurrences,
431            sharing.node_occurrences,
432            "{name}: node families do not partition occurrences"
433        );
434        assert_eq!(
435            sharing.distinct_results + sharing.distinct_support,
436            sharing.distinct_nodes,
437            "{name}: node families do not partition the table"
438        );
439        assert_eq!(
440            sharing.result_occurrences, evidence_nodes,
441            "{name}: interned judgments disagree with the typed walk"
442        );
443        assert_eq!(
444            results.occurrences, evidence_nodes,
445            "{name}: typed sharing pass disagrees with the typed walk"
446        );
447        assert_eq!(
448            results.distinct_payloads, sharing.distinct_results,
449            "{name}: distinct judgments disagree between the two passes"
450        );
451        // Structural equality implies key equality, never the reverse.
452        assert!(
453            results.distinct_keys <= results.distinct_payloads,
454            "{name}: more keys than payloads"
455        );
456        // Requests are the normalized selection the conformance pass counts;
457        // authored records are the source-preserving view over them.
458        assert_eq!(
459            results.normalized_requests, conformance.selected_pairs,
460            "{name}: requests disagree with the conformance selection"
461        );
462        assert_eq!(
463            results.authored_records, authored_pairs,
464            "{name}: authored records disagree with the run"
465        );
466
467        // Serialization is measured separately so its allocation traffic does
468        // not perturb the next validation arm.
469        let mut serialize_samples = Vec::with_capacity(iters);
470        for _ in 0..iters {
471            let start = Instant::now();
472            let json = run.to_json().expect("evidence run serializes");
473            serialize_samples.push(start.elapsed());
474            std::hint::black_box(json.len());
475        }
476
477        // Each ratio compares the corresponding raw samples from one rotated
478        // A/B/C round. The CSV retains those samples for independent checking.
479        let full_ratios: Vec<f64> = conformance_samples
480            .iter()
481            .zip(evidence_samples.iter())
482            .map(|(conformance, evidence)| {
483                evidence.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
484            })
485            .collect();
486        let on_demand_ratios: Vec<f64> = conformance_samples
487            .iter()
488            .zip(on_demand_samples.iter())
489            .map(|(conformance, on_demand)| {
490                on_demand.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
491            })
492            .collect();
493
494        let conformance_ms = summarize_durations(&conformance_samples);
495        let evidence_ms = summarize_durations(&evidence_samples);
496        let discovery_ms = summarize_durations(&discovery_samples);
497        let explanation_ms = summarize_durations(&explanation_samples);
498        let on_demand_ms = summarize_durations(&on_demand_samples);
499        let serialize_ms = summarize_durations(&serialize_samples);
500        let full_ratio = summarize_values(&full_ratios);
501        let on_demand_ratio = summarize_values(&on_demand_ratios);
502        let evaluated_pairs = conformance.selected_pairs.max(1) as f64;
503        let authored_pairs_denominator = authored_pairs.max(1) as f64;
504
505        println!(
506            "{suite},{name},{data_triples},{iters},\
507{c_med:.4},{c_mad:.4},{e_med:.4},{e_mad:.4},{full_ratio:.4},{full_ratio_mad:.4},\
508{find_med:.4},{find_mad:.4},{explain_med:.4},{explain_mad:.4},\
509{ondemand_med:.4},{ondemand_mad:.4},{ondemand_ratio:.4},{ondemand_ratio_mad:.4},\
510{serialize_med:.4},{serialize_mad:.4},\
511{conforms_c},{conforms_e},{conforms_o},\
512{pairs_eval},{pass_pairs},{fail_pairs},{fail_fraction:.6},\
513{authored_pairs},{authored_pass},{authored_fail},\
514{statements},\
515{evidence_nodes},{nodes_per_authored_pair:.3},\
516{evidence_bytes},{bytes_per_authored_pair:.1},\
517{full_with_catalog},{full_no_catalog},{compact_with_catalog},{compact_no_catalog},\
518{node_occurrences},{distinct_nodes},{node_redundancy:.3},\
519{result_occurrences},{distinct_results},{result_redundancy:.3},\
520{support_occurrences},{distinct_support},{support_redundancy:.3},{support_share:.4},\
521{term_occurrences},{distinct_terms},{term_redundancy:.3},\
522{normalized_requests},{duplicate_records},{divergent_duplicates},\
523{canonical_occurrences},{shared_payloads},{shared_payload_fraction:.4},\
524{shared_canonical_occurrences},{shared_occurrence_fraction:.4},\
525{request_reaches},{requests_per_payload:.3},{max_payload_requests},\
526{distinct_keys},{key_redundancy:.3},{multi_occurrence_keys},\
527{divergent_keys},{divergence_fraction:.4},{divergent_occurrences},\
528{distinct_payloads_per_key},{keys_over_payload_cap},{both_polarity_addresses},\
529{conformance_raw},{evidence_raw},{discovery_raw},{explanation_raw},{ondemand_raw}",
530            suite = args.suite,
531            node_occurrences = sharing.node_occurrences,
532            distinct_nodes = sharing.distinct_nodes,
533            node_redundancy = sharing.node_redundancy(),
534            result_occurrences = sharing.result_occurrences,
535            distinct_results = sharing.distinct_results,
536            result_redundancy = sharing.result_redundancy(),
537            support_occurrences = sharing.support_occurrences,
538            distinct_support = sharing.distinct_support,
539            support_redundancy = sharing.support_redundancy(),
540            support_share = sharing.support_share(),
541            term_occurrences = sharing.term_occurrences,
542            distinct_terms = sharing.distinct_terms,
543            term_redundancy = sharing.term_redundancy(),
544            normalized_requests = results.normalized_requests,
545            duplicate_records = results.duplicate_records,
546            divergent_duplicates = results.divergent_duplicates,
547            canonical_occurrences = results.canonical_occurrences,
548            shared_payloads = results.shared_payloads,
549            shared_payload_fraction = results.shared_payload_fraction(),
550            shared_canonical_occurrences = results.shared_canonical_occurrences,
551            shared_occurrence_fraction = results.shared_occurrence_fraction(),
552            request_reaches = results.request_reaches,
553            requests_per_payload = results.requests_per_payload(),
554            max_payload_requests = results.max_payload_requests,
555            distinct_keys = results.distinct_keys,
556            key_redundancy = results.key_redundancy(),
557            multi_occurrence_keys = results.multi_occurrence_keys,
558            divergent_keys = results.divergent_keys,
559            divergence_fraction = results.divergence_fraction(),
560            divergent_occurrences = results.divergent_occurrences,
561            distinct_payloads_per_key = results.distinct_payloads_per_key,
562            keys_over_payload_cap = results.keys_over_payload_cap,
563            both_polarity_addresses = results.both_polarity_addresses,
564            c_med = conformance_ms.median,
565            c_mad = conformance_ms.mad,
566            e_med = evidence_ms.median,
567            e_mad = evidence_ms.mad,
568            full_ratio = full_ratio.median,
569            full_ratio_mad = full_ratio.mad,
570            find_med = discovery_ms.median,
571            find_mad = discovery_ms.mad,
572            explain_med = explanation_ms.median,
573            explain_mad = explanation_ms.mad,
574            ondemand_med = on_demand_ms.median,
575            ondemand_mad = on_demand_ms.mad,
576            ondemand_ratio = on_demand_ratio.median,
577            ondemand_ratio_mad = on_demand_ratio.mad,
578            serialize_med = serialize_ms.median,
579            serialize_mad = serialize_ms.mad,
580            conforms_c = conformance.conforms,
581            conforms_e = run.conforms,
582            conforms_o = on_demand_conformance.conforms,
583            pairs_eval = conformance.selected_pairs,
584            pass_pairs = conformance.passed,
585            fail_pairs = conformance.failed,
586            fail_fraction = conformance.failed as f64 / evaluated_pairs,
587            authored_pass = authored_pairs - authored_fail_pairs,
588            authored_fail = authored_fail_pairs,
589            statements = run.statements.len(),
590            nodes_per_authored_pair = evidence_nodes as f64 / authored_pairs_denominator,
591            bytes_per_authored_pair = evidence_bytes as f64 / authored_pairs_denominator,
592            full_with_catalog = full_run_bytes_with_catalog,
593            full_no_catalog = full_run_bytes_no_catalog,
594            compact_with_catalog = compact_run_bytes_with_catalog,
595            compact_no_catalog = compact_run_bytes_no_catalog,
596            conformance_raw = samples_string(&conformance_samples),
597            evidence_raw = samples_string(&evidence_samples),
598            discovery_raw = samples_string(&discovery_samples),
599            explanation_raw = samples_string(&explanation_samples),
600            ondemand_raw = samples_string(&on_demand_samples),
601        );
602
603        eprintln!(
604            "median: {:.1}ms conformance, {:.1}ms full ({:.2}x), {:.1}ms on-demand ({:.2}x); {} failures / {} pairs; {iters} rounds",
605            conformance_ms.median,
606            evidence_ms.median,
607            full_ratio.median,
608            on_demand_ms.median,
609            on_demand_ratio.median,
610            conformance.failed,
611            conformance.selected_pairs,
612        );
613    }
614}
Source

pub fn result_redundancy(&self) -> f64

Occurrences per distinct validation-judgment node.

Examples found in repository?
examples/bench_evidence.rs (line 536)
132fn main() {
133    let args = parse_args();
134
135    eprintln!("loading shapes {}…", args.shapes.display());
136    let shapes_bytes = fs::read(&args.shapes).expect("cannot read --shapes");
137    let shapes = shifty_parse::load_turtle(&shapes_bytes, None).expect("cannot parse --shapes");
138    let compiled = CompiledShapes::compile(shapes).expect("cannot compile shapes");
139    drop(shapes_bytes);
140
141    if args.header {
142        println!("{COLUMNS}");
143    }
144
145    for model in models_in(&args.models) {
146        let name = model
147            .file_name()
148            .and_then(|name| name.to_str())
149            .unwrap_or("?")
150            .to_string();
151        eprint!("{name}… ");
152
153        // ---- untimed setup: everything common to all workflows --------------
154        let data_bytes = fs::read(&model).expect("cannot read model");
155        let data = shifty_parse::load_turtle(&data_bytes, None).expect("cannot parse model");
156        drop(data_bytes);
157        let data_graph = if args.no_infer {
158            data.graph.clone()
159        } else {
160            compiled
161                .session(
162                    SessionData::Separate(data.graph.clone()),
163                    SessionOptions {
164                        inference: true,
165                        ..SessionOptions::default()
166                    },
167                )
168                .unwrap_or_else(|error| panic!("{name}: inference failed: {error}"))
169                .data()
170                .clone()
171        };
172        let data_triples = data_graph.len();
173
174        let prepared = PreparedEvidenceValidator::with_graphs(
175            &data_graph,
176            &compiled.source().graph,
177            compiled.authored_schema(),
178            ValidationGraphMode::Union,
179        )
180        .unwrap_or_else(|error| panic!("{name}: schema is non-stratifiable: {error}"));
181        let options = ValidationOptions::default();
182        let scan_options = ConformanceOptions::default();
183
184        // ---- timed: validation only -----------------------------------------
185        // One warm-up round, discarded: it faults in the caches all arms share.
186        // Its duration is only consulted when `--budget-ms` is in play.
187        let probe = Instant::now();
188        let _ = prepared.validate_conformance(&scan_options);
189        let _ = prepared.validate_canonical(&options);
190        let (_, warm_failures) = prepared.find_failures(&scan_options);
191        for pair in &warm_failures {
192            std::hint::black_box(prepared.explain_canonical(pair));
193        }
194        let probe_ms = probe.elapsed().as_secs_f64() * 1e3;
195        // Every model gets the same iteration count. `--budget-ms` only caps the
196        // count for a corpus slow enough that a fixed count is impractical; it
197        // is off by default, and the emitted `iters` column records what was used.
198        let iters = if args.budget_ms > 0.0 {
199            ((args.budget_ms / probe_ms.max(f64::MIN_POSITIVE)) as usize)
200                .clamp(args.min_iters.min(args.iters), args.iters)
201        } else {
202            args.iters
203        };
204
205        let mut conformance_samples = Vec::with_capacity(iters);
206        let mut evidence_samples = Vec::with_capacity(iters);
207        let mut discovery_samples = Vec::with_capacity(iters);
208        let mut explanation_samples = Vec::with_capacity(iters);
209        let mut on_demand_samples = Vec::with_capacity(iters);
210        let mut last_run = None;
211        let mut last_conformance = None;
212        let mut last_on_demand_conformance = None;
213        let mut last_failures = Vec::new();
214        let mut last_explanations = Vec::new();
215
216        // Rotate A/B/C so no arm systematically inherits the cache, allocator,
217        // scheduler, or thermal state left by another arm.
218        for iteration in 0..iters {
219            for arm in ARM_ORDERS[iteration % ARM_ORDERS.len()] {
220                match arm {
221                    Arm::Conformance => {
222                        let start = Instant::now();
223                        let conformance_run = prepared.validate_conformance(&scan_options);
224                        conformance_samples.push(start.elapsed());
225                        std::hint::black_box(conformance_run.selected_pairs);
226                        last_conformance = Some(conformance_run);
227                    }
228                    Arm::FullEvidence => {
229                        let start = Instant::now();
230                        let run = prepared.validate_canonical(&options);
231                        evidence_samples.push(start.elapsed());
232                        std::hint::black_box(run.statements.len());
233                        last_run = Some(run);
234                    }
235                    Arm::OnDemand => {
236                        let start = Instant::now();
237                        let (on_demand_conformance, failures) =
238                            prepared.find_failures(&scan_options);
239                        let discovery = start.elapsed();
240
241                        let start = Instant::now();
242                        let explanations: Vec<_> = failures
243                            .iter()
244                            .flat_map(|pair| prepared.explain_canonical(pair))
245                            .collect();
246                        let explanation = start.elapsed();
247
248                        discovery_samples.push(discovery);
249                        explanation_samples.push(explanation);
250                        on_demand_samples.push(discovery + explanation);
251                        std::hint::black_box(explanations.len());
252                        last_on_demand_conformance = Some(on_demand_conformance);
253                        last_failures = failures;
254                        last_explanations = explanations;
255                    }
256                }
257            }
258        }
259
260        // ---- untimed: shape of the evidence produced -------------------------
261        // Reuse the final timed results rather than paying for another run.
262        let conformance = last_conformance.expect("at least one iteration runs");
263        let on_demand_conformance =
264            last_on_demand_conformance.expect("at least one iteration runs");
265        let run = last_run.expect("at least one iteration runs");
266        let authored_pairs: usize = run
267            .statements
268            .iter()
269            .map(|statement| statement.selected_foci.len())
270            .sum();
271        let authored_fail_pairs: usize = run
272            .statements
273            .iter()
274            .flat_map(|statement| &statement.selected_foci)
275            .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
276            .count();
277
278        // The three interfaces must describe the same normalized selection and
279        // polarity partition. Compare exact failed identities, not just the
280        // report-wide conformance bit.
281        assert_eq!(
282            conformance, on_demand_conformance,
283            "{name}: on-demand counts"
284        );
285        assert_eq!(
286            conformance.conforms, run.conforms,
287            "{name}: conformance bit"
288        );
289        let full_selected: HashSet<_> = run
290            .statements
291            .iter()
292            .flat_map(|statement| {
293                statement.selected_foci.iter().filter_map(|focus| {
294                    statement
295                        .normalized_statement_id
296                        .map(|id| (id, focus.focus.clone()))
297                })
298            })
299            .collect();
300        let full_failures: HashSet<_> = run
301            .statements
302            .iter()
303            .flat_map(|statement| {
304                statement
305                    .selected_foci
306                    .iter()
307                    .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
308                    .filter_map(|focus| {
309                        statement
310                            .normalized_statement_id
311                            .map(|id| (id, focus.focus.clone()))
312                    })
313            })
314            .collect();
315        let discovered_failures: HashSet<_> = last_failures
316            .iter()
317            .map(|pair| (pair.normalized_statement(), pair.focus().clone()))
318            .collect();
319        assert_eq!(
320            full_selected.len(),
321            conformance.selected_pairs,
322            "{name}: selected pairs"
323        );
324        assert_eq!(
325            full_failures.len(),
326            conformance.failed,
327            "{name}: failed pairs"
328        );
329        assert_eq!(
330            full_selected.len() - full_failures.len(),
331            conformance.passed,
332            "{name}: passed pairs"
333        );
334        assert_eq!(
335            full_failures, discovered_failures,
336            "{name}: failed identities"
337        );
338
339        let full_by_authored: HashMap<_, _> = run
340            .statements
341            .iter()
342            .flat_map(|statement| {
343                statement.selected_foci.iter().map(|focus| {
344                    (
345                        (statement.source_statement_id, focus.focus.clone()),
346                        &focus.evidence,
347                    )
348                })
349            })
350            .collect();
351        let mut explained_foci = 0;
352        for statement in &last_explanations {
353            for focus in &statement.selected_foci {
354                explained_foci += 1;
355                assert_eq!(
356                    focus.status(),
357                    shifty_engine::EvaluationStatus::Fail,
358                    "{name}: on-demand explanation polarity"
359                );
360                let key = (statement.source_statement_id, focus.focus.clone());
361                assert_eq!(
362                    &focus.evidence,
363                    *full_by_authored
364                        .get(&key)
365                        .unwrap_or_else(|| panic!("{name}: unexplained full-run pair {key:?}")),
366                    "{name}: full and on-demand evidence differ for {key:?}"
367                );
368            }
369        }
370        assert_eq!(
371            explained_foci, authored_fail_pairs,
372            "{name}: authored explanations"
373        );
374
375        let evidence_nodes = run.walk().len();
376        let evidence_bytes: usize = run
377            .statements
378            .iter()
379            .flat_map(|statement| &statement.selected_foci)
380            .map(|focus| focus.evidence.to_json().expect("evidence serializes").len())
381            .sum();
382        let full_json = run.to_json().expect("evidence run serializes");
383        let full_run_bytes_with_catalog = full_json.len();
384        let mut full_value_no_catalog =
385            serde_json::to_value(&run).expect("evidence run converts to JSON");
386        full_value_no_catalog
387            .as_object_mut()
388            .expect("evidence run is a JSON object")
389            .remove("constraints");
390        let full_run_bytes_no_catalog = serde_json::to_string(&full_value_no_catalog)
391            .expect("catalog-free evidence run serializes")
392            .len();
393
394        // Compare complete runs with the same catalog policy on each side, and
395        // verify both encodings reconstruct the exact typed result.
396        let encoded_with_catalog =
397            shifty_engine::compact(&run, true).expect("compact encoding succeeds");
398        let compact_run_bytes_with_catalog = serde_json::to_string(&encoded_with_catalog)
399            .expect("compact encoding serializes")
400            .len();
401        assert_eq!(
402            shifty_engine::expand(&encoded_with_catalog).expect("compact run expands"),
403            run,
404            "{name}: compact run with catalog is not lossless"
405        );
406        let encoded_no_catalog =
407            shifty_engine::compact(&run, false).expect("compact encoding succeeds");
408        let compact_run_bytes_no_catalog = serde_json::to_string(&encoded_no_catalog)
409            .expect("catalog-free compact encoding serializes")
410            .len();
411        let catalog = serde_json::to_value(&run.constraints).expect("catalog serializes");
412        assert_eq!(
413            shifty_engine::expand_with_catalog(&encoded_no_catalog, catalog)
414                .expect("catalog-free compact run expands"),
415            run,
416            "{name}: compact run without catalog is not lossless"
417        );
418        // Sharing is reported over the evidence alone. The node and term tables
419        // of `encoded` also hold catalog entries, which would understate the
420        // redundancy by adding distinct entries with no evidence occurrences.
421        let sharing = shifty_engine::sharing(&run).expect("sharing measures");
422        let results = shifty_engine::result_sharing(&run);
423
424        // The two passes reach the same judgment nodes by routes that share no
425        // code: `sharing` interns serialized JSON, `result_sharing` walks the
426        // typed evidence. Agreement on both counts is what makes either number
427        // reportable; a mismatch means the node families are misclassified or a
428        // traversal is missing a variant.
429        assert_eq!(
430            sharing.result_occurrences + sharing.support_occurrences,
431            sharing.node_occurrences,
432            "{name}: node families do not partition occurrences"
433        );
434        assert_eq!(
435            sharing.distinct_results + sharing.distinct_support,
436            sharing.distinct_nodes,
437            "{name}: node families do not partition the table"
438        );
439        assert_eq!(
440            sharing.result_occurrences, evidence_nodes,
441            "{name}: interned judgments disagree with the typed walk"
442        );
443        assert_eq!(
444            results.occurrences, evidence_nodes,
445            "{name}: typed sharing pass disagrees with the typed walk"
446        );
447        assert_eq!(
448            results.distinct_payloads, sharing.distinct_results,
449            "{name}: distinct judgments disagree between the two passes"
450        );
451        // Structural equality implies key equality, never the reverse.
452        assert!(
453            results.distinct_keys <= results.distinct_payloads,
454            "{name}: more keys than payloads"
455        );
456        // Requests are the normalized selection the conformance pass counts;
457        // authored records are the source-preserving view over them.
458        assert_eq!(
459            results.normalized_requests, conformance.selected_pairs,
460            "{name}: requests disagree with the conformance selection"
461        );
462        assert_eq!(
463            results.authored_records, authored_pairs,
464            "{name}: authored records disagree with the run"
465        );
466
467        // Serialization is measured separately so its allocation traffic does
468        // not perturb the next validation arm.
469        let mut serialize_samples = Vec::with_capacity(iters);
470        for _ in 0..iters {
471            let start = Instant::now();
472            let json = run.to_json().expect("evidence run serializes");
473            serialize_samples.push(start.elapsed());
474            std::hint::black_box(json.len());
475        }
476
477        // Each ratio compares the corresponding raw samples from one rotated
478        // A/B/C round. The CSV retains those samples for independent checking.
479        let full_ratios: Vec<f64> = conformance_samples
480            .iter()
481            .zip(evidence_samples.iter())
482            .map(|(conformance, evidence)| {
483                evidence.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
484            })
485            .collect();
486        let on_demand_ratios: Vec<f64> = conformance_samples
487            .iter()
488            .zip(on_demand_samples.iter())
489            .map(|(conformance, on_demand)| {
490                on_demand.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
491            })
492            .collect();
493
494        let conformance_ms = summarize_durations(&conformance_samples);
495        let evidence_ms = summarize_durations(&evidence_samples);
496        let discovery_ms = summarize_durations(&discovery_samples);
497        let explanation_ms = summarize_durations(&explanation_samples);
498        let on_demand_ms = summarize_durations(&on_demand_samples);
499        let serialize_ms = summarize_durations(&serialize_samples);
500        let full_ratio = summarize_values(&full_ratios);
501        let on_demand_ratio = summarize_values(&on_demand_ratios);
502        let evaluated_pairs = conformance.selected_pairs.max(1) as f64;
503        let authored_pairs_denominator = authored_pairs.max(1) as f64;
504
505        println!(
506            "{suite},{name},{data_triples},{iters},\
507{c_med:.4},{c_mad:.4},{e_med:.4},{e_mad:.4},{full_ratio:.4},{full_ratio_mad:.4},\
508{find_med:.4},{find_mad:.4},{explain_med:.4},{explain_mad:.4},\
509{ondemand_med:.4},{ondemand_mad:.4},{ondemand_ratio:.4},{ondemand_ratio_mad:.4},\
510{serialize_med:.4},{serialize_mad:.4},\
511{conforms_c},{conforms_e},{conforms_o},\
512{pairs_eval},{pass_pairs},{fail_pairs},{fail_fraction:.6},\
513{authored_pairs},{authored_pass},{authored_fail},\
514{statements},\
515{evidence_nodes},{nodes_per_authored_pair:.3},\
516{evidence_bytes},{bytes_per_authored_pair:.1},\
517{full_with_catalog},{full_no_catalog},{compact_with_catalog},{compact_no_catalog},\
518{node_occurrences},{distinct_nodes},{node_redundancy:.3},\
519{result_occurrences},{distinct_results},{result_redundancy:.3},\
520{support_occurrences},{distinct_support},{support_redundancy:.3},{support_share:.4},\
521{term_occurrences},{distinct_terms},{term_redundancy:.3},\
522{normalized_requests},{duplicate_records},{divergent_duplicates},\
523{canonical_occurrences},{shared_payloads},{shared_payload_fraction:.4},\
524{shared_canonical_occurrences},{shared_occurrence_fraction:.4},\
525{request_reaches},{requests_per_payload:.3},{max_payload_requests},\
526{distinct_keys},{key_redundancy:.3},{multi_occurrence_keys},\
527{divergent_keys},{divergence_fraction:.4},{divergent_occurrences},\
528{distinct_payloads_per_key},{keys_over_payload_cap},{both_polarity_addresses},\
529{conformance_raw},{evidence_raw},{discovery_raw},{explanation_raw},{ondemand_raw}",
530            suite = args.suite,
531            node_occurrences = sharing.node_occurrences,
532            distinct_nodes = sharing.distinct_nodes,
533            node_redundancy = sharing.node_redundancy(),
534            result_occurrences = sharing.result_occurrences,
535            distinct_results = sharing.distinct_results,
536            result_redundancy = sharing.result_redundancy(),
537            support_occurrences = sharing.support_occurrences,
538            distinct_support = sharing.distinct_support,
539            support_redundancy = sharing.support_redundancy(),
540            support_share = sharing.support_share(),
541            term_occurrences = sharing.term_occurrences,
542            distinct_terms = sharing.distinct_terms,
543            term_redundancy = sharing.term_redundancy(),
544            normalized_requests = results.normalized_requests,
545            duplicate_records = results.duplicate_records,
546            divergent_duplicates = results.divergent_duplicates,
547            canonical_occurrences = results.canonical_occurrences,
548            shared_payloads = results.shared_payloads,
549            shared_payload_fraction = results.shared_payload_fraction(),
550            shared_canonical_occurrences = results.shared_canonical_occurrences,
551            shared_occurrence_fraction = results.shared_occurrence_fraction(),
552            request_reaches = results.request_reaches,
553            requests_per_payload = results.requests_per_payload(),
554            max_payload_requests = results.max_payload_requests,
555            distinct_keys = results.distinct_keys,
556            key_redundancy = results.key_redundancy(),
557            multi_occurrence_keys = results.multi_occurrence_keys,
558            divergent_keys = results.divergent_keys,
559            divergence_fraction = results.divergence_fraction(),
560            divergent_occurrences = results.divergent_occurrences,
561            distinct_payloads_per_key = results.distinct_payloads_per_key,
562            keys_over_payload_cap = results.keys_over_payload_cap,
563            both_polarity_addresses = results.both_polarity_addresses,
564            c_med = conformance_ms.median,
565            c_mad = conformance_ms.mad,
566            e_med = evidence_ms.median,
567            e_mad = evidence_ms.mad,
568            full_ratio = full_ratio.median,
569            full_ratio_mad = full_ratio.mad,
570            find_med = discovery_ms.median,
571            find_mad = discovery_ms.mad,
572            explain_med = explanation_ms.median,
573            explain_mad = explanation_ms.mad,
574            ondemand_med = on_demand_ms.median,
575            ondemand_mad = on_demand_ms.mad,
576            ondemand_ratio = on_demand_ratio.median,
577            ondemand_ratio_mad = on_demand_ratio.mad,
578            serialize_med = serialize_ms.median,
579            serialize_mad = serialize_ms.mad,
580            conforms_c = conformance.conforms,
581            conforms_e = run.conforms,
582            conforms_o = on_demand_conformance.conforms,
583            pairs_eval = conformance.selected_pairs,
584            pass_pairs = conformance.passed,
585            fail_pairs = conformance.failed,
586            fail_fraction = conformance.failed as f64 / evaluated_pairs,
587            authored_pass = authored_pairs - authored_fail_pairs,
588            authored_fail = authored_fail_pairs,
589            statements = run.statements.len(),
590            nodes_per_authored_pair = evidence_nodes as f64 / authored_pairs_denominator,
591            bytes_per_authored_pair = evidence_bytes as f64 / authored_pairs_denominator,
592            full_with_catalog = full_run_bytes_with_catalog,
593            full_no_catalog = full_run_bytes_no_catalog,
594            compact_with_catalog = compact_run_bytes_with_catalog,
595            compact_no_catalog = compact_run_bytes_no_catalog,
596            conformance_raw = samples_string(&conformance_samples),
597            evidence_raw = samples_string(&evidence_samples),
598            discovery_raw = samples_string(&discovery_samples),
599            explanation_raw = samples_string(&explanation_samples),
600            ondemand_raw = samples_string(&on_demand_samples),
601        );
602
603        eprintln!(
604            "median: {:.1}ms conformance, {:.1}ms full ({:.2}x), {:.1}ms on-demand ({:.2}x); {} failures / {} pairs; {iters} rounds",
605            conformance_ms.median,
606            evidence_ms.median,
607            full_ratio.median,
608            on_demand_ms.median,
609            on_demand_ratio.median,
610            conformance.failed,
611            conformance.selected_pairs,
612        );
613    }
614}
Source

pub fn support_redundancy(&self) -> f64

Occurrences per distinct path-support certificate.

Examples found in repository?
examples/bench_evidence.rs (line 539)
132fn main() {
133    let args = parse_args();
134
135    eprintln!("loading shapes {}…", args.shapes.display());
136    let shapes_bytes = fs::read(&args.shapes).expect("cannot read --shapes");
137    let shapes = shifty_parse::load_turtle(&shapes_bytes, None).expect("cannot parse --shapes");
138    let compiled = CompiledShapes::compile(shapes).expect("cannot compile shapes");
139    drop(shapes_bytes);
140
141    if args.header {
142        println!("{COLUMNS}");
143    }
144
145    for model in models_in(&args.models) {
146        let name = model
147            .file_name()
148            .and_then(|name| name.to_str())
149            .unwrap_or("?")
150            .to_string();
151        eprint!("{name}… ");
152
153        // ---- untimed setup: everything common to all workflows --------------
154        let data_bytes = fs::read(&model).expect("cannot read model");
155        let data = shifty_parse::load_turtle(&data_bytes, None).expect("cannot parse model");
156        drop(data_bytes);
157        let data_graph = if args.no_infer {
158            data.graph.clone()
159        } else {
160            compiled
161                .session(
162                    SessionData::Separate(data.graph.clone()),
163                    SessionOptions {
164                        inference: true,
165                        ..SessionOptions::default()
166                    },
167                )
168                .unwrap_or_else(|error| panic!("{name}: inference failed: {error}"))
169                .data()
170                .clone()
171        };
172        let data_triples = data_graph.len();
173
174        let prepared = PreparedEvidenceValidator::with_graphs(
175            &data_graph,
176            &compiled.source().graph,
177            compiled.authored_schema(),
178            ValidationGraphMode::Union,
179        )
180        .unwrap_or_else(|error| panic!("{name}: schema is non-stratifiable: {error}"));
181        let options = ValidationOptions::default();
182        let scan_options = ConformanceOptions::default();
183
184        // ---- timed: validation only -----------------------------------------
185        // One warm-up round, discarded: it faults in the caches all arms share.
186        // Its duration is only consulted when `--budget-ms` is in play.
187        let probe = Instant::now();
188        let _ = prepared.validate_conformance(&scan_options);
189        let _ = prepared.validate_canonical(&options);
190        let (_, warm_failures) = prepared.find_failures(&scan_options);
191        for pair in &warm_failures {
192            std::hint::black_box(prepared.explain_canonical(pair));
193        }
194        let probe_ms = probe.elapsed().as_secs_f64() * 1e3;
195        // Every model gets the same iteration count. `--budget-ms` only caps the
196        // count for a corpus slow enough that a fixed count is impractical; it
197        // is off by default, and the emitted `iters` column records what was used.
198        let iters = if args.budget_ms > 0.0 {
199            ((args.budget_ms / probe_ms.max(f64::MIN_POSITIVE)) as usize)
200                .clamp(args.min_iters.min(args.iters), args.iters)
201        } else {
202            args.iters
203        };
204
205        let mut conformance_samples = Vec::with_capacity(iters);
206        let mut evidence_samples = Vec::with_capacity(iters);
207        let mut discovery_samples = Vec::with_capacity(iters);
208        let mut explanation_samples = Vec::with_capacity(iters);
209        let mut on_demand_samples = Vec::with_capacity(iters);
210        let mut last_run = None;
211        let mut last_conformance = None;
212        let mut last_on_demand_conformance = None;
213        let mut last_failures = Vec::new();
214        let mut last_explanations = Vec::new();
215
216        // Rotate A/B/C so no arm systematically inherits the cache, allocator,
217        // scheduler, or thermal state left by another arm.
218        for iteration in 0..iters {
219            for arm in ARM_ORDERS[iteration % ARM_ORDERS.len()] {
220                match arm {
221                    Arm::Conformance => {
222                        let start = Instant::now();
223                        let conformance_run = prepared.validate_conformance(&scan_options);
224                        conformance_samples.push(start.elapsed());
225                        std::hint::black_box(conformance_run.selected_pairs);
226                        last_conformance = Some(conformance_run);
227                    }
228                    Arm::FullEvidence => {
229                        let start = Instant::now();
230                        let run = prepared.validate_canonical(&options);
231                        evidence_samples.push(start.elapsed());
232                        std::hint::black_box(run.statements.len());
233                        last_run = Some(run);
234                    }
235                    Arm::OnDemand => {
236                        let start = Instant::now();
237                        let (on_demand_conformance, failures) =
238                            prepared.find_failures(&scan_options);
239                        let discovery = start.elapsed();
240
241                        let start = Instant::now();
242                        let explanations: Vec<_> = failures
243                            .iter()
244                            .flat_map(|pair| prepared.explain_canonical(pair))
245                            .collect();
246                        let explanation = start.elapsed();
247
248                        discovery_samples.push(discovery);
249                        explanation_samples.push(explanation);
250                        on_demand_samples.push(discovery + explanation);
251                        std::hint::black_box(explanations.len());
252                        last_on_demand_conformance = Some(on_demand_conformance);
253                        last_failures = failures;
254                        last_explanations = explanations;
255                    }
256                }
257            }
258        }
259
260        // ---- untimed: shape of the evidence produced -------------------------
261        // Reuse the final timed results rather than paying for another run.
262        let conformance = last_conformance.expect("at least one iteration runs");
263        let on_demand_conformance =
264            last_on_demand_conformance.expect("at least one iteration runs");
265        let run = last_run.expect("at least one iteration runs");
266        let authored_pairs: usize = run
267            .statements
268            .iter()
269            .map(|statement| statement.selected_foci.len())
270            .sum();
271        let authored_fail_pairs: usize = run
272            .statements
273            .iter()
274            .flat_map(|statement| &statement.selected_foci)
275            .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
276            .count();
277
278        // The three interfaces must describe the same normalized selection and
279        // polarity partition. Compare exact failed identities, not just the
280        // report-wide conformance bit.
281        assert_eq!(
282            conformance, on_demand_conformance,
283            "{name}: on-demand counts"
284        );
285        assert_eq!(
286            conformance.conforms, run.conforms,
287            "{name}: conformance bit"
288        );
289        let full_selected: HashSet<_> = run
290            .statements
291            .iter()
292            .flat_map(|statement| {
293                statement.selected_foci.iter().filter_map(|focus| {
294                    statement
295                        .normalized_statement_id
296                        .map(|id| (id, focus.focus.clone()))
297                })
298            })
299            .collect();
300        let full_failures: HashSet<_> = run
301            .statements
302            .iter()
303            .flat_map(|statement| {
304                statement
305                    .selected_foci
306                    .iter()
307                    .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
308                    .filter_map(|focus| {
309                        statement
310                            .normalized_statement_id
311                            .map(|id| (id, focus.focus.clone()))
312                    })
313            })
314            .collect();
315        let discovered_failures: HashSet<_> = last_failures
316            .iter()
317            .map(|pair| (pair.normalized_statement(), pair.focus().clone()))
318            .collect();
319        assert_eq!(
320            full_selected.len(),
321            conformance.selected_pairs,
322            "{name}: selected pairs"
323        );
324        assert_eq!(
325            full_failures.len(),
326            conformance.failed,
327            "{name}: failed pairs"
328        );
329        assert_eq!(
330            full_selected.len() - full_failures.len(),
331            conformance.passed,
332            "{name}: passed pairs"
333        );
334        assert_eq!(
335            full_failures, discovered_failures,
336            "{name}: failed identities"
337        );
338
339        let full_by_authored: HashMap<_, _> = run
340            .statements
341            .iter()
342            .flat_map(|statement| {
343                statement.selected_foci.iter().map(|focus| {
344                    (
345                        (statement.source_statement_id, focus.focus.clone()),
346                        &focus.evidence,
347                    )
348                })
349            })
350            .collect();
351        let mut explained_foci = 0;
352        for statement in &last_explanations {
353            for focus in &statement.selected_foci {
354                explained_foci += 1;
355                assert_eq!(
356                    focus.status(),
357                    shifty_engine::EvaluationStatus::Fail,
358                    "{name}: on-demand explanation polarity"
359                );
360                let key = (statement.source_statement_id, focus.focus.clone());
361                assert_eq!(
362                    &focus.evidence,
363                    *full_by_authored
364                        .get(&key)
365                        .unwrap_or_else(|| panic!("{name}: unexplained full-run pair {key:?}")),
366                    "{name}: full and on-demand evidence differ for {key:?}"
367                );
368            }
369        }
370        assert_eq!(
371            explained_foci, authored_fail_pairs,
372            "{name}: authored explanations"
373        );
374
375        let evidence_nodes = run.walk().len();
376        let evidence_bytes: usize = run
377            .statements
378            .iter()
379            .flat_map(|statement| &statement.selected_foci)
380            .map(|focus| focus.evidence.to_json().expect("evidence serializes").len())
381            .sum();
382        let full_json = run.to_json().expect("evidence run serializes");
383        let full_run_bytes_with_catalog = full_json.len();
384        let mut full_value_no_catalog =
385            serde_json::to_value(&run).expect("evidence run converts to JSON");
386        full_value_no_catalog
387            .as_object_mut()
388            .expect("evidence run is a JSON object")
389            .remove("constraints");
390        let full_run_bytes_no_catalog = serde_json::to_string(&full_value_no_catalog)
391            .expect("catalog-free evidence run serializes")
392            .len();
393
394        // Compare complete runs with the same catalog policy on each side, and
395        // verify both encodings reconstruct the exact typed result.
396        let encoded_with_catalog =
397            shifty_engine::compact(&run, true).expect("compact encoding succeeds");
398        let compact_run_bytes_with_catalog = serde_json::to_string(&encoded_with_catalog)
399            .expect("compact encoding serializes")
400            .len();
401        assert_eq!(
402            shifty_engine::expand(&encoded_with_catalog).expect("compact run expands"),
403            run,
404            "{name}: compact run with catalog is not lossless"
405        );
406        let encoded_no_catalog =
407            shifty_engine::compact(&run, false).expect("compact encoding succeeds");
408        let compact_run_bytes_no_catalog = serde_json::to_string(&encoded_no_catalog)
409            .expect("catalog-free compact encoding serializes")
410            .len();
411        let catalog = serde_json::to_value(&run.constraints).expect("catalog serializes");
412        assert_eq!(
413            shifty_engine::expand_with_catalog(&encoded_no_catalog, catalog)
414                .expect("catalog-free compact run expands"),
415            run,
416            "{name}: compact run without catalog is not lossless"
417        );
418        // Sharing is reported over the evidence alone. The node and term tables
419        // of `encoded` also hold catalog entries, which would understate the
420        // redundancy by adding distinct entries with no evidence occurrences.
421        let sharing = shifty_engine::sharing(&run).expect("sharing measures");
422        let results = shifty_engine::result_sharing(&run);
423
424        // The two passes reach the same judgment nodes by routes that share no
425        // code: `sharing` interns serialized JSON, `result_sharing` walks the
426        // typed evidence. Agreement on both counts is what makes either number
427        // reportable; a mismatch means the node families are misclassified or a
428        // traversal is missing a variant.
429        assert_eq!(
430            sharing.result_occurrences + sharing.support_occurrences,
431            sharing.node_occurrences,
432            "{name}: node families do not partition occurrences"
433        );
434        assert_eq!(
435            sharing.distinct_results + sharing.distinct_support,
436            sharing.distinct_nodes,
437            "{name}: node families do not partition the table"
438        );
439        assert_eq!(
440            sharing.result_occurrences, evidence_nodes,
441            "{name}: interned judgments disagree with the typed walk"
442        );
443        assert_eq!(
444            results.occurrences, evidence_nodes,
445            "{name}: typed sharing pass disagrees with the typed walk"
446        );
447        assert_eq!(
448            results.distinct_payloads, sharing.distinct_results,
449            "{name}: distinct judgments disagree between the two passes"
450        );
451        // Structural equality implies key equality, never the reverse.
452        assert!(
453            results.distinct_keys <= results.distinct_payloads,
454            "{name}: more keys than payloads"
455        );
456        // Requests are the normalized selection the conformance pass counts;
457        // authored records are the source-preserving view over them.
458        assert_eq!(
459            results.normalized_requests, conformance.selected_pairs,
460            "{name}: requests disagree with the conformance selection"
461        );
462        assert_eq!(
463            results.authored_records, authored_pairs,
464            "{name}: authored records disagree with the run"
465        );
466
467        // Serialization is measured separately so its allocation traffic does
468        // not perturb the next validation arm.
469        let mut serialize_samples = Vec::with_capacity(iters);
470        for _ in 0..iters {
471            let start = Instant::now();
472            let json = run.to_json().expect("evidence run serializes");
473            serialize_samples.push(start.elapsed());
474            std::hint::black_box(json.len());
475        }
476
477        // Each ratio compares the corresponding raw samples from one rotated
478        // A/B/C round. The CSV retains those samples for independent checking.
479        let full_ratios: Vec<f64> = conformance_samples
480            .iter()
481            .zip(evidence_samples.iter())
482            .map(|(conformance, evidence)| {
483                evidence.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
484            })
485            .collect();
486        let on_demand_ratios: Vec<f64> = conformance_samples
487            .iter()
488            .zip(on_demand_samples.iter())
489            .map(|(conformance, on_demand)| {
490                on_demand.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
491            })
492            .collect();
493
494        let conformance_ms = summarize_durations(&conformance_samples);
495        let evidence_ms = summarize_durations(&evidence_samples);
496        let discovery_ms = summarize_durations(&discovery_samples);
497        let explanation_ms = summarize_durations(&explanation_samples);
498        let on_demand_ms = summarize_durations(&on_demand_samples);
499        let serialize_ms = summarize_durations(&serialize_samples);
500        let full_ratio = summarize_values(&full_ratios);
501        let on_demand_ratio = summarize_values(&on_demand_ratios);
502        let evaluated_pairs = conformance.selected_pairs.max(1) as f64;
503        let authored_pairs_denominator = authored_pairs.max(1) as f64;
504
505        println!(
506            "{suite},{name},{data_triples},{iters},\
507{c_med:.4},{c_mad:.4},{e_med:.4},{e_mad:.4},{full_ratio:.4},{full_ratio_mad:.4},\
508{find_med:.4},{find_mad:.4},{explain_med:.4},{explain_mad:.4},\
509{ondemand_med:.4},{ondemand_mad:.4},{ondemand_ratio:.4},{ondemand_ratio_mad:.4},\
510{serialize_med:.4},{serialize_mad:.4},\
511{conforms_c},{conforms_e},{conforms_o},\
512{pairs_eval},{pass_pairs},{fail_pairs},{fail_fraction:.6},\
513{authored_pairs},{authored_pass},{authored_fail},\
514{statements},\
515{evidence_nodes},{nodes_per_authored_pair:.3},\
516{evidence_bytes},{bytes_per_authored_pair:.1},\
517{full_with_catalog},{full_no_catalog},{compact_with_catalog},{compact_no_catalog},\
518{node_occurrences},{distinct_nodes},{node_redundancy:.3},\
519{result_occurrences},{distinct_results},{result_redundancy:.3},\
520{support_occurrences},{distinct_support},{support_redundancy:.3},{support_share:.4},\
521{term_occurrences},{distinct_terms},{term_redundancy:.3},\
522{normalized_requests},{duplicate_records},{divergent_duplicates},\
523{canonical_occurrences},{shared_payloads},{shared_payload_fraction:.4},\
524{shared_canonical_occurrences},{shared_occurrence_fraction:.4},\
525{request_reaches},{requests_per_payload:.3},{max_payload_requests},\
526{distinct_keys},{key_redundancy:.3},{multi_occurrence_keys},\
527{divergent_keys},{divergence_fraction:.4},{divergent_occurrences},\
528{distinct_payloads_per_key},{keys_over_payload_cap},{both_polarity_addresses},\
529{conformance_raw},{evidence_raw},{discovery_raw},{explanation_raw},{ondemand_raw}",
530            suite = args.suite,
531            node_occurrences = sharing.node_occurrences,
532            distinct_nodes = sharing.distinct_nodes,
533            node_redundancy = sharing.node_redundancy(),
534            result_occurrences = sharing.result_occurrences,
535            distinct_results = sharing.distinct_results,
536            result_redundancy = sharing.result_redundancy(),
537            support_occurrences = sharing.support_occurrences,
538            distinct_support = sharing.distinct_support,
539            support_redundancy = sharing.support_redundancy(),
540            support_share = sharing.support_share(),
541            term_occurrences = sharing.term_occurrences,
542            distinct_terms = sharing.distinct_terms,
543            term_redundancy = sharing.term_redundancy(),
544            normalized_requests = results.normalized_requests,
545            duplicate_records = results.duplicate_records,
546            divergent_duplicates = results.divergent_duplicates,
547            canonical_occurrences = results.canonical_occurrences,
548            shared_payloads = results.shared_payloads,
549            shared_payload_fraction = results.shared_payload_fraction(),
550            shared_canonical_occurrences = results.shared_canonical_occurrences,
551            shared_occurrence_fraction = results.shared_occurrence_fraction(),
552            request_reaches = results.request_reaches,
553            requests_per_payload = results.requests_per_payload(),
554            max_payload_requests = results.max_payload_requests,
555            distinct_keys = results.distinct_keys,
556            key_redundancy = results.key_redundancy(),
557            multi_occurrence_keys = results.multi_occurrence_keys,
558            divergent_keys = results.divergent_keys,
559            divergence_fraction = results.divergence_fraction(),
560            divergent_occurrences = results.divergent_occurrences,
561            distinct_payloads_per_key = results.distinct_payloads_per_key,
562            keys_over_payload_cap = results.keys_over_payload_cap,
563            both_polarity_addresses = results.both_polarity_addresses,
564            c_med = conformance_ms.median,
565            c_mad = conformance_ms.mad,
566            e_med = evidence_ms.median,
567            e_mad = evidence_ms.mad,
568            full_ratio = full_ratio.median,
569            full_ratio_mad = full_ratio.mad,
570            find_med = discovery_ms.median,
571            find_mad = discovery_ms.mad,
572            explain_med = explanation_ms.median,
573            explain_mad = explanation_ms.mad,
574            ondemand_med = on_demand_ms.median,
575            ondemand_mad = on_demand_ms.mad,
576            ondemand_ratio = on_demand_ratio.median,
577            ondemand_ratio_mad = on_demand_ratio.mad,
578            serialize_med = serialize_ms.median,
579            serialize_mad = serialize_ms.mad,
580            conforms_c = conformance.conforms,
581            conforms_e = run.conforms,
582            conforms_o = on_demand_conformance.conforms,
583            pairs_eval = conformance.selected_pairs,
584            pass_pairs = conformance.passed,
585            fail_pairs = conformance.failed,
586            fail_fraction = conformance.failed as f64 / evaluated_pairs,
587            authored_pass = authored_pairs - authored_fail_pairs,
588            authored_fail = authored_fail_pairs,
589            statements = run.statements.len(),
590            nodes_per_authored_pair = evidence_nodes as f64 / authored_pairs_denominator,
591            bytes_per_authored_pair = evidence_bytes as f64 / authored_pairs_denominator,
592            full_with_catalog = full_run_bytes_with_catalog,
593            full_no_catalog = full_run_bytes_no_catalog,
594            compact_with_catalog = compact_run_bytes_with_catalog,
595            compact_no_catalog = compact_run_bytes_no_catalog,
596            conformance_raw = samples_string(&conformance_samples),
597            evidence_raw = samples_string(&evidence_samples),
598            discovery_raw = samples_string(&discovery_samples),
599            explanation_raw = samples_string(&explanation_samples),
600            ondemand_raw = samples_string(&on_demand_samples),
601        );
602
603        eprintln!(
604            "median: {:.1}ms conformance, {:.1}ms full ({:.2}x), {:.1}ms on-demand ({:.2}x); {} failures / {} pairs; {iters} rounds",
605            conformance_ms.median,
606            evidence_ms.median,
607            full_ratio.median,
608            on_demand_ms.median,
609            on_demand_ratio.median,
610            conformance.failed,
611            conformance.selected_pairs,
612        );
613    }
614}
Source

pub fn term_redundancy(&self) -> f64

Occurrences per distinct term; 1.0 when nothing repeats.

Examples found in repository?
examples/bench_evidence.rs (line 543)
132fn main() {
133    let args = parse_args();
134
135    eprintln!("loading shapes {}…", args.shapes.display());
136    let shapes_bytes = fs::read(&args.shapes).expect("cannot read --shapes");
137    let shapes = shifty_parse::load_turtle(&shapes_bytes, None).expect("cannot parse --shapes");
138    let compiled = CompiledShapes::compile(shapes).expect("cannot compile shapes");
139    drop(shapes_bytes);
140
141    if args.header {
142        println!("{COLUMNS}");
143    }
144
145    for model in models_in(&args.models) {
146        let name = model
147            .file_name()
148            .and_then(|name| name.to_str())
149            .unwrap_or("?")
150            .to_string();
151        eprint!("{name}… ");
152
153        // ---- untimed setup: everything common to all workflows --------------
154        let data_bytes = fs::read(&model).expect("cannot read model");
155        let data = shifty_parse::load_turtle(&data_bytes, None).expect("cannot parse model");
156        drop(data_bytes);
157        let data_graph = if args.no_infer {
158            data.graph.clone()
159        } else {
160            compiled
161                .session(
162                    SessionData::Separate(data.graph.clone()),
163                    SessionOptions {
164                        inference: true,
165                        ..SessionOptions::default()
166                    },
167                )
168                .unwrap_or_else(|error| panic!("{name}: inference failed: {error}"))
169                .data()
170                .clone()
171        };
172        let data_triples = data_graph.len();
173
174        let prepared = PreparedEvidenceValidator::with_graphs(
175            &data_graph,
176            &compiled.source().graph,
177            compiled.authored_schema(),
178            ValidationGraphMode::Union,
179        )
180        .unwrap_or_else(|error| panic!("{name}: schema is non-stratifiable: {error}"));
181        let options = ValidationOptions::default();
182        let scan_options = ConformanceOptions::default();
183
184        // ---- timed: validation only -----------------------------------------
185        // One warm-up round, discarded: it faults in the caches all arms share.
186        // Its duration is only consulted when `--budget-ms` is in play.
187        let probe = Instant::now();
188        let _ = prepared.validate_conformance(&scan_options);
189        let _ = prepared.validate_canonical(&options);
190        let (_, warm_failures) = prepared.find_failures(&scan_options);
191        for pair in &warm_failures {
192            std::hint::black_box(prepared.explain_canonical(pair));
193        }
194        let probe_ms = probe.elapsed().as_secs_f64() * 1e3;
195        // Every model gets the same iteration count. `--budget-ms` only caps the
196        // count for a corpus slow enough that a fixed count is impractical; it
197        // is off by default, and the emitted `iters` column records what was used.
198        let iters = if args.budget_ms > 0.0 {
199            ((args.budget_ms / probe_ms.max(f64::MIN_POSITIVE)) as usize)
200                .clamp(args.min_iters.min(args.iters), args.iters)
201        } else {
202            args.iters
203        };
204
205        let mut conformance_samples = Vec::with_capacity(iters);
206        let mut evidence_samples = Vec::with_capacity(iters);
207        let mut discovery_samples = Vec::with_capacity(iters);
208        let mut explanation_samples = Vec::with_capacity(iters);
209        let mut on_demand_samples = Vec::with_capacity(iters);
210        let mut last_run = None;
211        let mut last_conformance = None;
212        let mut last_on_demand_conformance = None;
213        let mut last_failures = Vec::new();
214        let mut last_explanations = Vec::new();
215
216        // Rotate A/B/C so no arm systematically inherits the cache, allocator,
217        // scheduler, or thermal state left by another arm.
218        for iteration in 0..iters {
219            for arm in ARM_ORDERS[iteration % ARM_ORDERS.len()] {
220                match arm {
221                    Arm::Conformance => {
222                        let start = Instant::now();
223                        let conformance_run = prepared.validate_conformance(&scan_options);
224                        conformance_samples.push(start.elapsed());
225                        std::hint::black_box(conformance_run.selected_pairs);
226                        last_conformance = Some(conformance_run);
227                    }
228                    Arm::FullEvidence => {
229                        let start = Instant::now();
230                        let run = prepared.validate_canonical(&options);
231                        evidence_samples.push(start.elapsed());
232                        std::hint::black_box(run.statements.len());
233                        last_run = Some(run);
234                    }
235                    Arm::OnDemand => {
236                        let start = Instant::now();
237                        let (on_demand_conformance, failures) =
238                            prepared.find_failures(&scan_options);
239                        let discovery = start.elapsed();
240
241                        let start = Instant::now();
242                        let explanations: Vec<_> = failures
243                            .iter()
244                            .flat_map(|pair| prepared.explain_canonical(pair))
245                            .collect();
246                        let explanation = start.elapsed();
247
248                        discovery_samples.push(discovery);
249                        explanation_samples.push(explanation);
250                        on_demand_samples.push(discovery + explanation);
251                        std::hint::black_box(explanations.len());
252                        last_on_demand_conformance = Some(on_demand_conformance);
253                        last_failures = failures;
254                        last_explanations = explanations;
255                    }
256                }
257            }
258        }
259
260        // ---- untimed: shape of the evidence produced -------------------------
261        // Reuse the final timed results rather than paying for another run.
262        let conformance = last_conformance.expect("at least one iteration runs");
263        let on_demand_conformance =
264            last_on_demand_conformance.expect("at least one iteration runs");
265        let run = last_run.expect("at least one iteration runs");
266        let authored_pairs: usize = run
267            .statements
268            .iter()
269            .map(|statement| statement.selected_foci.len())
270            .sum();
271        let authored_fail_pairs: usize = run
272            .statements
273            .iter()
274            .flat_map(|statement| &statement.selected_foci)
275            .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
276            .count();
277
278        // The three interfaces must describe the same normalized selection and
279        // polarity partition. Compare exact failed identities, not just the
280        // report-wide conformance bit.
281        assert_eq!(
282            conformance, on_demand_conformance,
283            "{name}: on-demand counts"
284        );
285        assert_eq!(
286            conformance.conforms, run.conforms,
287            "{name}: conformance bit"
288        );
289        let full_selected: HashSet<_> = run
290            .statements
291            .iter()
292            .flat_map(|statement| {
293                statement.selected_foci.iter().filter_map(|focus| {
294                    statement
295                        .normalized_statement_id
296                        .map(|id| (id, focus.focus.clone()))
297                })
298            })
299            .collect();
300        let full_failures: HashSet<_> = run
301            .statements
302            .iter()
303            .flat_map(|statement| {
304                statement
305                    .selected_foci
306                    .iter()
307                    .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
308                    .filter_map(|focus| {
309                        statement
310                            .normalized_statement_id
311                            .map(|id| (id, focus.focus.clone()))
312                    })
313            })
314            .collect();
315        let discovered_failures: HashSet<_> = last_failures
316            .iter()
317            .map(|pair| (pair.normalized_statement(), pair.focus().clone()))
318            .collect();
319        assert_eq!(
320            full_selected.len(),
321            conformance.selected_pairs,
322            "{name}: selected pairs"
323        );
324        assert_eq!(
325            full_failures.len(),
326            conformance.failed,
327            "{name}: failed pairs"
328        );
329        assert_eq!(
330            full_selected.len() - full_failures.len(),
331            conformance.passed,
332            "{name}: passed pairs"
333        );
334        assert_eq!(
335            full_failures, discovered_failures,
336            "{name}: failed identities"
337        );
338
339        let full_by_authored: HashMap<_, _> = run
340            .statements
341            .iter()
342            .flat_map(|statement| {
343                statement.selected_foci.iter().map(|focus| {
344                    (
345                        (statement.source_statement_id, focus.focus.clone()),
346                        &focus.evidence,
347                    )
348                })
349            })
350            .collect();
351        let mut explained_foci = 0;
352        for statement in &last_explanations {
353            for focus in &statement.selected_foci {
354                explained_foci += 1;
355                assert_eq!(
356                    focus.status(),
357                    shifty_engine::EvaluationStatus::Fail,
358                    "{name}: on-demand explanation polarity"
359                );
360                let key = (statement.source_statement_id, focus.focus.clone());
361                assert_eq!(
362                    &focus.evidence,
363                    *full_by_authored
364                        .get(&key)
365                        .unwrap_or_else(|| panic!("{name}: unexplained full-run pair {key:?}")),
366                    "{name}: full and on-demand evidence differ for {key:?}"
367                );
368            }
369        }
370        assert_eq!(
371            explained_foci, authored_fail_pairs,
372            "{name}: authored explanations"
373        );
374
375        let evidence_nodes = run.walk().len();
376        let evidence_bytes: usize = run
377            .statements
378            .iter()
379            .flat_map(|statement| &statement.selected_foci)
380            .map(|focus| focus.evidence.to_json().expect("evidence serializes").len())
381            .sum();
382        let full_json = run.to_json().expect("evidence run serializes");
383        let full_run_bytes_with_catalog = full_json.len();
384        let mut full_value_no_catalog =
385            serde_json::to_value(&run).expect("evidence run converts to JSON");
386        full_value_no_catalog
387            .as_object_mut()
388            .expect("evidence run is a JSON object")
389            .remove("constraints");
390        let full_run_bytes_no_catalog = serde_json::to_string(&full_value_no_catalog)
391            .expect("catalog-free evidence run serializes")
392            .len();
393
394        // Compare complete runs with the same catalog policy on each side, and
395        // verify both encodings reconstruct the exact typed result.
396        let encoded_with_catalog =
397            shifty_engine::compact(&run, true).expect("compact encoding succeeds");
398        let compact_run_bytes_with_catalog = serde_json::to_string(&encoded_with_catalog)
399            .expect("compact encoding serializes")
400            .len();
401        assert_eq!(
402            shifty_engine::expand(&encoded_with_catalog).expect("compact run expands"),
403            run,
404            "{name}: compact run with catalog is not lossless"
405        );
406        let encoded_no_catalog =
407            shifty_engine::compact(&run, false).expect("compact encoding succeeds");
408        let compact_run_bytes_no_catalog = serde_json::to_string(&encoded_no_catalog)
409            .expect("catalog-free compact encoding serializes")
410            .len();
411        let catalog = serde_json::to_value(&run.constraints).expect("catalog serializes");
412        assert_eq!(
413            shifty_engine::expand_with_catalog(&encoded_no_catalog, catalog)
414                .expect("catalog-free compact run expands"),
415            run,
416            "{name}: compact run without catalog is not lossless"
417        );
418        // Sharing is reported over the evidence alone. The node and term tables
419        // of `encoded` also hold catalog entries, which would understate the
420        // redundancy by adding distinct entries with no evidence occurrences.
421        let sharing = shifty_engine::sharing(&run).expect("sharing measures");
422        let results = shifty_engine::result_sharing(&run);
423
424        // The two passes reach the same judgment nodes by routes that share no
425        // code: `sharing` interns serialized JSON, `result_sharing` walks the
426        // typed evidence. Agreement on both counts is what makes either number
427        // reportable; a mismatch means the node families are misclassified or a
428        // traversal is missing a variant.
429        assert_eq!(
430            sharing.result_occurrences + sharing.support_occurrences,
431            sharing.node_occurrences,
432            "{name}: node families do not partition occurrences"
433        );
434        assert_eq!(
435            sharing.distinct_results + sharing.distinct_support,
436            sharing.distinct_nodes,
437            "{name}: node families do not partition the table"
438        );
439        assert_eq!(
440            sharing.result_occurrences, evidence_nodes,
441            "{name}: interned judgments disagree with the typed walk"
442        );
443        assert_eq!(
444            results.occurrences, evidence_nodes,
445            "{name}: typed sharing pass disagrees with the typed walk"
446        );
447        assert_eq!(
448            results.distinct_payloads, sharing.distinct_results,
449            "{name}: distinct judgments disagree between the two passes"
450        );
451        // Structural equality implies key equality, never the reverse.
452        assert!(
453            results.distinct_keys <= results.distinct_payloads,
454            "{name}: more keys than payloads"
455        );
456        // Requests are the normalized selection the conformance pass counts;
457        // authored records are the source-preserving view over them.
458        assert_eq!(
459            results.normalized_requests, conformance.selected_pairs,
460            "{name}: requests disagree with the conformance selection"
461        );
462        assert_eq!(
463            results.authored_records, authored_pairs,
464            "{name}: authored records disagree with the run"
465        );
466
467        // Serialization is measured separately so its allocation traffic does
468        // not perturb the next validation arm.
469        let mut serialize_samples = Vec::with_capacity(iters);
470        for _ in 0..iters {
471            let start = Instant::now();
472            let json = run.to_json().expect("evidence run serializes");
473            serialize_samples.push(start.elapsed());
474            std::hint::black_box(json.len());
475        }
476
477        // Each ratio compares the corresponding raw samples from one rotated
478        // A/B/C round. The CSV retains those samples for independent checking.
479        let full_ratios: Vec<f64> = conformance_samples
480            .iter()
481            .zip(evidence_samples.iter())
482            .map(|(conformance, evidence)| {
483                evidence.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
484            })
485            .collect();
486        let on_demand_ratios: Vec<f64> = conformance_samples
487            .iter()
488            .zip(on_demand_samples.iter())
489            .map(|(conformance, on_demand)| {
490                on_demand.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
491            })
492            .collect();
493
494        let conformance_ms = summarize_durations(&conformance_samples);
495        let evidence_ms = summarize_durations(&evidence_samples);
496        let discovery_ms = summarize_durations(&discovery_samples);
497        let explanation_ms = summarize_durations(&explanation_samples);
498        let on_demand_ms = summarize_durations(&on_demand_samples);
499        let serialize_ms = summarize_durations(&serialize_samples);
500        let full_ratio = summarize_values(&full_ratios);
501        let on_demand_ratio = summarize_values(&on_demand_ratios);
502        let evaluated_pairs = conformance.selected_pairs.max(1) as f64;
503        let authored_pairs_denominator = authored_pairs.max(1) as f64;
504
505        println!(
506            "{suite},{name},{data_triples},{iters},\
507{c_med:.4},{c_mad:.4},{e_med:.4},{e_mad:.4},{full_ratio:.4},{full_ratio_mad:.4},\
508{find_med:.4},{find_mad:.4},{explain_med:.4},{explain_mad:.4},\
509{ondemand_med:.4},{ondemand_mad:.4},{ondemand_ratio:.4},{ondemand_ratio_mad:.4},\
510{serialize_med:.4},{serialize_mad:.4},\
511{conforms_c},{conforms_e},{conforms_o},\
512{pairs_eval},{pass_pairs},{fail_pairs},{fail_fraction:.6},\
513{authored_pairs},{authored_pass},{authored_fail},\
514{statements},\
515{evidence_nodes},{nodes_per_authored_pair:.3},\
516{evidence_bytes},{bytes_per_authored_pair:.1},\
517{full_with_catalog},{full_no_catalog},{compact_with_catalog},{compact_no_catalog},\
518{node_occurrences},{distinct_nodes},{node_redundancy:.3},\
519{result_occurrences},{distinct_results},{result_redundancy:.3},\
520{support_occurrences},{distinct_support},{support_redundancy:.3},{support_share:.4},\
521{term_occurrences},{distinct_terms},{term_redundancy:.3},\
522{normalized_requests},{duplicate_records},{divergent_duplicates},\
523{canonical_occurrences},{shared_payloads},{shared_payload_fraction:.4},\
524{shared_canonical_occurrences},{shared_occurrence_fraction:.4},\
525{request_reaches},{requests_per_payload:.3},{max_payload_requests},\
526{distinct_keys},{key_redundancy:.3},{multi_occurrence_keys},\
527{divergent_keys},{divergence_fraction:.4},{divergent_occurrences},\
528{distinct_payloads_per_key},{keys_over_payload_cap},{both_polarity_addresses},\
529{conformance_raw},{evidence_raw},{discovery_raw},{explanation_raw},{ondemand_raw}",
530            suite = args.suite,
531            node_occurrences = sharing.node_occurrences,
532            distinct_nodes = sharing.distinct_nodes,
533            node_redundancy = sharing.node_redundancy(),
534            result_occurrences = sharing.result_occurrences,
535            distinct_results = sharing.distinct_results,
536            result_redundancy = sharing.result_redundancy(),
537            support_occurrences = sharing.support_occurrences,
538            distinct_support = sharing.distinct_support,
539            support_redundancy = sharing.support_redundancy(),
540            support_share = sharing.support_share(),
541            term_occurrences = sharing.term_occurrences,
542            distinct_terms = sharing.distinct_terms,
543            term_redundancy = sharing.term_redundancy(),
544            normalized_requests = results.normalized_requests,
545            duplicate_records = results.duplicate_records,
546            divergent_duplicates = results.divergent_duplicates,
547            canonical_occurrences = results.canonical_occurrences,
548            shared_payloads = results.shared_payloads,
549            shared_payload_fraction = results.shared_payload_fraction(),
550            shared_canonical_occurrences = results.shared_canonical_occurrences,
551            shared_occurrence_fraction = results.shared_occurrence_fraction(),
552            request_reaches = results.request_reaches,
553            requests_per_payload = results.requests_per_payload(),
554            max_payload_requests = results.max_payload_requests,
555            distinct_keys = results.distinct_keys,
556            key_redundancy = results.key_redundancy(),
557            multi_occurrence_keys = results.multi_occurrence_keys,
558            divergent_keys = results.divergent_keys,
559            divergence_fraction = results.divergence_fraction(),
560            divergent_occurrences = results.divergent_occurrences,
561            distinct_payloads_per_key = results.distinct_payloads_per_key,
562            keys_over_payload_cap = results.keys_over_payload_cap,
563            both_polarity_addresses = results.both_polarity_addresses,
564            c_med = conformance_ms.median,
565            c_mad = conformance_ms.mad,
566            e_med = evidence_ms.median,
567            e_mad = evidence_ms.mad,
568            full_ratio = full_ratio.median,
569            full_ratio_mad = full_ratio.mad,
570            find_med = discovery_ms.median,
571            find_mad = discovery_ms.mad,
572            explain_med = explanation_ms.median,
573            explain_mad = explanation_ms.mad,
574            ondemand_med = on_demand_ms.median,
575            ondemand_mad = on_demand_ms.mad,
576            ondemand_ratio = on_demand_ratio.median,
577            ondemand_ratio_mad = on_demand_ratio.mad,
578            serialize_med = serialize_ms.median,
579            serialize_mad = serialize_ms.mad,
580            conforms_c = conformance.conforms,
581            conforms_e = run.conforms,
582            conforms_o = on_demand_conformance.conforms,
583            pairs_eval = conformance.selected_pairs,
584            pass_pairs = conformance.passed,
585            fail_pairs = conformance.failed,
586            fail_fraction = conformance.failed as f64 / evaluated_pairs,
587            authored_pass = authored_pairs - authored_fail_pairs,
588            authored_fail = authored_fail_pairs,
589            statements = run.statements.len(),
590            nodes_per_authored_pair = evidence_nodes as f64 / authored_pairs_denominator,
591            bytes_per_authored_pair = evidence_bytes as f64 / authored_pairs_denominator,
592            full_with_catalog = full_run_bytes_with_catalog,
593            full_no_catalog = full_run_bytes_no_catalog,
594            compact_with_catalog = compact_run_bytes_with_catalog,
595            compact_no_catalog = compact_run_bytes_no_catalog,
596            conformance_raw = samples_string(&conformance_samples),
597            evidence_raw = samples_string(&evidence_samples),
598            discovery_raw = samples_string(&discovery_samples),
599            explanation_raw = samples_string(&explanation_samples),
600            ondemand_raw = samples_string(&on_demand_samples),
601        );
602
603        eprintln!(
604            "median: {:.1}ms conformance, {:.1}ms full ({:.2}x), {:.1}ms on-demand ({:.2}x); {} failures / {} pairs; {iters} rounds",
605            conformance_ms.median,
606            evidence_ms.median,
607            full_ratio.median,
608            on_demand_ms.median,
609            on_demand_ratio.median,
610            conformance.failed,
611            conformance.selected_pairs,
612        );
613    }
614}
Source

pub fn support_share(&self) -> f64

Share of tagged-node occurrences that are path support rather than validation judgments.

Examples found in repository?
examples/bench_evidence.rs (line 540)
132fn main() {
133    let args = parse_args();
134
135    eprintln!("loading shapes {}…", args.shapes.display());
136    let shapes_bytes = fs::read(&args.shapes).expect("cannot read --shapes");
137    let shapes = shifty_parse::load_turtle(&shapes_bytes, None).expect("cannot parse --shapes");
138    let compiled = CompiledShapes::compile(shapes).expect("cannot compile shapes");
139    drop(shapes_bytes);
140
141    if args.header {
142        println!("{COLUMNS}");
143    }
144
145    for model in models_in(&args.models) {
146        let name = model
147            .file_name()
148            .and_then(|name| name.to_str())
149            .unwrap_or("?")
150            .to_string();
151        eprint!("{name}… ");
152
153        // ---- untimed setup: everything common to all workflows --------------
154        let data_bytes = fs::read(&model).expect("cannot read model");
155        let data = shifty_parse::load_turtle(&data_bytes, None).expect("cannot parse model");
156        drop(data_bytes);
157        let data_graph = if args.no_infer {
158            data.graph.clone()
159        } else {
160            compiled
161                .session(
162                    SessionData::Separate(data.graph.clone()),
163                    SessionOptions {
164                        inference: true,
165                        ..SessionOptions::default()
166                    },
167                )
168                .unwrap_or_else(|error| panic!("{name}: inference failed: {error}"))
169                .data()
170                .clone()
171        };
172        let data_triples = data_graph.len();
173
174        let prepared = PreparedEvidenceValidator::with_graphs(
175            &data_graph,
176            &compiled.source().graph,
177            compiled.authored_schema(),
178            ValidationGraphMode::Union,
179        )
180        .unwrap_or_else(|error| panic!("{name}: schema is non-stratifiable: {error}"));
181        let options = ValidationOptions::default();
182        let scan_options = ConformanceOptions::default();
183
184        // ---- timed: validation only -----------------------------------------
185        // One warm-up round, discarded: it faults in the caches all arms share.
186        // Its duration is only consulted when `--budget-ms` is in play.
187        let probe = Instant::now();
188        let _ = prepared.validate_conformance(&scan_options);
189        let _ = prepared.validate_canonical(&options);
190        let (_, warm_failures) = prepared.find_failures(&scan_options);
191        for pair in &warm_failures {
192            std::hint::black_box(prepared.explain_canonical(pair));
193        }
194        let probe_ms = probe.elapsed().as_secs_f64() * 1e3;
195        // Every model gets the same iteration count. `--budget-ms` only caps the
196        // count for a corpus slow enough that a fixed count is impractical; it
197        // is off by default, and the emitted `iters` column records what was used.
198        let iters = if args.budget_ms > 0.0 {
199            ((args.budget_ms / probe_ms.max(f64::MIN_POSITIVE)) as usize)
200                .clamp(args.min_iters.min(args.iters), args.iters)
201        } else {
202            args.iters
203        };
204
205        let mut conformance_samples = Vec::with_capacity(iters);
206        let mut evidence_samples = Vec::with_capacity(iters);
207        let mut discovery_samples = Vec::with_capacity(iters);
208        let mut explanation_samples = Vec::with_capacity(iters);
209        let mut on_demand_samples = Vec::with_capacity(iters);
210        let mut last_run = None;
211        let mut last_conformance = None;
212        let mut last_on_demand_conformance = None;
213        let mut last_failures = Vec::new();
214        let mut last_explanations = Vec::new();
215
216        // Rotate A/B/C so no arm systematically inherits the cache, allocator,
217        // scheduler, or thermal state left by another arm.
218        for iteration in 0..iters {
219            for arm in ARM_ORDERS[iteration % ARM_ORDERS.len()] {
220                match arm {
221                    Arm::Conformance => {
222                        let start = Instant::now();
223                        let conformance_run = prepared.validate_conformance(&scan_options);
224                        conformance_samples.push(start.elapsed());
225                        std::hint::black_box(conformance_run.selected_pairs);
226                        last_conformance = Some(conformance_run);
227                    }
228                    Arm::FullEvidence => {
229                        let start = Instant::now();
230                        let run = prepared.validate_canonical(&options);
231                        evidence_samples.push(start.elapsed());
232                        std::hint::black_box(run.statements.len());
233                        last_run = Some(run);
234                    }
235                    Arm::OnDemand => {
236                        let start = Instant::now();
237                        let (on_demand_conformance, failures) =
238                            prepared.find_failures(&scan_options);
239                        let discovery = start.elapsed();
240
241                        let start = Instant::now();
242                        let explanations: Vec<_> = failures
243                            .iter()
244                            .flat_map(|pair| prepared.explain_canonical(pair))
245                            .collect();
246                        let explanation = start.elapsed();
247
248                        discovery_samples.push(discovery);
249                        explanation_samples.push(explanation);
250                        on_demand_samples.push(discovery + explanation);
251                        std::hint::black_box(explanations.len());
252                        last_on_demand_conformance = Some(on_demand_conformance);
253                        last_failures = failures;
254                        last_explanations = explanations;
255                    }
256                }
257            }
258        }
259
260        // ---- untimed: shape of the evidence produced -------------------------
261        // Reuse the final timed results rather than paying for another run.
262        let conformance = last_conformance.expect("at least one iteration runs");
263        let on_demand_conformance =
264            last_on_demand_conformance.expect("at least one iteration runs");
265        let run = last_run.expect("at least one iteration runs");
266        let authored_pairs: usize = run
267            .statements
268            .iter()
269            .map(|statement| statement.selected_foci.len())
270            .sum();
271        let authored_fail_pairs: usize = run
272            .statements
273            .iter()
274            .flat_map(|statement| &statement.selected_foci)
275            .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
276            .count();
277
278        // The three interfaces must describe the same normalized selection and
279        // polarity partition. Compare exact failed identities, not just the
280        // report-wide conformance bit.
281        assert_eq!(
282            conformance, on_demand_conformance,
283            "{name}: on-demand counts"
284        );
285        assert_eq!(
286            conformance.conforms, run.conforms,
287            "{name}: conformance bit"
288        );
289        let full_selected: HashSet<_> = run
290            .statements
291            .iter()
292            .flat_map(|statement| {
293                statement.selected_foci.iter().filter_map(|focus| {
294                    statement
295                        .normalized_statement_id
296                        .map(|id| (id, focus.focus.clone()))
297                })
298            })
299            .collect();
300        let full_failures: HashSet<_> = run
301            .statements
302            .iter()
303            .flat_map(|statement| {
304                statement
305                    .selected_foci
306                    .iter()
307                    .filter(|focus| focus.status() == shifty_engine::EvaluationStatus::Fail)
308                    .filter_map(|focus| {
309                        statement
310                            .normalized_statement_id
311                            .map(|id| (id, focus.focus.clone()))
312                    })
313            })
314            .collect();
315        let discovered_failures: HashSet<_> = last_failures
316            .iter()
317            .map(|pair| (pair.normalized_statement(), pair.focus().clone()))
318            .collect();
319        assert_eq!(
320            full_selected.len(),
321            conformance.selected_pairs,
322            "{name}: selected pairs"
323        );
324        assert_eq!(
325            full_failures.len(),
326            conformance.failed,
327            "{name}: failed pairs"
328        );
329        assert_eq!(
330            full_selected.len() - full_failures.len(),
331            conformance.passed,
332            "{name}: passed pairs"
333        );
334        assert_eq!(
335            full_failures, discovered_failures,
336            "{name}: failed identities"
337        );
338
339        let full_by_authored: HashMap<_, _> = run
340            .statements
341            .iter()
342            .flat_map(|statement| {
343                statement.selected_foci.iter().map(|focus| {
344                    (
345                        (statement.source_statement_id, focus.focus.clone()),
346                        &focus.evidence,
347                    )
348                })
349            })
350            .collect();
351        let mut explained_foci = 0;
352        for statement in &last_explanations {
353            for focus in &statement.selected_foci {
354                explained_foci += 1;
355                assert_eq!(
356                    focus.status(),
357                    shifty_engine::EvaluationStatus::Fail,
358                    "{name}: on-demand explanation polarity"
359                );
360                let key = (statement.source_statement_id, focus.focus.clone());
361                assert_eq!(
362                    &focus.evidence,
363                    *full_by_authored
364                        .get(&key)
365                        .unwrap_or_else(|| panic!("{name}: unexplained full-run pair {key:?}")),
366                    "{name}: full and on-demand evidence differ for {key:?}"
367                );
368            }
369        }
370        assert_eq!(
371            explained_foci, authored_fail_pairs,
372            "{name}: authored explanations"
373        );
374
375        let evidence_nodes = run.walk().len();
376        let evidence_bytes: usize = run
377            .statements
378            .iter()
379            .flat_map(|statement| &statement.selected_foci)
380            .map(|focus| focus.evidence.to_json().expect("evidence serializes").len())
381            .sum();
382        let full_json = run.to_json().expect("evidence run serializes");
383        let full_run_bytes_with_catalog = full_json.len();
384        let mut full_value_no_catalog =
385            serde_json::to_value(&run).expect("evidence run converts to JSON");
386        full_value_no_catalog
387            .as_object_mut()
388            .expect("evidence run is a JSON object")
389            .remove("constraints");
390        let full_run_bytes_no_catalog = serde_json::to_string(&full_value_no_catalog)
391            .expect("catalog-free evidence run serializes")
392            .len();
393
394        // Compare complete runs with the same catalog policy on each side, and
395        // verify both encodings reconstruct the exact typed result.
396        let encoded_with_catalog =
397            shifty_engine::compact(&run, true).expect("compact encoding succeeds");
398        let compact_run_bytes_with_catalog = serde_json::to_string(&encoded_with_catalog)
399            .expect("compact encoding serializes")
400            .len();
401        assert_eq!(
402            shifty_engine::expand(&encoded_with_catalog).expect("compact run expands"),
403            run,
404            "{name}: compact run with catalog is not lossless"
405        );
406        let encoded_no_catalog =
407            shifty_engine::compact(&run, false).expect("compact encoding succeeds");
408        let compact_run_bytes_no_catalog = serde_json::to_string(&encoded_no_catalog)
409            .expect("catalog-free compact encoding serializes")
410            .len();
411        let catalog = serde_json::to_value(&run.constraints).expect("catalog serializes");
412        assert_eq!(
413            shifty_engine::expand_with_catalog(&encoded_no_catalog, catalog)
414                .expect("catalog-free compact run expands"),
415            run,
416            "{name}: compact run without catalog is not lossless"
417        );
418        // Sharing is reported over the evidence alone. The node and term tables
419        // of `encoded` also hold catalog entries, which would understate the
420        // redundancy by adding distinct entries with no evidence occurrences.
421        let sharing = shifty_engine::sharing(&run).expect("sharing measures");
422        let results = shifty_engine::result_sharing(&run);
423
424        // The two passes reach the same judgment nodes by routes that share no
425        // code: `sharing` interns serialized JSON, `result_sharing` walks the
426        // typed evidence. Agreement on both counts is what makes either number
427        // reportable; a mismatch means the node families are misclassified or a
428        // traversal is missing a variant.
429        assert_eq!(
430            sharing.result_occurrences + sharing.support_occurrences,
431            sharing.node_occurrences,
432            "{name}: node families do not partition occurrences"
433        );
434        assert_eq!(
435            sharing.distinct_results + sharing.distinct_support,
436            sharing.distinct_nodes,
437            "{name}: node families do not partition the table"
438        );
439        assert_eq!(
440            sharing.result_occurrences, evidence_nodes,
441            "{name}: interned judgments disagree with the typed walk"
442        );
443        assert_eq!(
444            results.occurrences, evidence_nodes,
445            "{name}: typed sharing pass disagrees with the typed walk"
446        );
447        assert_eq!(
448            results.distinct_payloads, sharing.distinct_results,
449            "{name}: distinct judgments disagree between the two passes"
450        );
451        // Structural equality implies key equality, never the reverse.
452        assert!(
453            results.distinct_keys <= results.distinct_payloads,
454            "{name}: more keys than payloads"
455        );
456        // Requests are the normalized selection the conformance pass counts;
457        // authored records are the source-preserving view over them.
458        assert_eq!(
459            results.normalized_requests, conformance.selected_pairs,
460            "{name}: requests disagree with the conformance selection"
461        );
462        assert_eq!(
463            results.authored_records, authored_pairs,
464            "{name}: authored records disagree with the run"
465        );
466
467        // Serialization is measured separately so its allocation traffic does
468        // not perturb the next validation arm.
469        let mut serialize_samples = Vec::with_capacity(iters);
470        for _ in 0..iters {
471            let start = Instant::now();
472            let json = run.to_json().expect("evidence run serializes");
473            serialize_samples.push(start.elapsed());
474            std::hint::black_box(json.len());
475        }
476
477        // Each ratio compares the corresponding raw samples from one rotated
478        // A/B/C round. The CSV retains those samples for independent checking.
479        let full_ratios: Vec<f64> = conformance_samples
480            .iter()
481            .zip(evidence_samples.iter())
482            .map(|(conformance, evidence)| {
483                evidence.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
484            })
485            .collect();
486        let on_demand_ratios: Vec<f64> = conformance_samples
487            .iter()
488            .zip(on_demand_samples.iter())
489            .map(|(conformance, on_demand)| {
490                on_demand.as_secs_f64() / conformance.as_secs_f64().max(f64::MIN_POSITIVE)
491            })
492            .collect();
493
494        let conformance_ms = summarize_durations(&conformance_samples);
495        let evidence_ms = summarize_durations(&evidence_samples);
496        let discovery_ms = summarize_durations(&discovery_samples);
497        let explanation_ms = summarize_durations(&explanation_samples);
498        let on_demand_ms = summarize_durations(&on_demand_samples);
499        let serialize_ms = summarize_durations(&serialize_samples);
500        let full_ratio = summarize_values(&full_ratios);
501        let on_demand_ratio = summarize_values(&on_demand_ratios);
502        let evaluated_pairs = conformance.selected_pairs.max(1) as f64;
503        let authored_pairs_denominator = authored_pairs.max(1) as f64;
504
505        println!(
506            "{suite},{name},{data_triples},{iters},\
507{c_med:.4},{c_mad:.4},{e_med:.4},{e_mad:.4},{full_ratio:.4},{full_ratio_mad:.4},\
508{find_med:.4},{find_mad:.4},{explain_med:.4},{explain_mad:.4},\
509{ondemand_med:.4},{ondemand_mad:.4},{ondemand_ratio:.4},{ondemand_ratio_mad:.4},\
510{serialize_med:.4},{serialize_mad:.4},\
511{conforms_c},{conforms_e},{conforms_o},\
512{pairs_eval},{pass_pairs},{fail_pairs},{fail_fraction:.6},\
513{authored_pairs},{authored_pass},{authored_fail},\
514{statements},\
515{evidence_nodes},{nodes_per_authored_pair:.3},\
516{evidence_bytes},{bytes_per_authored_pair:.1},\
517{full_with_catalog},{full_no_catalog},{compact_with_catalog},{compact_no_catalog},\
518{node_occurrences},{distinct_nodes},{node_redundancy:.3},\
519{result_occurrences},{distinct_results},{result_redundancy:.3},\
520{support_occurrences},{distinct_support},{support_redundancy:.3},{support_share:.4},\
521{term_occurrences},{distinct_terms},{term_redundancy:.3},\
522{normalized_requests},{duplicate_records},{divergent_duplicates},\
523{canonical_occurrences},{shared_payloads},{shared_payload_fraction:.4},\
524{shared_canonical_occurrences},{shared_occurrence_fraction:.4},\
525{request_reaches},{requests_per_payload:.3},{max_payload_requests},\
526{distinct_keys},{key_redundancy:.3},{multi_occurrence_keys},\
527{divergent_keys},{divergence_fraction:.4},{divergent_occurrences},\
528{distinct_payloads_per_key},{keys_over_payload_cap},{both_polarity_addresses},\
529{conformance_raw},{evidence_raw},{discovery_raw},{explanation_raw},{ondemand_raw}",
530            suite = args.suite,
531            node_occurrences = sharing.node_occurrences,
532            distinct_nodes = sharing.distinct_nodes,
533            node_redundancy = sharing.node_redundancy(),
534            result_occurrences = sharing.result_occurrences,
535            distinct_results = sharing.distinct_results,
536            result_redundancy = sharing.result_redundancy(),
537            support_occurrences = sharing.support_occurrences,
538            distinct_support = sharing.distinct_support,
539            support_redundancy = sharing.support_redundancy(),
540            support_share = sharing.support_share(),
541            term_occurrences = sharing.term_occurrences,
542            distinct_terms = sharing.distinct_terms,
543            term_redundancy = sharing.term_redundancy(),
544            normalized_requests = results.normalized_requests,
545            duplicate_records = results.duplicate_records,
546            divergent_duplicates = results.divergent_duplicates,
547            canonical_occurrences = results.canonical_occurrences,
548            shared_payloads = results.shared_payloads,
549            shared_payload_fraction = results.shared_payload_fraction(),
550            shared_canonical_occurrences = results.shared_canonical_occurrences,
551            shared_occurrence_fraction = results.shared_occurrence_fraction(),
552            request_reaches = results.request_reaches,
553            requests_per_payload = results.requests_per_payload(),
554            max_payload_requests = results.max_payload_requests,
555            distinct_keys = results.distinct_keys,
556            key_redundancy = results.key_redundancy(),
557            multi_occurrence_keys = results.multi_occurrence_keys,
558            divergent_keys = results.divergent_keys,
559            divergence_fraction = results.divergence_fraction(),
560            divergent_occurrences = results.divergent_occurrences,
561            distinct_payloads_per_key = results.distinct_payloads_per_key,
562            keys_over_payload_cap = results.keys_over_payload_cap,
563            both_polarity_addresses = results.both_polarity_addresses,
564            c_med = conformance_ms.median,
565            c_mad = conformance_ms.mad,
566            e_med = evidence_ms.median,
567            e_mad = evidence_ms.mad,
568            full_ratio = full_ratio.median,
569            full_ratio_mad = full_ratio.mad,
570            find_med = discovery_ms.median,
571            find_mad = discovery_ms.mad,
572            explain_med = explanation_ms.median,
573            explain_mad = explanation_ms.mad,
574            ondemand_med = on_demand_ms.median,
575            ondemand_mad = on_demand_ms.mad,
576            ondemand_ratio = on_demand_ratio.median,
577            ondemand_ratio_mad = on_demand_ratio.mad,
578            serialize_med = serialize_ms.median,
579            serialize_mad = serialize_ms.mad,
580            conforms_c = conformance.conforms,
581            conforms_e = run.conforms,
582            conforms_o = on_demand_conformance.conforms,
583            pairs_eval = conformance.selected_pairs,
584            pass_pairs = conformance.passed,
585            fail_pairs = conformance.failed,
586            fail_fraction = conformance.failed as f64 / evaluated_pairs,
587            authored_pass = authored_pairs - authored_fail_pairs,
588            authored_fail = authored_fail_pairs,
589            statements = run.statements.len(),
590            nodes_per_authored_pair = evidence_nodes as f64 / authored_pairs_denominator,
591            bytes_per_authored_pair = evidence_bytes as f64 / authored_pairs_denominator,
592            full_with_catalog = full_run_bytes_with_catalog,
593            full_no_catalog = full_run_bytes_no_catalog,
594            compact_with_catalog = compact_run_bytes_with_catalog,
595            compact_no_catalog = compact_run_bytes_no_catalog,
596            conformance_raw = samples_string(&conformance_samples),
597            evidence_raw = samples_string(&evidence_samples),
598            discovery_raw = samples_string(&discovery_samples),
599            explanation_raw = samples_string(&explanation_samples),
600            ondemand_raw = samples_string(&on_demand_samples),
601        );
602
603        eprintln!(
604            "median: {:.1}ms conformance, {:.1}ms full ({:.2}x), {:.1}ms on-demand ({:.2}x); {} failures / {} pairs; {iters} rounds",
605            conformance_ms.median,
606            evidence_ms.median,
607            full_ratio.median,
608            on_demand_ms.median,
609            on_demand_ratio.median,
610            conformance.failed,
611            conformance.selected_pairs,
612        );
613    }
614}

Trait Implementations§

Source§

impl Clone for Sharing

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Sharing

Source§

impl Debug for Sharing

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Sharing

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for Sharing

Source§

impl PartialEq for Sharing

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Sharing

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V