mathhook_core/calculus/ode/educational/
steps.rs

1//! ODE Solution Step Representation
2//!
3//! Provides structured representation of ODE solution steps including:
4//! - Detection phase (identifying ODE type)
5//! - Transformation phase (manipulating equation)
6//! - Integration phase (solving integrals)
7//! - Solution phase (final form and verification)
8
9use crate::core::{Expression, Symbol};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13/// ODE solution step phase
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum ODEPhase {
16    /// Detection of ODE type and characteristics
17    Detection,
18    /// Transformation to standard form
19    Transformation,
20    /// Integration step
21    Integration,
22    /// Final solution construction
23    Solution,
24    /// Verification step
25    Verification,
26}
27
28/// ODE solution step with mathematical and educational context
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ODESolutionStep {
31    /// Unique step identifier
32    pub step_id: String,
33
34    /// Phase of the solution process
35    pub phase: ODEPhase,
36
37    /// Human-readable title
38    pub title: String,
39
40    /// Detailed description
41    pub description: String,
42
43    /// Mathematical justification
44    pub justification: String,
45
46    /// Expression state before this step
47    pub before: Expression,
48
49    /// Expression state after this step
50    pub after: Expression,
51
52    /// LaTeX representation of the step
53    pub latex: String,
54
55    /// Additional metadata
56    pub metadata: HashMap<String, String>,
57}
58
59impl ODESolutionStep {
60    /// Create a new ODE solution step
61    pub fn new(
62        step_id: String,
63        phase: ODEPhase,
64        title: String,
65        description: String,
66        justification: String,
67        before: Expression,
68        after: Expression,
69    ) -> Self {
70        let latex = format!("{} \\rightarrow {}", before, after);
71        Self {
72            step_id,
73            phase,
74            title,
75            description,
76            justification,
77            before,
78            after,
79            latex,
80            metadata: HashMap::new(),
81        }
82    }
83
84    /// Add metadata to the step
85    pub fn with_metadata(mut self, key: String, value: String) -> Self {
86        self.metadata.insert(key, value);
87        self
88    }
89
90    /// Get LaTeX representation with custom formatting
91    pub fn to_latex_detailed(&self) -> String {
92        format!(
93            "\\text{{{}}} &: \\quad {} \\\\\n\\text{{Justification}}: &\\quad \\text{{{}}}",
94            self.title, self.latex, self.justification
95        )
96    }
97}
98
99/// Builder for ODE solution steps
100pub struct ODESolutionStepBuilder {
101    step_id: Option<String>,
102    phase: Option<ODEPhase>,
103    title: Option<String>,
104    description: Option<String>,
105    justification: Option<String>,
106    before: Option<Expression>,
107    after: Option<Expression>,
108    metadata: HashMap<String, String>,
109}
110
111impl ODESolutionStepBuilder {
112    /// Create a new builder
113    pub fn new() -> Self {
114        Self {
115            step_id: None,
116            phase: None,
117            title: None,
118            description: None,
119            justification: None,
120            before: None,
121            after: None,
122            metadata: HashMap::new(),
123        }
124    }
125
126    /// Set step ID
127    pub fn step_id(mut self, id: String) -> Self {
128        self.step_id = Some(id);
129        self
130    }
131
132    /// Set phase
133    pub fn phase(mut self, phase: ODEPhase) -> Self {
134        self.phase = Some(phase);
135        self
136    }
137
138    /// Set title
139    pub fn title(mut self, title: String) -> Self {
140        self.title = Some(title);
141        self
142    }
143
144    /// Set description
145    pub fn description(mut self, desc: String) -> Self {
146        self.description = Some(desc);
147        self
148    }
149
150    /// Set justification
151    pub fn justification(mut self, just: String) -> Self {
152        self.justification = Some(just);
153        self
154    }
155
156    /// Set before state
157    pub fn before(mut self, expr: Expression) -> Self {
158        self.before = Some(expr);
159        self
160    }
161
162    /// Set after state
163    pub fn after(mut self, expr: Expression) -> Self {
164        self.after = Some(expr);
165        self
166    }
167
168    /// Add metadata
169    pub fn metadata(mut self, key: String, value: String) -> Self {
170        self.metadata.insert(key, value);
171        self
172    }
173
174    /// Build the step
175    pub fn build(self) -> Result<ODESolutionStep, String> {
176        let mut step = ODESolutionStep::new(
177            self.step_id.ok_or("Missing step_id")?,
178            self.phase.ok_or("Missing phase")?,
179            self.title.ok_or("Missing title")?,
180            self.description.ok_or("Missing description")?,
181            self.justification.ok_or("Missing justification")?,
182            self.before.ok_or("Missing before expression")?,
183            self.after.ok_or("Missing after expression")?,
184        );
185
186        step.metadata = self.metadata;
187        Ok(step)
188    }
189}
190
191impl Default for ODESolutionStepBuilder {
192    fn default() -> Self {
193        Self::new()
194    }
195}
196
197/// Factory for creating common ODE solution steps
198pub struct ODEStepFactory;
199
200impl ODEStepFactory {
201    /// Create detection step
202    pub fn detection(ode_type: &str, equation: &Expression, reason: &str) -> ODESolutionStep {
203        ODESolutionStepBuilder::new()
204            .step_id(format!("detect_{}", ode_type))
205            .phase(ODEPhase::Detection)
206            .title(format!("Identify as {} ODE", ode_type))
207            .description(format!(
208                "Analyzing the equation, we identify it as a {} ordinary differential equation.",
209                ode_type
210            ))
211            .justification(reason.to_owned())
212            .before(equation.clone())
213            .after(equation.clone())
214            .metadata("ode_type".to_owned(), ode_type.to_owned())
215            .build()
216            .expect("Valid detection step")
217    }
218
219    /// Create separation step for separable ODEs
220    pub fn separation(
221        original: &Expression,
222        separated: &Expression,
223        g_x: &str,
224        h_y: &str,
225    ) -> ODESolutionStep {
226        ODESolutionStepBuilder::new()
227            .step_id("separate_variables".to_owned())
228            .phase(ODEPhase::Transformation)
229            .title("Separate Variables".to_owned())
230            .description(format!(
231                "Rewrite the equation to separate x and y terms: dy/h(y) = g(x)dx where g(x) = {} and h(y) = {}",
232                g_x, h_y
233            ))
234            .justification("Variable separation allows us to integrate each side independently".to_owned())
235            .before(original.clone())
236            .after(separated.clone())
237            .metadata("g_x".to_owned(), g_x.to_owned())
238            .metadata("h_y".to_owned(), h_y.to_owned())
239            .build()
240            .expect("Valid separation step")
241    }
242
243    /// Create integration step
244    pub fn integration(
245        integrand: &Expression,
246        result: &Expression,
247        variable: &Symbol,
248        side: &str,
249    ) -> ODESolutionStep {
250        ODESolutionStepBuilder::new()
251            .step_id(format!("integrate_{}", side))
252            .phase(ODEPhase::Integration)
253            .title(format!("Integrate {} Side", side))
254            .description(format!(
255                "Compute the integral: ∫({}) d{}",
256                integrand,
257                variable.name()
258            ))
259            .justification(
260                "Integration yields the antiderivative plus constant of integration".to_owned(),
261            )
262            .before(integrand.clone())
263            .after(result.clone())
264            .metadata("variable".to_owned(), variable.name().to_owned())
265            .metadata("side".to_owned(), side.to_owned())
266            .build()
267            .expect("Valid integration step")
268    }
269
270    /// Create solution construction step
271    pub fn solution_construction(
272        implicit: &Expression,
273        explicit: &Expression,
274        method: &str,
275    ) -> ODESolutionStep {
276        ODESolutionStepBuilder::new()
277            .step_id("construct_solution".to_owned())
278            .phase(ODEPhase::Solution)
279            .title("Construct Final Solution".to_owned())
280            .description(format!("Solve for the dependent variable using {}", method))
281            .justification(
282                "Rearranging the integrated equation to express y explicitly in terms of x"
283                    .to_owned(),
284            )
285            .before(implicit.clone())
286            .after(explicit.clone())
287            .metadata("method".to_owned(), method.to_owned())
288            .build()
289            .expect("Valid solution step")
290    }
291
292    /// Create verification step
293    pub fn verification(
294        solution: &Expression,
295        original_ode: &Expression,
296        verification_result: bool,
297    ) -> ODESolutionStep {
298        let status = if verification_result {
299            "verified"
300        } else {
301            "pending"
302        };
303        ODESolutionStepBuilder::new()
304            .step_id("verify_solution".to_owned())
305            .phase(ODEPhase::Verification)
306            .title("Verify Solution".to_owned())
307            .description(format!(
308                "Substitute the solution back into the original equation to verify correctness: {}",
309                status
310            ))
311            .justification(
312                "Verification confirms the solution satisfies the differential equation".to_owned(),
313            )
314            .before(solution.clone())
315            .after(original_ode.clone())
316            .metadata("verified".to_owned(), verification_result.to_string())
317            .build()
318            .expect("Valid verification step")
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use crate::{expr, symbol};
326
327    #[test]
328    fn test_ode_solution_step_creation() {
329        let before = expr!(x);
330        let after = expr!(x ^ 2);
331
332        let step = ODESolutionStep::new(
333            "step1".to_string(),
334            ODEPhase::Integration,
335            "Integrate".to_string(),
336            "Integrate both sides".to_string(),
337            "Fundamental theorem of calculus".to_string(),
338            before.clone(),
339            after.clone(),
340        );
341
342        assert_eq!(step.step_id, "step1");
343        assert_eq!(step.phase, ODEPhase::Integration);
344        assert_eq!(step.title, "Integrate");
345        assert_eq!(step.before, before);
346        assert_eq!(step.after, after);
347    }
348
349    #[test]
350    fn test_step_builder() {
351        let before = expr!(1);
352        let after = expr!(2);
353
354        let step = ODESolutionStepBuilder::new()
355            .step_id("test".to_string())
356            .phase(ODEPhase::Detection)
357            .title("Test".to_string())
358            .description("Test step".to_string())
359            .justification("Testing".to_string())
360            .before(before.clone())
361            .after(after.clone())
362            .metadata("key".to_string(), "value".to_string())
363            .build()
364            .unwrap();
365
366        assert_eq!(step.step_id, "test");
367        assert_eq!(step.metadata.get("key"), Some(&"value".to_string()));
368    }
369
370    #[test]
371    fn test_detection_factory() {
372        let equation = expr!(x);
373        let step = ODEStepFactory::detection("separable", &equation, "Can factor into g(x)h(y)");
374
375        assert_eq!(step.phase, ODEPhase::Detection);
376        assert!(step.title.contains("separable"));
377        assert!(step.description.contains("separable"));
378        assert_eq!(
379            step.metadata.get("ode_type"),
380            Some(&"separable".to_string())
381        );
382    }
383
384    #[test]
385    fn test_separation_factory() {
386        let original = expr!(x);
387        let separated = expr!(x ^ 2);
388
389        let step = ODEStepFactory::separation(&original, &separated, "x", "1");
390
391        assert_eq!(step.phase, ODEPhase::Transformation);
392        assert!(step.title.contains("Separate"));
393        assert_eq!(step.metadata.get("g_x"), Some(&"x".to_string()));
394        assert_eq!(step.metadata.get("h_y"), Some(&"1".to_string()));
395    }
396
397    #[test]
398    fn test_integration_factory() {
399        let x = symbol!(x);
400        let integrand = expr!(x);
401        let result = expr!(x ^ 2);
402
403        let step = ODEStepFactory::integration(&integrand, &result, &x, "left");
404
405        assert_eq!(step.phase, ODEPhase::Integration);
406        assert!(step.title.contains("Integrate"));
407        assert_eq!(step.metadata.get("side"), Some(&"left".to_string()));
408    }
409}