use super::ast::Comprehension;
use super::predicate::CoordSet;
use super::strategies::for_name;
use super::strategy::StrategyName;
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, ReducibilityFinding, Reduction, 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 {
analyze_at(ast, Place::Free)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Place {
Free,
Ranked { through_orders: bool },
}
impl Place {
fn of_child(self, node: &Comprehension) -> Place {
if let (Comprehension::Filter { .. }, Place::Ranked { through_orders }) = (node, self) {
return Place::Ranked { through_orders };
}
let Comprehension::Order {
strategy,
truncation,
..
} = node
else {
return Place::Free;
};
let inherited = truncation.is_none()
&& self
== Place::Ranked {
through_orders: true,
};
if *strategy != StrategyName::Lex {
Place::Ranked {
through_orders: inherited || for_name(*strategy).selects_from_shape(),
}
} else if inherited {
self
} else {
Place::Free
}
}
}
fn analyze_at(ast: &Comprehension, place: Place) -> ReducibilityFinding {
if let Some(finding) = try_rewrite_child_first(ast, place) {
return finding;
}
try_rules_at_node(ast, place)
}
fn try_rewrite_child_first(ast: &Comprehension, place: Place) -> Option<ReducibilityFinding> {
let children: Vec<Comprehension> = ast.children().cloned().collect();
for (i, child) in children.iter().enumerate() {
let child_finding = analyze_at(child, place.of_child(ast));
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, place: Place) -> ReducibilityFinding {
let reshapes = place == Place::Free;
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).filter(|_| reshapes) {
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)).filter(|_| reshapes)
{
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,
seed,
..
} => Comprehension::Order {
child: Box::new(replacement),
strategy: *strategy,
truncation: *truncation,
seed: *seed,
},
}
}
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);
}
}