mathhook_core/calculus/ode/educational/
examples.rs1use crate::calculus::ode::educational::{ODEExplanation, ODEStepFactory};
9use crate::{expr, symbol};
10
11pub struct ODEExamples;
13
14impl ODEExamples {
15 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 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 steps.push(ODEStepFactory::separation(&rhs, &expr!(x), "x", "1"));
41
42 steps.push(ODEStepFactory::integration(
44 &expr!(1),
45 &expr!(y),
46 &y,
47 "left",
48 ));
49
50 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 let solution = expr!(y); 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 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 ODEStepFactory::integration(
100 &expr!(1 / y),
101 &expr!(y), &y,
103 "left",
104 ),
105 ODEStepFactory::integration(&expr!(1), &expr!(x), &x, "right"),
107 ];
108
109 let solution = expr!(y); 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 pub fn linear_first_order() -> ODEExplanation {
136 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 let solution = expr!(y); 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 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), &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); 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 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 let mut ic_step = ODEStepFactory::solution_construction(
242 &expr!(y),
243 &expr!(y), "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); 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 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 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 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}