conjure_cp_core/rule_engine/
rewriter_common.rs

1//! Common utilities and types for rewriters.
2use super::{
3    Reduction,
4    resolve_rules::{ResolveRulesError, RuleData},
5};
6use crate::ast::{
7    Expression, SubModel,
8    pretty::{pretty_variable_declaration, pretty_vec},
9};
10
11use itertools::Itertools;
12use serde_json::json;
13use std::fmt::Debug;
14use thiserror::Error;
15use tracing::{info, trace};
16
17#[derive(Debug, Clone)]
18pub struct RuleResult<'a> {
19    pub rule_data: RuleData<'a>,
20    pub reduction: Reduction,
21}
22
23/// Logs, to the main log, and the human readable traces used by the integration tester, that the
24/// rule has been applied to the expression
25pub fn log_rule_application(
26    result: &RuleResult,
27    initial_expression: &Expression,
28    initial_model: &SubModel,
29) {
30    let red = &result.reduction;
31    let rule = result.rule_data.rule;
32
33    // A reduction can only modify either constraints or clauses, not both. So the the same
34    // variable is used to hold changes in both (or empty if neither are changed).
35    let new_top_string = if !red.new_top.is_empty() {
36        pretty_vec(&red.new_top)
37    } else {
38        pretty_vec(&red.new_clauses)
39    };
40
41    info!(
42        %new_top_string,
43        "Applying rule: {} ({:?}), to expression: {}, resulting in: {}",
44        rule.name,
45        rule.rule_sets,
46        initial_expression,
47        red.new_expression
48    );
49
50    // empty if no top level constraints
51    let top_level_str = if !red.new_top.is_empty() {
52        let mut exprs: Vec<String> = vec![];
53
54        for expr in &red.new_top {
55            exprs.push(format!("  {expr}"));
56        }
57
58        let exprs = exprs.iter().join("\n");
59
60        format!("new constraints:\n{exprs}\n")
61    } else if !red.new_clauses.is_empty() {
62        let mut exprs: Vec<String> = vec![];
63
64        for clause in &red.new_clauses {
65            exprs.push(format!("  {clause}"));
66        }
67
68        let exprs = exprs.iter().join("\n");
69
70        format!("new clauses:\n{exprs}\n")
71    } else {
72        String::new()
73    };
74
75    // empty if no new variables
76    // TODO: consider printing modified and removed declarations too, though removing a declaration in a rule is less likely.
77    let new_variables_str = {
78        let mut vars: Vec<String> = vec![];
79
80        for var_name in red.added_symbols(&initial_model.symbols()) {
81            #[allow(clippy::unwrap_used)]
82            vars.push(format!(
83                "  {}",
84                pretty_variable_declaration(&red.symbols, &var_name).unwrap()
85            ));
86        }
87        if vars.is_empty() {
88            String::new()
89        } else {
90            format!("new variables:\n{}", vars.join("\n"))
91        }
92    };
93
94    trace!(
95        target: "rule_engine_human",
96        "{}, \n   ~~> {} ({:?}) \n{} \n{}\n{}--\n",
97        initial_expression,
98        rule.name,
99        rule.rule_sets,
100        red.new_expression,
101        new_variables_str,
102        top_level_str
103    );
104
105    trace!(
106        target: "rule_engine",
107        "{}",
108    json!({
109        "rule_name": result.rule_data.rule.name,
110        "rule_priority": result.rule_data.priority,
111        "rule_set": {
112            "name": result.rule_data.rule_set.name,
113        },
114        "initial_expression": serde_json::to_value(initial_expression).unwrap(),
115        "transformed_expression": serde_json::to_value(&red.new_expression).unwrap()
116    })
117
118    )
119}
120
121/// Represents errors that can occur during the model rewriting process.
122#[derive(Debug, Error)]
123pub enum RewriteError {
124    #[error("Error resolving rules {0}")]
125    ResolveRulesError(ResolveRulesError),
126}
127
128impl From<ResolveRulesError> for RewriteError {
129    fn from(error: ResolveRulesError) -> Self {
130        RewriteError::ResolveRulesError(error)
131    }
132}