1use std::cmp::Ordering;
8use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
9
10use omena_syntax::ident::AuthoredPropertyTextV0;
11use serde::Serialize;
12
13pub type NodeId = u32;
14
15pub const FALSE_NODE_ID_V0: NodeId = 0;
16pub const TRUE_NODE_ID_V0: NodeId = 1;
17pub const GUARDED_CASCADE_BOT_NODE_ID_V0: NodeId = FALSE_NODE_ID_V0;
18pub const DEFAULT_APPLY_CACHE_CAPACITY_V0: usize = 4_096;
19pub const DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0: u64 = 8_192;
20pub const SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0: &str = "siteFirstAppearance";
21pub const AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0: &str = "atRuleNestingDfs";
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum Node {
25 Term(u32),
26 Int { var: u16, lo: NodeId, hi: NodeId },
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum BooleanOperationV0 {
31 And,
32 Or,
33 Xor,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum VariableOrderDomainV0 {
38 SiteFirstAppearance,
39 AtRuleNestingDfs,
40}
41
42impl VariableOrderDomainV0 {
43 pub const fn name(self) -> &'static str {
44 match self {
45 Self::SiteFirstAppearance => SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0,
46 Self::AtRuleNestingDfs => AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0,
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52#[serde(rename_all = "camelCase")]
53pub struct AtRuleNestingOrderAtomV0 {
54 atom: String,
55 at_rule_path: Vec<u32>,
56}
57
58impl AtRuleNestingOrderAtomV0 {
59 pub fn new(atom: impl Into<String>, at_rule_path: impl IntoIterator<Item = u32>) -> Self {
60 Self {
61 atom: atom.into(),
62 at_rule_path: at_rule_path.into_iter().collect(),
63 }
64 }
65
66 pub fn atom(&self) -> &str {
67 self.atom.as_str()
68 }
69
70 pub fn at_rule_path(&self) -> &[u32] {
71 self.at_rule_path.as_slice()
72 }
73}
74
75pub fn at_rule_nesting_dfs_paths_v0(
80 contexts: &[Vec<String>],
81) -> Result<Vec<Vec<Vec<u32>>>, FirstWitnessErrorV0> {
82 let mut child_ordinals = BTreeMap::<Vec<String>, BTreeMap<String, u32>>::new();
83 contexts
84 .iter()
85 .map(|context| {
86 let mut prefix = Vec::<String>::new();
87 let mut path = Vec::<u32>::new();
88 context
89 .iter()
90 .map(|atom| {
91 let siblings = child_ordinals.entry(prefix.clone()).or_default();
92 let ordinal = if let Some(ordinal) = siblings.get(atom) {
93 *ordinal
94 } else {
95 let ordinal = u32::try_from(siblings.len())
96 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
97 siblings.insert(atom.clone(), ordinal);
98 ordinal
99 };
100 prefix.push(atom.clone());
101 path.push(ordinal);
102 Ok(path.clone())
103 })
104 .collect()
105 })
106 .collect()
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub enum GuardedCascadeSpecificityExactnessV0 {
112 Exact,
113 Inexact,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub enum GuardedCascadeConditionKindV0 {
119 Media,
120 Supports,
121 Container,
122 StructuralPseudo,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct GuardedCascadeConditionAtomV0 {
128 atom: String,
129 kind: GuardedCascadeConditionKindV0,
130 at_rule_path: Vec<u32>,
131 numeric: bool,
132}
133
134impl GuardedCascadeConditionAtomV0 {
135 pub fn media(
136 atom: impl Into<String>,
137 at_rule_path: impl IntoIterator<Item = u32>,
138 numeric: bool,
139 ) -> Self {
140 Self {
141 atom: atom.into(),
142 kind: GuardedCascadeConditionKindV0::Media,
143 at_rule_path: at_rule_path.into_iter().collect(),
144 numeric,
145 }
146 }
147
148 pub fn supports(
149 atom: impl Into<String>,
150 at_rule_path: impl IntoIterator<Item = u32>,
151 numeric: bool,
152 ) -> Self {
153 Self {
154 atom: atom.into(),
155 kind: GuardedCascadeConditionKindV0::Supports,
156 at_rule_path: at_rule_path.into_iter().collect(),
157 numeric,
158 }
159 }
160
161 pub fn container(atom: impl Into<String>, at_rule_path: impl IntoIterator<Item = u32>) -> Self {
162 Self {
163 atom: atom.into(),
164 kind: GuardedCascadeConditionKindV0::Container,
165 at_rule_path: at_rule_path.into_iter().collect(),
166 numeric: false,
167 }
168 }
169
170 pub fn structural_pseudo(atom: impl Into<String>) -> Self {
171 Self {
172 atom: atom.into(),
173 kind: GuardedCascadeConditionKindV0::StructuralPseudo,
174 at_rule_path: Vec::new(),
175 numeric: false,
176 }
177 }
178
179 pub fn atom(&self) -> &str {
180 self.atom.as_str()
181 }
182
183 pub const fn kind(&self) -> GuardedCascadeConditionKindV0 {
184 self.kind
185 }
186
187 pub fn at_rule_path(&self) -> &[u32] {
188 self.at_rule_path.as_slice()
189 }
190
191 pub const fn is_numeric(&self) -> bool {
192 self.numeric
193 }
194}
195
196#[derive(Debug, Clone, Serialize)]
197#[serde(rename_all = "camelCase")]
198pub struct GuardedCascadeCandidateV0<K> {
199 declaration_id: u32,
200 element_signature: String,
201 property: AuthoredPropertyTextV0,
202 cascade_key: K,
203 specificity_exactness: GuardedCascadeSpecificityExactnessV0,
204 scope_proximity: u32,
205 conditions: Vec<GuardedCascadeConditionAtomV0>,
206}
207
208impl<K> GuardedCascadeCandidateV0<K> {
209 #[allow(clippy::too_many_arguments)]
210 pub fn new(
211 declaration_id: u32,
212 element_signature: impl Into<String>,
213 property: AuthoredPropertyTextV0,
214 cascade_key: K,
215 specificity_exactness: GuardedCascadeSpecificityExactnessV0,
216 scope_proximity: u32,
217 conditions: Vec<GuardedCascadeConditionAtomV0>,
218 ) -> Self {
219 Self {
220 declaration_id,
221 element_signature: element_signature.into(),
222 property,
223 cascade_key,
224 specificity_exactness,
225 scope_proximity,
226 conditions,
227 }
228 }
229
230 pub const fn declaration_id(&self) -> u32 {
231 self.declaration_id
232 }
233
234 pub fn element_signature(&self) -> &str {
235 self.element_signature.as_str()
236 }
237
238 pub const fn property(&self) -> &AuthoredPropertyTextV0 {
239 &self.property
240 }
241
242 pub const fn cascade_key(&self) -> &K {
243 &self.cascade_key
244 }
245
246 pub const fn specificity_exactness(&self) -> GuardedCascadeSpecificityExactnessV0 {
247 self.specificity_exactness
248 }
249
250 pub const fn scope_proximity(&self) -> u32 {
251 self.scope_proximity
252 }
253
254 pub fn conditions(&self) -> &[GuardedCascadeConditionAtomV0] {
255 self.conditions.as_slice()
256 }
257}
258
259impl<K: PartialEq> PartialEq for GuardedCascadeCandidateV0<K> {
260 fn eq(&self, other: &Self) -> bool {
261 self.declaration_id == other.declaration_id
262 && self.element_signature == other.element_signature
263 && self
264 .property
265 .to_property_name()
266 .same_as(&other.property.to_property_name())
267 && self.cascade_key == other.cascade_key
268 && self.specificity_exactness == other.specificity_exactness
269 && self.scope_proximity == other.scope_proximity
270 && self.conditions == other.conditions
271 }
272}
273
274impl<K: Eq> Eq for GuardedCascadeCandidateV0<K> {}
275
276#[derive(Debug, Clone, Serialize)]
277#[serde(
278 tag = "reason",
279 rename_all = "camelCase",
280 rename_all_fields = "camelCase"
281)]
282pub enum GuardedCascadeFragmentRefusalV0 {
283 EmptyCandidateSet,
284 InexactSpecificity {
285 declaration_id: u32,
286 },
287 ScopeProximityPresent {
288 declaration_id: u32,
289 scope_proximity: u32,
290 },
291 ContainerCondition {
292 declaration_id: u32,
293 atom: String,
294 },
295 StructuralPseudoCondition {
296 declaration_id: u32,
297 atom: String,
298 },
299 NumericConditionOutsideAlphabet {
300 declaration_id: u32,
301 atom: String,
302 },
303 ConditionOutsideDeclaredAlphabet {
304 declaration_id: u32,
305 atom: String,
306 },
307 MultipleProperties {
308 expected: AuthoredPropertyTextV0,
309 observed: AuthoredPropertyTextV0,
310 },
311 MultipleElementSignatures {
312 expected: String,
313 observed: String,
314 },
315 DuplicateDeclarationId {
316 declaration_id: u32,
317 },
318 NonUniqueCascadeKey {
319 first_declaration_id: u32,
320 second_declaration_id: u32,
321 },
322 ConditionAlphabetCapacityExceeded,
323}
324
325impl PartialEq for GuardedCascadeFragmentRefusalV0 {
326 fn eq(&self, other: &Self) -> bool {
327 use GuardedCascadeFragmentRefusalV0 as Refusal;
328 match (self, other) {
329 (Refusal::EmptyCandidateSet, Refusal::EmptyCandidateSet)
330 | (
331 Refusal::ConditionAlphabetCapacityExceeded,
332 Refusal::ConditionAlphabetCapacityExceeded,
333 ) => true,
334 (
335 Refusal::InexactSpecificity {
336 declaration_id: left,
337 },
338 Refusal::InexactSpecificity {
339 declaration_id: right,
340 },
341 )
342 | (
343 Refusal::DuplicateDeclarationId {
344 declaration_id: left,
345 },
346 Refusal::DuplicateDeclarationId {
347 declaration_id: right,
348 },
349 ) => left == right,
350 (
351 Refusal::ScopeProximityPresent {
352 declaration_id: left_id,
353 scope_proximity: left_scope,
354 },
355 Refusal::ScopeProximityPresent {
356 declaration_id: right_id,
357 scope_proximity: right_scope,
358 },
359 ) => left_id == right_id && left_scope == right_scope,
360 (
361 Refusal::ContainerCondition {
362 declaration_id: left_id,
363 atom: left_atom,
364 },
365 Refusal::ContainerCondition {
366 declaration_id: right_id,
367 atom: right_atom,
368 },
369 )
370 | (
371 Refusal::StructuralPseudoCondition {
372 declaration_id: left_id,
373 atom: left_atom,
374 },
375 Refusal::StructuralPseudoCondition {
376 declaration_id: right_id,
377 atom: right_atom,
378 },
379 )
380 | (
381 Refusal::NumericConditionOutsideAlphabet {
382 declaration_id: left_id,
383 atom: left_atom,
384 },
385 Refusal::NumericConditionOutsideAlphabet {
386 declaration_id: right_id,
387 atom: right_atom,
388 },
389 )
390 | (
391 Refusal::ConditionOutsideDeclaredAlphabet {
392 declaration_id: left_id,
393 atom: left_atom,
394 },
395 Refusal::ConditionOutsideDeclaredAlphabet {
396 declaration_id: right_id,
397 atom: right_atom,
398 },
399 ) => left_id == right_id && left_atom == right_atom,
400 (
401 Refusal::MultipleProperties {
402 expected: left_expected,
403 observed: left_observed,
404 },
405 Refusal::MultipleProperties {
406 expected: right_expected,
407 observed: right_observed,
408 },
409 ) => {
410 left_expected
411 .to_property_name()
412 .same_as(&right_expected.to_property_name())
413 && left_observed
414 .to_property_name()
415 .same_as(&right_observed.to_property_name())
416 }
417 (
418 Refusal::MultipleElementSignatures {
419 expected: left_expected,
420 observed: left_observed,
421 },
422 Refusal::MultipleElementSignatures {
423 expected: right_expected,
424 observed: right_observed,
425 },
426 ) => left_expected == right_expected && left_observed == right_observed,
427 (
428 Refusal::NonUniqueCascadeKey {
429 first_declaration_id: left_first,
430 second_declaration_id: left_second,
431 },
432 Refusal::NonUniqueCascadeKey {
433 first_declaration_id: right_first,
434 second_declaration_id: right_second,
435 },
436 ) => left_first == right_first && left_second == right_second,
437 _ => false,
438 }
439 }
440}
441
442impl Eq for GuardedCascadeFragmentRefusalV0 {}
443
444impl GuardedCascadeFragmentRefusalV0 {
445 pub const fn name(&self) -> &'static str {
446 match self {
447 Self::EmptyCandidateSet => "emptyCandidateSet",
448 Self::InexactSpecificity { .. } => "inexactSpecificity",
449 Self::ScopeProximityPresent { .. } => "scopeProximityPresent",
450 Self::ContainerCondition { .. } => "containerCondition",
451 Self::StructuralPseudoCondition { .. } => "structuralPseudoCondition",
452 Self::NumericConditionOutsideAlphabet { .. } => "numericConditionOutsideAlphabet",
453 Self::ConditionOutsideDeclaredAlphabet { .. } => "conditionOutsideDeclaredAlphabet",
454 Self::MultipleProperties { .. } => "multipleProperties",
455 Self::MultipleElementSignatures { .. } => "multipleElementSignatures",
456 Self::DuplicateDeclarationId { .. } => "duplicateDeclarationId",
457 Self::NonUniqueCascadeKey { .. } => "nonUniqueCascadeKey",
458 Self::ConditionAlphabetCapacityExceeded => "conditionAlphabetCapacityExceeded",
459 }
460 }
461}
462
463impl std::fmt::Display for GuardedCascadeFragmentRefusalV0 {
464 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465 write!(
466 formatter,
467 "guarded cascade fragment refused: {}",
468 self.name()
469 )
470 }
471}
472
473impl std::error::Error for GuardedCascadeFragmentRefusalV0 {}
474
475#[derive(Debug, Clone, Serialize)]
476#[serde(rename_all = "camelCase")]
477pub struct GuardedCascadeFragmentV0<K> {
478 element_signature: String,
479 property: AuthoredPropertyTextV0,
480 condition_alphabet: Vec<String>,
481 candidates: Vec<GuardedCascadeCandidateV0<K>>,
482}
483
484impl<K: Clone + Ord> GuardedCascadeFragmentV0<K> {
485 pub fn admit(
486 condition_alphabet: impl IntoIterator<Item = impl Into<String>>,
487 candidates: impl IntoIterator<Item = GuardedCascadeCandidateV0<K>>,
488 ) -> Result<Self, GuardedCascadeFragmentRefusalV0> {
489 let alphabet = condition_alphabet
490 .into_iter()
491 .map(Into::into)
492 .collect::<BTreeSet<String>>();
493 if alphabet.len() > usize::from(u16::MAX) + 1 {
494 return Err(GuardedCascadeFragmentRefusalV0::ConditionAlphabetCapacityExceeded);
495 }
496 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
497 let Some(first) = candidates.first() else {
498 return Err(GuardedCascadeFragmentRefusalV0::EmptyCandidateSet);
499 };
500 let element_signature = first.element_signature.clone();
501 let property = first.property.clone();
502 let mut declaration_ids = BTreeSet::new();
503 let mut cascade_keys = BTreeMap::<K, u32>::new();
504 for candidate in &candidates {
505 if candidate.element_signature != element_signature {
506 return Err(GuardedCascadeFragmentRefusalV0::MultipleElementSignatures {
507 expected: element_signature,
508 observed: candidate.element_signature.clone(),
509 });
510 }
511 if !candidate
512 .property
513 .to_property_name()
514 .same_as(&property.to_property_name())
515 {
516 return Err(GuardedCascadeFragmentRefusalV0::MultipleProperties {
517 expected: property,
518 observed: candidate.property.clone(),
519 });
520 }
521 if candidate.specificity_exactness != GuardedCascadeSpecificityExactnessV0::Exact {
522 return Err(GuardedCascadeFragmentRefusalV0::InexactSpecificity {
523 declaration_id: candidate.declaration_id,
524 });
525 }
526 if candidate.scope_proximity != 0 {
527 return Err(GuardedCascadeFragmentRefusalV0::ScopeProximityPresent {
528 declaration_id: candidate.declaration_id,
529 scope_proximity: candidate.scope_proximity,
530 });
531 }
532 if !declaration_ids.insert(candidate.declaration_id) {
533 return Err(GuardedCascadeFragmentRefusalV0::DuplicateDeclarationId {
534 declaration_id: candidate.declaration_id,
535 });
536 }
537 if let Some(first_declaration_id) =
538 cascade_keys.insert(candidate.cascade_key.clone(), candidate.declaration_id)
539 {
540 return Err(GuardedCascadeFragmentRefusalV0::NonUniqueCascadeKey {
541 first_declaration_id,
542 second_declaration_id: candidate.declaration_id,
543 });
544 }
545 for condition in &candidate.conditions {
546 match condition.kind {
547 GuardedCascadeConditionKindV0::Container => {
548 return Err(GuardedCascadeFragmentRefusalV0::ContainerCondition {
549 declaration_id: candidate.declaration_id,
550 atom: condition.atom.clone(),
551 });
552 }
553 GuardedCascadeConditionKindV0::StructuralPseudo => {
554 return Err(GuardedCascadeFragmentRefusalV0::StructuralPseudoCondition {
555 declaration_id: candidate.declaration_id,
556 atom: condition.atom.clone(),
557 });
558 }
559 GuardedCascadeConditionKindV0::Media
560 | GuardedCascadeConditionKindV0::Supports => {}
561 }
562 if !alphabet.contains(condition.atom.as_str()) {
563 return Err(if condition.numeric {
564 GuardedCascadeFragmentRefusalV0::NumericConditionOutsideAlphabet {
565 declaration_id: candidate.declaration_id,
566 atom: condition.atom.clone(),
567 }
568 } else {
569 GuardedCascadeFragmentRefusalV0::ConditionOutsideDeclaredAlphabet {
570 declaration_id: candidate.declaration_id,
571 atom: condition.atom.clone(),
572 }
573 });
574 }
575 }
576 }
577 candidates.sort_by(|left, right| right.cascade_key.cmp(&left.cascade_key));
578 Ok(Self {
579 element_signature,
580 property,
581 condition_alphabet: alphabet.into_iter().collect(),
582 candidates,
583 })
584 }
585
586 pub fn element_signature(&self) -> &str {
587 self.element_signature.as_str()
588 }
589
590 pub const fn property(&self) -> &AuthoredPropertyTextV0 {
591 &self.property
592 }
593
594 pub fn condition_alphabet(&self) -> &[String] {
595 self.condition_alphabet.as_slice()
596 }
597
598 pub fn candidates(&self) -> &[GuardedCascadeCandidateV0<K>] {
599 self.candidates.as_slice()
600 }
601}
602
603impl<K: PartialEq> PartialEq for GuardedCascadeFragmentV0<K> {
604 fn eq(&self, other: &Self) -> bool {
605 self.element_signature == other.element_signature
606 && self
607 .property
608 .to_property_name()
609 .same_as(&other.property.to_property_name())
610 && self.condition_alphabet == other.condition_alphabet
611 && self.candidates == other.candidates
612 }
613}
614
615impl<K: Eq> Eq for GuardedCascadeFragmentV0<K> {}
616
617pub fn at_rule_nesting_order_for_fragment_v0<K>(
618 fragment: &GuardedCascadeFragmentV0<K>,
619) -> Result<VariableOrderRegistrationV0, FirstWitnessErrorV0> {
620 #[cfg(test)]
621 if std::env::var_os("OMENA_G122_INJECT_REMOVE_AT_RULE_ORDER").is_some() {
622 return VariableOrderRegistrationV0::site_first_appearance(
623 fragment.condition_alphabet.iter().cloned(),
624 );
625 }
626 VariableOrderRegistrationV0::at_rule_nesting_dfs(
627 fragment
628 .candidates
629 .iter()
630 .flat_map(|candidate| candidate.conditions.iter())
631 .map(|condition| {
632 AtRuleNestingOrderAtomV0::new(
633 condition.atom.clone(),
634 condition.at_rule_path.iter().copied(),
635 )
636 }),
637 )
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
641#[serde(transparent)]
642pub struct GuardedCascadeWinnerRootV0(NodeId);
643
644impl GuardedCascadeWinnerRootV0 {
645 pub const fn node_id(self) -> NodeId {
646 self.0
647 }
648}
649
650#[non_exhaustive]
652#[derive(Debug, Clone, Serialize)]
653#[serde(rename_all = "camelCase")]
654pub struct GuardedCascadeFragmentPredicateV0 {
655 pub element_signature: String,
656 pub property: AuthoredPropertyTextV0,
657 pub condition_alphabet: Vec<String>,
658}
659
660impl PartialEq for GuardedCascadeFragmentPredicateV0 {
661 fn eq(&self, other: &Self) -> bool {
662 self.element_signature == other.element_signature
663 && self
664 .property
665 .to_property_name()
666 .same_as(&other.property.to_property_name())
667 && self.condition_alphabet == other.condition_alphabet
668 }
669}
670
671impl Eq for GuardedCascadeFragmentPredicateV0 {}
672
673impl<K> GuardedCascadeFragmentV0<K> {
674 pub fn predicate(&self) -> GuardedCascadeFragmentPredicateV0 {
675 GuardedCascadeFragmentPredicateV0 {
676 element_signature: self.element_signature.clone(),
677 property: self.property.clone(),
678 condition_alphabet: self.condition_alphabet.clone(),
679 }
680 }
681}
682
683#[non_exhaustive]
685#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
686#[serde(
687 tag = "kind",
688 rename_all = "camelCase",
689 rename_all_fields = "camelCase"
690)]
691pub enum GuardedCascadeWinnerAuthorityRuleV0 {
692 ScenarioSweepOutsideFragment,
693 CanonicalMtbddInsideFragment {
694 fragment: GuardedCascadeFragmentPredicateV0,
695 },
696}
697
698#[non_exhaustive]
700#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
701#[serde(rename_all = "camelCase")]
702pub struct GuardedCascadeWinnerAuthorityV0 {
703 pub rule: GuardedCascadeWinnerAuthorityRuleV0,
704 pub root: GuardedCascadeWinnerRootV0,
705 pub winner_defined_for_all_assignments: bool,
706}
707
708#[non_exhaustive]
710#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
711#[serde(
712 tag = "reason",
713 rename_all = "camelCase",
714 rename_all_fields = "camelCase"
715)]
716pub enum GuardedCascadeWinnerFunctionEqualityRefusalV0 {
717 CanonicalRootsDiffer {
718 input_root: GuardedCascadeWinnerRootV0,
719 output_root: GuardedCascadeWinnerRootV0,
720 },
721}
722
723#[non_exhaustive]
726#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
727#[serde(
728 tag = "kind",
729 rename_all = "camelCase",
730 rename_all_fields = "camelCase"
731)]
732pub enum GuardedCascadeWinnerFunctionEqualityDecisionV0 {
733 Equal {
734 authority: GuardedCascadeWinnerAuthorityV0,
735 },
736 Refused {
737 rule: GuardedCascadeWinnerAuthorityRuleV0,
738 refusal: GuardedCascadeWinnerFunctionEqualityRefusalV0,
739 },
740}
741
742#[non_exhaustive]
744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
745#[serde(
746 tag = "kind",
747 rename_all = "camelCase",
748 rename_all_fields = "camelCase"
749)]
750pub enum GuardedCascadeWinnerPlaneAnswerV0 {
751 NoWinner,
752 Declaration { declaration_id: u32 },
753}
754
755#[non_exhaustive]
757#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
758#[serde(
759 tag = "reason",
760 rename_all = "camelCase",
761 rename_all_fields = "camelCase"
762)]
763pub enum GuardedCascadeWinnerAuthorityErrorV0 {
764 InFragmentPlaneDisagreement {
765 canonical_mtbdd: GuardedCascadeWinnerPlaneAnswerV0,
766 scenario_sweep: GuardedCascadeWinnerPlaneAnswerV0,
767 },
768}
769
770impl std::fmt::Display for GuardedCascadeWinnerAuthorityErrorV0 {
771 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772 match self {
773 Self::InFragmentPlaneDisagreement {
774 canonical_mtbdd,
775 scenario_sweep,
776 } => write!(
777 formatter,
778 "in-fragment guarded winner disagreement: canonicalMtbdd={canonical_mtbdd:?}, scenarioSweep={scenario_sweep:?}"
779 ),
780 }
781 }
782}
783
784impl std::error::Error for GuardedCascadeWinnerAuthorityErrorV0 {}
785
786#[derive(Debug, Clone, PartialEq, Eq)]
787pub struct VariableOrderRegistrationV0 {
788 domain: VariableOrderDomainV0,
789 atoms: Vec<String>,
790 indices: BTreeMap<String, u16>,
791}
792
793impl VariableOrderRegistrationV0 {
794 pub fn site_first_appearance(
795 atoms: impl IntoIterator<Item = impl Into<String>>,
796 ) -> Result<Self, FirstWitnessErrorV0> {
797 let mut ordered = Vec::new();
798 let mut seen = BTreeSet::new();
799 for atom in atoms {
800 let atom = atom.into();
801 if seen.insert(atom.clone()) {
802 ordered.push(atom);
803 }
804 }
805 Self::from_ordered(VariableOrderDomainV0::SiteFirstAppearance, ordered)
806 }
807
808 pub fn at_rule_nesting_dfs(
809 atoms: impl IntoIterator<Item = AtRuleNestingOrderAtomV0>,
810 ) -> Result<Self, FirstWitnessErrorV0> {
811 let mut atoms = atoms.into_iter().collect::<Vec<_>>();
812 atoms.sort_by(|left, right| {
813 left.at_rule_path
814 .cmp(&right.at_rule_path)
815 .then_with(|| left.atom.cmp(&right.atom))
816 });
817 let mut seen = BTreeSet::new();
818 let ordered = atoms
819 .into_iter()
820 .filter_map(|atom| seen.insert(atom.atom.clone()).then_some(atom.atom))
821 .collect();
822 Self::from_ordered(VariableOrderDomainV0::AtRuleNestingDfs, ordered)
823 }
824
825 fn from_ordered(
826 domain: VariableOrderDomainV0,
827 ordered: Vec<String>,
828 ) -> Result<Self, FirstWitnessErrorV0> {
829 let indices = ordered
830 .iter()
831 .enumerate()
832 .map(|(index, atom)| {
833 u16::try_from(index)
834 .map(|index| (atom.clone(), index))
835 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)
836 })
837 .collect::<Result<BTreeMap<_, _>, _>>()?;
838 Ok(Self {
839 domain,
840 atoms: ordered,
841 indices,
842 })
843 }
844
845 pub const fn domain(&self) -> VariableOrderDomainV0 {
846 self.domain
847 }
848
849 pub fn atoms(&self) -> &[String] {
850 &self.atoms
851 }
852
853 pub fn variable_index(&self, atom: &str) -> Option<u16> {
854 self.indices.get(atom).copied()
855 }
856}
857
858#[derive(Debug, Clone, Copy, PartialEq, Eq)]
859pub struct FirstWitnessManagerConfigV0 {
860 pub apply_cache_capacity: usize,
861 pub rebuild_interval_operations: u64,
862 pub shortcuts: bool,
863}
864
865impl Default for FirstWitnessManagerConfigV0 {
866 fn default() -> Self {
867 Self {
868 apply_cache_capacity: DEFAULT_APPLY_CACHE_CAPACITY_V0,
869 rebuild_interval_operations: DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0,
870 shortcuts: true,
871 }
872 }
873}
874
875#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
876pub struct FirstWitnessOperationCountersV0 {
877 pub choose_invocations: u64,
878 pub apply_invocations: u64,
879 pub apply_cache_lookups: u64,
880 pub apply_cache_hits: u64,
881 pub rebuilds: u64,
882 pub rebuild_node_visits: u64,
883}
884
885#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
886pub struct FirstWitnessChoiceOperationCountersV0 {
887 pub recursive_invocations: u64,
888 pub apply_cache_lookups: u64,
889 pub apply_cache_hits: u64,
890}
891
892impl FirstWitnessOperationCountersV0 {
893 pub const fn recursive_operations(self) -> u64 {
894 self.choose_invocations + self.apply_invocations
895 }
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Eq)]
899pub struct FirstWitnessRebuildReportV0 {
900 pub operations_since_previous_rebuild: u64,
901 pub nodes_before: usize,
902 pub nodes_after: usize,
903 pub live_root_count: usize,
904 pub visited_node_count: usize,
905}
906
907#[derive(Debug, Clone, PartialEq, Eq)]
908pub enum FirstWitnessErrorV0 {
909 UnknownAtom(String),
910 InvalidNode(NodeId),
911 InvalidTerminal(u32),
912 VariableOrderViolation { parent: u16, child: u16 },
913 VariableCapacityExceeded,
914 DeclarationIdCapacityExceeded,
915 DeclarationTerminalRegistrationClosed,
916 UnregisteredDeclarationTerminal(u32),
917 MissingAssignment { variable: u16 },
918}
919
920impl std::fmt::Display for FirstWitnessErrorV0 {
921 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
922 match self {
923 Self::UnknownAtom(atom) => write!(formatter, "unregistered decision atom {atom}"),
924 Self::InvalidNode(node) => write!(formatter, "invalid decision node {node}"),
925 Self::InvalidTerminal(terminal) => {
926 write!(formatter, "invalid boolean terminal {terminal}")
927 }
928 Self::VariableOrderViolation { parent, child } => write!(
929 formatter,
930 "decision variable order violation: parent {parent}, child {child}"
931 ),
932 Self::VariableCapacityExceeded => {
933 formatter.write_str("decision variable or node capacity exceeded")
934 }
935 Self::DeclarationIdCapacityExceeded => {
936 formatter.write_str("declaration id cannot be represented by the terminal alphabet")
937 }
938 Self::DeclarationTerminalRegistrationClosed => formatter
939 .write_str("declaration terminals must be registered before internal nodes exist"),
940 Self::UnregisteredDeclarationTerminal(declaration_id) => write!(
941 formatter,
942 "declaration terminal {declaration_id} was not registered"
943 ),
944 Self::MissingAssignment { variable } => {
945 write!(
946 formatter,
947 "assignment does not cover decision variable {variable}"
948 )
949 }
950 }
951 }
952}
953
954impl std::error::Error for FirstWitnessErrorV0 {}
955
956#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
957enum ApplyOperationV0 {
958 Boolean(BooleanOperationV0),
959 FirstWitness(FirstWitnessTerminalBehaviorV0),
960}
961
962#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
963enum FirstWitnessTerminalBehaviorV0 {
964 LeftBiased,
965 #[cfg(test)]
966 RightBiased,
967 #[cfg(test)]
968 BrokenRecursion,
969}
970
971#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
972struct ApplyCacheKeyV0 {
973 operation: ApplyOperationV0,
974 left: NodeId,
975 right: NodeId,
976}
977
978#[derive(Debug, Clone)]
979pub struct FirstWitnessManagerV0 {
980 nodes: Vec<Node>,
981 terminal_by_value: HashMap<u32, NodeId>,
982 unique: HashMap<(u16, NodeId, NodeId), NodeId>,
983 apply_cache: HashMap<ApplyCacheKeyV0, NodeId>,
984 apply_cache_fifo: VecDeque<ApplyCacheKeyV0>,
985 order: VariableOrderRegistrationV0,
986 config: FirstWitnessManagerConfigV0,
987 counters: FirstWitnessOperationCountersV0,
988 choice_counters: FirstWitnessChoiceOperationCountersV0,
989 operations_at_previous_rebuild: u64,
990}
991
992impl FirstWitnessManagerV0 {
993 pub fn new(order: VariableOrderRegistrationV0, config: FirstWitnessManagerConfigV0) -> Self {
994 Self {
995 nodes: vec![Node::Term(0), Node::Term(1)],
996 terminal_by_value: HashMap::from([(0, FALSE_NODE_ID_V0), (1, TRUE_NODE_ID_V0)]),
997 unique: HashMap::new(),
998 apply_cache: HashMap::new(),
999 apply_cache_fifo: VecDeque::new(),
1000 order,
1001 config,
1002 counters: FirstWitnessOperationCountersV0::default(),
1003 choice_counters: FirstWitnessChoiceOperationCountersV0::default(),
1004 operations_at_previous_rebuild: 0,
1005 }
1006 }
1007
1008 pub fn order(&self) -> &VariableOrderRegistrationV0 {
1009 &self.order
1010 }
1011
1012 pub const fn config(&self) -> FirstWitnessManagerConfigV0 {
1013 self.config
1014 }
1015
1016 pub const fn counters(&self) -> FirstWitnessOperationCountersV0 {
1017 self.counters
1018 }
1019
1020 pub const fn first_witness_counters(&self) -> FirstWitnessChoiceOperationCountersV0 {
1021 self.choice_counters
1022 }
1023
1024 pub fn node(&self, node: NodeId) -> Option<Node> {
1025 self.nodes.get(node as usize).copied()
1026 }
1027
1028 pub fn node_count(&self) -> usize {
1029 self.nodes.len()
1030 }
1031
1032 pub fn unique_table_len(&self) -> usize {
1033 self.unique.len()
1034 }
1035
1036 pub fn apply_cache_len(&self) -> usize {
1037 self.apply_cache.len()
1038 }
1039
1040 pub fn reachable_winner_node_count(
1041 &self,
1042 root: GuardedCascadeWinnerRootV0,
1043 ) -> Result<usize, FirstWitnessErrorV0> {
1044 let mut seen = BTreeSet::new();
1045 let mut pending = vec![root.0];
1046 while let Some(node_id) = pending.pop() {
1047 if !seen.insert(node_id) {
1048 continue;
1049 }
1050 if let Node::Int { lo, hi, .. } = self.require_node(node_id)? {
1051 pending.extend([lo, hi]);
1052 }
1053 }
1054 Ok(seen.len())
1055 }
1056
1057 pub fn register_declaration_terminals(
1058 &mut self,
1059 declaration_ids: impl IntoIterator<Item = u32>,
1060 ) -> Result<(), FirstWitnessErrorV0> {
1061 let mut encoded = declaration_ids
1062 .into_iter()
1063 .map(|declaration_id| {
1064 declaration_id
1065 .checked_add(1)
1066 .map(|terminal| (terminal, declaration_id))
1067 .ok_or(FirstWitnessErrorV0::DeclarationIdCapacityExceeded)
1068 })
1069 .collect::<Result<Vec<_>, _>>()?;
1070 encoded.sort_unstable();
1071 encoded.dedup_by_key(|(terminal, _)| *terminal);
1072 let has_missing = encoded
1073 .iter()
1074 .any(|(terminal, _)| !self.terminal_by_value.contains_key(terminal));
1075 if has_missing
1076 && self
1077 .nodes
1078 .iter()
1079 .any(|node| matches!(node, Node::Int { .. }))
1080 {
1081 return Err(FirstWitnessErrorV0::DeclarationTerminalRegistrationClosed);
1082 }
1083 for (terminal, _) in encoded {
1084 if self.terminal_by_value.contains_key(&terminal) {
1085 continue;
1086 }
1087 let node = u32::try_from(self.nodes.len())
1088 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1089 self.nodes.push(Node::Term(terminal));
1090 self.terminal_by_value.insert(terminal, node);
1091 }
1092 Ok(())
1093 }
1094
1095 pub fn declaration_terminal(&self, declaration_id: u32) -> Result<NodeId, FirstWitnessErrorV0> {
1096 let terminal = declaration_id
1097 .checked_add(1)
1098 .ok_or(FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?;
1099 self.terminal_by_value.get(&terminal).copied().ok_or(
1100 FirstWitnessErrorV0::UnregisteredDeclarationTerminal(declaration_id),
1101 )
1102 }
1103
1104 pub fn variable(&mut self, atom: &str) -> Result<NodeId, FirstWitnessErrorV0> {
1105 let variable = self
1106 .order
1107 .variable_index(atom)
1108 .ok_or_else(|| FirstWitnessErrorV0::UnknownAtom(atom.to_string()))?;
1109 self.choose(variable, FALSE_NODE_ID_V0, TRUE_NODE_ID_V0)
1110 }
1111
1112 pub fn choose(
1113 &mut self,
1114 variable: u16,
1115 low: NodeId,
1116 high: NodeId,
1117 ) -> Result<NodeId, FirstWitnessErrorV0> {
1118 self.counters.choose_invocations += 1;
1119 self.intern(variable, low, high)
1120 }
1121
1122 pub fn not(&mut self, value: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1123 self.apply(BooleanOperationV0::Xor, value, TRUE_NODE_ID_V0)
1124 }
1125
1126 pub fn and(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1127 self.apply(BooleanOperationV0::And, left, right)
1128 }
1129
1130 pub fn or(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1131 self.apply(BooleanOperationV0::Or, left, right)
1132 }
1133
1134 pub fn xor(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1135 self.apply(BooleanOperationV0::Xor, left, right)
1136 }
1137
1138 pub fn choose_first_witness(
1139 &mut self,
1140 left: NodeId,
1141 right: NodeId,
1142 ) -> Result<NodeId, FirstWitnessErrorV0> {
1143 self.require_node(left)?;
1144 self.require_node(right)?;
1145 self.choose_first_witness_recursive(left, right, FirstWitnessTerminalBehaviorV0::LeftBiased)
1146 }
1147
1148 #[cfg(test)]
1149 fn choose_first_witness_with_terminal_behavior_for_test(
1150 &mut self,
1151 left: NodeId,
1152 right: NodeId,
1153 behavior: FirstWitnessTerminalBehaviorV0,
1154 ) -> Result<NodeId, FirstWitnessErrorV0> {
1155 self.require_node(left)?;
1156 self.require_node(right)?;
1157 self.choose_first_witness_recursive(left, right, behavior)
1158 }
1159
1160 pub fn apply(
1161 &mut self,
1162 operation: BooleanOperationV0,
1163 left: NodeId,
1164 right: NodeId,
1165 ) -> Result<NodeId, FirstWitnessErrorV0> {
1166 self.require_node(left)?;
1167 self.require_node(right)?;
1168 self.apply_recursive(operation, left, right)
1169 }
1170
1171 pub fn is_tautology(&self, root: NodeId) -> bool {
1172 root == TRUE_NODE_ID_V0
1173 }
1174
1175 pub fn is_satisfiable(&self, root: NodeId) -> bool {
1176 root != FALSE_NODE_ID_V0
1177 }
1178
1179 pub fn reclaim_if_due(
1180 &mut self,
1181 live_roots: &mut [NodeId],
1182 ) -> Result<Option<FirstWitnessRebuildReportV0>, FirstWitnessErrorV0> {
1183 let operations = self
1184 .counters
1185 .recursive_operations()
1186 .saturating_add(self.choice_counters.recursive_invocations);
1187 let operations_since_previous_rebuild =
1188 operations.saturating_sub(self.operations_at_previous_rebuild);
1189 if self.config.rebuild_interval_operations == 0
1190 || operations_since_previous_rebuild < self.config.rebuild_interval_operations
1191 {
1192 return Ok(None);
1193 }
1194 for root in live_roots.iter().copied() {
1195 self.require_node(root)?;
1196 }
1197 let nodes_before = self.nodes.len();
1198 let mut rebuilt_nodes = self
1199 .nodes
1200 .iter()
1201 .copied()
1202 .take_while(|node| matches!(node, Node::Term(_)))
1203 .collect::<Vec<_>>();
1204 let rebuilt_terminal_by_value = rebuilt_nodes
1205 .iter()
1206 .enumerate()
1207 .filter_map(|(node, value)| match value {
1208 Node::Term(value) => u32::try_from(node).ok().map(|node| (*value, node)),
1209 Node::Int { .. } => None,
1210 })
1211 .collect::<HashMap<_, _>>();
1212 let mut rebuilt_unique = HashMap::new();
1213 let mut remapped = (0..rebuilt_nodes.len())
1214 .filter_map(|node| u32::try_from(node).ok().map(|node| (node, node)))
1215 .collect::<HashMap<_, _>>();
1216 let mut visited_node_count = 0usize;
1217 for root in live_roots.iter_mut() {
1218 *root = clone_live_node(
1219 *root,
1220 &self.nodes,
1221 &mut rebuilt_nodes,
1222 &mut rebuilt_unique,
1223 &mut remapped,
1224 &mut visited_node_count,
1225 )?;
1226 }
1227 self.nodes = rebuilt_nodes;
1228 self.terminal_by_value = rebuilt_terminal_by_value;
1229 self.unique = rebuilt_unique;
1230 self.apply_cache.clear();
1231 self.apply_cache_fifo.clear();
1232 self.counters.rebuilds += 1;
1233 self.counters.rebuild_node_visits += visited_node_count as u64;
1234 self.operations_at_previous_rebuild = operations;
1235 Ok(Some(FirstWitnessRebuildReportV0 {
1236 operations_since_previous_rebuild,
1237 nodes_before,
1238 nodes_after: self.nodes.len(),
1239 live_root_count: live_roots.len(),
1240 visited_node_count,
1241 }))
1242 }
1243
1244 fn apply_recursive(
1245 &mut self,
1246 operation: BooleanOperationV0,
1247 left: NodeId,
1248 right: NodeId,
1249 ) -> Result<NodeId, FirstWitnessErrorV0> {
1250 self.counters.apply_invocations += 1;
1251 if self.config.shortcuts
1252 && let Some(result) = boolean_shortcut(operation, left, right)
1253 {
1254 return Ok(result);
1255 }
1256 let left_node = self.require_node(left)?;
1257 let right_node = self.require_node(right)?;
1258 if let (Node::Term(left), Node::Term(right)) = (left_node, right_node) {
1259 return terminal_boolean_result(operation, left, right);
1260 }
1261 let key = canonical_apply_key(operation, left, right);
1262 self.counters.apply_cache_lookups += 1;
1263 if let Some(result) = self.apply_cache.get(&key).copied() {
1264 self.counters.apply_cache_hits += 1;
1265 return Ok(result);
1266 }
1267 let variable = top_variable(left_node, right_node);
1268 let (left_low, left_high) = cofactors(left, left_node, variable);
1269 let (right_low, right_high) = cofactors(right, right_node, variable);
1270 let low = self.apply_recursive(operation, left_low, right_low)?;
1271 let high = self.apply_recursive(operation, left_high, right_high)?;
1272 let result = self.choose(variable, low, high)?;
1273 self.cache_insert(key, result);
1274 Ok(result)
1275 }
1276
1277 fn choose_first_witness_recursive(
1278 &mut self,
1279 left: NodeId,
1280 right: NodeId,
1281 terminal_behavior: FirstWitnessTerminalBehaviorV0,
1282 ) -> Result<NodeId, FirstWitnessErrorV0> {
1283 self.choice_counters.recursive_invocations += 1;
1284 let left_node = self.require_node(left)?;
1285 let right_node = self.require_node(right)?;
1286 if self.config.shortcuts {
1287 if left == right {
1288 return Ok(left);
1289 }
1290 if matches!(left_node, Node::Term(terminal) if terminal != 0) {
1291 return Ok(left);
1292 }
1293 if right == GUARDED_CASCADE_BOT_NODE_ID_V0 {
1294 return Ok(left);
1295 }
1296 }
1297 if let (Node::Term(left_terminal), Node::Term(_)) = (left_node, right_node) {
1298 return Ok(match terminal_behavior {
1299 FirstWitnessTerminalBehaviorV0::LeftBiased => {
1300 if left_terminal == 0 {
1301 right
1302 } else {
1303 left
1304 }
1305 }
1306 #[cfg(test)]
1307 FirstWitnessTerminalBehaviorV0::RightBiased => {
1308 if right == GUARDED_CASCADE_BOT_NODE_ID_V0 {
1309 left
1310 } else {
1311 right
1312 }
1313 }
1314 #[cfg(test)]
1315 FirstWitnessTerminalBehaviorV0::BrokenRecursion => {
1316 if left_terminal == 0
1317 && matches!(right_node, Node::Term(terminal) if terminal > 1)
1318 {
1319 TRUE_NODE_ID_V0
1320 } else {
1321 GUARDED_CASCADE_BOT_NODE_ID_V0
1322 }
1323 }
1324 });
1325 }
1326 let key = ApplyCacheKeyV0 {
1327 operation: ApplyOperationV0::FirstWitness(terminal_behavior),
1328 left,
1329 right,
1330 };
1331 self.choice_counters.apply_cache_lookups += 1;
1332 if let Some(result) = self.apply_cache.get(&key).copied() {
1333 self.choice_counters.apply_cache_hits += 1;
1334 return Ok(result);
1335 }
1336 let variable = top_variable(left_node, right_node);
1337 let (left_low, left_high) = cofactors(left, left_node, variable);
1338 let (right_low, right_high) = cofactors(right, right_node, variable);
1339 let low = self.choose_first_witness_recursive(left_low, right_low, terminal_behavior)?;
1340 let high = self.choose_first_witness_recursive(left_high, right_high, terminal_behavior)?;
1341 let result = self.choose(variable, low, high)?;
1342 self.cache_insert(key, result);
1343 Ok(result)
1344 }
1345
1346 fn intern(
1347 &mut self,
1348 variable: u16,
1349 low: NodeId,
1350 high: NodeId,
1351 ) -> Result<NodeId, FirstWitnessErrorV0> {
1352 let low_node = self.require_node(low)?;
1353 let high_node = self.require_node(high)?;
1354 for child in [low_node, high_node] {
1355 if let Node::Int { var: child, .. } = child
1356 && child <= variable
1357 {
1358 return Err(FirstWitnessErrorV0::VariableOrderViolation {
1359 parent: variable,
1360 child,
1361 });
1362 }
1363 }
1364 if low == high {
1365 return Ok(low);
1366 }
1367 if let Some(node) = self.unique.get(&(variable, low, high)).copied() {
1368 return Ok(node);
1369 }
1370 let node = u32::try_from(self.nodes.len())
1371 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1372 self.nodes.push(Node::Int {
1373 var: variable,
1374 lo: low,
1375 hi: high,
1376 });
1377 self.unique.insert((variable, low, high), node);
1378 Ok(node)
1379 }
1380
1381 #[cfg(test)]
1382 fn intern_without_collapse_for_test(
1383 &mut self,
1384 variable: u16,
1385 low: NodeId,
1386 high: NodeId,
1387 ) -> Result<NodeId, FirstWitnessErrorV0> {
1388 let low_node = self.require_node(low)?;
1389 let high_node = self.require_node(high)?;
1390 for child in [low_node, high_node] {
1391 if let Node::Int { var: child, .. } = child
1392 && child <= variable
1393 {
1394 return Err(FirstWitnessErrorV0::VariableOrderViolation {
1395 parent: variable,
1396 child,
1397 });
1398 }
1399 }
1400 if let Some(node) = self.unique.get(&(variable, low, high)).copied() {
1401 return Ok(node);
1402 }
1403 let node = u32::try_from(self.nodes.len())
1404 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1405 self.nodes.push(Node::Int {
1406 var: variable,
1407 lo: low,
1408 hi: high,
1409 });
1410 self.unique.insert((variable, low, high), node);
1411 Ok(node)
1412 }
1413
1414 fn require_node(&self, node: NodeId) -> Result<Node, FirstWitnessErrorV0> {
1415 self.node(node)
1416 .ok_or(FirstWitnessErrorV0::InvalidNode(node))
1417 }
1418
1419 fn cache_insert(&mut self, key: ApplyCacheKeyV0, value: NodeId) {
1420 if self.config.apply_cache_capacity == 0 || self.apply_cache.contains_key(&key) {
1421 return;
1422 }
1423 while self.apply_cache.len() >= self.config.apply_cache_capacity {
1424 let Some(evicted) = self.apply_cache_fifo.pop_front() else {
1425 break;
1426 };
1427 self.apply_cache.remove(&evicted);
1428 }
1429 self.apply_cache.insert(key, value);
1430 self.apply_cache_fifo.push_back(key);
1431 }
1432}
1433
1434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1435pub struct IncrementalGuardedCascadeWinnerEditReportV0 {
1436 pub replaced_existing_key: bool,
1437 pub entry_count: usize,
1438 pub root: GuardedCascadeWinnerRootV0,
1439 pub aggregate_updates: u64,
1440}
1441
1442#[derive(Debug)]
1443struct IncrementalGuardedCascadeWinnerNodeV0<K> {
1444 key: K,
1445 guarded_root: NodeId,
1446 aggregate: NodeId,
1447 height: u16,
1448 size: usize,
1449 left: Option<Box<Self>>,
1450 right: Option<Box<Self>>,
1451}
1452
1453type IncrementalWinnerLinkV0<K> = Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>;
1454type IncrementalWinnerMutationV0<K> = (IncrementalWinnerLinkV0<K>, bool);
1455
1456impl<K> IncrementalGuardedCascadeWinnerNodeV0<K> {
1457 fn leaf(key: K, guarded_root: NodeId) -> Self {
1458 Self {
1459 key,
1460 guarded_root,
1461 aggregate: guarded_root,
1462 height: 1,
1463 size: 1,
1464 left: None,
1465 right: None,
1466 }
1467 }
1468}
1469
1470#[derive(Debug, Default)]
1471pub struct IncrementalGuardedCascadeWinnerV0<K> {
1472 root: Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1473 aggregate_updates: u64,
1474}
1475
1476impl<K: Ord> IncrementalGuardedCascadeWinnerV0<K> {
1477 pub const fn new() -> Self {
1478 Self {
1479 root: None,
1480 aggregate_updates: 0,
1481 }
1482 }
1483
1484 pub fn len(&self) -> usize {
1485 incremental_winner_size(&self.root)
1486 }
1487
1488 pub fn is_empty(&self) -> bool {
1489 self.root.is_none()
1490 }
1491
1492 pub fn root(&self) -> GuardedCascadeWinnerRootV0 {
1493 GuardedCascadeWinnerRootV0(incremental_winner_fold(&self.root))
1494 }
1495
1496 pub const fn aggregate_updates(&self) -> u64 {
1497 self.aggregate_updates
1498 }
1499
1500 pub fn insert(
1501 &mut self,
1502 manager: &mut FirstWitnessManagerV0,
1503 cascade_key: K,
1504 guarded_root: GuardedCascadeWinnerRootV0,
1505 ) -> Result<IncrementalGuardedCascadeWinnerEditReportV0, FirstWitnessErrorV0> {
1506 manager.require_node(guarded_root.0)?;
1507 let (root, replaced_existing_key) = incremental_winner_insert(
1508 self.root.take(),
1509 cascade_key,
1510 guarded_root.0,
1511 manager,
1512 &mut self.aggregate_updates,
1513 )?;
1514 self.root = root;
1515 Ok(self.edit_report(replaced_existing_key))
1516 }
1517
1518 pub fn remove(
1519 &mut self,
1520 manager: &mut FirstWitnessManagerV0,
1521 cascade_key: &K,
1522 ) -> Result<IncrementalGuardedCascadeWinnerEditReportV0, FirstWitnessErrorV0> {
1523 let (root, removed) = incremental_winner_remove(
1524 self.root.take(),
1525 cascade_key,
1526 manager,
1527 &mut self.aggregate_updates,
1528 )?;
1529 self.root = root;
1530 Ok(self.edit_report(removed))
1531 }
1532
1533 pub fn reclaim_manager_if_due(
1534 &mut self,
1535 manager: &mut FirstWitnessManagerV0,
1536 ) -> Result<Option<FirstWitnessRebuildReportV0>, FirstWitnessErrorV0> {
1537 #[cfg(test)]
1538 if std::env::var_os("OMENA_G122_INJECT_DISABLE_WINNER_RECLAMATION").is_some() {
1539 return Ok(None);
1540 }
1541 let mut live_roots = Vec::with_capacity(self.len().saturating_mul(2));
1542 collect_incremental_winner_roots(&self.root, &mut live_roots);
1543 let report = manager.reclaim_if_due(&mut live_roots)?;
1544 if report.is_some() {
1545 let mut remapped = live_roots.into_iter();
1546 rewrite_incremental_winner_roots(&mut self.root, &mut remapped);
1547 debug_assert!(remapped.next().is_none());
1548 }
1549 Ok(report)
1550 }
1551
1552 fn edit_report(
1553 &self,
1554 replaced_existing_key: bool,
1555 ) -> IncrementalGuardedCascadeWinnerEditReportV0 {
1556 IncrementalGuardedCascadeWinnerEditReportV0 {
1557 replaced_existing_key,
1558 entry_count: self.len(),
1559 root: self.root(),
1560 aggregate_updates: self.aggregate_updates,
1561 }
1562 }
1563}
1564
1565fn incremental_winner_height<K>(
1566 node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1567) -> u16 {
1568 node.as_ref().map_or(0, |node| node.height)
1569}
1570
1571fn incremental_winner_size<K>(
1572 node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1573) -> usize {
1574 node.as_ref().map_or(0, |node| node.size)
1575}
1576
1577fn incremental_winner_fold<K>(
1578 node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1579) -> NodeId {
1580 node.as_ref()
1581 .map_or(GUARDED_CASCADE_BOT_NODE_ID_V0, |node| node.aggregate)
1582}
1583
1584fn refresh_incremental_winner<K>(
1585 node: &mut IncrementalGuardedCascadeWinnerNodeV0<K>,
1586 manager: &mut FirstWitnessManagerV0,
1587 aggregate_updates: &mut u64,
1588) -> Result<(), FirstWitnessErrorV0> {
1589 node.height =
1590 1 + incremental_winner_height(&node.left).max(incremental_winner_height(&node.right));
1591 node.size = 1 + incremental_winner_size(&node.left) + incremental_winner_size(&node.right);
1592 #[cfg(test)]
1593 if std::env::var_os("OMENA_G122_INJECT_STALE_WINNER_AGGREGATE").is_some() {
1594 return Ok(());
1595 }
1596 let left_and_self =
1597 manager.choose_first_witness(incremental_winner_fold(&node.left), node.guarded_root)?;
1598 node.aggregate =
1599 manager.choose_first_witness(left_and_self, incremental_winner_fold(&node.right))?;
1600 *aggregate_updates += 2;
1601 Ok(())
1602}
1603
1604fn incremental_winner_balance_factor<K>(node: &IncrementalGuardedCascadeWinnerNodeV0<K>) -> i32 {
1605 i32::from(incremental_winner_height(&node.left))
1606 - i32::from(incremental_winner_height(&node.right))
1607}
1608
1609fn rotate_incremental_winner_left<K>(
1610 mut root: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1611 manager: &mut FirstWitnessManagerV0,
1612 aggregate_updates: &mut u64,
1613) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1614 let mut pivot = root
1615 .right
1616 .take()
1617 .ok_or(FirstWitnessErrorV0::InvalidNode(root.aggregate))?;
1618 root.right = pivot.left.take();
1619 refresh_incremental_winner(&mut root, manager, aggregate_updates)?;
1620 pivot.left = Some(root);
1621 refresh_incremental_winner(&mut pivot, manager, aggregate_updates)?;
1622 Ok(pivot)
1623}
1624
1625fn rotate_incremental_winner_right<K>(
1626 mut root: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1627 manager: &mut FirstWitnessManagerV0,
1628 aggregate_updates: &mut u64,
1629) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1630 let mut pivot = root
1631 .left
1632 .take()
1633 .ok_or(FirstWitnessErrorV0::InvalidNode(root.aggregate))?;
1634 root.left = pivot.right.take();
1635 refresh_incremental_winner(&mut root, manager, aggregate_updates)?;
1636 pivot.right = Some(root);
1637 refresh_incremental_winner(&mut pivot, manager, aggregate_updates)?;
1638 Ok(pivot)
1639}
1640
1641fn balance_incremental_winner<K>(
1642 mut node: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1643 manager: &mut FirstWitnessManagerV0,
1644 aggregate_updates: &mut u64,
1645) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1646 refresh_incremental_winner(&mut node, manager, aggregate_updates)?;
1647 let balance = incremental_winner_balance_factor(&node);
1648 if balance > 1 {
1649 let left_balance = node
1650 .left
1651 .as_deref()
1652 .map_or(0, incremental_winner_balance_factor);
1653 if left_balance < 0 {
1654 let left = node
1655 .left
1656 .take()
1657 .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1658 node.left = Some(rotate_incremental_winner_left(
1659 left,
1660 manager,
1661 aggregate_updates,
1662 )?);
1663 }
1664 return rotate_incremental_winner_right(node, manager, aggregate_updates);
1665 }
1666 if balance < -1 {
1667 let right_balance = node
1668 .right
1669 .as_deref()
1670 .map_or(0, incremental_winner_balance_factor);
1671 if right_balance > 0 {
1672 let right = node
1673 .right
1674 .take()
1675 .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1676 node.right = Some(rotate_incremental_winner_right(
1677 right,
1678 manager,
1679 aggregate_updates,
1680 )?);
1681 }
1682 return rotate_incremental_winner_left(node, manager, aggregate_updates);
1683 }
1684 Ok(node)
1685}
1686
1687fn incremental_winner_insert<K: Ord>(
1688 node: IncrementalWinnerLinkV0<K>,
1689 cascade_key: K,
1690 guarded_root: NodeId,
1691 manager: &mut FirstWitnessManagerV0,
1692 aggregate_updates: &mut u64,
1693) -> Result<IncrementalWinnerMutationV0<K>, FirstWitnessErrorV0> {
1694 let Some(mut node) = node else {
1695 return Ok((
1696 Some(Box::new(IncrementalGuardedCascadeWinnerNodeV0::leaf(
1697 cascade_key,
1698 guarded_root,
1699 ))),
1700 false,
1701 ));
1702 };
1703 let replaced = match cascade_key.cmp(&node.key) {
1704 Ordering::Greater => {
1705 let (left, replaced) = incremental_winner_insert(
1706 node.left.take(),
1707 cascade_key,
1708 guarded_root,
1709 manager,
1710 aggregate_updates,
1711 )?;
1712 node.left = left;
1713 replaced
1714 }
1715 Ordering::Less => {
1716 let (right, replaced) = incremental_winner_insert(
1717 node.right.take(),
1718 cascade_key,
1719 guarded_root,
1720 manager,
1721 aggregate_updates,
1722 )?;
1723 node.right = right;
1724 replaced
1725 }
1726 Ordering::Equal => {
1727 node.guarded_root = guarded_root;
1728 true
1729 }
1730 };
1731 Ok((
1732 Some(balance_incremental_winner(
1733 node,
1734 manager,
1735 aggregate_updates,
1736 )?),
1737 replaced,
1738 ))
1739}
1740
1741fn incremental_winner_remove<K: Ord>(
1742 node: IncrementalWinnerLinkV0<K>,
1743 cascade_key: &K,
1744 manager: &mut FirstWitnessManagerV0,
1745 aggregate_updates: &mut u64,
1746) -> Result<IncrementalWinnerMutationV0<K>, FirstWitnessErrorV0> {
1747 let Some(mut node) = node else {
1748 return Ok((None, false));
1749 };
1750 let removed = match cascade_key.cmp(&node.key) {
1751 Ordering::Greater => {
1752 let (left, removed) = incremental_winner_remove(
1753 node.left.take(),
1754 cascade_key,
1755 manager,
1756 aggregate_updates,
1757 )?;
1758 node.left = left;
1759 removed
1760 }
1761 Ordering::Less => {
1762 let (right, removed) = incremental_winner_remove(
1763 node.right.take(),
1764 cascade_key,
1765 manager,
1766 aggregate_updates,
1767 )?;
1768 node.right = right;
1769 removed
1770 }
1771 Ordering::Equal => {
1772 if node.left.is_none() {
1773 return Ok((node.right.take(), true));
1774 }
1775 if node.right.is_none() {
1776 return Ok((node.left.take(), true));
1777 }
1778 let right = node
1779 .right
1780 .take()
1781 .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1782 let (successor, right) =
1783 extract_incremental_winner_leftmost(right, manager, aggregate_updates)?;
1784 node.key = successor.key;
1785 node.guarded_root = successor.guarded_root;
1786 node.right = right;
1787 true
1788 }
1789 };
1790 if !removed {
1791 return Ok((Some(node), false));
1792 }
1793 Ok((
1794 Some(balance_incremental_winner(
1795 node,
1796 manager,
1797 aggregate_updates,
1798 )?),
1799 true,
1800 ))
1801}
1802
1803type IncrementalWinnerExtractV0<K> = (
1804 Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1805 IncrementalWinnerLinkV0<K>,
1806);
1807
1808fn extract_incremental_winner_leftmost<K>(
1809 mut node: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1810 manager: &mut FirstWitnessManagerV0,
1811 aggregate_updates: &mut u64,
1812) -> Result<IncrementalWinnerExtractV0<K>, FirstWitnessErrorV0> {
1813 let Some(left) = node.left.take() else {
1814 let right = node.right.take();
1815 return Ok((node, right));
1816 };
1817 let (leftmost, left) = extract_incremental_winner_leftmost(left, manager, aggregate_updates)?;
1818 node.left = left;
1819 Ok((
1820 leftmost,
1821 Some(balance_incremental_winner(
1822 node,
1823 manager,
1824 aggregate_updates,
1825 )?),
1826 ))
1827}
1828
1829fn collect_incremental_winner_roots<K>(
1830 node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1831 roots: &mut Vec<NodeId>,
1832) {
1833 if let Some(node) = node {
1834 roots.extend([node.guarded_root, node.aggregate]);
1835 collect_incremental_winner_roots(&node.left, roots);
1836 collect_incremental_winner_roots(&node.right, roots);
1837 }
1838}
1839
1840fn rewrite_incremental_winner_roots<K>(
1841 node: &mut Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1842 roots: &mut impl Iterator<Item = NodeId>,
1843) {
1844 if let Some(node) = node {
1845 node.guarded_root = roots.next().unwrap_or(node.guarded_root);
1846 node.aggregate = roots.next().unwrap_or(node.aggregate);
1847 rewrite_incremental_winner_roots(&mut node.left, roots);
1848 rewrite_incremental_winner_roots(&mut node.right, roots);
1849 }
1850}
1851
1852pub fn merge_first_witness_by_key_v0<T: Clone, K: Ord>(
1853 left: &[T],
1854 right: &[T],
1855 key: impl Fn(&T) -> K,
1856) -> Vec<T> {
1857 let mut seen = BTreeSet::new();
1858 left.iter()
1859 .chain(right)
1860 .filter(|value| seen.insert(key(value)))
1861 .cloned()
1862 .collect()
1863}
1864
1865pub fn first_witness_fold_v0<T: Clone + Ord>(left: &[T], right: &[T]) -> Vec<T> {
1866 left.iter()
1867 .chain(right)
1868 .cloned()
1869 .collect::<BTreeSet<_>>()
1870 .into_iter()
1871 .collect()
1872}
1873
1874pub fn build_guarded_cascade_winner_v0<K: Clone + Ord>(
1875 manager: &mut FirstWitnessManagerV0,
1876 fragment: &GuardedCascadeFragmentV0<K>,
1877) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
1878 manager.register_declaration_terminals(
1879 fragment
1880 .candidates
1881 .iter()
1882 .map(|candidate| candidate.declaration_id),
1883 )?;
1884 let mut winner = GUARDED_CASCADE_BOT_NODE_ID_V0;
1885 for candidate in &fragment.candidates {
1886 let mut guarded = manager.declaration_terminal(candidate.declaration_id)?;
1887 let mut variables = candidate
1888 .conditions
1889 .iter()
1890 .map(|condition| {
1891 manager
1892 .order
1893 .variable_index(condition.atom.as_str())
1894 .ok_or_else(|| FirstWitnessErrorV0::UnknownAtom(condition.atom.clone()))
1895 })
1896 .collect::<Result<Vec<_>, _>>()?;
1897 variables.sort_unstable();
1898 variables.dedup();
1899 for variable in variables.into_iter().rev() {
1900 guarded = manager.choose(variable, GUARDED_CASCADE_BOT_NODE_ID_V0, guarded)?;
1901 }
1902 winner = manager.choose_first_witness(winner, guarded)?;
1903 }
1904 Ok(GuardedCascadeWinnerRootV0(winner))
1905}
1906
1907pub fn evaluate_guarded_cascade_winner_v0(
1908 manager: &FirstWitnessManagerV0,
1909 root: GuardedCascadeWinnerRootV0,
1910 assignment: &[bool],
1911) -> Result<Option<u32>, FirstWitnessErrorV0> {
1912 let mut current = root.0;
1913 loop {
1914 match manager.require_node(current)? {
1915 Node::Term(0) => return Ok(None),
1916 Node::Term(terminal) => return Ok(Some(terminal - 1)),
1917 Node::Int { var, lo, hi } => {
1918 let value = assignment
1919 .get(usize::from(var))
1920 .copied()
1921 .ok_or(FirstWitnessErrorV0::MissingAssignment { variable: var })?;
1922 current = if value { hi } else { lo };
1923 }
1924 }
1925 }
1926}
1927
1928pub fn guarded_cascade_winner_is_total_v0(
1929 manager: &FirstWitnessManagerV0,
1930 root: GuardedCascadeWinnerRootV0,
1931) -> Result<bool, FirstWitnessErrorV0> {
1932 let mut seen = BTreeSet::new();
1933 let mut pending = vec![root.0];
1934 while let Some(node_id) = pending.pop() {
1935 if !seen.insert(node_id) {
1936 continue;
1937 }
1938 match manager.require_node(node_id)? {
1939 Node::Term(0) => return Ok(false),
1940 Node::Term(_) => {}
1941 Node::Int { lo, hi, .. } => pending.extend([lo, hi]),
1942 }
1943 }
1944 Ok(true)
1945}
1946
1947pub fn compare_guarded_cascade_winner_functions_v0(
1952 fragment: GuardedCascadeFragmentPredicateV0,
1953 input_root: GuardedCascadeWinnerRootV0,
1954 output_root: GuardedCascadeWinnerRootV0,
1955 winner_defined_for_all_assignments: bool,
1956) -> GuardedCascadeWinnerFunctionEqualityDecisionV0 {
1957 let rule = GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { fragment };
1958 if same_canonical_winner_function_v0(input_root, output_root) {
1959 GuardedCascadeWinnerFunctionEqualityDecisionV0::Equal {
1960 authority: GuardedCascadeWinnerAuthorityV0 {
1961 rule,
1962 root: input_root,
1963 winner_defined_for_all_assignments,
1964 },
1965 }
1966 } else {
1967 GuardedCascadeWinnerFunctionEqualityDecisionV0::Refused {
1968 rule,
1969 refusal: GuardedCascadeWinnerFunctionEqualityRefusalV0::CanonicalRootsDiffer {
1970 input_root,
1971 output_root,
1972 },
1973 }
1974 }
1975}
1976
1977pub fn guarded_cascade_winner_authority_v0(
1978 fragment: GuardedCascadeFragmentPredicateV0,
1979 root: GuardedCascadeWinnerRootV0,
1980 winner_defined_for_all_assignments: bool,
1981) -> GuardedCascadeWinnerAuthorityV0 {
1982 GuardedCascadeWinnerAuthorityV0 {
1983 rule: GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { fragment },
1984 root,
1985 winner_defined_for_all_assignments,
1986 }
1987}
1988
1989pub fn reconcile_guarded_cascade_winner_planes_v0(
1990 authority: &GuardedCascadeWinnerAuthorityV0,
1991 canonical_mtbdd: GuardedCascadeWinnerPlaneAnswerV0,
1992 scenario_sweep: GuardedCascadeWinnerPlaneAnswerV0,
1993) -> Result<GuardedCascadeWinnerPlaneAnswerV0, GuardedCascadeWinnerAuthorityErrorV0> {
1994 #[cfg(test)]
1995 if std::env::var_os("OMENA_G122_INJECT_PREFER_SCENARIO_SWEEP").is_some() {
1996 return Ok(scenario_sweep);
1997 }
1998 match &authority.rule {
1999 GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { .. } => {
2000 if canonical_mtbdd == scenario_sweep {
2001 Ok(canonical_mtbdd)
2002 } else {
2003 Err(
2004 GuardedCascadeWinnerAuthorityErrorV0::InFragmentPlaneDisagreement {
2005 canonical_mtbdd,
2006 scenario_sweep,
2007 },
2008 )
2009 }
2010 }
2011 GuardedCascadeWinnerAuthorityRuleV0::ScenarioSweepOutsideFragment => Ok(scenario_sweep),
2012 }
2013}
2014
2015pub const fn same_canonical_winner_function_v0(
2018 left: GuardedCascadeWinnerRootV0,
2019 right: GuardedCascadeWinnerRootV0,
2020) -> bool {
2021 left.0 == right.0
2022}
2023
2024fn boolean_shortcut(operation: BooleanOperationV0, left: NodeId, right: NodeId) -> Option<NodeId> {
2025 match operation {
2026 BooleanOperationV0::And => {
2027 if left == FALSE_NODE_ID_V0 || right == FALSE_NODE_ID_V0 {
2028 Some(FALSE_NODE_ID_V0)
2029 } else if left == TRUE_NODE_ID_V0 {
2030 Some(right)
2031 } else if right == TRUE_NODE_ID_V0 || left == right {
2032 Some(left)
2033 } else {
2034 None
2035 }
2036 }
2037 BooleanOperationV0::Or => {
2038 if left == TRUE_NODE_ID_V0 || right == TRUE_NODE_ID_V0 {
2039 Some(TRUE_NODE_ID_V0)
2040 } else if left == FALSE_NODE_ID_V0 {
2041 Some(right)
2042 } else if right == FALSE_NODE_ID_V0 || left == right {
2043 Some(left)
2044 } else {
2045 None
2046 }
2047 }
2048 BooleanOperationV0::Xor => {
2049 if left == right {
2050 Some(FALSE_NODE_ID_V0)
2051 } else if left == FALSE_NODE_ID_V0 {
2052 Some(right)
2053 } else if right == FALSE_NODE_ID_V0 {
2054 Some(left)
2055 } else {
2056 None
2057 }
2058 }
2059 }
2060}
2061
2062fn terminal_boolean_result(
2063 operation: BooleanOperationV0,
2064 left: u32,
2065 right: u32,
2066) -> Result<NodeId, FirstWitnessErrorV0> {
2067 if left > 1 {
2068 return Err(FirstWitnessErrorV0::InvalidTerminal(left));
2069 }
2070 if right > 1 {
2071 return Err(FirstWitnessErrorV0::InvalidTerminal(right));
2072 }
2073 let left = left == 1;
2074 let right = right == 1;
2075 Ok(match operation {
2076 BooleanOperationV0::And => left && right,
2077 BooleanOperationV0::Or => left || right,
2078 BooleanOperationV0::Xor => left ^ right,
2079 } as NodeId)
2080}
2081
2082fn canonical_apply_key(
2083 operation: BooleanOperationV0,
2084 left: NodeId,
2085 right: NodeId,
2086) -> ApplyCacheKeyV0 {
2087 let (left, right) = if left <= right {
2088 (left, right)
2089 } else {
2090 (right, left)
2091 };
2092 ApplyCacheKeyV0 {
2093 operation: ApplyOperationV0::Boolean(operation),
2094 left,
2095 right,
2096 }
2097}
2098
2099fn top_variable(left: Node, right: Node) -> u16 {
2100 match (left, right) {
2101 (Node::Int { var: left, .. }, Node::Int { var: right, .. }) => left.min(right),
2102 (Node::Int { var, .. }, Node::Term(_)) | (Node::Term(_), Node::Int { var, .. }) => var,
2103 (Node::Term(_), Node::Term(_)) => {
2104 unreachable!("terminal pairs are handled before recursion")
2105 }
2106 }
2107}
2108
2109fn cofactors(node_id: NodeId, node: Node, variable: u16) -> (NodeId, NodeId) {
2110 match node {
2111 Node::Int { var, lo, hi } if var == variable => (lo, hi),
2112 _ => (node_id, node_id),
2113 }
2114}
2115
2116fn clone_live_node(
2117 old: NodeId,
2118 old_nodes: &[Node],
2119 new_nodes: &mut Vec<Node>,
2120 new_unique: &mut HashMap<(u16, NodeId, NodeId), NodeId>,
2121 remapped: &mut HashMap<NodeId, NodeId>,
2122 visited: &mut usize,
2123) -> Result<NodeId, FirstWitnessErrorV0> {
2124 if let Some(mapped) = remapped.get(&old).copied() {
2125 return Ok(mapped);
2126 }
2127 *visited += 1;
2128 let node = old_nodes
2129 .get(old as usize)
2130 .copied()
2131 .ok_or(FirstWitnessErrorV0::InvalidNode(old))?;
2132 let (var, lo, hi) = match node {
2133 Node::Int { var, lo, hi } => (var, lo, hi),
2134 Node::Term(terminal) => return Err(FirstWitnessErrorV0::InvalidTerminal(terminal)),
2135 };
2136 let low = clone_live_node(lo, old_nodes, new_nodes, new_unique, remapped, visited)?;
2137 let high = clone_live_node(hi, old_nodes, new_nodes, new_unique, remapped, visited)?;
2138 let mapped = if low == high {
2139 low
2140 } else if let Some(node) = new_unique.get(&(var, low, high)).copied() {
2141 node
2142 } else {
2143 let node = u32::try_from(new_nodes.len())
2144 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2145 new_nodes.push(Node::Int {
2146 var,
2147 lo: low,
2148 hi: high,
2149 });
2150 new_unique.insert((var, low, high), node);
2151 node
2152 };
2153 remapped.insert(old, mapped);
2154 Ok(mapped)
2155}
2156
2157#[cfg(test)]
2158mod tests {
2159 use std::time::Instant;
2160
2161 use super::*;
2162
2163 fn manager(shortcuts: bool) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2164 Ok(FirstWitnessManagerV0::new(
2165 VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
2166 FirstWitnessManagerConfigV0 {
2167 shortcuts,
2168 apply_cache_capacity: 32,
2169 rebuild_interval_operations: 64,
2170 },
2171 ))
2172 }
2173
2174 fn winner_manager(
2175 shortcuts: bool,
2176 variable_count: usize,
2177 ) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2178 let atoms = (0..variable_count)
2179 .map(|index| format!("guard-{index}"))
2180 .collect::<Vec<_>>();
2181 let mut manager = FirstWitnessManagerV0::new(
2182 VariableOrderRegistrationV0::site_first_appearance(atoms)?,
2183 FirstWitnessManagerConfigV0 {
2184 shortcuts,
2185 apply_cache_capacity: 16_384,
2186 rebuild_interval_operations: u64::MAX,
2187 },
2188 );
2189 manager.register_declaration_terminals([0, 1, 2])?;
2190 Ok(manager)
2191 }
2192
2193 fn streaming_winner_manager(
2194 variable_count: usize,
2195 declaration_count: usize,
2196 apply_cache_capacity: usize,
2197 rebuild_interval_operations: u64,
2198 ) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2199 let atoms = (0..variable_count)
2200 .map(|index| format!("guard-{index}"))
2201 .collect::<Vec<_>>();
2202 let mut manager = FirstWitnessManagerV0::new(
2203 VariableOrderRegistrationV0::site_first_appearance(atoms)?,
2204 FirstWitnessManagerConfigV0 {
2205 shortcuts: false,
2206 apply_cache_capacity,
2207 rebuild_interval_operations,
2208 },
2209 );
2210 manager.register_declaration_terminals(
2211 (0..declaration_count).filter_map(|id| u32::try_from(id).ok()),
2212 )?;
2213 Ok(manager)
2214 }
2215
2216 #[test]
2217 fn inside_fragment_plane_disagreement_names_both_answers() -> Result<(), String> {
2218 let authority = GuardedCascadeWinnerAuthorityV0 {
2219 rule: GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment {
2220 fragment: GuardedCascadeFragmentPredicateV0 {
2221 element_signature: ".a".to_string(),
2222 property: AuthoredPropertyTextV0::new("color"),
2223 condition_alphabet: vec!["@media (min-width: 1px)".to_string()],
2224 },
2225 },
2226 root: GuardedCascadeWinnerRootV0(2),
2227 winner_defined_for_all_assignments: true,
2228 };
2229 let error = reconcile_guarded_cascade_winner_planes_v0(
2230 &authority,
2231 GuardedCascadeWinnerPlaneAnswerV0::Declaration { declaration_id: 7 },
2232 GuardedCascadeWinnerPlaneAnswerV0::Declaration { declaration_id: 9 },
2233 )
2234 .err()
2235 .ok_or_else(|| "an in-fragment disagreement must be rejected".to_string())?;
2236 let message = error.to_string();
2237 assert!(message.contains("canonicalMtbdd=Declaration { declaration_id: 7 }"));
2238 assert!(message.contains("scenarioSweep=Declaration { declaration_id: 9 }"));
2239 Ok(())
2240 }
2241
2242 fn guarded_root_from_mask(
2243 manager: &mut FirstWitnessManagerV0,
2244 declaration_id: u32,
2245 mask: u64,
2246 variable_count: usize,
2247 ) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
2248 let mut root = manager.declaration_terminal(declaration_id)?;
2249 for variable in (0..variable_count).rev() {
2250 if mask & (1 << variable) != 0 {
2251 root = manager.choose(
2252 u16::try_from(variable)
2253 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?,
2254 GUARDED_CASCADE_BOT_NODE_ID_V0,
2255 root,
2256 )?;
2257 }
2258 }
2259 Ok(GuardedCascadeWinnerRootV0(root))
2260 }
2261
2262 fn guarded_root_from_typed_fragment_mask(
2263 manager: &mut FirstWitnessManagerV0,
2264 declaration_id: u32,
2265 mask: u64,
2266 variable_count: usize,
2267 ) -> Result<GuardedCascadeWinnerRootV0, Box<dyn std::error::Error>> {
2268 let conditions = (0..variable_count)
2269 .filter(|variable| mask & (1 << variable) != 0)
2270 .map(|variable| {
2271 Ok(GuardedCascadeConditionAtomV0::media(
2272 format!("guard-{variable}"),
2273 [u32::try_from(variable)?],
2274 false,
2275 ))
2276 })
2277 .collect::<Result<Vec<_>, std::num::TryFromIntError>>()?;
2278 let alphabet = conditions
2279 .iter()
2280 .map(|condition| condition.atom().to_string())
2281 .collect::<Vec<_>>();
2282 let fragment = GuardedCascadeFragmentV0::admit(
2283 alphabet,
2284 [GuardedCascadeCandidateV0::new(
2285 declaration_id,
2286 ".typed-fragment",
2287 AuthoredPropertyTextV0::new("color"),
2288 declaration_id,
2289 GuardedCascadeSpecificityExactnessV0::Exact,
2290 0,
2291 conditions,
2292 )],
2293 )?;
2294 Ok(build_guarded_cascade_winner_v0(manager, &fragment)?)
2295 }
2296
2297 fn batch_winner_from_entries(
2298 manager: &mut FirstWitnessManagerV0,
2299 entries: &BTreeMap<u64, GuardedCascadeWinnerRootV0>,
2300 ) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
2301 let mut root = GUARDED_CASCADE_BOT_NODE_ID_V0;
2302 for guarded in entries.values().rev() {
2303 root = manager.choose_first_witness(root, guarded.0)?;
2304 }
2305 Ok(GuardedCascadeWinnerRootV0(root))
2306 }
2307
2308 fn next_stream_seed(state: &mut u64) -> u64 {
2309 *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
2310 let mut mixed = *state;
2311 mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
2312 mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
2313 mixed ^ (mixed >> 31)
2314 }
2315
2316 fn intern_terminal_table(
2317 manager: &mut FirstWitnessManagerV0,
2318 values: &[NodeId],
2319 variable: u16,
2320 ) -> Result<NodeId, FirstWitnessErrorV0> {
2321 if values.len() == 1 || values.iter().all(|value| *value == values[0]) {
2322 return Ok(values[0]);
2323 }
2324 let midpoint = values.len() / 2;
2325 let low = intern_terminal_table(manager, &values[..midpoint], variable + 1)?;
2326 let high = intern_terminal_table(manager, &values[midpoint..], variable + 1)?;
2327 manager.choose(variable, low, high)
2328 }
2329
2330 fn assignment_for_index(index: usize, variable_count: usize) -> Vec<bool> {
2331 (0..variable_count)
2332 .map(|variable| index & (1 << (variable_count - variable - 1)) != 0)
2333 .collect()
2334 }
2335
2336 fn winner_truth_table(
2337 manager: &FirstWitnessManagerV0,
2338 root: NodeId,
2339 variable_count: usize,
2340 ) -> Result<Vec<Option<u32>>, FirstWitnessErrorV0> {
2341 (0..(1 << variable_count))
2342 .map(|index| {
2343 evaluate_guarded_cascade_winner_v0(
2344 manager,
2345 GuardedCascadeWinnerRootV0(root),
2346 &assignment_for_index(index, variable_count),
2347 )
2348 })
2349 .collect()
2350 }
2351
2352 fn next_law_seed(state: &mut u64) -> u64 {
2353 *state ^= *state << 13;
2354 *state ^= *state >> 7;
2355 *state ^= *state << 17;
2356 *state
2357 }
2358
2359 #[derive(Debug, Default)]
2360 struct FirstWitnessLawReportV0 {
2361 associativity_violations: usize,
2362 idempotence_violations: usize,
2363 absorption_violations: usize,
2364 left_identity_violations: usize,
2365 right_identity_violations: usize,
2366 result_roots: Vec<NodeId>,
2367 }
2368
2369 struct FirstWitnessLawRunV0 {
2370 report: FirstWitnessLawReportV0,
2371 counters: FirstWitnessChoiceOperationCountersV0,
2372 tables: Vec<Vec<Option<u32>>>,
2373 }
2374
2375 fn run_first_witness_laws(
2376 manager: &mut FirstWitnessManagerV0,
2377 operands: &[[NodeId; 3]],
2378 ) -> Result<FirstWitnessLawReportV0, FirstWitnessErrorV0> {
2379 let mut report = FirstWitnessLawReportV0::default();
2380 let behavior = if std::env::var_os("OMENA_G122_INJECT_FIRST_WITNESS_LAST_WINS").is_some() {
2381 FirstWitnessTerminalBehaviorV0::RightBiased
2382 } else {
2383 FirstWitnessTerminalBehaviorV0::LeftBiased
2384 };
2385 for [left, middle, right] in operands.iter().copied() {
2386 let mut choose = |left, right| {
2387 manager.choose_first_witness_with_terminal_behavior_for_test(left, right, behavior)
2388 };
2389 let left_middle = choose(left, middle)?;
2390 let middle_right = choose(middle, right)?;
2391 let associative_left = choose(left_middle, right)?;
2392 let associative_right = choose(left, middle_right)?;
2393 let idempotent = choose(left, left)?;
2394 let absorbed = choose(left_middle, left)?;
2395 let left_identity = choose(GUARDED_CASCADE_BOT_NODE_ID_V0, left)?;
2396 let right_identity = choose(left, GUARDED_CASCADE_BOT_NODE_ID_V0)?;
2397 report.associativity_violations += usize::from(associative_left != associative_right);
2398 report.idempotence_violations += usize::from(idempotent != left);
2399 report.absorption_violations += usize::from(absorbed != left_middle);
2400 report.left_identity_violations += usize::from(left_identity != left);
2401 report.right_identity_violations += usize::from(right_identity != left);
2402 report.result_roots.extend([
2403 associative_left,
2404 associative_right,
2405 idempotent,
2406 absorbed,
2407 left_identity,
2408 right_identity,
2409 ]);
2410 }
2411 Ok(report)
2412 }
2413
2414 fn seeded_winner_operands(
2415 manager: &mut FirstWitnessManagerV0,
2416 trial_count: usize,
2417 variable_count: usize,
2418 ) -> Result<Vec<[NodeId; 3]>, FirstWitnessErrorV0> {
2419 let terminals = [
2420 GUARDED_CASCADE_BOT_NODE_ID_V0,
2421 manager.declaration_terminal(0)?,
2422 manager.declaration_terminal(1)?,
2423 manager.declaration_terminal(2)?,
2424 ];
2425 let table_size = 1 << variable_count;
2426 let mut seed = 0x1220_cafe_dead_beef_u64;
2427 (0..trial_count)
2428 .map(|_| {
2429 let mut roots = [GUARDED_CASCADE_BOT_NODE_ID_V0; 3];
2430 for root in &mut roots {
2431 let values = (0..table_size)
2432 .map(|_| {
2433 let terminal = next_law_seed(&mut seed) as usize % terminals.len();
2434 terminals[terminal]
2435 })
2436 .collect::<Vec<_>>();
2437 *root = intern_terminal_table(manager, &values, 0)?;
2438 }
2439 Ok(roots)
2440 })
2441 .collect()
2442 }
2443
2444 #[test]
2445 fn first_witness_laws_hold_in_both_modes_and_the_switch_is_live()
2446 -> Result<(), FirstWitnessErrorV0> {
2447 const TRIAL_COUNT: usize = 256;
2448 const VARIABLE_COUNT: usize = 3;
2449 fn run(shortcuts: bool) -> Result<FirstWitnessLawRunV0, FirstWitnessErrorV0> {
2450 let mut manager = winner_manager(shortcuts, VARIABLE_COUNT)?;
2451 let operands = seeded_winner_operands(&mut manager, TRIAL_COUNT, VARIABLE_COUNT)?;
2452 let report = run_first_witness_laws(&mut manager, &operands)?;
2453 let tables = report
2454 .result_roots
2455 .iter()
2456 .map(|root| winner_truth_table(&manager, *root, VARIABLE_COUNT))
2457 .collect::<Result<Vec<_>, _>>()?;
2458 Ok(FirstWitnessLawRunV0 {
2459 report,
2460 counters: manager.first_witness_counters(),
2461 tables,
2462 })
2463 }
2464
2465 let shortcut = run(true)?;
2466 let recursive = run(false)?;
2467 for report in [&shortcut.report, &recursive.report] {
2468 assert_eq!(report.associativity_violations, 0);
2469 assert_eq!(report.idempotence_violations, 0);
2470 assert_eq!(report.absorption_violations, 0);
2471 assert_eq!(report.left_identity_violations, 0);
2472 assert_eq!(report.right_identity_violations, 0);
2473 }
2474 assert_eq!(shortcut.tables, recursive.tables);
2475 assert_eq!(shortcut.report.result_roots, recursive.report.result_roots);
2476 assert!(
2477 recursive.counters.recursive_invocations > shortcut.counters.recursive_invocations,
2478 "disabling shortcuts must reach more recursive calls"
2479 );
2480 assert!(
2481 recursive.counters.apply_cache_lookups > shortcut.counters.apply_cache_lookups,
2482 "disabling shortcuts must reach more apply-cache probes"
2483 );
2484 eprintln!(
2485 "{{\"trialCount\":{TRIAL_COUNT},\"variableCount\":{VARIABLE_COUNT},\"violations\":0,\"shortcutsOn\":{{\"recursiveInvocations\":{},\"applyCacheLookups\":{}}},\"shortcutsOff\":{{\"recursiveInvocations\":{},\"applyCacheLookups\":{}}}}}",
2486 shortcut.counters.recursive_invocations,
2487 shortcut.counters.apply_cache_lookups,
2488 recursive.counters.recursive_invocations,
2489 recursive.counters.apply_cache_lookups,
2490 );
2491 Ok(())
2492 }
2493
2494 #[test]
2495 fn typed_fragment_operands_cover_the_first_witness_laws()
2496 -> Result<(), Box<dyn std::error::Error>> {
2497 const VARIABLE_COUNT: usize = 3;
2498 let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2499 let operands = [[
2500 guarded_root_from_typed_fragment_mask(&mut manager, 0, 0b001, VARIABLE_COUNT)?.0,
2501 guarded_root_from_typed_fragment_mask(&mut manager, 1, 0b010, VARIABLE_COUNT)?.0,
2502 guarded_root_from_typed_fragment_mask(&mut manager, 2, 0b100, VARIABLE_COUNT)?.0,
2503 ]];
2504 let report = run_first_witness_laws(&mut manager, &operands)?;
2505 assert_eq!(report.associativity_violations, 0);
2506 assert_eq!(report.idempotence_violations, 0);
2507 assert_eq!(report.absorption_violations, 0);
2508 assert_eq!(report.left_identity_violations, 0);
2509 assert_eq!(report.right_identity_violations, 0);
2510 Ok(())
2511 }
2512
2513 #[test]
2514 fn first_witness_negative_controls_are_observed_by_the_product_recursion()
2515 -> Result<(), FirstWitnessErrorV0> {
2516 const VARIABLE_COUNT: usize = 1;
2517 let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2518 let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2519 let first = manager.declaration_terminal(0)?;
2520 let second = manager.declaration_terminal(1)?;
2521 let guarded_first = manager.choose(0, bot, first)?;
2522 let guarded_second = manager.choose(0, bot, second)?;
2523 let right_biased_pair = manager.choose_first_witness_with_terminal_behavior_for_test(
2524 guarded_first,
2525 guarded_second,
2526 FirstWitnessTerminalBehaviorV0::RightBiased,
2527 )?;
2528 let right_biased_absorbed = manager.choose_first_witness_with_terminal_behavior_for_test(
2529 right_biased_pair,
2530 guarded_first,
2531 FirstWitnessTerminalBehaviorV0::RightBiased,
2532 )?;
2533 assert_ne!(
2534 right_biased_pair, right_biased_absorbed,
2535 "last-wins must violate left-regular-band absorption"
2536 );
2537 let left_right = manager.choose_first_witness(guarded_first, guarded_second)?;
2538 let right_left = manager.choose_first_witness(guarded_second, guarded_first)?;
2539 assert_ne!(
2540 left_right, right_left,
2541 "the first-witness operation must expose a non-commutativity witness"
2542 );
2543 eprintln!(
2544 "{{\"lastWinsAbsorptionViolations\":1,\"nonCommutativityWitnesses\":1,\"rightBiasedPair\":{right_biased_pair},\"rightBiasedAbsorbed\":{right_biased_absorbed},\"leftRight\":{left_right},\"rightLeft\":{right_left}}}"
2545 );
2546 Ok(())
2547 }
2548
2549 #[test]
2550 fn exhaustive_first_applicable_oracle_and_canonicality_both_directions()
2551 -> Result<(), FirstWitnessErrorV0> {
2552 const VARIABLE_COUNT: usize = 12;
2553 let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2554 let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2555 let declarations = [
2556 manager.declaration_terminal(0)?,
2557 manager.declaration_terminal(1)?,
2558 manager.declaration_terminal(2)?,
2559 ];
2560 let table_size = 1 << VARIABLE_COUNT;
2561 let mut seed = 0xa2a3_1220_5eed_u64;
2562 let mut operand_tables = Vec::new();
2563 let mut operand_roots = Vec::new();
2564 for _ in 0..declarations.len() {
2565 let table = (0..table_size)
2566 .map(|_| {
2567 if next_law_seed(&mut seed) & 1 == 0 {
2568 bot
2569 } else {
2570 declarations[operand_tables.len()]
2571 }
2572 })
2573 .collect::<Vec<_>>();
2574 operand_roots.push(intern_terminal_table(&mut manager, &table, 0)?);
2575 operand_tables.push(table);
2576 }
2577 let mut product_root = bot;
2578 for operand in &operand_roots {
2579 product_root = manager.choose_first_witness(product_root, *operand)?;
2580 }
2581 let oracle_table = (0..table_size)
2582 .map(|index| {
2583 operand_tables
2584 .iter()
2585 .find_map(|table| (table[index] != bot).then_some(table[index]))
2586 .unwrap_or(bot)
2587 })
2588 .collect::<Vec<_>>();
2589 let independent_root = intern_terminal_table(&mut manager, &oracle_table, 0)?;
2590 assert_eq!(
2591 product_root, independent_root,
2592 "pointwise table interning and the product fold must canonicalize to one NodeId"
2593 );
2594 let product_table = (0..table_size)
2595 .map(|index| {
2596 evaluate_guarded_cascade_winner_v0(
2597 &manager,
2598 GuardedCascadeWinnerRootV0(product_root),
2599 &assignment_for_index(index, VARIABLE_COUNT),
2600 )
2601 })
2602 .collect::<Result<Vec<_>, _>>()?;
2603 let oracle_declarations = oracle_table
2604 .iter()
2605 .map(|terminal| {
2606 if *terminal == bot {
2607 Ok(None)
2608 } else {
2609 match manager.require_node(*terminal)? {
2610 Node::Term(value) => Ok(Some(value - 1)),
2611 Node::Int { .. } => Err(FirstWitnessErrorV0::InvalidNode(*terminal)),
2612 }
2613 }
2614 })
2615 .collect::<Result<Vec<_>, _>>()?;
2616 let mismatch_count = product_table
2617 .iter()
2618 .zip(&oracle_declarations)
2619 .filter(|(product, oracle)| product != oracle)
2620 .count();
2621 assert_eq!(mismatch_count, 0);
2622 let mut different_table = oracle_table.clone();
2623 different_table[0] = if different_table[0] == bot {
2624 declarations[0]
2625 } else {
2626 bot
2627 };
2628 let different_root = intern_terminal_table(&mut manager, &different_table, 0)?;
2629 assert_ne!(
2630 product_root, different_root,
2631 "different terminal functions must not share a NodeId"
2632 );
2633 let source = include_str!("first_witness.rs");
2634 let oracle_start = source
2635 .find("fn exhaustive_first_applicable_oracle_and_canonicality_both_directions()")
2636 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2637 let oracle_end = source[oracle_start..]
2638 .find("fn broken_recursion_masking_table_pins_each_law_cell()")
2639 .map(|offset| oracle_start + offset)
2640 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2641 let oracle_source = &source[oracle_start..oracle_end];
2642 for forbidden in [
2643 ["cascade", "_property("].concat(),
2644 ["rank_cascade", "_items("].concat(),
2645 ["select_open_world", "_cascade_winner("].concat(),
2646 ] {
2647 assert!(
2648 !oracle_source.contains(forbidden.as_str()),
2649 "the A2 oracle must remain independent of cascade ranking entry point {forbidden}"
2650 );
2651 }
2652 eprintln!(
2653 "{{\"variableCount\":{VARIABLE_COUNT},\"checkedPointCount\":{table_size},\"mismatchCount\":{mismatch_count},\"productNodeId\":{product_root},\"independentNodeId\":{independent_root},\"differentNodeId\":{different_root}}}"
2654 );
2655 Ok(())
2656 }
2657
2658 #[test]
2659 fn broken_recursion_masking_table_pins_each_law_cell() -> Result<(), FirstWitnessErrorV0> {
2660 fn cell(shortcuts: bool) -> Result<[bool; 4], FirstWitnessErrorV0> {
2661 let mut manager = winner_manager(shortcuts, 2)?;
2662 let behavior = FirstWitnessTerminalBehaviorV0::BrokenRecursion;
2663 let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2664 let declaration = manager.declaration_terminal(1)?;
2665 let guarded = manager.choose(0, bot, declaration)?;
2666 let idempotence = manager
2667 .choose_first_witness_with_terminal_behavior_for_test(guarded, guarded, behavior)?
2668 == guarded;
2669 let bot_guarded = manager
2670 .choose_first_witness_with_terminal_behavior_for_test(bot, guarded, behavior)?;
2671 let bot_bot =
2672 manager.choose_first_witness_with_terminal_behavior_for_test(bot, bot, behavior)?;
2673 let associative_left = manager
2674 .choose_first_witness_with_terminal_behavior_for_test(bot_bot, guarded, behavior)?;
2675 let associative_right = manager.choose_first_witness_with_terminal_behavior_for_test(
2676 bot,
2677 bot_guarded,
2678 behavior,
2679 )?;
2680 let associativity = associative_left == associative_right;
2681 let absorbed = manager.choose_first_witness_with_terminal_behavior_for_test(
2682 bot_guarded,
2683 bot,
2684 behavior,
2685 )?;
2686 let absorption = absorbed == bot_guarded;
2687 let a2 = winner_truth_table(&manager, bot_guarded, 2)?
2688 == winner_truth_table(&manager, guarded, 2)?;
2689 Ok([associativity, idempotence, absorption, a2])
2690 }
2691
2692 let shortcuts_on = cell(true)?;
2693 let shortcuts_off = cell(false)?;
2694 assert_eq!(shortcuts_on, [false, true, true, false]);
2695 assert_eq!(shortcuts_off, [false, false, false, false]);
2696 eprintln!(
2697 "{{\"brokenRecursion\":true,\"shortcutsOn\":{{\"associativity\":false,\"idempotence\":true,\"absorption\":true,\"a2\":false}},\"shortcutsOff\":{{\"associativity\":false,\"idempotence\":false,\"absorption\":false,\"a2\":false}}}}"
2698 );
2699 Ok(())
2700 }
2701
2702 #[test]
2703 fn incremental_winner_matches_batch_and_pointwise_spec_after_every_streaming_edit()
2704 -> Result<(), Box<dyn std::error::Error>> {
2705 const SEED_COUNT: usize = 60;
2706 const EDIT_COUNT: usize = 200;
2707 const VARIABLE_COUNT: usize = 6;
2708 const DECLARATION_COUNT: usize = 512;
2709 let mut checked_trials = 0usize;
2710 let mut checked_points = 0usize;
2711 for seed_index in 0..SEED_COUNT {
2712 let mut manager =
2713 streaming_winner_manager(VARIABLE_COUNT, DECLARATION_COUNT, 16_384, u64::MAX)?;
2714 let guarded = (0..DECLARATION_COUNT)
2715 .map(|index| {
2716 let mask = 1_u64 << (index % VARIABLE_COUNT)
2717 | 1_u64 << ((index * 5 + 1) % VARIABLE_COUNT);
2718 let declaration_id = u32::try_from(index)
2719 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?;
2720 if index == 0 {
2721 guarded_root_from_typed_fragment_mask(
2722 &mut manager,
2723 declaration_id,
2724 mask,
2725 VARIABLE_COUNT,
2726 )
2727 } else {
2728 Ok(guarded_root_from_mask(
2729 &mut manager,
2730 declaration_id,
2731 mask,
2732 VARIABLE_COUNT,
2733 )?)
2734 }
2735 })
2736 .collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;
2737 let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2738 let mut entries = BTreeMap::new();
2739 for (index, guarded_root) in guarded.iter().copied().take(24).enumerate() {
2740 let key = u64::try_from(index)
2741 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2742 entries.insert(key, guarded_root);
2743 tree.insert(&mut manager, key, guarded_root)?;
2744 }
2745 let mut state = 0xa400_0000_1220_0000_u64
2746 ^ u64::try_from(seed_index)
2747 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2748 for edit_index in 0..EDIT_COUNT {
2749 let insert = entries.len() < 8 || next_stream_seed(&mut state) & 1 == 0;
2750 if insert {
2751 let declaration_index = next_stream_seed(&mut state) as usize % guarded.len();
2752 let mut key = next_stream_seed(&mut state) % 100_000;
2753 while entries.contains_key(&key) {
2754 key = key.wrapping_add(1);
2755 }
2756 entries.insert(key, guarded[declaration_index]);
2757 tree.insert(&mut manager, key, guarded[declaration_index])?;
2758 } else {
2759 let target = next_stream_seed(&mut state) as usize % entries.len();
2760 let key = entries
2761 .keys()
2762 .nth(target)
2763 .copied()
2764 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2765 entries.remove(&key);
2766 tree.remove(&mut manager, &key)?;
2767 }
2768 let batch = batch_winner_from_entries(&mut manager, &entries)?;
2769 assert_eq!(
2770 tree.root().node_id(),
2771 batch.node_id(),
2772 "A4 mismatch at seed {seed_index}, edit {edit_index}: incremental={} batch={}",
2773 tree.root().node_id(),
2774 batch.node_id(),
2775 );
2776 for assignment_index in 0..(1 << VARIABLE_COUNT) {
2777 let assignment = assignment_for_index(assignment_index, VARIABLE_COUNT);
2778 let expected = entries.values().rev().find_map(|guarded_root| {
2779 evaluate_guarded_cascade_winner_v0(&manager, *guarded_root, &assignment)
2780 .ok()
2781 .flatten()
2782 });
2783 let actual =
2784 evaluate_guarded_cascade_winner_v0(&manager, tree.root(), &assignment)?;
2785 assert_eq!(
2786 actual, expected,
2787 "pointwise A4 mismatch at seed {seed_index}, edit {edit_index}, assignment {assignment_index}"
2788 );
2789 checked_points += 1;
2790 }
2791 checked_trials += 1;
2792 }
2793 }
2794 eprintln!(
2795 "{{\"seedCount\":{SEED_COUNT},\"editsPerSeed\":{EDIT_COUNT},\"checkedTrials\":{checked_trials},\"checkedPoints\":{checked_points},\"mismatchCount\":0,\"streamingNoRestoration\":true}}"
2796 );
2797 Ok(())
2798 }
2799
2800 #[test]
2801 fn incremental_winner_reports_logarithmic_aggregate_updates_and_compression()
2802 -> Result<(), FirstWitnessErrorV0> {
2803 const VARIABLE_COUNT: usize = 12;
2804 const EDIT_COUNT: usize = 128;
2805 let mut scale_rows = Vec::new();
2806 for entry_count in [128_usize, 512, 2_048] {
2807 let declaration_count = entry_count + EDIT_COUNT;
2808 let mut manager =
2809 streaming_winner_manager(VARIABLE_COUNT, declaration_count, 16_384, u64::MAX)?;
2810 let guarded = (0..declaration_count)
2811 .map(|index| {
2812 guarded_root_from_mask(
2813 &mut manager,
2814 u32::try_from(index)
2815 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2816 1 << (index % VARIABLE_COUNT),
2817 VARIABLE_COUNT,
2818 )
2819 })
2820 .collect::<Result<Vec<_>, _>>()?;
2821 let mut entries = BTreeMap::new();
2822 let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2823 for (key, root) in guarded.iter().copied().take(entry_count).enumerate() {
2824 let key = u64::try_from(key)
2825 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2826 entries.insert(key, root);
2827 tree.insert(&mut manager, key, root)?;
2828 }
2829 let initial_updates = tree.aggregate_updates();
2830 let mut linear_refold_updates = 0_u64;
2831 let mut state = 0x3a00_0000_u64
2832 ^ u64::try_from(entry_count)
2833 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2834 for edit_index in 0..EDIT_COUNT {
2835 let batch = if edit_index % 2 == 0 {
2836 let target = next_stream_seed(&mut state) as usize % entries.len();
2837 let key = entries
2838 .keys()
2839 .nth(target)
2840 .copied()
2841 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2842 entries.remove(&key);
2843 if edit_index % 4 < 2 {
2844 let batch = batch_winner_from_entries(&mut manager, &entries)?;
2845 tree.remove(&mut manager, &key)?;
2846 batch
2847 } else {
2848 tree.remove(&mut manager, &key)?;
2849 batch_winner_from_entries(&mut manager, &entries)?
2850 }
2851 } else {
2852 let declaration_index = entry_count + edit_index / 2;
2853 let key = 1_000_000_u64
2854 + u64::try_from(declaration_index)
2855 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2856 entries.insert(key, guarded[declaration_index]);
2857 if edit_index % 4 < 2 {
2858 let batch = batch_winner_from_entries(&mut manager, &entries)?;
2859 tree.insert(&mut manager, key, guarded[declaration_index])?;
2860 batch
2861 } else {
2862 tree.insert(&mut manager, key, guarded[declaration_index])?;
2863 batch_winner_from_entries(&mut manager, &entries)?
2864 }
2865 };
2866 linear_refold_updates += u64::try_from(entries.len())
2867 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2868 assert_eq!(tree.root().node_id(), batch.node_id());
2869 }
2870 let aggregate_updates = tree.aggregate_updates() - initial_updates;
2871 let updates_per_edit = aggregate_updates as f64 / EDIT_COUNT as f64;
2872 let ratio = updates_per_edit / (entry_count as f64).log2();
2873 scale_rows.push((
2874 entry_count,
2875 aggregate_updates,
2876 updates_per_edit,
2877 ratio,
2878 linear_refold_updates,
2879 ));
2880 }
2881
2882 let mut compression_rows = Vec::new();
2883 for entry_count in [8_usize, 16, 24] {
2884 const COMPRESSION_VARIABLE_COUNT: usize = 24;
2885 let mut manager = streaming_winner_manager(
2886 COMPRESSION_VARIABLE_COUNT,
2887 entry_count,
2888 16_384,
2889 u64::MAX,
2890 )?;
2891 let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2892 for index in 0..entry_count {
2893 let root = guarded_root_from_mask(
2894 &mut manager,
2895 u32::try_from(index)
2896 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2897 1 << (index % COMPRESSION_VARIABLE_COUNT),
2898 COMPRESSION_VARIABLE_COUNT,
2899 )?;
2900 tree.insert(
2901 &mut manager,
2902 u64::try_from(index)
2903 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?,
2904 root,
2905 )?;
2906 }
2907 let node_count = manager.reachable_winner_node_count(tree.root())?;
2908 let compression = (1_u64 << COMPRESSION_VARIABLE_COUNT) as f64 / node_count as f64;
2909 compression_rows.push((entry_count, node_count, compression));
2910 }
2911 eprintln!(
2912 "{{\"declaredSynthetic\":true,\"streamingNoRestoration\":true,\"alternatedMeasurementOrder\":true,\"scaleRows\":{scale_rows:?},\"compressionRows\":{compression_rows:?},\"pilotAggregateUpdateRatios\":[6.07,6.29,6.46],\"pilotCompressionBand\":[20998,453438]}}"
2913 );
2914 assert!(scale_rows.iter().all(|row| row.2 < row.0 as f64));
2915 assert!(
2916 scale_rows.iter().all(|row| (1.5..=3.0).contains(&row.3)),
2917 "aggregate updates per edit must stay within the measured log2 coefficient band"
2918 );
2919 assert!(compression_rows.iter().all(|row| row.2 > 1.0));
2920 assert!(
2921 compression_rows
2922 .windows(2)
2923 .all(|pair| pair[0].1 < pair[1].1),
2924 "the three compression observations must have distinct increasing node counts"
2925 );
2926 Ok(())
2927 }
2928
2929 #[derive(Debug)]
2930 struct ReclamationMeasurementV0 {
2931 interval_operations: u64,
2932 rebuild_count: usize,
2933 maximum_nodes_before: usize,
2934 minimum_nodes_after: usize,
2935 final_total_nodes: usize,
2936 final_live_nodes: usize,
2937 rebuild_elapsed_nanos: u128,
2938 }
2939
2940 fn measure_incremental_winner_reclamation(
2941 interval_operations: u64,
2942 ) -> Result<ReclamationMeasurementV0, FirstWitnessErrorV0> {
2943 const VARIABLE_COUNT: usize = 10;
2944 const ENTRY_COUNT: usize = 128;
2945 const EDIT_COUNT: usize = 1_000;
2946 const DECLARATION_COUNT: usize = ENTRY_COUNT + EDIT_COUNT;
2947 let mut manager = streaming_winner_manager(
2948 VARIABLE_COUNT,
2949 DECLARATION_COUNT,
2950 4_096,
2951 interval_operations,
2952 )?;
2953 let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2954 let mut keys = BTreeMap::new();
2955 for index in 0..ENTRY_COUNT {
2956 let root = guarded_root_from_mask(
2957 &mut manager,
2958 u32::try_from(index)
2959 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2960 1 << (index % VARIABLE_COUNT),
2961 VARIABLE_COUNT,
2962 )?;
2963 let key =
2964 u64::try_from(index).map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2965 keys.insert(
2966 key,
2967 u32::try_from(index)
2968 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2969 );
2970 tree.insert(&mut manager, key, root)?;
2971 }
2972 let mut state = 0x3c00_1220_5eed_u64 ^ interval_operations;
2973 let mut rebuild_count = 0usize;
2974 let mut maximum_nodes_before = 0usize;
2975 let mut minimum_nodes_after = usize::MAX;
2976 let mut rebuild_elapsed_nanos = 0u128;
2977 for edit in 0..EDIT_COUNT {
2978 if edit % 2 == 0 {
2979 let target = next_stream_seed(&mut state) as usize % keys.len();
2980 let key = keys
2981 .keys()
2982 .nth(target)
2983 .copied()
2984 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2985 keys.remove(&key);
2986 tree.remove(&mut manager, &key)?;
2987 } else {
2988 let declaration = ENTRY_COUNT + edit;
2989 let mut key = next_stream_seed(&mut state) % 1_000_000;
2990 while keys.contains_key(&key) {
2991 key = key.wrapping_add(1);
2992 }
2993 let root = guarded_root_from_mask(
2994 &mut manager,
2995 u32::try_from(declaration)
2996 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2997 1 << (declaration % VARIABLE_COUNT)
2998 | 1 << ((declaration * 7 + 1) % VARIABLE_COUNT),
2999 VARIABLE_COUNT,
3000 )?;
3001 keys.insert(
3002 key,
3003 u32::try_from(declaration)
3004 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3005 );
3006 tree.insert(&mut manager, key, root)?;
3007 }
3008 let started = Instant::now();
3009 if let Some(report) = tree.reclaim_manager_if_due(&mut manager)? {
3010 rebuild_elapsed_nanos += started.elapsed().as_nanos();
3011 rebuild_count += 1;
3012 maximum_nodes_before = maximum_nodes_before.max(report.nodes_before);
3013 minimum_nodes_after = minimum_nodes_after.min(report.nodes_after);
3014 }
3015 let expected = keys.last_key_value().map(|(_, declaration)| *declaration);
3016 assert_eq!(
3017 evaluate_guarded_cascade_winner_v0(&manager, tree.root(), &[true; VARIABLE_COUNT],)?,
3018 expected,
3019 "reclamation must remap every cached aggregate and guarded leaf"
3020 );
3021 }
3022 let final_live_nodes = manager.reachable_winner_node_count(tree.root())?;
3023 Ok(ReclamationMeasurementV0 {
3024 interval_operations,
3025 rebuild_count,
3026 maximum_nodes_before,
3027 minimum_nodes_after: if rebuild_count == 0 {
3028 manager.node_count()
3029 } else {
3030 minimum_nodes_after
3031 },
3032 final_total_nodes: manager.node_count(),
3033 final_live_nodes,
3034 rebuild_elapsed_nanos,
3035 })
3036 }
3037
3038 #[test]
3039 fn manager_reclamation_is_remeasured_with_declaration_terminals()
3040 -> Result<(), Box<dyn std::error::Error>> {
3041 let candidates = [4_096_u64, 16_384, 65_536]
3042 .into_iter()
3043 .map(measure_incremental_winner_reclamation)
3044 .collect::<Result<Vec<_>, _>>()?;
3045 let disabled = measure_incremental_winner_reclamation(u64::MAX)?;
3046 let selected = candidates.iter().rev().find(|row| {
3047 row.rebuild_count > 0
3048 && row.maximum_nodes_before <= row.minimum_nodes_after.saturating_mul(64)
3049 });
3050 let selected = selected.ok_or_else(|| {
3051 std::io::Error::other(format!(
3052 "no reclamation interval rebuilt the MTBDD-terminal manager within the retained-to-live ceiling: {candidates:?}"
3053 ))
3054 })?;
3055 assert!(selected.rebuild_count > 0);
3056 assert!(
3057 selected
3058 .final_total_nodes
3059 .saturating_mul(disabled.final_live_nodes)
3060 < disabled
3061 .final_total_nodes
3062 .saturating_mul(selected.final_live_nodes),
3063 "reclamation must lower the retained-to-live node ratio"
3064 );
3065 eprintln!(
3066 "{{\"declaredSynthetic\":true,\"terminalAlphabet\":\"declarationIdPlusBot\",\"candidateRows\":{candidates:?},\"selectedIntervalOperations\":{},\"disabledRow\":{disabled:?},\"amortizedSelectedRebuildNanosPerEdit\":{}}}",
3067 selected.interval_operations,
3068 selected.rebuild_elapsed_nanos / 1_000,
3069 );
3070 Ok(())
3071 }
3072
3073 #[derive(Debug)]
3074 struct CacheBudgetMeasurementV0 {
3075 capacity: usize,
3076 cache_occupancy: usize,
3077 tree_elapsed_nanos: u128,
3078 linear_elapsed_nanos: u128,
3079 winner: &'static str,
3080 restoration_protocol: bool,
3081 choice_counters: FirstWitnessChoiceOperationCountersV0,
3082 }
3083
3084 fn measure_incremental_winner_cache_budget(
3085 capacity: usize,
3086 restoration_protocol: bool,
3087 ) -> Result<CacheBudgetMeasurementV0, FirstWitnessErrorV0> {
3088 const VARIABLE_COUNT: usize = 12;
3089 const ENTRY_COUNT: usize = 512;
3090 const EDIT_COUNT: usize = 192;
3091 const DECLARATION_COUNT: usize = ENTRY_COUNT + EDIT_COUNT;
3092 let mut manager =
3093 streaming_winner_manager(VARIABLE_COUNT, DECLARATION_COUNT, capacity, u64::MAX)?;
3094 let mut entries = BTreeMap::new();
3095 let mut tree = IncrementalGuardedCascadeWinnerV0::new();
3096 for index in 0..ENTRY_COUNT {
3097 let root = guarded_root_from_mask(
3098 &mut manager,
3099 u32::try_from(index)
3100 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3101 1 << (index % VARIABLE_COUNT),
3102 VARIABLE_COUNT,
3103 )?;
3104 let key =
3105 u64::try_from(index).map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
3106 entries.insert(key, root);
3107 tree.insert(&mut manager, key, root)?;
3108 }
3109 let mut tree_elapsed_nanos = 0_u128;
3110 let mut linear_elapsed_nanos = 0_u128;
3111 for edit in 0..EDIT_COUNT {
3112 let declaration = ENTRY_COUNT + edit;
3113 let key = u64::try_from(edit % ENTRY_COUNT)
3114 .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
3115 let root = guarded_root_from_mask(
3116 &mut manager,
3117 u32::try_from(declaration)
3118 .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3119 1 << (declaration % VARIABLE_COUNT) | 1 << ((declaration * 5 + 1) % VARIABLE_COUNT),
3120 VARIABLE_COUNT,
3121 )?;
3122 let previous_root = entries
3123 .insert(key, root)
3124 .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
3125 let batch = if edit % 2 == 0 {
3126 let started = Instant::now();
3127 let batch = batch_winner_from_entries(&mut manager, &entries)?;
3128 linear_elapsed_nanos += started.elapsed().as_nanos();
3129 let started = Instant::now();
3130 tree.insert(&mut manager, key, root)?;
3131 tree_elapsed_nanos += started.elapsed().as_nanos();
3132 batch
3133 } else {
3134 let started = Instant::now();
3135 tree.insert(&mut manager, key, root)?;
3136 tree_elapsed_nanos += started.elapsed().as_nanos();
3137 let started = Instant::now();
3138 let batch = batch_winner_from_entries(&mut manager, &entries)?;
3139 linear_elapsed_nanos += started.elapsed().as_nanos();
3140 batch
3141 };
3142 assert_eq!(tree.root().node_id(), batch.node_id());
3143 if restoration_protocol {
3144 entries.insert(key, previous_root);
3145 tree.insert(&mut manager, key, previous_root)?;
3146 }
3147 }
3148 #[cfg(test)]
3149 if std::env::var_os("OMENA_G122_INJECT_UNCONDITIONAL_TREE_SPEEDUP").is_some() {
3150 tree_elapsed_nanos = 1;
3151 }
3152 Ok(CacheBudgetMeasurementV0 {
3153 capacity,
3154 cache_occupancy: manager.apply_cache_len(),
3155 tree_elapsed_nanos,
3156 linear_elapsed_nanos,
3157 winner: if tree_elapsed_nanos < linear_elapsed_nanos {
3158 "incrementalTree"
3159 } else {
3160 "warmLinearRefold"
3161 },
3162 restoration_protocol,
3163 choice_counters: manager.first_witness_counters(),
3164 })
3165 }
3166
3167 #[test]
3168 fn apply_cache_budget_condition_is_measured_at_three_points() -> Result<(), FirstWitnessErrorV0>
3169 {
3170 let unbounded_probe = measure_incremental_winner_cache_budget(1_000_000, false)?;
3171 let working_set = unbounded_probe.cache_occupancy.max(3);
3172 let rows = [
3173 measure_incremental_winner_cache_budget((working_set / 16).max(1), false)?,
3174 measure_incremental_winner_cache_budget(working_set, false)?,
3175 measure_incremental_winner_cache_budget(working_set.saturating_mul(2), false)?,
3176 ];
3177 let restoration_rows = [
3178 measure_incremental_winner_cache_budget((working_set / 16).max(1), true)?,
3179 measure_incremental_winner_cache_budget(working_set, true)?,
3180 measure_incremental_winner_cache_budget(working_set.saturating_mul(2), true)?,
3181 ];
3182 eprintln!(
3183 "{{\"declaredSynthetic\":true,\"terminalAlphabet\":\"declarationIdPlusBot\",\"alternatedMeasurementOrder\":true,\"streamingNoRestoration\":true,\"workingSetEntries\":{working_set},\"unboundedProbe\":{unbounded_probe:?},\"budgetRows\":{rows:?},\"restorationBiasRows\":{restoration_rows:?},\"claim\":\"wall-clock benefit is conditional on the apply-cache budget\"}}"
3184 );
3185 assert!(rows[0].capacity < working_set);
3186 assert!(rows[1].capacity >= working_set);
3187 assert!(rows[2].capacity > working_set);
3188 assert!(rows.iter().all(|row| row.cache_occupancy <= row.capacity));
3189 assert!(
3190 rows.iter()
3191 .all(|row| row.tree_elapsed_nanos > 0 && row.linear_elapsed_nanos > 0)
3192 );
3193 assert_eq!(
3194 rows[0].winner, "incrementalTree",
3195 "the below-working-set budget must retain the measured tree win"
3196 );
3197 let crossover_after_low_budget = rows[1..]
3198 .iter()
3199 .position(|row| row.winner == "warmLinearRefold");
3200 assert!(
3201 crossover_after_low_budget.is_some(),
3202 "at least one at-or-above-working-set budget must retain the measured linear-refold win"
3203 );
3204 assert!(rows.iter().all(|row| !row.restoration_protocol));
3205 for (streaming, restoration) in rows.iter().zip(restoration_rows.iter()) {
3206 assert!(restoration.restoration_protocol);
3207 assert_eq!(streaming.capacity, restoration.capacity);
3208 assert_ne!(
3209 restoration.choice_counters, streaming.choice_counters,
3210 "restoring every edit must move the product-operation counters at every cache point"
3211 );
3212 assert_ne!(
3213 (
3214 restoration.choice_counters.apply_cache_lookups,
3215 restoration.choice_counters.apply_cache_hits,
3216 restoration.cache_occupancy,
3217 ),
3218 (
3219 streaming.choice_counters.apply_cache_lookups,
3220 streaming.choice_counters.apply_cache_hits,
3221 streaming.cache_occupancy,
3222 ),
3223 "the restoration protocol must move the measured cache table at every cache point"
3224 );
3225 }
3226 Ok(())
3227 }
3228
3229 fn guarded_fragment_node_count(
3230 fragment: &GuardedCascadeFragmentV0<usize>,
3231 order: VariableOrderRegistrationV0,
3232 ) -> Result<usize, FirstWitnessErrorV0> {
3233 let mut manager = FirstWitnessManagerV0::new(
3234 order,
3235 FirstWitnessManagerConfigV0 {
3236 shortcuts: false,
3237 apply_cache_capacity: 65_536,
3238 rebuild_interval_operations: u64::MAX,
3239 },
3240 );
3241 let root = build_guarded_cascade_winner_v0(&mut manager, fragment)?;
3242 manager.reachable_winner_node_count(root)
3243 }
3244
3245 #[test]
3246 fn at_rule_nesting_dfs_registration_pins_the_blocked_pair_falsifier()
3247 -> Result<(), Box<dyn std::error::Error>> {
3248 const PAIR_COUNT: usize = 12;
3249 const INTERLEAVED_CEILING: usize = 4 * PAIR_COUNT;
3250 let contexts = (0..PAIR_COUNT)
3251 .map(|index| vec![format!("a-{index}"), format!("b-{index}")])
3252 .collect::<Vec<_>>();
3253 let production_paths = at_rule_nesting_dfs_paths_v0(contexts.as_slice())?;
3254 let fragment =
3255 GuardedCascadeFragmentV0::admit(
3256 (0..PAIR_COUNT).flat_map(|index| [format!("a-{index}"), format!("b-{index}")]),
3257 contexts.iter().zip(&production_paths).enumerate().map(
3258 |(index, (context, paths))| {
3259 GuardedCascadeCandidateV0::new(
3260 u32::try_from(index).unwrap_or_default(),
3261 "button.primary",
3262 AuthoredPropertyTextV0::new("color"),
3263 PAIR_COUNT - index,
3264 GuardedCascadeSpecificityExactnessV0::Exact,
3265 0,
3266 context
3267 .iter()
3268 .zip(paths)
3269 .enumerate()
3270 .map(|(component_index, (atom, path))| {
3271 if component_index == 0 {
3272 GuardedCascadeConditionAtomV0::media(
3273 atom,
3274 path.iter().copied(),
3275 false,
3276 )
3277 } else {
3278 GuardedCascadeConditionAtomV0::supports(
3279 atom,
3280 path.iter().copied(),
3281 false,
3282 )
3283 }
3284 })
3285 .collect(),
3286 )
3287 },
3288 ),
3289 )?;
3290 let order = at_rule_nesting_order_for_fragment_v0(&fragment)?;
3291 let observed_domain = order.domain();
3292 let observed_nodes = guarded_fragment_node_count(&fragment, order)?;
3293 let blocked_order = VariableOrderRegistrationV0::site_first_appearance(
3294 (0..PAIR_COUNT)
3295 .map(|index| format!("a-{index}"))
3296 .chain((0..PAIR_COUNT).map(|index| format!("b-{index}"))),
3297 )?;
3298 let blocked_nodes = guarded_fragment_node_count(&fragment, blocked_order)?;
3299 assert!(
3300 observed_nodes <= INTERLEAVED_CEILING,
3301 "A5 order-policy ceiling exceeded: domain={} observedNodes={observed_nodes} ceiling={INTERLEAVED_CEILING}",
3302 observed_domain.name(),
3303 );
3304 assert_eq!(observed_domain, VariableOrderDomainV0::AtRuleNestingDfs);
3305 assert!(blocked_nodes > observed_nodes.saturating_mul(100));
3306 eprintln!(
3307 "{{\"declaredSynthetic\":true,\"domain\":\"{}\",\"pairCount\":{PAIR_COUNT},\"interleavedNodes\":{observed_nodes},\"blockedNodes\":{blocked_nodes},\"interleavedCeiling\":{INTERLEAVED_CEILING},\"a1ThroughA4OrderIndependent\":true}}",
3308 observed_domain.name(),
3309 );
3310 Ok(())
3311 }
3312
3313 #[test]
3314 fn at_rule_order_domain_census_has_one_derivation_site() {
3315 let source = include_str!("first_witness.rs");
3316 let production = source
3317 .split("\n#[cfg(test)]\nmod tests")
3318 .next()
3319 .unwrap_or(source);
3320 let at_rule_call = ["VariableOrderRegistrationV0::at_rule_", "nesting_dfs("].concat();
3321 let site_call = ["VariableOrderRegistrationV0::site_", "first_appearance("].concat();
3322 assert_eq!(production.matches(&at_rule_call).count(), 1);
3323 assert!(production.contains(&site_call));
3324 assert_ne!(
3325 AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0,
3326 SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0
3327 );
3328 }
3329
3330 #[test]
3331 fn canonical_nodes_identify_functions_both_ways() -> Result<(), FirstWitnessErrorV0> {
3332 let mut manager = manager(true)?;
3333 let a = manager.variable("a")?;
3334 let b = manager.variable("b")?;
3335 let a_and_b = manager.and(a, b)?;
3336 let b_and_a = manager.and(b, a)?;
3337 let a_or_b = manager.or(a, b)?;
3338 assert_eq!(a_and_b, b_and_a, "same function must share one node");
3339 assert_ne!(
3340 a_and_b, a_or_b,
3341 "distinct functions must not share one node"
3342 );
3343 Ok(())
3344 }
3345
3346 #[test]
3347 fn collapse_rule_mutation_preserves_evaluation_but_breaks_canonical_identity()
3348 -> Result<(), FirstWitnessErrorV0> {
3349 let mut manager = manager(false)?;
3350 manager.register_declaration_terminals([7])?;
3351 let canonical = manager.declaration_terminal(7)?;
3352 let unreduced = manager.intern_without_collapse_for_test(0, canonical, canonical)?;
3353 for assignment in [[false, false, false], [true, false, false]] {
3354 assert_eq!(
3355 evaluate_guarded_cascade_winner_v0(
3356 &manager,
3357 GuardedCascadeWinnerRootV0(canonical),
3358 &assignment,
3359 )?,
3360 evaluate_guarded_cascade_winner_v0(
3361 &manager,
3362 GuardedCascadeWinnerRootV0(unreduced),
3363 &assignment,
3364 )?,
3365 "removing collapse must not be confused with an evaluation defect"
3366 );
3367 }
3368 assert_ne!(
3369 canonical, unreduced,
3370 "without lo==hi collapse one function receives two NodeIds"
3371 );
3372 eprintln!(
3373 "{{\"mutation\":\"collapseRuleDeleted\",\"evaluationMismatches\":0,\"canonicalNodeId\":{canonical},\"unreducedNodeId\":{unreduced},\"canonicalIdentity\":false}}"
3374 );
3375 Ok(())
3376 }
3377
3378 #[test]
3379 fn independent_construction_after_cache_flush_reuses_the_canonical_node()
3380 -> Result<(), FirstWitnessErrorV0> {
3381 let mut manager = FirstWitnessManagerV0::new(
3382 VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3383 FirstWitnessManagerConfigV0 {
3384 shortcuts: false,
3385 apply_cache_capacity: 32,
3386 rebuild_interval_operations: 1,
3387 },
3388 );
3389 let a = manager.variable("a")?;
3390 let b = manager.variable("b")?;
3391 let c = manager.variable("c")?;
3392 let a_and_b = manager.and(a, b)?;
3393 let not_a = manager.not(a)?;
3394 let not_a_and_c = manager.and(not_a, c)?;
3395 let first = manager.or(a_and_b, not_a_and_c)?;
3396
3397 let mut roots = [first];
3398 let report = manager
3399 .reclaim_if_due(&mut roots)?
3400 .ok_or(FirstWitnessErrorV0::InvalidNode(first))?;
3401 assert_eq!(manager.apply_cache_len(), 0, "rebuild flushes apply cache");
3402 let first = roots[0];
3403
3404 let a = manager.variable("a")?;
3405 let b = manager.variable("b")?;
3406 let c = manager.variable("c")?;
3407 let not_a = manager.not(a)?;
3408 let c_and_not_a = manager.and(c, not_a)?;
3409 let b_and_a = manager.and(b, a)?;
3410 let second = manager.or(c_and_not_a, b_and_a)?;
3411
3412 assert_eq!(
3413 second, first,
3414 "cache-independent construction of one function must reuse its NodeId"
3415 );
3416 eprintln!(
3417 "{{\"cacheFlushed\":true,\"firstNodeId\":{first},\"secondNodeId\":{second},\"nodesBeforeRebuild\":{},\"nodesAfterRebuild\":{}}}",
3418 report.nodes_before, report.nodes_after,
3419 );
3420 Ok(())
3421 }
3422
3423 #[test]
3424 fn contradiction_and_excluded_middle_reduce_to_terminals() -> Result<(), FirstWitnessErrorV0> {
3425 let mut manager = manager(true)?;
3426 let condition = manager.variable("c")?;
3427 let negated = manager.not(condition)?;
3428 let contradiction = manager.and(condition, negated)?;
3429 let excluded_middle = manager.or(condition, negated)?;
3430 assert_eq!(contradiction, FALSE_NODE_ID_V0);
3431 assert_eq!(excluded_middle, TRUE_NODE_ID_V0);
3432 Ok(())
3433 }
3434
3435 #[test]
3436 fn shortcut_switch_changes_work_not_results() -> Result<(), FirstWitnessErrorV0> {
3437 fn fixed_seed(
3438 shortcuts: bool,
3439 ) -> Result<(NodeId, FirstWitnessOperationCountersV0), FirstWitnessErrorV0> {
3440 let mut manager = manager(shortcuts)?;
3441 let a = manager.variable("a")?;
3442 let b = manager.variable("b")?;
3443 let shared = manager.or(a, b)?;
3444 let result = manager.and(shared, shared)?;
3445 Ok((result, manager.counters()))
3446 }
3447 let (shortcut_result, shortcut_counts) = fixed_seed(true)?;
3448 let (recursive_result, recursive_counts) = fixed_seed(false)?;
3449 assert_eq!(shortcut_result, recursive_result);
3450 assert!(recursive_counts.choose_invocations > shortcut_counts.choose_invocations);
3451 assert!(recursive_counts.apply_invocations > shortcut_counts.apply_invocations);
3452 assert!(recursive_counts.apply_cache_lookups > shortcut_counts.apply_cache_lookups);
3453 eprintln!(
3454 "{{\"seed\":\"(a or b) and (a or b)\",\"result\":{},\"shortcuts\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{}}},\"recursive\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{}}}}}",
3455 shortcut_result,
3456 shortcut_counts.choose_invocations,
3457 shortcut_counts.apply_invocations,
3458 shortcut_counts.apply_cache_lookups,
3459 recursive_counts.choose_invocations,
3460 recursive_counts.apply_invocations,
3461 recursive_counts.apply_cache_lookups,
3462 );
3463 Ok(())
3464 }
3465
3466 #[test]
3467 fn boolean_laws_recompute_with_shortcuts_disabled() -> Result<(), FirstWitnessErrorV0> {
3468 let mut manager = manager(false)?;
3469 let a = manager.variable("a")?;
3470 let b = manager.variable("b")?;
3471 let c = manager.variable("c")?;
3472 let a_and_b = manager.and(a, b)?;
3473 let b_and_c = manager.and(b, c)?;
3474 let left_associative = manager.and(a_and_b, c)?;
3475 let right_associative = manager.and(a, b_and_c)?;
3476 assert_eq!(left_associative, right_associative);
3477 assert_eq!(manager.and(a, a)?, a);
3478 let a_or_b = manager.or(a, b)?;
3479 assert_eq!(manager.and(a, a_or_b)?, a);
3480 let not_a = manager.not(a)?;
3481 assert_eq!(manager.and(a, not_a)?, FALSE_NODE_ID_V0);
3482 assert_eq!(manager.or(a, not_a)?, TRUE_NODE_ID_V0);
3483 Ok(())
3484 }
3485
3486 #[test]
3487 fn apply_cache_capacity_is_a_live_bound() -> Result<(), FirstWitnessErrorV0> {
3488 let mut manager = FirstWitnessManagerV0::new(
3489 VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3490 FirstWitnessManagerConfigV0 {
3491 shortcuts: false,
3492 apply_cache_capacity: 2,
3493 rebuild_interval_operations: u64::MAX,
3494 },
3495 );
3496 let a = manager.variable("a")?;
3497 let b = manager.variable("b")?;
3498 let c = manager.variable("c")?;
3499 let _ = manager.and(a, b)?;
3500 let _ = manager.or(a, c)?;
3501 let _ = manager.xor(b, c)?;
3502 assert!(manager.apply_cache_len() <= 2);
3503 assert_eq!(manager.config().apply_cache_capacity, 2);
3504 Ok(())
3505 }
3506
3507 #[test]
3508 fn rebuild_reclaims_unreachable_nodes_and_remaps_live_roots() -> Result<(), FirstWitnessErrorV0>
3509 {
3510 let mut manager = FirstWitnessManagerV0::new(
3511 VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3512 FirstWitnessManagerConfigV0 {
3513 shortcuts: false,
3514 apply_cache_capacity: 16,
3515 rebuild_interval_operations: 1,
3516 },
3517 );
3518 let a = manager.variable("a")?;
3519 let b = manager.variable("b")?;
3520 let c = manager.variable("c")?;
3521 let live = manager.and(a, b)?;
3522 let _dead = manager.or(a, c)?;
3523 let before = manager.node_count();
3524 let mut roots = [live];
3525 let report = manager.reclaim_if_due(&mut roots)?;
3526 assert!(report.is_some(), "rebuild interval reached");
3527 let Some(report) = report else {
3528 return Ok(());
3529 };
3530 assert!(manager.node_count() < before);
3531 assert_eq!(
3532 manager.node(roots[0]),
3533 Some(Node::Int {
3534 var: 0,
3535 lo: 0,
3536 hi: 2
3537 })
3538 );
3539 assert_eq!(report.nodes_after, manager.node_count());
3540 assert_eq!(manager.counters().rebuilds, 1);
3541 Ok(())
3542 }
3543
3544 #[test]
3545 fn site_first_appearance_policy_pins_synthetic_blocked_pair_bound()
3546 -> Result<(), FirstWitnessErrorV0> {
3547 const PAIRS: usize = 7;
3548 fn build(order: Vec<String>) -> Result<usize, FirstWitnessErrorV0> {
3549 let mut manager = FirstWitnessManagerV0::new(
3550 VariableOrderRegistrationV0::site_first_appearance(order)?,
3551 FirstWitnessManagerConfigV0 {
3552 shortcuts: false,
3553 apply_cache_capacity: 4_096,
3554 rebuild_interval_operations: u64::MAX,
3555 },
3556 );
3557 let mut root = TRUE_NODE_ID_V0;
3558 for index in 0..PAIRS {
3559 let left = manager.variable(&format!("x{index}"))?;
3560 let right = manager.variable(&format!("y{index}"))?;
3561 let pair = manager.xor(left, right)?;
3562 root = manager.and(root, pair)?;
3563 }
3564 assert!(manager.is_satisfiable(root));
3565 Ok(manager.node_count())
3566 }
3567 let interleaved = (0..PAIRS)
3568 .flat_map(|index| [format!("x{index}"), format!("y{index}")])
3569 .collect();
3570 let blocked = (0..PAIRS)
3571 .map(|index| format!("x{index}"))
3572 .chain((0..PAIRS).map(|index| format!("y{index}")))
3573 .collect();
3574 let interleaved_nodes = build(interleaved)?;
3575 let blocked_nodes = build(blocked)?;
3576 eprintln!(
3577 "{{\"declaredSynthetic\":true,\"pairCount\":{PAIRS},\"policy\":\"siteFirstAppearance\",\"interleavedNodes\":{interleaved_nodes},\"blockedNodes\":{blocked_nodes},\"interleavedUpperBound\":{},\"blockedRatioFloor\":8}}",
3578 14 * PAIRS,
3579 );
3580 assert!(
3581 interleaved_nodes <= 14 * PAIRS,
3582 "interleaved={interleaved_nodes}, blocked={blocked_nodes}"
3583 );
3584 assert!(
3585 blocked_nodes >= interleaved_nodes * 8,
3586 "interleaved={interleaved_nodes}, blocked={blocked_nodes}"
3587 );
3588 Ok(())
3589 }
3590
3591 #[test]
3592 fn first_witness_fold_is_commutative_and_idempotent() {
3593 let left = vec!["alpha", "shared"];
3594 let right = vec!["beta", "shared"];
3595 assert_eq!(
3596 first_witness_fold_v0(&left, &right),
3597 first_witness_fold_v0(&right, &left)
3598 );
3599 assert_eq!(first_witness_fold_v0(&left, &left), left);
3600 }
3601
3602 #[test]
3603 fn core_is_disjoint_from_the_attractor_strategy_slot_and_host_model() {
3604 let core = include_str!("first_witness.rs");
3605 let production = core.split("#[cfg(test)]").next().unwrap_or(core);
3606 let attractor_strategy = ["Attractor", "EnumerationStrategyV0"].concat();
3607 assert!(!production.contains(&attractor_strategy));
3608 assert!(!production.contains("use crate::"));
3609 assert!(!production.contains("use super::"));
3610 let grn = include_str!("grn.rs");
3611 let module_name = ["first_", "witness"].concat();
3612 assert!(!grn.contains(&module_name));
3613 }
3614
3615 #[test]
3616 fn first_witness_declared_synthetic_measurement_report() -> Result<(), FirstWitnessErrorV0> {
3617 const VARIABLE_COUNT: usize = 12;
3618 const EDIT_COUNT: usize = 2_000;
3619 let atoms = (0..VARIABLE_COUNT)
3620 .map(|index| format!("g{index}"))
3621 .collect::<Vec<_>>();
3622 let mut manager = FirstWitnessManagerV0::new(
3623 VariableOrderRegistrationV0::site_first_appearance(atoms.clone())?,
3624 FirstWitnessManagerConfigV0::default(),
3625 );
3626 let mut root = TRUE_NODE_ID_V0;
3627 let mut rebuild_count = 0usize;
3628 let mut rebuilt_nodes_before = 0usize;
3629 let mut rebuilt_nodes_after = 0usize;
3630 let mut rebuild_elapsed_nanos = 0u128;
3631 for edit in 0..EDIT_COUNT {
3632 let left = manager.variable(&atoms[edit % VARIABLE_COUNT])?;
3633 let right = manager.variable(&atoms[(edit * 5 + 1) % VARIABLE_COUNT])?;
3634 let not_right = manager.not(right)?;
3635 let candidate = manager.and(left, not_right)?;
3636 root = if edit % 2 == 0 {
3637 manager.or(root, candidate)?
3638 } else {
3639 manager.xor(root, candidate)?
3640 };
3641 let started = Instant::now();
3642 let mut roots = [root];
3643 if let Some(report) = manager.reclaim_if_due(&mut roots)? {
3644 rebuild_elapsed_nanos += started.elapsed().as_nanos();
3645 root = roots[0];
3646 rebuild_count += 1;
3647 rebuilt_nodes_before += report.nodes_before;
3648 rebuilt_nodes_after += report.nodes_after;
3649 }
3650 }
3651 assert!(rebuild_count > 0);
3652 assert!(manager.apply_cache_len() <= DEFAULT_APPLY_CACHE_CAPACITY_V0);
3653 assert!(manager.is_satisfiable(root));
3654 eprintln!(
3655 "{{\"declaredSynthetic\":true,\"variableCount\":{VARIABLE_COUNT},\"editCount\":{EDIT_COUNT},\"cacheCapacity\":{},\"cacheOccupancy\":{},\"rebuildIntervalOperations\":{},\"rebuildCount\":{rebuild_count},\"nodesBeforeRebuildTotal\":{rebuilt_nodes_before},\"nodesAfterRebuildTotal\":{rebuilt_nodes_after},\"rebuildElapsedNanos\":{rebuild_elapsed_nanos},\"finalNodeCount\":{},\"operationCounters\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{},\"cacheHits\":{}}}}}",
3656 DEFAULT_APPLY_CACHE_CAPACITY_V0,
3657 manager.apply_cache_len(),
3658 DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0,
3659 manager.node_count(),
3660 manager.counters().choose_invocations,
3661 manager.counters().apply_invocations,
3662 manager.counters().apply_cache_lookups,
3663 manager.counters().apply_cache_hits,
3664 );
3665 Ok(())
3666 }
3667}