mathhook_core/calculus/ode/educational/
examples.rs

1//! Pre-built Educational ODE Examples
2//!
3//! Provides worked examples for each ODE type, including:
4//! - Complete solutions with all steps
5//! - Common pitfalls and mistakes
6//! - Reference material for students
7
8use crate::calculus::ode::educational::{ODEExplanation, ODEStepFactory};
9use crate::{expr, symbol};
10
11/// Collection of educational ODE examples
12pub struct ODEExamples;
13
14impl ODEExamples {
15    /// Example: Simplest separable ODE dy/dx = x
16    ///
17    /// # Solution
18    ///
19    /// y = x²/2 + C
20    ///
21    /// # Common Pitfalls
22    ///
23    /// - Forgetting the constant of integration
24    /// - Incorrect integration of x (getting x²/2 instead of x²/2)
25    pub fn separable_simple() -> ODEExplanation {
26        let x = symbol!(x);
27        let y = symbol!(y);
28        let rhs = expr!(x);
29
30        let mut steps = Vec::new();
31
32        // Step 1: Detection
33        steps.push(ODEStepFactory::detection(
34            "separable",
35            &rhs,
36            "The right-hand side depends only on x, so we can write dy/dx = g(x)·h(y) where g(x) = x and h(y) = 1",
37        ));
38
39        // Step 2: Separation
40        steps.push(ODEStepFactory::separation(&rhs, &expr!(x), "x", "1"));
41
42        // Step 3: Integration (left side)
43        steps.push(ODEStepFactory::integration(
44            &expr!(1),
45            &expr!(y),
46            &y,
47            "left",
48        ));
49
50        // Step 4: Integration (right side)
51        let integral_result = expr!((x ^ 2) / 2);
52        steps.push(ODEStepFactory::integration(
53            &expr!(x),
54            &integral_result,
55            &x,
56            "right",
57        ));
58
59        // Step 5: Solution construction
60        let solution = expr!(y); // Placeholder: would be y = x²/2 + C
61        steps.push(ODEStepFactory::solution_construction(
62            &expr!(y),
63            &solution,
64            "algebraic rearrangement",
65        ));
66
67        ODEExplanation::new(
68            solution,
69            steps,
70            "Separable".to_owned(),
71            "Variable separation: Separate variables and integrate both sides".to_owned(),
72        )
73    }
74
75    /// Example: Exponential growth dy/dx = y
76    ///
77    /// # Solution
78    ///
79    /// y = C·e^x
80    ///
81    /// # Common Pitfalls
82    ///
83    /// - Forgetting absolute value in ln|y| = x + C
84    /// - Incorrect handling of integration constant when exponentiating
85    /// - Not recognizing this as both separable and linear
86    pub fn exponential_growth() -> ODEExplanation {
87        let x = symbol!(x);
88        let y = symbol!(y);
89        let rhs = expr!(y);
90
91        let mut steps: Vec<crate::calculus::ode::ODESolutionStep> = vec![
92            ODEStepFactory::detection(
93                "separable (exponential)",
94                &rhs,
95                "Can be written as dy/dx = 1·y, where g(x) = 1 and h(y) = y",
96            ),
97            ODEStepFactory::separation(&rhs, &expr!(1 / y), "1", "y"),
98            // Integration: ∫(1/y)dy = ln|y|
99            ODEStepFactory::integration(
100                &expr!(1 / y),
101                &expr!(y), // Placeholder: ln|y|
102                &y,
103                "left",
104            ),
105            // Integration: ∫1 dx = x
106            ODEStepFactory::integration(&expr!(1), &expr!(x), &x, "right"),
107        ];
108
109        let solution = expr!(y); // Placeholder: y = C·e^x
110        steps.push(ODEStepFactory::solution_construction(
111            &expr!(y),
112            &solution,
113            "exponentiation and constant manipulation",
114        ));
115
116        ODEExplanation::new(
117            solution,
118            steps,
119            "Separable (Exponential Growth)".to_owned(),
120            "Separation and integration yields exponential solution".to_owned(),
121        )
122    }
123
124    /// Example: Linear first-order dy/dx + y = x
125    ///
126    /// # Solution
127    ///
128    /// y = x - 1 + C·e^(-x)
129    ///
130    /// # Common Pitfalls
131    ///
132    /// - Incorrectly computing integrating factor μ(x) = e^(∫P(x)dx)
133    /// - Forgetting to multiply Q(x) by μ(x) before integrating
134    /// - Sign errors in the exponent
135    pub fn linear_first_order() -> ODEExplanation {
136        // dy/dx + y = x means rhs = x - y (rearranged form)
137        let rhs = expr!(x - y);
138
139        let steps = vec![ODEStepFactory::detection(
140            "linear first-order",
141            &rhs,
142            "Can be written as dy/dx + P(x)y = Q(x) where P(x) = 1 and Q(x) = x",
143        )];
144
145        // Additional steps would include:
146        // - Computing integrating factor μ(x) = e^x
147        // - Multiplying through by μ(x)
148        // - Recognizing left side as d/dx[μ(x)y]
149        // - Integrating both sides
150
151        let solution = expr!(y); // Placeholder: y = x - 1 + C·e^(-x)
152        ODEExplanation::new(
153            solution,
154            steps,
155            "Linear First-Order".to_owned(),
156            "Integrating factor method: μ(x) = e^(∫P(x)dx)".to_owned(),
157        )
158    }
159
160    /// Example: Product form dy/dx = xy
161    ///
162    /// # Solution
163    ///
164    /// y = C·e^(x²/2)
165    ///
166    /// # Common Pitfalls
167    ///
168    /// - Incorrectly integrating x to get x²/2 vs x²
169    /// - Forgetting to include constant in exponential
170    /// - Not simplifying e^(x²/2 + C) to C'·e^(x²/2)
171    pub fn product_separable() -> ODEExplanation {
172        let x = symbol!(x);
173        let y = symbol!(y);
174        let rhs = expr!(x * y);
175
176        let mut steps = vec![
177            ODEStepFactory::detection(
178                "separable (product form)",
179                &rhs,
180                "Equation dy/dx = xy can be written as dy/y = x dx (product of functions of different variables)",
181            ),
182            ODEStepFactory::separation(&rhs, &expr!(1 / y), "x", "y"),
183            ODEStepFactory::integration(
184                &expr!(1 / y),
185                &expr!(y), // Placeholder: ln|y|
186                &y,
187                "left",
188            ),
189            ODEStepFactory::integration(
190                &expr!(x),
191                &expr!((x ^ 2) / 2),
192                &x,
193                "right",
194            ),
195
196        ];
197
198        let solution = expr!(y); // Placeholder: y = C·e^(x²/2)
199        steps.push(ODEStepFactory::solution_construction(
200            &expr!(y),
201            &solution,
202            "exponentiation and constant simplification",
203        ));
204
205        ODEExplanation::new(
206            solution,
207            steps,
208            "Separable (Product Form)".to_owned(),
209            "Separate and integrate: ln|y| = x²/2 + C → y = C·e^(x²/2)".to_owned(),
210        )
211    }
212
213    /// Example: Initial value problem dy/dx = x, y(0) = 1
214    ///
215    /// # Solution
216    ///
217    /// y = x²/2 + 1
218    ///
219    /// # Common Pitfalls
220    ///
221    /// - Forgetting to apply initial condition
222    /// - Incorrectly solving for constant C
223    /// - Not verifying that y(0) = 1 in final solution
224    pub fn initial_value_problem() -> ODEExplanation {
225        let x = symbol!(x);
226        let y = symbol!(y);
227        let rhs = expr!(x);
228
229        let mut steps = vec![
230            ODEStepFactory::detection(
231                "separable with initial condition",
232                &rhs,
233                "Standard separable form dy/dx = g(x) with initial condition y(0) = 1",
234            ),
235            ODEStepFactory::separation(&rhs, &expr!(x), "x", "1"),
236            ODEStepFactory::integration(&expr!(1), &expr!(y), &y, "left"),
237            ODEStepFactory::integration(&expr!(x), &expr!((x ^ 2) / 2), &x, "right"),
238        ];
239
240        // Add initial condition application step
241        let mut ic_step = ODEStepFactory::solution_construction(
242            &expr!(y),
243            &expr!(y), // Placeholder: y = x²/2 + 1
244            "applying initial condition y(0) = 1",
245        );
246        ic_step.description =
247            "Substitute x = 0, y = 1 into general solution to find C = 1".to_owned();
248        steps.push(ic_step);
249
250        let solution = expr!(y); // Placeholder: y = x²/2 + 1
251        ODEExplanation::new(
252            solution,
253            steps,
254            "Initial Value Problem".to_owned(),
255            "Solve general solution, then apply initial condition to find constant".to_owned(),
256        )
257    }
258
259    /// Get all example explanations
260    pub fn all_examples() -> Vec<ODEExplanation> {
261        vec![
262            Self::separable_simple(),
263            Self::exponential_growth(),
264            Self::linear_first_order(),
265            Self::product_separable(),
266            Self::initial_value_problem(),
267        ]
268    }
269
270    /// Get example by name
271    pub fn get_example(name: &str) -> Option<ODEExplanation> {
272        match name {
273            "separable_simple" => Some(Self::separable_simple()),
274            "exponential_growth" => Some(Self::exponential_growth()),
275            "linear_first_order" => Some(Self::linear_first_order()),
276            "product_separable" => Some(Self::product_separable()),
277            "initial_value_problem" => Some(Self::initial_value_problem()),
278            _ => None,
279        }
280    }
281
282    /// Get list of available example names
283    pub fn example_names() -> Vec<&'static str> {
284        vec![
285            "separable_simple",
286            "exponential_growth",
287            "linear_first_order",
288            "product_separable",
289            "initial_value_problem",
290        ]
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_separable_simple_example() {
300        let example = ODEExamples::separable_simple();
301        assert_eq!(example.ode_type, "Separable");
302        assert!(!example.steps.is_empty());
303        assert!(example.steps.len() >= 5);
304    }
305
306    #[test]
307    fn test_exponential_growth_example() {
308        let example = ODEExamples::exponential_growth();
309        assert!(example.ode_type.contains("Exponential"));
310        assert!(!example.steps.is_empty());
311    }
312
313    #[test]
314    fn test_linear_first_order_example() {
315        let example = ODEExamples::linear_first_order();
316        assert!(example.ode_type.contains("Linear"));
317        assert!(!example.steps.is_empty());
318    }
319
320    #[test]
321    fn test_product_separable_example() {
322        let example = ODEExamples::product_separable();
323        assert!(example.ode_type.contains("Product"));
324        assert!(!example.steps.is_empty());
325    }
326
327    #[test]
328    fn test_initial_value_problem_example() {
329        let example = ODEExamples::initial_value_problem();
330        assert!(example.ode_type.contains("Initial Value"));
331        assert!(!example.steps.is_empty());
332    }
333
334    #[test]
335    fn test_all_examples() {
336        let examples = ODEExamples::all_examples();
337        assert_eq!(examples.len(), 5);
338        for example in examples {
339            assert!(!example.steps.is_empty());
340            assert!(!example.ode_type.is_empty());
341        }
342    }
343
344    #[test]
345    fn test_get_example_by_name() {
346        let example = ODEExamples::get_example("separable_simple");
347        assert!(example.is_some());
348
349        let example = ODEExamples::get_example("nonexistent");
350        assert!(example.is_none());
351    }
352
353    #[test]
354    fn test_example_names() {
355        let names = ODEExamples::example_names();
356        assert_eq!(names.len(), 5);
357        assert!(names.contains(&"separable_simple"));
358        assert!(names.contains(&"exponential_growth"));
359    }
360
361    #[test]
362    fn test_example_human_readable() {
363        let example = ODEExamples::separable_simple();
364        let human = example.to_human_readable();
365        assert!(human.contains("ODE Type"));
366        assert!(human.contains("Method"));
367        assert!(human.contains("Step"));
368    }
369
370    #[test]
371    fn test_example_latex() {
372        let example = ODEExamples::exponential_growth();
373        let latex = example.to_latex();
374        assert!(latex.contains("\\begin{align*}"));
375        assert!(latex.contains("\\end{align*}"));
376    }
377}