1mod 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
20pub(crate) fn format_expr(expr: &Expression) -> String {
22 format!("{}", expr)
23}
24
25pub trait DerivativeWithSteps {
27 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(¤t, 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(¤t)),
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
114fn 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
173fn is_power_rule_applicable(base: &Expression, _exp: &Expression, variable: &Symbol) -> bool {
175 matches!(base, Expression::Symbol(sym) if sym == variable)
176}
177
178fn 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
211fn 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
243fn 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
276pub(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}