mathhook_core/calculus/ode/educational/
wrapper.rs1use crate::algebra::solvers::SolverResult;
6
7use crate::calculus::ode::educational::steps::{ODEPhase, ODESolutionStep, ODEStepFactory};
8use crate::core::{Expression, Symbol};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ODEExplanation {
14 pub solution: Expression,
16
17 pub steps: Vec<ODESolutionStep>,
19
20 pub ode_type: String,
22
23 pub method_summary: String,
25}
26
27impl ODEExplanation {
28 pub fn new(
30 solution: Expression,
31 steps: Vec<ODESolutionStep>,
32 ode_type: String,
33 method_summary: String,
34 ) -> Self {
35 Self {
36 solution,
37 steps,
38 ode_type,
39 method_summary,
40 }
41 }
42
43 pub fn to_human_readable(&self) -> String {
45 let mut output = String::new();
46 output.push_str(&format!("ODE Type: {}\n", self.ode_type));
47 output.push_str(&format!("Method: {}\n\n", self.method_summary));
48 output.push_str("Solution Steps:\n");
49 output.push_str(&"=".repeat(60));
50 output.push('\n');
51
52 for (i, step) in self.steps.iter().enumerate() {
53 output.push_str(&format!("\nStep {}: {}\n", i + 1, step.title));
54 output.push_str(&format!("Description: {}\n", step.description));
55 output.push_str(&format!("Justification: {}\n", step.justification));
56 output.push_str(&format!("Before: {}\n", step.before));
57 output.push_str(&format!("After: {}\n", step.after));
58 output.push_str(&"-".repeat(60));
59 output.push('\n');
60 }
61
62 output.push_str(&format!("\nFinal Solution: {}\n", self.solution));
63 output
64 }
65
66 pub fn to_latex(&self) -> String {
68 let mut latex = String::new();
69 latex.push_str("\\begin{align*}\n");
70 latex.push_str(&format!("\\text{{ODE Type: }} & {} \\\\\n", self.ode_type));
71 latex.push_str(&format!(
72 "\\text{{Method: }} & {} \\\\\n",
73 self.method_summary
74 ));
75 latex.push_str("\\end{align*}\n\n");
76 latex.push_str("\\begin{align*}\n");
77
78 for step in &self.steps {
79 latex.push_str(&step.to_latex_detailed());
80 latex.push_str(" \\\\\n");
81 }
82
83 latex.push_str(&format!("\\text{{Solution: }} & {} \\\\\n", self.solution));
84 latex.push_str("\\end{align*}\n");
85 latex
86 }
87
88 pub fn steps_by_phase(&self, phase: &ODEPhase) -> Vec<&ODESolutionStep> {
90 self.steps.iter().filter(|s| s.phase == *phase).collect()
91 }
92}
93
94pub struct EducationalODESolver;
96
97impl EducationalODESolver {
98 pub fn new() -> Self {
100 Self
101 }
102
103 pub fn solve_separable_with_steps(
130 &self,
131 rhs: &Expression,
132 dependent: &Symbol,
133 independent: &Symbol,
134 _initial_condition: Option<(Expression, Expression)>,
135 ) -> Result<ODEExplanation, String> {
136 let mut steps = Vec::new();
137
138 let detection_step = ODEStepFactory::detection(
140 "separable",
141 rhs,
142 "The equation can be written in the form dy/dx = g(x)h(y), allowing variable separation",
143 );
144 steps.push(detection_step);
145
146 let separation_step = ODEStepFactory::separation(rhs, rhs, "g(x)", "h(y)");
148 steps.push(separation_step);
149
150 let integration_left = ODEStepFactory::integration(
152 &Expression::integer(1),
153 &Expression::integer(1),
154 dependent,
155 "left",
156 );
157 steps.push(integration_left);
158
159 let integration_right =
161 ODEStepFactory::integration(rhs, &Expression::integer(1), independent, "right");
162 steps.push(integration_right);
163
164 let solution = Expression::symbol(dependent.clone());
165
166 let explanation = ODEExplanation::new(
167 solution,
168 steps,
169 "Separable".to_owned(),
170 "Variable separation followed by integration".to_owned(),
171 );
172
173 Ok(explanation)
174 }
175
176 pub fn solve_linear_with_steps(
189 &self,
190 rhs: &Expression,
191 dependent: &Symbol,
192 _independent: &Symbol,
193 _initial_condition: Option<(Expression, Expression)>,
194 ) -> Result<ODEExplanation, String> {
195 let mut steps = Vec::new();
196
197 let detection_step = ODEStepFactory::detection(
199 "linear first-order",
200 rhs,
201 "The equation has the form dy/dx + P(x)y = Q(x), which is linear in y",
202 );
203 steps.push(detection_step);
204
205 let solution = Expression::symbol(dependent.clone());
214
215 let explanation = ODEExplanation::new(
216 solution,
217 steps,
218 "Linear First-Order".to_owned(),
219 "Integrating factor method".to_owned(),
220 );
221
222 Ok(explanation)
223 }
224}
225
226impl Default for EducationalODESolver {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl crate::algebra::solvers::EquationSolver for EducationalODESolver {
234 fn solve(
235 &self,
236 _equation: &Expression,
237 _variable: &Symbol,
238 ) -> crate::algebra::solvers::SolverResult {
239 SolverResult::NoSolution
241 }
242
243 fn solve_with_explanation(
244 &self,
245 equation: &Expression,
246 variable: &Symbol,
247 ) -> (
248 crate::algebra::solvers::SolverResult,
249 crate::educational::step_by_step::StepByStepExplanation,
250 ) {
251 use crate::educational::step_by_step::{Step, StepByStepExplanation};
252
253 let steps = vec![
254 Step::new(
255 "ODE Classification",
256 "Analyzing differential equation structure...",
257 ),
258 Step::new("Status", "ODE solving integration in progress (Wave 1-INT)"),
259 ];
260
261 let result = self.solve(equation, variable);
262 (result, StepByStepExplanation::new(steps))
263 }
264
265 fn can_solve(&self, _equation: &Expression) -> bool {
266 false
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::{expr, symbol};
276
277 #[test]
278 fn test_separable_with_steps() {
279 let x = symbol!(x);
280 let y = symbol!(y);
281 let rhs = expr!(x);
282
283 let solver = EducationalODESolver::new();
284 let result = solver.solve_separable_with_steps(&rhs, &y, &x, None);
285
286 assert!(result.is_ok(), "Should solve separable ODE");
287 let explanation = result.unwrap();
288 assert_eq!(explanation.ode_type, "Separable");
289 assert!(!explanation.steps.is_empty(), "Should have steps");
290 assert!(explanation.steps.len() >= 4, "Should have at least 4 steps");
291 }
292
293 #[test]
294 fn test_linear_with_steps() {
295 let x = symbol!(x);
296 let y = symbol!(y);
297 let rhs = expr!(y);
298
299 let solver = EducationalODESolver::new();
300 let result = solver.solve_linear_with_steps(&rhs, &y, &x, None);
301
302 assert!(result.is_ok(), "Should solve ODE: {:?}", result.err());
303 let explanation = result.unwrap();
304 assert!(!explanation.steps.is_empty(), "Should have steps");
305 }
306
307 #[test]
308 fn test_explanation_human_readable() {
309 let x = symbol!(x);
310 let y = symbol!(y);
311 let rhs = expr!(x);
312
313 let solver = EducationalODESolver::new();
314 let explanation = solver
315 .solve_separable_with_steps(&rhs, &y, &x, None)
316 .unwrap();
317
318 let human = explanation.to_human_readable();
319 assert!(human.contains("ODE Type"));
320 assert!(human.contains("Method"));
321 assert!(human.contains("Step"));
322 }
323
324 #[test]
325 fn test_explanation_latex() {
326 let x = symbol!(x);
327 let y = symbol!(y);
328 let rhs = expr!(x);
329
330 let solver = EducationalODESolver::new();
331 let explanation = solver
332 .solve_separable_with_steps(&rhs, &y, &x, None)
333 .unwrap();
334
335 let latex = explanation.to_latex();
336 assert!(latex.contains("\\begin{align*}"));
337 assert!(latex.contains("\\end{align*}"));
338 assert!(latex.contains("ODE Type"));
339 }
340
341 #[test]
342 fn test_steps_by_phase() {
343 let x = symbol!(x);
344 let y = symbol!(y);
345 let rhs = expr!(x);
346
347 let solver = EducationalODESolver::new();
348 let explanation = solver
349 .solve_separable_with_steps(&rhs, &y, &x, None)
350 .unwrap();
351
352 let detection_steps = explanation.steps_by_phase(&ODEPhase::Detection);
353 assert!(!detection_steps.is_empty(), "Should have detection step");
354
355 let integration_steps = explanation.steps_by_phase(&ODEPhase::Integration);
356 assert!(
357 !integration_steps.is_empty(),
358 "Should have integration steps"
359 );
360 }
361}