conjure_cp_core/ast/
assertions.rs1use super::Model;
9
10#[cfg(debug_assertions)]
11use std::collections::{BTreeSet, HashSet};
12
13#[cfg(debug_assertions)]
14use super::{Expression, Name, Reference, SymbolTablePtr, serde::HasId};
15#[cfg(debug_assertions)]
16use uniplate::Biplate;
17
18#[cfg(debug_assertions)]
20pub fn debug_assert_model_well_formed(model: &Model, origin: &str) {
21 debug_assert_root_at_top_level_only(model, origin);
22 debug_assert_all_names_resolved(model, origin);
23}
24
25#[cfg(not(debug_assertions))]
27pub fn debug_assert_model_well_formed(_model: &Model, _origin: &str) {}
28
29#[cfg(debug_assertions)]
31pub fn debug_assert_all_names_resolved(model: &Model, origin: &str) {
32 let mut declared_names: BTreeSet<Name> = BTreeSet::new();
33 let mut referenced_names: BTreeSet<Name> = BTreeSet::new();
34
35 for table_ptr in collect_reachable_symbol_tables(model) {
36 let table = table_ptr.read();
37
38 for (name, decl) in table.iter_local() {
39 declared_names.insert(name.clone());
40
41 if let Some(expr) = decl.as_value_letting() {
42 referenced_names.extend(Biplate::<Reference>::universe_bi(&*expr).into_iter().map(
43 |reference| {
44 let name = reference.name();
45 canonical_resolution_name(&name).clone()
46 },
47 ));
48 }
49
50 if let Some(domain) = decl.domain() {
51 referenced_names.extend(
52 Biplate::<Reference>::universe_bi(domain.as_ref())
53 .into_iter()
54 .map(|reference| {
55 let name = reference.name();
56 canonical_resolution_name(&name).clone()
57 }),
58 );
59 }
60 }
61 }
62
63 referenced_names.extend(
64 Biplate::<Reference>::universe_bi(model.root())
65 .into_iter()
66 .map(|reference| {
67 let name = reference.name();
68 canonical_resolution_name(&name).clone()
69 }),
70 );
71
72 if let Some(dominance) = &model.dominance {
73 referenced_names.extend(
74 Biplate::<Reference>::universe_bi(dominance)
75 .into_iter()
76 .map(|reference| {
77 let name = reference.name();
78 canonical_resolution_name(&name).clone()
79 }),
80 );
81 }
82
83 if let Some(objective) = &model.objective {
84 referenced_names.extend(
85 Biplate::<Reference>::universe_bi(&objective.expression)
86 .into_iter()
87 .map(|reference| {
88 let name = reference.name();
89 canonical_resolution_name(&name).clone()
90 }),
91 );
92 }
93
94 for clause in model.clauses() {
95 for literal in clause.iter() {
96 referenced_names.extend(Biplate::<Reference>::universe_bi(literal).into_iter().map(
97 |reference| {
98 let name = reference.name();
99 canonical_resolution_name(&name).clone()
100 },
101 ));
102 }
103 }
104
105 let unresolved: Vec<Name> = referenced_names
106 .difference(&declared_names)
107 .cloned()
108 .collect();
109
110 debug_assert!(
111 unresolved.is_empty(),
112 "Model from '{origin}' contains unresolved names: {unresolved:?}"
113 );
114}
115
116#[cfg(not(debug_assertions))]
118pub fn debug_assert_all_names_resolved(_model: &Model, _origin: &str) {}
119
120#[cfg(debug_assertions)]
121fn canonical_resolution_name(name: &Name) -> &Name {
122 match name {
123 Name::WithRepresentation(inner, _) => canonical_resolution_name(inner),
125 _ => name,
126 }
127}
128
129#[cfg(debug_assertions)]
131pub fn debug_assert_root_at_top_level_only(model: &Model, origin: &str) {
132 debug_assert!(
133 matches!(model.root(), Expression::Root(_, _)),
134 "Model from '{origin}' does not have Root at top-level"
135 );
136
137 let root_count_in_main_tree = Biplate::<Expression>::universe_bi(model)
138 .iter()
139 .filter(|expr| matches!(expr, Expression::Root(_, _)))
140 .count();
141
142 let root_count_in_clauses = model
143 .clauses()
144 .iter()
145 .flat_map(|clause| clause.iter())
146 .map(|expr| {
147 Biplate::<Expression>::universe_bi(expr)
148 .iter()
149 .filter(|inner| matches!(inner, Expression::Root(_, _)))
150 .count()
151 })
152 .sum::<usize>();
153
154 let total_root_count = root_count_in_main_tree + root_count_in_clauses;
155 debug_assert_eq!(
156 total_root_count, 1,
157 "Model from '{origin}' should contain exactly one Root expression at top-level, found {total_root_count}"
158 );
159}
160
161#[cfg(not(debug_assertions))]
163pub fn debug_assert_root_at_top_level_only(_model: &Model, _origin: &str) {}
164
165#[cfg(debug_assertions)]
166fn collect_reachable_symbol_tables(model: &Model) -> Vec<SymbolTablePtr> {
167 let mut pending_tables: Vec<SymbolTablePtr> = vec![model.symbols_ptr_unchecked().clone()];
168 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(model.root()));
169
170 if let Some(dominance) = &model.dominance {
171 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(dominance));
172 }
173
174 if let Some(objective) = &model.objective {
175 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(
176 &objective.expression,
177 ));
178 }
179
180 for clause in model.clauses() {
181 for literal in clause.iter() {
182 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(literal));
183 }
184 }
185
186 let mut seen_tables = HashSet::new();
187 let mut out = Vec::new();
188
189 while let Some(table_ptr) = pending_tables.pop() {
190 if !seen_tables.insert(table_ptr.id()) {
191 continue;
192 }
193
194 let parent = table_ptr.read().parent().clone();
195 if let Some(parent) = parent {
196 pending_tables.push(parent);
197 }
198
199 out.push(table_ptr);
200 }
201
202 out
203}