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