mathhook_core/calculus/derivatives/
educational.rs

1//! Educational step-by-step explanations for derivative operations
2//!
3//! Provides detailed explanations for all derivative rules including power rule,
4//! chain rule, product rule, quotient rule, and basic differentiation rules.
5
6mod basic_rules;
7mod composition_rules;
8
9use crate::calculus::derivatives::Derivative;
10use crate::core::{Expression, Symbol};
11use crate::educational::step_by_step::{Step, StepByStepExplanation};
12use crate::formatter::latex::LaTeXFormatter;
13
14pub use basic_rules::{
15    explain_constant_derivative, explain_power_rule, explain_sum_derivative,
16    explain_variable_derivative,
17};
18pub use composition_rules::{explain_chain_rule, explain_product_rule, explain_quotient_rule};
19
20/// Helper to format expressions for display
21pub(crate) fn format_expr(expr: &Expression) -> String {
22    format!("{}", expr)
23}
24
25/// Educational derivative operations trait
26pub trait DerivativeWithSteps {
27    /// Compute derivative with step-by-step explanation
28    ///
29    /// # Arguments
30    ///
31    /// * `variable` - The variable to differentiate with respect to
32    /// * `order` - Order of derivative (1 for first derivative, 2 for second, etc.)
33    ///
34    /// # Examples
35    ///
36    /// ```rust
37    /// use mathhook_core::{expr, symbol};
38    /// use mathhook_core::calculus::derivatives::DerivativeWithSteps;
39    ///
40    /// let x = symbol!(x);
41    /// let expr = expr!(x ^ 3);
42    /// let explanation = expr.derivative_with_steps(&x, 1);
43    /// assert!(explanation.steps.len() >= 4);
44    /// ```
45    fn derivative_with_steps(&self, variable: &Symbol, order: u32) -> StepByStepExplanation;
46}
47
48impl DerivativeWithSteps for Expression {
49    fn derivative_with_steps(&self, variable: &Symbol, order: u32) -> StepByStepExplanation {
50        if order == 0 {
51            let steps = vec![Step {
52                title: "Zero Order Derivative".to_owned(),
53                description: "The 0th derivative of a function is the function itself".to_owned(),
54                expression: self.clone(),
55                rule_applied: "Identity".to_owned(),
56                latex: Some(self.to_latex(None).unwrap_or_else(|_| "f(x)".to_owned())),
57            }];
58            return StepByStepExplanation {
59                initial_expression: self.clone(),
60                final_expression: self.clone(),
61                steps,
62                total_steps: 1,
63                rules_used: vec!["Identity".to_owned()],
64            };
65        }
66
67        let mut steps = Vec::new();
68
69        steps.push(Step {
70            title: if order == 1 {
71                format!("Find Derivative of {}", format_expr(self))
72            } else {
73                format!("Find {}-Order Derivative", order)
74            },
75            description: format!(
76                "Differentiate {} with respect to {}",
77                format_expr(self),
78                variable.name()
79            ),
80            expression: self.clone(),
81            rule_applied: "Initial".to_owned(),
82            latex: Some(self.to_latex(None).unwrap_or_else(|_| "f(x)".to_owned())),
83        });
84
85        let mut current = self.clone();
86
87        for n in 1..=order {
88            let derivative_steps = compute_single_derivative_steps(&current, variable);
89            steps.extend(derivative_steps.steps.clone());
90            current = derivative_steps.final_expression.clone();
91
92            if n < order {
93                steps.push(Step {
94                    title: format!("{}-Order Derivative Complete", n),
95                    description: format!("Result: {}", format_expr(&current)),
96                    expression: current.clone(),
97                    rule_applied: "Intermediate".to_owned(),
98                    latex: Some(current.to_latex(None).unwrap_or_else(|_| "f(x)".to_owned())),
99                });
100            }
101        }
102
103        let step_count = steps.len();
104        StepByStepExplanation {
105            initial_expression: self.clone(),
106            final_expression: current,
107            steps,
108            total_steps: step_count,
109            rules_used: vec!["Differentiation".to_owned()],
110        }
111    }
112}
113
114/// Compute step-by-step derivative for a single differentiation
115fn compute_single_derivative_steps(expr: &Expression, variable: &Symbol) -> StepByStepExplanation {
116    match expr {
117        Expression::Number(_) | Expression::Constant(_) => {
118            explain_constant_derivative(expr, variable)
119        }
120        Expression::Symbol(sym) => explain_variable_derivative(sym, variable),
121        Expression::Add(terms) => explain_sum_derivative(terms, variable),
122        Expression::Pow(base, exp) => {
123            if is_power_rule_applicable(base, exp, variable) {
124                explain_power_rule(base, exp, variable)
125            } else {
126                explain_general_power_derivative(base, exp, variable)
127            }
128        }
129        Expression::Mul(factors) => {
130            if let Some((numerator, denominator)) = detect_quotient(factors) {
131                explain_quotient_rule(&numerator, &denominator, variable)
132            } else if factors.len() == 2 {
133                explain_product_rule(&factors[0], &factors[1], variable)
134            } else {
135                explain_general_product_rule(factors, variable)
136            }
137        }
138        Expression::Function { name, args } => {
139            if args.len() == 1 {
140                explain_chain_rule(name, &args[0], variable)
141            } else {
142                explain_general_function_derivative(name, args, variable)
143            }
144        }
145        _ => {
146            let result = expr.derivative(variable.clone());
147            StepByStepExplanation {
148                initial_expression: expr.clone(),
149                final_expression: result.clone(),
150                steps: vec![Step {
151                    title: "Compute Derivative".to_owned(),
152                    description: format!(
153                        "d/d{}({}) = {}",
154                        variable.name(),
155                        format_expr(expr),
156                        format_expr(&result)
157                    ),
158                    expression: result.clone(),
159                    rule_applied: "General Differentiation".to_owned(),
160                    latex: Some(
161                        result
162                            .to_latex(None)
163                            .unwrap_or_else(|_| "result".to_owned()),
164                    ),
165                }],
166                total_steps: 1,
167                rules_used: vec!["General Differentiation".to_owned()],
168            }
169        }
170    }
171}
172
173/// Check if power rule is directly applicable
174fn is_power_rule_applicable(base: &Expression, _exp: &Expression, variable: &Symbol) -> bool {
175    matches!(base, Expression::Symbol(sym) if sym == variable)
176}
177
178/// Explain general power derivative (using chain rule if needed)
179fn explain_general_power_derivative(
180    base: &Expression,
181    exp: &Expression,
182    variable: &Symbol,
183) -> StepByStepExplanation {
184    let expr = Expression::pow(base.clone(), exp.clone());
185    let result = expr.derivative(variable.clone());
186
187    StepByStepExplanation {
188        initial_expression: expr.clone(),
189        final_expression: result.clone(),
190        steps: vec![Step {
191            title: "Apply General Power Rule".to_owned(),
192            description: format!(
193                "d/d{}({}) = {}",
194                variable.name(),
195                format_expr(&expr),
196                format_expr(&result)
197            ),
198            expression: result.clone(),
199            rule_applied: "General Power Rule".to_owned(),
200            latex: Some(
201                result
202                    .to_latex(None)
203                    .unwrap_or_else(|_| "result".to_owned()),
204            ),
205        }],
206        total_steps: 1,
207        rules_used: vec!["General Power Rule".to_owned()],
208    }
209}
210
211/// Explain general product rule for multiple factors
212fn explain_general_product_rule(
213    factors: &[Expression],
214    variable: &Symbol,
215) -> StepByStepExplanation {
216    let expr = Expression::mul(factors.to_vec());
217    let result = expr.derivative(variable.clone());
218
219    StepByStepExplanation {
220        initial_expression: expr.clone(),
221        final_expression: result.clone(),
222        steps: vec![Step {
223            title: "Apply General Product Rule".to_owned(),
224            description: format!(
225                "d/d{}({}) = {}",
226                variable.name(),
227                format_expr(&expr),
228                format_expr(&result)
229            ),
230            expression: result.clone(),
231            rule_applied: "General Product Rule".to_owned(),
232            latex: Some(
233                result
234                    .to_latex(None)
235                    .unwrap_or_else(|_| "result".to_owned()),
236            ),
237        }],
238        total_steps: 1,
239        rules_used: vec!["General Product Rule".to_owned()],
240    }
241}
242
243/// Explain general function derivative
244fn explain_general_function_derivative(
245    name: &str,
246    args: &[Expression],
247    variable: &Symbol,
248) -> StepByStepExplanation {
249    let expr = Expression::function(name, args.to_vec());
250    let result = expr.derivative(variable.clone());
251
252    StepByStepExplanation {
253        initial_expression: expr.clone(),
254        final_expression: result.clone(),
255        steps: vec![Step {
256            title: "Apply Function Derivative".to_owned(),
257            description: format!(
258                "d/d{}({}) = {}",
259                variable.name(),
260                format_expr(&expr),
261                format_expr(&result)
262            ),
263            expression: result.clone(),
264            rule_applied: "Function Derivative".to_owned(),
265            latex: Some(
266                result
267                    .to_latex(None)
268                    .unwrap_or_else(|_| "result".to_owned()),
269            ),
270        }],
271        total_steps: 1,
272        rules_used: vec!["Function Derivative".to_owned()],
273    }
274}
275
276/// Detect if a multiplication represents a quotient (division)
277pub(crate) fn detect_quotient(factors: &[Expression]) -> Option<(Expression, Expression)> {
278    use crate::core::Number;
279
280    if factors.len() != 2 {
281        return None;
282    }
283
284    let (first, second) = (&factors[0], &factors[1]);
285
286    if let Expression::Pow(base, exp) = second {
287        if let Expression::Number(Number::Integer(-1)) = exp.as_ref() {
288            return Some((first.clone(), base.as_ref().clone()));
289        }
290    }
291
292    None
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::symbol;
299
300    #[test]
301    fn test_constant_derivative_explanation() {
302        let x = symbol!(x);
303        let expr = Expression::integer(5);
304        let explanation = expr.derivative_with_steps(&x, 1);
305        assert!(explanation.steps.len() >= 2);
306        assert_eq!(explanation.final_expression, Expression::integer(0));
307    }
308
309    #[test]
310    fn test_variable_derivative_explanation() {
311        let x = symbol!(x);
312        let expr = Expression::symbol(x.clone());
313        let explanation = expr.derivative_with_steps(&x, 1);
314        assert!(explanation.steps.len() >= 2);
315        assert_eq!(explanation.final_expression, Expression::integer(1));
316    }
317
318    #[test]
319    fn test_power_rule_explanation() {
320        let x = symbol!(x);
321        let expr = Expression::pow(Expression::symbol(x.clone()), Expression::integer(3));
322        let explanation = expr.derivative_with_steps(&x, 1);
323        assert!(explanation.steps.len() >= 4);
324    }
325
326    #[test]
327    fn test_sum_rule_explanation() {
328        let x = symbol!(x);
329        let expr = Expression::add(vec![
330            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
331            Expression::mul(vec![Expression::integer(3), Expression::symbol(x.clone())]),
332            Expression::integer(5),
333        ]);
334        let explanation = expr.derivative_with_steps(&x, 1);
335        assert!(explanation.steps.len() >= 4);
336    }
337
338    #[test]
339    fn test_product_rule_explanation() {
340        let x = symbol!(x);
341        let expr = Expression::mul(vec![
342            Expression::symbol(x.clone()),
343            Expression::function("sin", vec![Expression::symbol(x.clone())]),
344        ]);
345        let explanation = expr.derivative_with_steps(&x, 1);
346        assert!(explanation.steps.len() >= 5);
347    }
348
349    #[test]
350    fn test_chain_rule_explanation() {
351        let x = symbol!(x);
352        let expr = Expression::function(
353            "sin",
354            vec![Expression::pow(
355                Expression::symbol(x.clone()),
356                Expression::integer(2),
357            )],
358        );
359        let explanation = expr.derivative_with_steps(&x, 1);
360        assert!(explanation.steps.len() >= 5);
361    }
362}