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

pub fn result_redundancy(&self) -> f64

Occurrences per distinct validation-judgment node.

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

pub fn support_redundancy(&self) -> f64

Occurrences per distinct path-support certificate.

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

Trait Implementations§

Source§

impl Clone for Sharing

Source§

fn clone(&self) -> Sharing

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() -> Sharing

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

impl Eq for Sharing

Source§

impl PartialEq for Sharing

Source§

fn eq(&self, other: &Sharing) -> 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<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, <T as TryFrom<U>>::Error>

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