polydat_core/iteration/comprehension/optimize/
mod.rs1use super::ast::Comprehension;
65use super::predicate::CoordSet;
66use crate::iteration::comprehension::metadata::Metadata;
67
68pub mod finding;
69pub mod r0a_identity;
70pub mod r0b_flatten;
71pub mod r3_commute;
72pub mod r4_distribute;
73pub mod r5_factorize;
74pub mod r6_filter_fold;
75pub mod r7_order_fold;
76
77pub use finding::{
78 ComplexityDelta, Ordering as ComplexityOrdering, ReducibilityFinding, Reduction, RuleId,
79};
80
81pub fn optimize(ast: Comprehension) -> Comprehension {
93 let mut current = ast;
94 let mut steps_remaining = max_steps(¤t);
95 while steps_remaining > 0 {
96 match analyze_reducibility(¤t) {
97 ReducibilityFinding {
98 reduction: Some(Reduction::Rewrite { witness, .. }),
99 ..
100 } => {
101 current = witness;
102 }
103 ReducibilityFinding {
104 reduction: Some(Reduction::Replace { with }),
105 ..
106 } => {
107 current = with;
108 }
109 _ => break,
110 }
111 steps_remaining -= 1;
112 }
113 current
114}
115
116pub fn analyze_reducibility(ast: &Comprehension) -> ReducibilityFinding {
122 if let Some(finding) = try_rewrite_child_first(ast) {
127 return finding;
128 }
129 try_rules_at_node(ast)
131}
132
133fn try_rewrite_child_first(ast: &Comprehension) -> Option<ReducibilityFinding> {
136 let children: Vec<Comprehension> = ast.children().cloned().collect();
137 for (i, child) in children.iter().enumerate() {
138 let child_finding = analyze_reducibility(child);
139 let rewritten = match child_finding.reduction {
140 Some(Reduction::Rewrite { witness, .. }) => witness,
141 Some(Reduction::Replace { with }) => with,
142 None => continue,
143 };
144 let new_ast = replace_child_at(ast, i, rewritten);
146 return Some(ReducibilityFinding {
147 reduction: Some(Reduction::Rewrite {
148 rule: child_finding.rule.unwrap_or(RuleId::R0a),
149 witness: new_ast,
150 }),
151 rule: child_finding.rule,
152 improvement: child_finding.improvement,
153 });
154 }
155 None
156}
157
158fn try_rules_at_node(ast: &Comprehension) -> ReducibilityFinding {
161 if let Some(witness) = r0a_identity::apply(ast) {
163 return ReducibilityFinding {
164 reduction: Some(Reduction::Rewrite {
165 rule: RuleId::R0a,
166 witness,
167 }),
168 rule: Some(RuleId::R0a),
169 improvement: ComplexityDelta::less_compute(),
170 };
171 }
172 if let Some(witness) = r0b_flatten::apply(ast) {
174 return ReducibilityFinding {
175 reduction: Some(Reduction::Rewrite {
176 rule: RuleId::R0b,
177 witness,
178 }),
179 rule: Some(RuleId::R0b),
180 improvement: ComplexityDelta::less_compute(),
181 };
182 }
183 if let Some(witness) = r3_commute::apply(ast) {
185 return ReducibilityFinding {
186 reduction: Some(Reduction::Rewrite {
187 rule: RuleId::R3,
188 witness,
189 }),
190 rule: Some(RuleId::R3),
191 improvement: ComplexityDelta::less_memory(),
192 };
193 }
194 if let Some(witness) = r4_distribute::apply(ast) {
196 return ReducibilityFinding {
197 reduction: Some(Reduction::Rewrite {
198 rule: RuleId::R4,
199 witness,
200 }),
201 rule: Some(RuleId::R4),
202 improvement: ComplexityDelta::less_memory(),
203 };
204 }
205 if let Some(witness) = r5_factorize::apply(ast, &|p, c| super::predicate::analyze(p, c)) {
207 return ReducibilityFinding {
208 reduction: Some(Reduction::Rewrite {
209 rule: RuleId::R5,
210 witness,
211 }),
212 rule: Some(RuleId::R5),
213 improvement: ComplexityDelta::less_both(),
214 };
215 }
216 if let Some(witness) = r6_filter_fold::apply(ast) {
218 return ReducibilityFinding {
219 reduction: Some(Reduction::Rewrite {
220 rule: RuleId::R6,
221 witness,
222 }),
223 rule: Some(RuleId::R6),
224 improvement: ComplexityDelta::less_compute(),
225 };
226 }
227 if let Some(witness) = r7_order_fold::apply(ast) {
229 return ReducibilityFinding {
230 reduction: Some(Reduction::Rewrite {
231 rule: RuleId::R7,
232 witness,
233 }),
234 rule: Some(RuleId::R7),
235 improvement: ComplexityDelta::less_both(),
236 };
237 }
238 ReducibilityFinding {
240 reduction: None,
241 rule: None,
242 improvement: ComplexityDelta::equal(),
243 }
244}
245
246fn replace_child_at(ast: &Comprehension, i: usize, replacement: Comprehension) -> Comprehension {
250 match ast {
251 Comprehension::Clause { .. } => unreachable!("clause has no children"),
252 Comprehension::Cartesian { children } => {
253 let mut new_children = children.clone();
254 new_children[i] = replacement;
255 Comprehension::Cartesian {
256 children: new_children,
257 }
258 }
259 Comprehension::Zip { children, mode } => {
260 let mut new_children = children.clone();
261 new_children[i] = replacement;
262 Comprehension::Zip {
263 children: new_children,
264 mode: *mode,
265 }
266 }
267 Comprehension::Union { children } => {
268 let mut new_children = children.clone();
269 new_children[i] = replacement;
270 Comprehension::Union {
271 children: new_children,
272 }
273 }
274 Comprehension::Filter { predicate, .. } => Comprehension::Filter {
275 child: Box::new(replacement),
276 predicate: predicate.clone(),
277 },
278 Comprehension::Order {
279 strategy,
280 truncation,
281 ..
282 } => Comprehension::Order {
283 child: Box::new(replacement),
284 strategy: *strategy,
285 truncation: *truncation,
286 },
287 }
288}
289
290fn max_steps(ast: &Comprehension) -> usize {
296 let n = ast.node_count();
297 n.saturating_mul(n).saturating_add(16)
298}
299
300pub fn coord_set_for(ast: &Comprehension) -> CoordSet {
304 let names = ast.coordinate_names();
305 let metadata = ast.metadata();
306 coord_set_from(&names, &metadata)
307}
308
309fn coord_set_from(names: &[String], metadata: &Metadata) -> CoordSet {
310 CoordSet::from_metadata(names, metadata)
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::iteration::comprehension::source::{LiteralValue, Source};
317 use crate::iteration::comprehension::strategy::StrategyName;
318
319 fn clause(name: &str, vs: &[i64]) -> Comprehension {
320 Comprehension::clause(
321 name,
322 Source::Literal {
323 values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
324 },
325 )
326 }
327
328 #[test]
329 fn optimize_well_formed_ast_does_not_panic() {
330 let ast = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20])]);
331 let _ = optimize(ast);
332 }
333
334 #[test]
335 fn optimize_singleton_cartesian_eliminates() {
336 let ast = Comprehension::cartesian(vec![clause("k", &[1, 2, 3])]);
338 let optimized = optimize(ast);
339 assert!(matches!(optimized, Comprehension::Clause { .. }));
340 }
341
342 #[test]
343 fn optimize_lex_none_eliminates() {
344 let inner = clause("k", &[1, 2, 3]);
346 let ast = Comprehension::order(inner.clone(), StrategyName::Lex, None);
347 let optimized = optimize(ast);
348 assert_eq!(optimized, inner);
349 }
350
351 #[test]
352 fn optimize_is_idempotent() {
353 let ast = Comprehension::cartesian(vec![
354 Comprehension::cartesian(vec![clause("a", &[1])]),
355 clause("b", &[2]),
356 ]);
357 let once = optimize(ast.clone());
358 let twice = optimize(once.clone());
359 assert_eq!(once, twice);
360 }
361}