mathhook_core/calculus/pde/educational/
wrapper.rs1use crate::algebra::solvers::SolverResult;
7use crate::calculus::pde::registry::PDESolverRegistry;
8use crate::core::{Expression, Symbol};
9use crate::educational::step_by_step::{Step, StepByStepExplanation};
10
11pub struct EducationalPDESolver {
30 registry: PDESolverRegistry,
31}
32
33impl EducationalPDESolver {
34 pub fn new() -> Self {
44 Self {
45 registry: PDESolverRegistry::new(),
46 }
47 }
48
49 fn is_pde(equation: &Expression) -> bool {
51 equation.to_string().contains("∂") || has_multiple_variables(equation)
52 }
53
54 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
130impl 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
172fn 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
210fn 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 }
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}