use super::ast::Comprehension;
use super::predicate::CoordSet;
use crate::iteration::comprehension::metadata::Metadata;
pub mod finding;
pub mod r0a_identity;
pub mod r0b_flatten;
pub mod r3_commute;
pub mod r4_distribute;
pub mod r5_factorize;
pub mod r6_filter_fold;
pub mod r7_order_fold;
pub use finding::{ComplexityDelta, Ordering as ComplexityOrdering, Reduction, ReducibilityFinding, RuleId};
pub fn optimize(ast: Comprehension) -> Comprehension {
let mut current = ast;
let mut steps_remaining = max_steps(¤t);
while steps_remaining > 0 {
match analyze_reducibility(¤t) {
ReducibilityFinding {
reduction: Some(Reduction::Rewrite { witness, .. }),
..
} => {
current = witness;
}
ReducibilityFinding {
reduction: Some(Reduction::Replace { with }),
..
} => {
current = with;
}
_ => break,
}
steps_remaining -= 1;
}
current
}
pub fn analyze_reducibility(ast: &Comprehension) -> ReducibilityFinding {
if let Some(finding) = try_rewrite_child_first(ast) {
return finding;
}
try_rules_at_node(ast)
}
fn try_rewrite_child_first(ast: &Comprehension) -> Option<ReducibilityFinding> {
let children: Vec<Comprehension> = ast.children().cloned().collect();
for (i, child) in children.iter().enumerate() {
let child_finding = analyze_reducibility(child);
let rewritten = match child_finding.reduction {
Some(Reduction::Rewrite { witness, .. }) => witness,
Some(Reduction::Replace { with }) => with,
None => continue,
};
let new_ast = replace_child_at(ast, i, rewritten);
return Some(ReducibilityFinding {
reduction: Some(Reduction::Rewrite {
rule: child_finding.rule.unwrap_or(RuleId::R0a),
witness: new_ast,
}),
rule: child_finding.rule,
improvement: child_finding.improvement,
});
}
None
}
fn try_rules_at_node(ast: &Comprehension) -> ReducibilityFinding {
if let Some(witness) = r0a_identity::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R0a, witness }),
rule: Some(RuleId::R0a),
improvement: ComplexityDelta::less_compute(),
};
}
if let Some(witness) = r0b_flatten::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R0b, witness }),
rule: Some(RuleId::R0b),
improvement: ComplexityDelta::less_compute(),
};
}
if let Some(witness) = r3_commute::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R3, witness }),
rule: Some(RuleId::R3),
improvement: ComplexityDelta::less_memory(),
};
}
if let Some(witness) = r4_distribute::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R4, witness }),
rule: Some(RuleId::R4),
improvement: ComplexityDelta::less_memory(),
};
}
if let Some(witness) = r5_factorize::apply(ast, &|p, c| {
super::predicate::analyze(p, c)
}) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R5, witness }),
rule: Some(RuleId::R5),
improvement: ComplexityDelta::less_both(),
};
}
if let Some(witness) = r6_filter_fold::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R6, witness }),
rule: Some(RuleId::R6),
improvement: ComplexityDelta::less_compute(),
};
}
if let Some(witness) = r7_order_fold::apply(ast) {
return ReducibilityFinding {
reduction: Some(Reduction::Rewrite { rule: RuleId::R7, witness }),
rule: Some(RuleId::R7),
improvement: ComplexityDelta::less_both(),
};
}
ReducibilityFinding {
reduction: None,
rule: None,
improvement: ComplexityDelta::equal(),
}
}
fn replace_child_at(ast: &Comprehension, i: usize, replacement: Comprehension) -> Comprehension {
match ast {
Comprehension::Clause { .. } => unreachable!("clause has no children"),
Comprehension::Cartesian { children } => {
let mut new_children = children.clone();
new_children[i] = replacement;
Comprehension::Cartesian { children: new_children }
}
Comprehension::Zip { children, mode } => {
let mut new_children = children.clone();
new_children[i] = replacement;
Comprehension::Zip { children: new_children, mode: *mode }
}
Comprehension::Union { children } => {
let mut new_children = children.clone();
new_children[i] = replacement;
Comprehension::Union { children: new_children }
}
Comprehension::Filter { predicate, .. } => Comprehension::Filter {
child: Box::new(replacement),
predicate: predicate.clone(),
},
Comprehension::Order { strategy, truncation, .. } => Comprehension::Order {
child: Box::new(replacement),
strategy: *strategy,
truncation: *truncation,
},
}
}
fn max_steps(ast: &Comprehension) -> usize {
let n = ast.node_count();
n.saturating_mul(n).saturating_add(16)
}
pub fn coord_set_for(ast: &Comprehension) -> CoordSet {
let names = ast.coordinate_names();
let metadata = ast.metadata();
coord_set_from(&names, &metadata)
}
fn coord_set_from(names: &[String], metadata: &Metadata) -> CoordSet {
CoordSet::from_metadata(names, metadata)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iteration::comprehension::source::{LiteralValue, Source};
use crate::iteration::comprehension::strategy::StrategyName;
fn clause(name: &str, vs: &[i64]) -> Comprehension {
Comprehension::clause(
name,
Source::Literal {
values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
},
)
}
#[test]
fn optimize_well_formed_ast_does_not_panic() {
let ast = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20])]);
let _ = optimize(ast);
}
#[test]
fn optimize_singleton_cartesian_eliminates() {
let ast = Comprehension::cartesian(vec![clause("k", &[1, 2, 3])]);
let optimized = optimize(ast);
assert!(matches!(optimized, Comprehension::Clause { .. }));
}
#[test]
fn optimize_lex_none_eliminates() {
let inner = clause("k", &[1, 2, 3]);
let ast = Comprehension::order(inner.clone(), StrategyName::Lex, None);
let optimized = optimize(ast);
assert_eq!(optimized, inner);
}
#[test]
fn optimize_is_idempotent() {
let ast = Comprehension::cartesian(vec![
Comprehension::cartesian(vec![clause("a", &[1])]),
clause("b", &[2]),
]);
let once = optimize(ast.clone());
let twice = optimize(once.clone());
assert_eq!(once, twice);
}
}