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