mathhook_core/calculus/ode/educational/
wrapper.rs

1//!
2//! Wraps ODE solvers to capture intermediate steps and provide
3//! human-readable explanations of the solution process.
4
5use 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/// Educational ODE explanation containing solution and steps
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ODEExplanation {
14    /// The final solution
15    pub solution: Expression,
16
17    /// Step-by-step explanation
18    pub steps: Vec<ODESolutionStep>,
19
20    /// ODE type identified
21    pub ode_type: String,
22
23    /// Summary of the solution method
24    pub method_summary: String,
25}
26
27impl ODEExplanation {
28    /// Create a new ODE explanation
29    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    /// Get human-readable explanation
44    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    /// Get LaTeX formatted explanation
67    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    /// Get steps for a specific phase
89    pub fn steps_by_phase(&self, phase: &ODEPhase) -> Vec<&ODESolutionStep> {
90        self.steps.iter().filter(|s| s.phase == *phase).collect()
91    }
92}
93
94/// Educational ODE solver that provides step-by-step explanations
95pub struct EducationalODESolver;
96
97impl EducationalODESolver {
98    /// Create a new educational ODE solver
99    pub fn new() -> Self {
100        Self
101    }
102
103    /// Solve separable ODE with step-by-step explanation
104    ///
105    /// # Arguments
106    ///
107    /// * `rhs` - Right-hand side of dy/dx = rhs
108    /// * `dependent` - Dependent variable (y)
109    /// * `independent` - Independent variable (x)
110    /// * `initial_condition` - Optional (x0, y0) for particular solution
111    ///
112    /// # Returns
113    ///
114    /// ODEExplanation containing solution and all steps
115    ///
116    /// # Examples
117    ///
118    /// ```rust
119    /// use mathhook_core::{symbol, expr};
120    /// use mathhook_core::calculus::ode::educational::EducationalODESolver;
121    ///
122    /// let x = symbol!(x);
123    /// let y = symbol!(y);
124    /// let rhs = expr!(x);
125    ///
126    /// let solver = EducationalODESolver::new();
127    /// let explanation = solver.solve_separable_with_steps(&rhs, &y, &x, None);
128    /// ```
129    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        // Step 1: Detection
139        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        // Step 2: Separation (placeholder - would need actual separation logic)
147        let separation_step = ODEStepFactory::separation(rhs, rhs, "g(x)", "h(y)");
148        steps.push(separation_step);
149
150        // Step 3: Integration (left side)
151        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        // Step 4: Integration (right side)
160        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    /// Solve linear first-order ODE with step-by-step explanation
177    ///
178    /// # Arguments
179    ///
180    /// * `rhs` - Right-hand side of dy/dx = rhs
181    /// * `dependent` - Dependent variable (y)
182    /// * `_independent` - Independent variable (x)
183    /// * `_initial_condition` - Optional (x0, y0) for particular solution
184    ///
185    /// # Returns
186    ///
187    /// ODEExplanation containing solution and all steps
188    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        // Step 1: Detection
198        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        // Placeholder for additional steps
206        // In full implementation:
207        // - Identify P(x) and Q(x)
208        // - Calculate integrating factor μ(x) = exp(∫P(x)dx)
209        // - Multiply equation by μ(x)
210        // - Integrate both sides
211        // - Solve for y
212
213        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
232/// Implement the EquationSolver trait for integration with SmartEquationSolver
233impl crate::algebra::solvers::EquationSolver for EducationalODESolver {
234    fn solve(
235        &self,
236        _equation: &Expression,
237        _variable: &Symbol,
238    ) -> crate::algebra::solvers::SolverResult {
239        // Placeholder: Full integration will classify ODE type and route to appropriate solver
240        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        // Check if equation contains derivatives
267        // Placeholder: will use has_derivatives() from EquationAnalyzer
268        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}