mathhook_core/calculus/summation/
educational.rs1use crate::calculus::summation::{ConvergenceResult, Summation, SummationMethods};
4use crate::core::{Expression, Number, Symbol};
5use crate::educational::message_registry::core::{
6 MessageCategory, MessageKey, MessageType, MESSAGE_REGISTRY,
7};
8use crate::educational::step_by_step::{Step, StepByStepExplanation};
9use crate::expr;
10use crate::simplify::Simplify;
11
12pub trait SummationEducational {
14 fn explain_finite_sum(
31 &self,
32 variable: &Symbol,
33 start: &Expression,
34 end: &Expression,
35 ) -> StepByStepExplanation;
36
37 fn explain_infinite_sum(&self, variable: &Symbol, start: &Expression) -> StepByStepExplanation;
54}
55
56impl SummationEducational for Expression {
57 fn explain_finite_sum(
58 &self,
59 variable: &Symbol,
60 start: &Expression,
61 end: &Expression,
62 ) -> StepByStepExplanation {
63 let mut steps = Vec::new();
64 let initial_expr = self.clone();
65
66 let num_terms = Expression::add(vec![
67 end.clone(),
68 Expression::mul(vec![expr!(-1), start.clone()]),
69 expr!(1),
70 ])
71 .simplify();
72
73 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
74 MessageCategory::Calculus,
75 MessageType::SummationIntroduction,
76 0,
77 )) {
78 let mut desc = template.content.to_owned();
79 desc = desc.replace("{variable}", variable.name());
80 desc = desc.replace("{start}", &format!("{}", start));
81 desc = desc.replace("{end}", &format!("{}", end));
82 desc = desc.replace("{term_count}", &format!("{}", num_terms));
83
84 steps.push(Step {
85 title: template.title.to_owned(),
86 description: desc,
87 expression: self.clone(),
88 rule_applied: "Introduction".to_owned(),
89 latex: None,
90 });
91 }
92
93 let series_type = detect_series_type(self, variable);
94 match series_type {
95 SeriesType::Arithmetic(first, diff) => {
96 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
97 MessageCategory::Calculus,
98 MessageType::SummationArithmeticSeries,
99 0,
100 )) {
101 let mut desc = template.content.to_owned();
102 desc = desc.replace("{first_term}", &format!("{}", first));
103 desc = desc.replace("{common_difference}", &format!("{}", diff));
104 desc = desc.replace("{term_count}", &format!("{}", num_terms));
105
106 steps.push(Step {
107 title: template.title.to_owned(),
108 description: desc,
109 expression: self.clone(),
110 rule_applied: "Series Detection".to_owned(),
111 latex: None,
112 });
113 }
114
115 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
116 MessageCategory::Calculus,
117 MessageType::SummationArithmeticSeries,
118 1,
119 )) {
120 steps.push(Step {
121 title: template.title.to_owned(),
122 description: template.content.to_owned(),
123 expression: self.clone(),
124 rule_applied: "Formula".to_owned(),
125 latex: None,
126 });
127 }
128 }
129 SeriesType::PowerSum(k) => {
130 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
131 MessageCategory::Calculus,
132 MessageType::SummationPowerSum,
133 k.min(4) as u8,
134 )) {
135 let mut desc = template.content.to_owned();
136 if k > 3 {
137 desc = desc.replace("{power}", &format!("{}", k));
138 }
139
140 steps.push(Step {
141 title: template.title.to_owned(),
142 description: desc,
143 expression: self.clone(),
144 rule_applied: "Power Sum Formula".to_owned(),
145 latex: None,
146 });
147 }
148 }
149 SeriesType::General => {}
150 }
151
152 let result = self.finite_sum(variable, start, end);
153
154 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
155 MessageCategory::Calculus,
156 MessageType::SummationResult,
157 0,
158 )) {
159 let mut desc = template.content.to_owned();
160 desc = desc.replace("{result}", &format!("{}", result));
161
162 steps.push(Step {
163 title: template.title.to_owned(),
164 description: desc,
165 expression: result.clone(),
166 rule_applied: "Final Result".to_owned(),
167 latex: None,
168 });
169 }
170
171 let total_steps = steps.len().saturating_sub(2);
172
173 StepByStepExplanation {
174 initial_expression: initial_expr,
175 final_expression: result,
176 steps,
177 total_steps,
178 rules_used: vec!["Summation".to_owned()],
179 }
180 }
181
182 fn explain_infinite_sum(&self, variable: &Symbol, start: &Expression) -> StepByStepExplanation {
183 let mut steps = Vec::new();
184 let initial_expr = self.clone();
185
186 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
187 MessageCategory::Calculus,
188 MessageType::SummationIntroduction,
189 1,
190 )) {
191 let mut desc = template.content.to_owned();
192 desc = desc.replace("{expression}", &format!("{}", self));
193 desc = desc.replace("{start}", &format!("{}", start));
194
195 steps.push(Step {
196 title: template.title.to_owned(),
197 description: desc,
198 expression: self.clone(),
199 rule_applied: "Introduction".to_owned(),
200 latex: None,
201 });
202 }
203
204 let convergence = SummationMethods::convergence_test(self, variable);
205 let (convergent, reason) = match convergence {
206 ConvergenceResult::Convergent => (
207 true,
208 "the terms decay fast enough (p-series test with p > 1)",
209 ),
210 ConvergenceResult::Divergent => (
211 false,
212 "the terms do not decay to zero fast enough (p-series test with p ≤ 1)",
213 ),
214 _ => (false, "convergence cannot be determined automatically"),
215 };
216
217 let variant = if convergent { 0 } else { 1 };
218 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
219 MessageCategory::Calculus,
220 MessageType::SummationConvergence,
221 variant,
222 )) {
223 let mut desc = template.content.to_owned();
224 desc = desc.replace("{reason}", reason);
225
226 steps.push(Step {
227 title: template.title.to_owned(),
228 description: desc,
229 expression: self.clone(),
230 rule_applied: "Convergence Test".to_owned(),
231 latex: None,
232 });
233 }
234
235 let result = self.infinite_sum(variable, start);
236
237 if let Some(template) = MESSAGE_REGISTRY.get(&MessageKey::new(
238 MessageCategory::Calculus,
239 MessageType::SummationResult,
240 0,
241 )) {
242 let mut desc = template.content.to_owned();
243 desc = desc.replace("{result}", &format!("{}", result));
244
245 steps.push(Step {
246 title: template.title.to_owned(),
247 description: desc,
248 expression: result.clone(),
249 rule_applied: "Final Result".to_owned(),
250 latex: None,
251 });
252 }
253
254 let total_steps = steps.len().saturating_sub(2);
255
256 StepByStepExplanation {
257 initial_expression: initial_expr,
258 final_expression: result,
259 steps,
260 total_steps,
261 rules_used: vec!["Infinite Sum".to_owned()],
262 }
263 }
264}
265
266fn detect_series_type(expr: &Expression, variable: &Symbol) -> SeriesType {
267 if let Expression::Symbol(sym) = expr {
268 if sym == variable {
269 return SeriesType::Arithmetic(expr!(1), expr!(1));
270 }
271 }
272
273 if let Expression::Pow(base, exp) = expr {
274 if let (Expression::Symbol(sym), Expression::Number(Number::Integer(k))) =
275 (base.as_ref(), exp.as_ref())
276 {
277 if sym == variable && k.is_positive() && *k <= 100 {
278 return SeriesType::PowerSum(*k as u32);
279 }
280 }
281 }
282
283 SeriesType::General
284}
285
286#[derive(Debug, Clone)]
287#[allow(dead_code)]
288enum SeriesType {
289 Arithmetic(Expression, Expression),
290 PowerSum(u32),
291 General,
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::symbol;
298
299 #[test]
300 fn test_explain_finite_sum_arithmetic() {
301 let i = symbol!(i);
302 let expr_i: Expression = i.clone().into();
303 let explanation = expr_i.explain_finite_sum(&i, &expr!(1), &expr!(10));
304
305 assert!(explanation.steps.len() >= 3);
306 assert_eq!(explanation.final_expression, expr!(55));
307 }
308
309 #[test]
310 fn test_explain_finite_sum_power() {
311 let i = symbol!(i);
312 let expr = expr!(i ^ 2);
313 let explanation = expr.explain_finite_sum(&i, &expr!(1), &expr!(3));
314
315 assert!(
316 !explanation.steps.is_empty(),
317 "Should have at least some steps"
318 );
319
320 assert!(
321 explanation.steps.len() >= 2,
322 "Should have at least intro and result"
323 );
324 }
325
326 #[test]
327 fn test_explain_infinite_sum_convergent() {
328 let n = symbol!(n);
329 let expr = expr!(n ^ (-2));
330 let explanation = expr.explain_infinite_sum(&n, &expr!(1));
331
332 assert!(explanation.steps.len() >= 2);
333 }
334}