mathhook_core/calculus/pde/educational/
wrapper.rs

1//! Educational PDE solver wrapper
2//!
3//! Wraps PDE solvers to capture intermediate steps and provide
4//! human-readable explanations of the solution process.
5
6use crate::algebra::solvers::SolverResult;
7use crate::calculus::pde::registry::PDESolverRegistry;
8use crate::core::{Expression, Symbol};
9use crate::educational::step_by_step::{Step, StepByStepExplanation};
10
11/// Educational PDE solver that provides step-by-step explanations
12///
13/// # Examples
14///
15/// ```
16/// use mathhook_core::calculus::pde::educational::wrapper::EducationalPDESolver;
17/// use mathhook_core::{symbol, expr};
18///
19/// let solver = EducationalPDESolver::new();
20/// let u = symbol!(u);
21/// let x = symbol!(x);
22/// let t = symbol!(t);
23///
24/// let equation = expr!(u + x + t);
25/// let (result, explanation) = solver.solve_pde(&equation, &u, &[x, t]);
26///
27/// assert!(!explanation.steps.is_empty());
28/// ```
29pub struct EducationalPDESolver {
30    registry: PDESolverRegistry,
31}
32
33impl EducationalPDESolver {
34    /// Create a new educational PDE solver
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use mathhook_core::calculus::pde::educational::wrapper::EducationalPDESolver;
40    ///
41    /// let solver = EducationalPDESolver::new();
42    /// ```
43    pub fn new() -> Self {
44        Self {
45            registry: PDESolverRegistry::new(),
46        }
47    }
48
49    /// Detect if equation contains partial derivatives (PDE indicator)
50    fn is_pde(equation: &Expression) -> bool {
51        equation.to_string().contains("∂") || has_multiple_variables(equation)
52    }
53
54    /// Solve PDE with detailed educational steps
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use mathhook_core::calculus::pde::educational::wrapper::EducationalPDESolver;
60    /// use mathhook_core::{symbol, expr};
61    ///
62    /// let solver = EducationalPDESolver::new();
63    /// let u = symbol!(u);
64    /// let x = symbol!(x);
65    /// let t = symbol!(t);
66    ///
67    /// let equation = expr!(u + x);
68    /// let (result, explanation) = solver.solve_pde(&equation, &u, &[x, t]);
69    ///
70    /// // Educational explanation is always provided
71    /// assert!(!explanation.steps.is_empty());
72    /// ```
73    pub fn solve_pde(
74        &self,
75        equation: &Expression,
76        variable: &Symbol,
77        independent_vars: &[Symbol],
78    ) -> (SolverResult, StepByStepExplanation) {
79        use crate::calculus::pde::types::Pde;
80
81        let pde = Pde::new(
82            equation.clone(),
83            variable.clone(),
84            independent_vars.to_vec(),
85        );
86
87        let mut steps = vec![Step::new(
88            "PDE Classification",
89            "Analyzing partial differential equation structure",
90        )];
91
92        if let Ok(pde_type) = crate::calculus::pde::classification::classify_pde(&pde) {
93            steps.push(Step::new(
94                "PDE Type Detected",
95                format!("This is a {:?} PDE", pde_type),
96            ));
97        }
98
99        match self.registry.solve(&pde) {
100            Ok(solution) => {
101                steps.push(Step::new("Solution Found", "PDE solved successfully"));
102                steps.push(Step::new(
103                    "General Solution",
104                    format!("Solution: {}", solution.solution),
105                ));
106
107                (
108                    SolverResult::Single(solution.solution),
109                    StepByStepExplanation::new(steps),
110                )
111            }
112            Err(err) => {
113                steps.push(Step::new(
114                    "Solver Status",
115                    format!("PDE solving: {:?}", err),
116                ));
117
118                (SolverResult::NoSolution, StepByStepExplanation::new(steps))
119            }
120        }
121    }
122}
123
124impl Default for EducationalPDESolver {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130/// Implement the EquationSolver trait for integration with SmartEquationSolver
131impl crate::algebra::solvers::EquationSolver for EducationalPDESolver {
132    fn solve(
133        &self,
134        equation: &Expression,
135        variable: &Symbol,
136    ) -> crate::algebra::solvers::SolverResult {
137        if !Self::is_pde(equation) {
138            return SolverResult::NoSolution;
139        }
140
141        let independent_vars = extract_independent_vars(equation, variable);
142
143        let (result, _) = self.solve_pde(equation, variable, &independent_vars);
144        result
145    }
146
147    fn solve_with_explanation(
148        &self,
149        equation: &Expression,
150        variable: &Symbol,
151    ) -> (
152        crate::algebra::solvers::SolverResult,
153        crate::educational::step_by_step::StepByStepExplanation,
154    ) {
155        if !Self::is_pde(equation) {
156            let steps = vec![Step::new(
157                "Equation Analysis",
158                "This equation does not appear to be a PDE",
159            )];
160            return (SolverResult::NoSolution, StepByStepExplanation::new(steps));
161        }
162
163        let independent_vars = extract_independent_vars(equation, variable);
164        self.solve_pde(equation, variable, &independent_vars)
165    }
166
167    fn can_solve(&self, equation: &Expression) -> bool {
168        Self::is_pde(equation)
169    }
170}
171
172/// Check if expression contains multiple variables (PDE indicator)
173fn has_multiple_variables(expr: &Expression) -> bool {
174    use crate::core::Expression;
175    use std::collections::HashSet;
176
177    fn count_vars(e: &Expression, vars: &mut HashSet<String>) {
178        match e {
179            Expression::Symbol(s) => {
180                vars.insert(s.name().to_owned());
181            }
182            Expression::Add(terms) => {
183                for term in terms.as_ref() {
184                    count_vars(term, vars);
185                }
186            }
187            Expression::Mul(factors) => {
188                for factor in factors.as_ref() {
189                    count_vars(factor, vars);
190                }
191            }
192            Expression::Pow(base, exp) => {
193                count_vars(base, vars);
194                count_vars(exp, vars);
195            }
196            Expression::Function { args, .. } => {
197                for arg in args.as_ref() {
198                    count_vars(arg, vars);
199                }
200            }
201            _ => {}
202        }
203    }
204
205    let mut vars = HashSet::new();
206    count_vars(expr, &mut vars);
207    vars.len() >= 2
208}
209
210/// Extract independent variables from equation
211///
212/// Uses heuristic approach: collects all variables except the dependent variable.
213/// Returns variables found in the equation structure.
214fn extract_independent_vars(equation: &Expression, dependent: &Symbol) -> Vec<Symbol> {
215    use crate::core::Expression;
216    use std::collections::HashSet;
217
218    fn collect_vars(e: &Expression, vars: &mut HashSet<Symbol>) {
219        match e {
220            Expression::Symbol(s) => {
221                vars.insert(s.clone());
222            }
223            Expression::Add(terms) => {
224                for term in terms.as_ref() {
225                    collect_vars(term, vars);
226                }
227            }
228            Expression::Mul(factors) => {
229                for factor in factors.as_ref() {
230                    collect_vars(factor, vars);
231                }
232            }
233            Expression::Pow(base, exp) => {
234                collect_vars(base, vars);
235                collect_vars(exp, vars);
236            }
237            Expression::Function { args, .. } => {
238                for arg in args.as_ref() {
239                    collect_vars(arg, vars);
240                }
241            }
242            _ => {}
243        }
244    }
245
246    let mut vars = HashSet::new();
247    collect_vars(equation, &mut vars);
248
249    vars.remove(dependent);
250
251    vars.into_iter().collect()
252}
253
254#[cfg(test)]
255use crate::algebra::solvers::EquationSolver;
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::{expr, symbol};
261
262    #[test]
263    fn test_educational_pde_solver_creation() {
264        let solver = EducationalPDESolver::new();
265        let _ = solver;
266    }
267
268    #[test]
269    fn test_is_pde_true() {
270        let _x = symbol!(x);
271        let _y = symbol!(y);
272        let eq = expr!(_x + _y);
273        assert!(EducationalPDESolver::is_pde(&eq));
274    }
275
276    #[test]
277    fn test_is_pde_false() {
278        let _x = symbol!(x);
279        let eq = expr!(_x);
280        assert!(!EducationalPDESolver::is_pde(&eq));
281    }
282
283    #[test]
284    fn test_has_multiple_variables() {
285        let _x = symbol!(x);
286        let _y = symbol!(y);
287        let eq = expr!(_x + _y);
288        assert!(has_multiple_variables(&eq));
289    }
290
291    #[test]
292    fn test_has_multiple_variables_single() {
293        let _x = symbol!(x);
294        let eq = expr!(_x * (_x ^ 2));
295        assert!(!has_multiple_variables(&eq));
296    }
297
298    #[test]
299    fn test_extract_independent_vars() {
300        let u = symbol!(u);
301        let _x = symbol!(x);
302        let _t = symbol!(t);
303        let eq = expr!(u + _x + _t);
304
305        let independent = extract_independent_vars(&eq, &u);
306        assert_eq!(independent.len(), 2);
307    }
308
309    #[test]
310    fn test_can_solve_pde() {
311        let solver = EducationalPDESolver::new();
312        let _x = symbol!(x);
313        let _y = symbol!(y);
314        let eq = expr!(_x + _y);
315
316        assert!(solver.can_solve(&eq));
317    }
318
319    #[test]
320    fn test_can_solve_non_pde() {
321        let solver = EducationalPDESolver::new();
322        let _x = symbol!(x);
323        let eq = expr!(_x);
324
325        assert!(!solver.can_solve(&eq));
326    }
327
328    #[test]
329    fn test_solve_returns_no_solution_for_non_pde() {
330        let solver = EducationalPDESolver::new();
331        let _x = symbol!(x);
332        let eq = expr!(_x);
333
334        let result = solver.solve(&eq, &_x);
335        assert!(matches!(result, SolverResult::NoSolution));
336    }
337
338    #[test]
339    fn test_solve_pde_basic() {
340        let solver = EducationalPDESolver::new();
341        let u = symbol!(u);
342        let _x = symbol!(x);
343        let _t = symbol!(t);
344        let eq = expr!(u + _x + _t);
345
346        let (result, explanation) = solver.solve_pde(&eq, &u, &[_x, _t]);
347
348        match result {
349            SolverResult::Single(_) | SolverResult::NoSolution => {
350                // Either is acceptable for this test
351            }
352            _ => panic!("Unexpected result type"),
353        }
354
355        assert!(!explanation.steps.is_empty());
356    }
357
358    #[test]
359    fn test_solve_with_explanation_pde() {
360        let solver = EducationalPDESolver::new();
361        let u = symbol!(u);
362        let _x = symbol!(x);
363        let _y = symbol!(y);
364        let eq = expr!(u + _x + _y);
365
366        let (_, explanation) = solver.solve_with_explanation(&eq, &u);
367        assert!(!explanation.steps.is_empty());
368    }
369
370    #[test]
371    fn test_solve_with_explanation_non_pde() {
372        let solver = EducationalPDESolver::new();
373        let _x = symbol!(x);
374        let eq = expr!(_x);
375
376        let (result, explanation) = solver.solve_with_explanation(&eq, &_x);
377        assert!(matches!(result, SolverResult::NoSolution));
378        assert!(!explanation.steps.is_empty());
379    }
380
381    #[test]
382    fn test_default_impl() {
383        let solver = EducationalPDESolver::default();
384        let _ = solver;
385    }
386}