mathhook_core/calculus/limits/
educational.rs

1use super::methods::LimitMethods;
2use super::Limits;
3use crate::calculus::derivatives::Derivative;
4use crate::educational::enhanced_steps::{EnhancedStepBuilder, EnhancedStepExplanation};
5use crate::formatter::latex::LaTeXFormatter;
6use crate::{Expression, Symbol};
7
8/// Educational explanations for limit operations
9pub struct LimitEducation;
10
11impl LimitEducation {
12    /// Helper to safely format expression to LaTeX
13    fn format_latex(expr: &Expression) -> String {
14        expr.to_latex(None)
15            .unwrap_or_else(|_| format!("{:?}", expr))
16    }
17
18    /// Find numerator and denominator in a two-factor Mul expression.
19    /// Returns (numerator, denominator_base) where denominator is Pow(base, -1).
20    /// Handles both Integer(-1) and Rational(-1/1) exponents, and checks both factor positions.
21    fn find_num_denom_in_mul(factors: &[Expression]) -> (Option<&Expression>, Option<&Expression>) {
22        use crate::core::Number;
23        use num_bigint::BigInt;
24
25        fn is_minus_one(exp: &Expression) -> bool {
26            match exp {
27                Expression::Number(Number::Integer(-1)) => true,
28                Expression::Number(Number::Rational(r)) => {
29                    r.denom() == &BigInt::from(1) && r.numer() == &BigInt::from(-1)
30                }
31                _ => false,
32            }
33        }
34
35        if factors.len() != 2 {
36            return (None, None);
37        }
38
39        // Check if factors[1] is Pow(_, -1)
40        if let Expression::Pow(denom_base, exp) = &factors[1] {
41            if is_minus_one(exp) {
42                return (Some(&factors[0]), Some(denom_base.as_ref()));
43            }
44        }
45
46        // Check if factors[0] is Pow(_, -1) (due to canonical ordering)
47        if let Expression::Pow(denom_base, exp) = &factors[0] {
48            if is_minus_one(exp) {
49                return (Some(&factors[1]), Some(denom_base.as_ref()));
50            }
51        }
52
53        (None, None)
54    }
55
56    /// Generate educational explanation for direct substitution limit
57    ///
58    /// Explains the process of evaluating a limit by directly substituting
59    /// the limit point into the expression.
60    ///
61    /// # Arguments
62    ///
63    /// * `expr` - The expression to take the limit of
64    /// * `variable` - The variable approaching the limit point
65    /// * `point` - The value the variable approaches
66    /// * `result` - The computed limit result
67    ///
68    /// # Returns
69    ///
70    /// Complete step-by-step explanation with at least 3 steps
71    pub fn direct_substitution_explanation(
72        expr: &Expression,
73        variable: &Symbol,
74        point: &Expression,
75        result: &Expression,
76    ) -> EnhancedStepExplanation {
77        let mut steps = Vec::new();
78
79        let expr_latex = Self::format_latex(expr);
80        let var_name = variable.name();
81        let point_latex = Self::format_latex(point);
82        let result_latex = Self::format_latex(result);
83
84        steps.push(
85            EnhancedStepBuilder::new("limit_direct_1")
86                .with_human_message(
87                    "Attempt Direct Substitution",
88                    &format!(
89                        "To find lim({} -> {}) {}, first try direct substitution.\nSubstitute {} = {} into the expression.",
90                        var_name, point_latex, expr_latex, var_name, point_latex
91                    )
92                )
93                .with_api_data("limit", "direct_substitution", "substitute")
94                .with_input("expression", &expr_latex)
95                .with_input("variable", var_name)
96                .with_input("point", &point_latex)
97                .with_message_key("calculus", "limit_direct", 0)
98                .build()
99        );
100
101        let substituted_expr = LimitMethods::substitute_and_evaluate(expr, variable, point);
102        let substituted_latex = Self::format_latex(&substituted_expr);
103
104        steps.push(
105            EnhancedStepBuilder::new("limit_direct_2")
106                .with_human_message(
107                    "Evaluate Expression",
108                    &format!(
109                        "After substituting {} = {} into {}:\nResult: {}",
110                        var_name, point_latex, expr_latex, substituted_latex
111                    ),
112                )
113                .with_api_data("limit", "evaluation", "compute")
114                .with_input("substituted_expression", &substituted_latex)
115                .with_output("intermediate_result", &result_latex)
116                .with_message_key("calculus", "limit_direct", 1)
117                .build(),
118        );
119
120        steps.push(
121            EnhancedStepBuilder::new("limit_direct_3")
122                .with_human_message(
123                    "Verify No Indeterminate Form",
124                    &format!(
125                        "The result {} is well-defined (not 0/0 or infinity/infinity).\nTherefore, the limit exists and equals {}.",
126                        result_latex, result_latex
127                    )
128                )
129                .with_api_data("limit", "verification", "check_form")
130                .with_output("limit_result", &result_latex)
131                .with_output("form_type", "determinate")
132                .with_message_key("calculus", "limit_direct", 1)
133                .build()
134        );
135
136        EnhancedStepExplanation::new(steps)
137    }
138
139    /// Generate educational explanation for indeterminate form detection
140    ///
141    /// Explains how to identify indeterminate forms (0/0, infinity/infinity, etc.)
142    /// and the strategies needed to resolve them.
143    ///
144    /// # Arguments
145    ///
146    /// * `expr` - The expression producing an indeterminate form
147    /// * `variable` - The variable in the limit
148    /// * `point` - The limit point
149    /// * `form_type` - The type of indeterminate form (e.g., "0/0")
150    ///
151    /// # Returns
152    ///
153    /// Complete step-by-step explanation with at least 4 steps
154    pub fn indeterminate_form_explanation(
155        expr: &Expression,
156        variable: &Symbol,
157        point: &Expression,
158        form_type: &str,
159    ) -> EnhancedStepExplanation {
160        let mut steps = Vec::new();
161
162        let expr_latex = Self::format_latex(expr);
163        let var_name = variable.name();
164        let point_latex = Self::format_latex(point);
165
166        steps.push(
167            EnhancedStepBuilder::new("limit_indet_1")
168                .with_human_message(
169                    "Attempt Direct Substitution",
170                    &format!(
171                        "To find lim({} -> {}) {}, substitute {} = {}:",
172                        var_name, point_latex, expr_latex, var_name, point_latex
173                    ),
174                )
175                .with_api_data("limit", "substitution", "direct_attempt")
176                .with_input("expression", &expr_latex)
177                .with_input("variable", var_name)
178                .with_input("point", &point_latex)
179                .with_message_key("calculus", "limit_indeterminate", 0)
180                .build(),
181        );
182
183        if let Expression::Mul(factors) = expr {
184            if factors.len() == 2 {
185                // Find numerator and denominator - Pow(_, -1) could be at either index
186                // due to canonical ordering. Also handle both Integer(-1) and Rational(-1/1).
187                let (numerator, denominator) = Self::find_num_denom_in_mul(factors);
188
189                if let Some((num, denom)) = numerator.zip(denominator) {
190                    let num_latex = Self::format_latex(num);
191                    let den_latex = Self::format_latex(denom);
192
193                    let num_at_point = LimitMethods::substitute_and_evaluate(num, variable, point);
194                    let den_at_point =
195                        LimitMethods::substitute_and_evaluate(denom, variable, point);
196
197                    let num_result_latex = Self::format_latex(&num_at_point);
198                    let den_result_latex = Self::format_latex(&den_at_point);
199
200                    steps.push(
201                        EnhancedStepBuilder::new("limit_indet_2")
202                            .with_human_message(
203                                "Evaluate Components",
204                                &format!(
205                                    "Numerator at {} = {}: {} = {}\nDenominator at {} = {}: {} = {}",
206                                    var_name, point_latex, num_latex, num_result_latex,
207                                    var_name, point_latex, den_latex, den_result_latex
208                                )
209                            )
210                            .with_api_data("limit", "component_evaluation", "rational")
211                            .with_input("numerator", &num_latex)
212                            .with_input("denominator", &den_latex)
213                            .with_output("numerator_value", &num_result_latex)
214                            .with_output("denominator_value", &den_result_latex)
215                            .with_message_key("calculus", "limit_indeterminate", 1)
216                            .build()
217                    );
218                }
219            }
220        }
221
222        steps.push(
223            EnhancedStepBuilder::new("limit_indet_3")
224                .with_human_message(
225                    "Identify Indeterminate Form",
226                    &format!(
227                        "The result is {} (indeterminate form).\nThis form is undefined and requires further analysis.",
228                        form_type
229                    )
230                )
231                .with_api_data("limit", "form_detection", "indeterminate")
232                .with_output("form_type", form_type)
233                .with_output("requires_resolution", "true")
234                .with_message_key("calculus", "limit_indeterminate", 0)
235                .build()
236        );
237
238        let resolution_strategy = match form_type {
239            "0/0" => "Factorization: Factor numerator and denominator to cancel common terms, or apply L'Hopital's rule",
240            "infinity/infinity" => "Divide by highest power: Divide numerator and denominator by highest power term",
241            "0*infinity" => "Rewrite as fraction: Convert to 0/0 or infinity/infinity form",
242            _ => "Apply algebraic manipulation or L'Hopital's rule"
243        };
244
245        steps.push(
246            EnhancedStepBuilder::new("limit_indet_4")
247                .with_human_message(
248                    "Resolution Strategy",
249                    &format!("To resolve {} form:\n{}", form_type, resolution_strategy),
250                )
251                .with_api_data("limit", "strategy", "resolution")
252                .with_input("indeterminate_form", form_type)
253                .with_output("strategy", resolution_strategy)
254                .with_message_key("calculus", "limit_indeterminate", 1)
255                .build(),
256        );
257
258        EnhancedStepExplanation::new(steps)
259    }
260
261    /// Generate educational explanation for L'Hopital's rule application
262    ///
263    /// Explains the complete process of applying L'Hopital's rule to resolve
264    /// indeterminate forms by differentiating numerator and denominator.
265    ///
266    /// # Arguments
267    ///
268    /// * `numerator` - The numerator expression
269    /// * `denominator` - The denominator expression
270    /// * `variable` - The variable in the limit
271    /// * `point` - The limit point
272    ///
273    /// # Returns
274    ///
275    /// Complete step-by-step explanation with at least 6 steps
276    pub fn lhopital_rule_explanation(
277        numerator: &Expression,
278        denominator: &Expression,
279        variable: &Symbol,
280        point: &Expression,
281    ) -> EnhancedStepExplanation {
282        let mut steps = Vec::new();
283
284        let num_latex = Self::format_latex(numerator);
285        let den_latex = Self::format_latex(denominator);
286        let var_name = variable.name();
287        let point_latex = Self::format_latex(point);
288
289        let num_at_point = LimitMethods::substitute_and_evaluate(numerator, variable, point);
290        let den_at_point = LimitMethods::substitute_and_evaluate(denominator, variable, point);
291
292        let form_type = if num_at_point.is_zero() && den_at_point.is_zero() {
293            "0/0"
294        } else {
295            "infinity/infinity"
296        };
297
298        steps.push(
299            EnhancedStepBuilder::new("limit_lhopital_1")
300                .with_human_message(
301                    "Check Conditions for L'Hopital's Rule",
302                    &format!(
303                        "For lim({} -> {}) {}/{}, direct substitution gives {}.\nL'Hopital's rule applies to indeterminate forms 0/0 and infinity/infinity.",
304                        var_name, point_latex, num_latex, den_latex, form_type
305                    )
306                )
307                .with_api_data("limit", "lhopital_check", "conditions")
308                .with_input("numerator", &num_latex)
309                .with_input("denominator", &den_latex)
310                .with_output("form_type", form_type)
311                .with_output("rule_applies", "true")
312                .with_message_key("calculus", "limit_lhopital", 0)
313                .build()
314        );
315
316        steps.push(
317            EnhancedStepBuilder::new("limit_lhopital_2")
318                .with_human_message(
319                    "State L'Hopital's Rule",
320                    "If lim(f(x)/g(x)) gives 0/0 or infinity/infinity, then:\nlim(f(x)/g(x)) = lim(f'(x)/g'(x))\nprovided the limit on the right exists."
321                )
322                .with_api_data("limit", "rule_statement", "lhopital_theorem")
323                .with_output("theorem", "lhopital")
324                .with_output("applicable_forms", "0/0, infinity/infinity")
325                .with_message_key("calculus", "limit_lhopital", 0)
326                .build()
327        );
328
329        let num_derivative = numerator.derivative(variable.clone());
330        let num_deriv_latex = Self::format_latex(&num_derivative);
331
332        steps.push(
333            EnhancedStepBuilder::new("limit_lhopital_3")
334                .with_human_message(
335                    "Differentiate Numerator",
336                    &format!(
337                        "Find derivative of numerator with respect to {}:\nd/d{}[{}] = {}",
338                        var_name, var_name, num_latex, num_deriv_latex
339                    ),
340                )
341                .with_api_data("limit", "differentiation", "numerator")
342                .with_input("original", &num_latex)
343                .with_output("derivative", &num_deriv_latex)
344                .with_message_key("calculus", "limit_lhopital", 1)
345                .build(),
346        );
347
348        let den_derivative = denominator.derivative(variable.clone());
349        let den_deriv_latex = Self::format_latex(&den_derivative);
350
351        steps.push(
352            EnhancedStepBuilder::new("limit_lhopital_4")
353                .with_human_message(
354                    "Differentiate Denominator",
355                    &format!(
356                        "Find derivative of denominator with respect to {}:\nd/d{}[{}] = {}",
357                        var_name, var_name, den_latex, den_deriv_latex
358                    ),
359                )
360                .with_api_data("limit", "differentiation", "denominator")
361                .with_input("original", &den_latex)
362                .with_output("derivative", &den_deriv_latex)
363                .with_message_key("calculus", "limit_lhopital", 1)
364                .build(),
365        );
366
367        steps.push(
368            EnhancedStepBuilder::new("limit_lhopital_5")
369                .with_human_message(
370                    "Apply L'Hopital's Rule",
371                    &format!(
372                        "By L'Hopital's rule:\nlim({} -> {}) {}/{} = lim({} -> {}) {}/{}",
373                        var_name,
374                        point_latex,
375                        num_latex,
376                        den_latex,
377                        var_name,
378                        point_latex,
379                        num_deriv_latex,
380                        den_deriv_latex
381                    ),
382                )
383                .with_api_data("limit", "rule_application", "substitute_derivatives")
384                .with_input("original_limit", &format!("{}/{}", num_latex, den_latex))
385                .with_output(
386                    "new_limit",
387                    &format!("{}/{}", num_deriv_latex, den_deriv_latex),
388                )
389                .with_message_key("calculus", "limit_lhopital", 1)
390                .build(),
391        );
392
393        let new_limit_result =
394            LimitMethods::rational_limit(&num_derivative, &den_derivative, variable, point);
395        let result_latex = Self::format_latex(&new_limit_result);
396
397        steps.push(
398            EnhancedStepBuilder::new("limit_lhopital_6")
399                .with_human_message(
400                    "Evaluate New Limit",
401                    &format!(
402                        "Now evaluate lim({} -> {}) {}/{}:\nResult: {}",
403                        var_name, point_latex, num_deriv_latex, den_deriv_latex, result_latex
404                    ),
405                )
406                .with_api_data("limit", "final_evaluation", "result")
407                .with_output("final_result", &result_latex)
408                .with_message_key("calculus", "limit_lhopital", 1)
409                .build(),
410        );
411
412        EnhancedStepExplanation::new(steps)
413    }
414
415    /// Generate educational explanation for limit laws application
416    ///
417    /// Explains how limit laws (sum, product, quotient, constant multiple)
418    /// can be used to break down complex limits into simpler parts.
419    ///
420    /// # Arguments
421    ///
422    /// * `expr` - The expression whose limit to find
423    /// * `variable` - The variable in the limit
424    /// * `point` - The limit point
425    ///
426    /// # Returns
427    ///
428    /// Complete step-by-step explanation with at least 4 steps
429    pub fn limit_laws_explanation(
430        expr: &Expression,
431        variable: &Symbol,
432        point: &Expression,
433    ) -> EnhancedStepExplanation {
434        let mut steps = Vec::new();
435
436        let expr_latex = Self::format_latex(expr);
437        let var_name = variable.name();
438        let point_latex = Self::format_latex(point);
439
440        steps.push(
441            EnhancedStepBuilder::new("limit_laws_1")
442                .with_human_message(
443                    "Identify Applicable Limit Laws",
444                    &format!(
445                        "To find lim({} -> {}) {}, we can apply limit laws:\n• Sum Law: lim[f(x) + g(x)] = lim f(x) + lim g(x)\n• Product Law: lim[f(x) * g(x)] = lim f(x) * lim g(x)\n• Constant Multiple Law: lim[c * f(x)] = c * lim f(x)",
446                        var_name, point_latex, expr_latex
447                    )
448                )
449                .with_api_data("limit", "laws", "introduction")
450                .with_input("expression", &expr_latex)
451                .with_message_key("calculus", "limit_laws", 0)
452                .build()
453        );
454
455        match expr {
456            Expression::Add(terms) => {
457                steps.push(
458                    EnhancedStepBuilder::new("limit_laws_2")
459                        .with_human_message(
460                            "Apply Sum Law",
461                            &format!(
462                                "The expression is a sum of {} terms.\nBy Sum Law: lim[f + g] = lim f + lim g\nEvaluate limit of each term separately.",
463                                terms.len()
464                            )
465                        )
466                        .with_api_data("limit", "law_application", "sum")
467                        .with_input("term_count", &terms.len().to_string())
468                        .with_output("law_used", "sum_law")
469                        .with_message_key("calculus", "limit_laws", 0)
470                        .build()
471                );
472
473                let term_limits: Vec<String> = terms
474                    .iter()
475                    .map(|term| {
476                        let limit_result = term.limit(variable, point);
477                        format!(
478                            "lim {} = {}",
479                            Self::format_latex(term),
480                            Self::format_latex(&limit_result)
481                        )
482                    })
483                    .collect();
484
485                steps.push(
486                    EnhancedStepBuilder::new("limit_laws_3")
487                        .with_human_message(
488                            "Evaluate Individual Terms",
489                            &format!("Evaluate limit of each term:\n{}", term_limits.join("\n")),
490                        )
491                        .with_api_data("limit", "term_evaluation", "individual")
492                        .with_message_key("calculus", "limit_laws", 1)
493                        .build(),
494                );
495            }
496            Expression::Mul(factors) => {
497                let has_constant = factors.iter().any(|f| matches!(f, Expression::Number(_)));
498
499                if has_constant {
500                    steps.push(
501                        EnhancedStepBuilder::new("limit_laws_2")
502                            .with_human_message(
503                                "Apply Constant Multiple Law",
504                                "By Constant Multiple Law: lim[c * f(x)] = c * lim f(x)\nFactor out constants and evaluate limit of remaining expression."
505                            )
506                            .with_api_data("limit", "law_application", "constant_multiple")
507                            .with_output("law_used", "constant_multiple_law")
508                            .with_message_key("calculus", "limit_laws", 0)
509                            .build()
510                    );
511                } else {
512                    steps.push(
513                        EnhancedStepBuilder::new("limit_laws_2")
514                            .with_human_message(
515                                "Apply Product Law",
516                                &format!(
517                                    "The expression is a product of {} factors.\nBy Product Law: lim[f * g] = lim f * lim g\nEvaluate limit of each factor separately.",
518                                    factors.len()
519                                )
520                            )
521                            .with_api_data("limit", "law_application", "product")
522                            .with_input("factor_count", &factors.len().to_string())
523                            .with_output("law_used", "product_law")
524                            .with_message_key("calculus", "limit_laws", 1)
525                            .build()
526                    );
527                }
528
529                steps.push(
530                    EnhancedStepBuilder::new("limit_laws_3")
531                        .with_human_message(
532                            "Evaluate Components",
533                            "Evaluate limit of each component and combine results using the limit law."
534                        )
535                        .with_api_data("limit", "component_evaluation", "combine")
536                        .with_message_key("calculus", "limit_laws", 1)
537                        .build()
538                );
539            }
540            _ => {
541                steps.push(
542                    EnhancedStepBuilder::new("limit_laws_2")
543                        .with_human_message(
544                            "Direct Evaluation",
545                            "For simple expressions, directly substitute the limit point.",
546                        )
547                        .with_api_data("limit", "direct", "simple")
548                        .with_message_key("calculus", "limit_laws", 0)
549                        .build(),
550                );
551            }
552        }
553
554        let result = expr.limit(variable, point);
555        let result_latex = Self::format_latex(&result);
556
557        steps.push(
558            EnhancedStepBuilder::new("limit_laws_4")
559                .with_human_message(
560                    "Combine Results",
561                    &format!(
562                        "Combining the individual limits:\nlim({} -> {}) {} = {}",
563                        var_name, point_latex, expr_latex, result_latex
564                    ),
565                )
566                .with_api_data("limit", "combination", "final")
567                .with_output("final_result", &result_latex)
568                .with_message_key("calculus", "limit_laws", 1)
569                .build(),
570        );
571
572        EnhancedStepExplanation::new(steps)
573    }
574
575    /// Generate educational explanation for limits at infinity
576    ///
577    /// Explains the technique of dividing by the highest power to evaluate
578    /// limits as the variable approaches infinity.
579    ///
580    /// # Arguments
581    ///
582    /// * `expr` - The expression whose limit to find
583    /// * `variable` - The variable approaching infinity
584    ///
585    /// # Returns
586    ///
587    /// Complete step-by-step explanation with at least 4 steps
588    pub fn limit_at_infinity_explanation(
589        expr: &Expression,
590        variable: &Symbol,
591    ) -> EnhancedStepExplanation {
592        let mut steps = Vec::new();
593
594        let expr_latex = Self::format_latex(expr);
595        let var_name = variable.name();
596
597        steps.push(
598            EnhancedStepBuilder::new("limit_infinity_1")
599                .with_human_message(
600                    "Identify Form",
601                    &format!(
602                        "To find lim({} -> infinity) {}, first identify the form.\nFor rational functions, both numerator and denominator may approach infinity (infinity/infinity form).",
603                        var_name, expr_latex
604                    )
605                )
606                .with_api_data("limit", "infinity", "identify_form")
607                .with_input("expression", &expr_latex)
608                .with_input("limit_point", "infinity")
609                .with_message_key("calculus", "limit_infinity", 0)
610                .build()
611        );
612
613        if let Expression::Mul(factors) = expr {
614            if factors.len() == 2 {
615                if let Expression::Pow(denom, exp) = &factors[1] {
616                    if **exp == Expression::integer(-1) {
617                        let numerator = &factors[0];
618                        let num_latex = Self::format_latex(numerator);
619                        let den_latex = Self::format_latex(denom);
620
621                        steps.push(
622                            EnhancedStepBuilder::new("limit_infinity_2")
623                                .with_human_message(
624                                    "Divide by Highest Power",
625                                    &format!(
626                                        "For rational function {}/{}:\nDivide both numerator and denominator by the highest power of {} in the denominator.\nThis transforms terms: {}/infinity -> 0",
627                                        num_latex, den_latex, var_name, var_name
628                                    )
629                                )
630                                .with_api_data("limit", "technique", "divide_highest_power")
631                                .with_input("numerator", &num_latex)
632                                .with_input("denominator", &den_latex)
633                                .with_output("technique", "divide_by_highest_power")
634                                .with_message_key("calculus", "limit_infinity", 1)
635                                .build()
636                        );
637
638                        steps.push(
639                            EnhancedStepBuilder::new("limit_infinity_3")
640                                .with_human_message(
641                                    "Evaluate as x Approaches Infinity",
642                                    &format!(
643                                        "As {} -> infinity:\n• Terms like 1/{}, 1/{}^2, etc. approach 0\n• Leading coefficients remain\n• Evaluate the simplified expression",
644                                        var_name, var_name, var_name
645                                    )
646                                )
647                                .with_api_data("limit", "evaluation", "infinity_behavior")
648                                .with_message_key("calculus", "limit_infinity", 1)
649                                .build()
650                        );
651                    }
652                }
653            }
654        } else {
655            steps.push(
656                EnhancedStepBuilder::new("limit_infinity_2")
657                    .with_human_message(
658                        "Analyze Dominant Term",
659                        &format!(
660                            "For polynomial or simple expressions, identify the dominant term (highest power of {}).\nThe limit behavior is determined by this dominant term.",
661                            var_name
662                        )
663                    )
664                    .with_api_data("limit", "analysis", "dominant_term")
665                    .with_message_key("calculus", "limit_infinity", 0)
666                    .build()
667            );
668
669            steps.push(
670                EnhancedStepBuilder::new("limit_infinity_3")
671                    .with_human_message(
672                        "Evaluate Limit",
673                        &format!(
674                            "As {} -> infinity, evaluate the behavior of the expression.",
675                            var_name
676                        ),
677                    )
678                    .with_api_data("limit", "evaluation", "determine")
679                    .with_message_key("calculus", "limit_infinity", 1)
680                    .build(),
681            );
682        }
683
684        let result = expr.limit_at_infinity(variable);
685        let result_latex = Self::format_latex(&result);
686
687        steps.push(
688            EnhancedStepBuilder::new("limit_infinity_4")
689                .with_human_message(
690                    "Final Result",
691                    &format!(
692                        "Therefore:\nlim({} -> infinity) {} = {}",
693                        var_name, expr_latex, result_latex
694                    ),
695                )
696                .with_api_data("limit", "result", "infinity")
697                .with_output("final_result", &result_latex)
698                .with_message_key("calculus", "limit_infinity", 1)
699                .build(),
700        );
701
702        EnhancedStepExplanation::new(steps)
703    }
704}