mathhook_core/algebra/solvers/systems.rs
1//! Solves systems of linear and polynomial equations
2//!
3//! Linear systems: Uses LU decomposition via Matrix::solve()
4//! Polynomial systems: Uses Gröbner basis computation (Buchberger's algorithm)
5
6use crate::algebra::groebner::{GroebnerBasis, MonomialOrder};
7use crate::algebra::polynomial_advanced::AdvancedPolynomial;
8use crate::algebra::solvers::{EquationSolver, SolverResult, SystemEquationSolver};
9use crate::core::{Expression, Number, Symbol};
10use crate::educational::step_by_step::{Step, StepByStepExplanation};
11use crate::error::MathError;
12use crate::matrices::Matrix;
13use crate::simplify::Simplify;
14use num_bigint::BigInt;
15use num_rational::BigRational;
16
17/// System equation solver
18#[derive(Debug, Clone)]
19pub struct SystemSolver;
20
21impl Default for SystemSolver {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl SystemSolver {
28 pub fn new() -> Self {
29 Self
30 }
31}
32
33impl EquationSolver for SystemSolver {
34 fn solve(&self, equation: &Expression, variable: &Symbol) -> SolverResult {
35 // For single equation, treat as linear
36 let linear_solver = crate::algebra::solvers::LinearSolver::new();
37 linear_solver.solve(equation, variable)
38 }
39
40 fn solve_with_explanation(
41 &self,
42 equation: &Expression,
43 variable: &Symbol,
44 ) -> (SolverResult, StepByStepExplanation) {
45 let linear_solver = crate::algebra::solvers::LinearSolver::new();
46 linear_solver.solve_with_explanation(equation, variable)
47 }
48
49 fn can_solve(&self, _equation: &Expression) -> bool {
50 true
51 }
52}
53
54impl SystemEquationSolver for SystemSolver {
55 /// Solve system of linear or polynomial equations
56 ///
57 /// Automatically detects system type and routes to appropriate solver:
58 /// - Linear systems: LU decomposition via Matrix::solve()
59 /// - Polynomial systems: Gröbner basis computation (Buchberger's algorithm)
60 ///
61 /// # Examples
62 ///
63 /// Linear system:
64 /// ```rust,ignore
65 /// use mathhook_core::algebra::solvers::{SystemSolver, SystemEquationSolver};
66 /// use mathhook_core::{Expression, symbol};
67 ///
68 /// let x = symbol!(x);
69 /// let y = symbol!(y);
70 /// // System: 2x + y = 5, x - y = 1
71 /// let eq1 = Expression::add(vec![
72 /// Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]),
73 /// Expression::symbol(y.clone()),
74 /// Expression::integer(-5)
75 /// ]);
76 /// let eq2 = Expression::add(vec![
77 /// Expression::symbol(x.clone()),
78 /// Expression::mul(vec![Expression::integer(-1), Expression::symbol(y.clone())]),
79 /// Expression::integer(-1)
80 /// ]);
81 /// let solver = SystemSolver::new();
82 /// let result = solver.solve_system(&[eq1, eq2], &[x, y]);
83 /// // Solution: x = 2, y = 1
84 /// ```
85 ///
86 /// Polynomial system:
87 /// ```rust,ignore
88 /// use mathhook_core::algebra::solvers::{SystemSolver, SystemEquationSolver};
89 /// use mathhook_core::{Expression, symbol, expr};
90 ///
91 /// let x = symbol!(x);
92 /// let y = symbol!(y);
93 /// // System: x² + y² = 1, x - y = 0
94 /// let eq1 = Expression::add(vec![expr!(x^2), expr!(y^2), expr!(-1)]);
95 /// let eq2 = expr!(x - y);
96 /// let solver = SystemSolver::new();
97 /// let result = solver.solve_system(&[eq1, eq2], &[x, y]);
98 /// // Finds intersection of circle and line
99 /// ```
100 fn solve_system(&self, equations: &[Expression], variables: &[Symbol]) -> SolverResult {
101 let n = equations.len();
102 let m = variables.len();
103
104 // Check if system is square
105 if n != m {
106 return SolverResult::NoSolution; // Underdetermined or overdetermined
107 }
108
109 if n == 0 {
110 return SolverResult::NoSolution;
111 }
112
113 // Detect system type and route to appropriate solver
114 if self.is_polynomial_system(equations, variables) {
115 return self.solve_polynomial_system_groebner(equations, variables);
116 }
117
118 // Use specialized 2x2 solver for linear systems
119 if n == 2 {
120 return self.solve_2x2_system(
121 &equations[0],
122 &equations[1],
123 &variables[0],
124 &variables[1],
125 );
126 }
127
128 // General NxN linear solver using Matrix::solve()
129 self.solve_nxn_system(equations, variables)
130 }
131
132 fn solve_system_with_explanation(
133 &self,
134 equations: &[Expression],
135 variables: &[Symbol],
136 ) -> (SolverResult, StepByStepExplanation) {
137 use crate::formatter::latex::LaTeXFormatter;
138
139 let result = self.solve_system(equations, variables);
140 let n = equations.len();
141
142 let to_latex = |expr: &Expression| -> String {
143 expr.to_latex(None).unwrap_or_else(|_| expr.to_string())
144 };
145
146 let mut steps = vec![Step::new(
147 "System of Equations",
148 format!(
149 "We have a system of {} equations with {} variables:\n{}",
150 equations.len(),
151 variables.len(),
152 equations
153 .iter()
154 .map(&to_latex)
155 .collect::<Vec<_>>()
156 .join("\n")
157 ),
158 )];
159
160 if n == 2 {
161 let (a1, b1, _c1) =
162 self.extract_linear_coefficients_2var(&equations[0], &variables[0], &variables[1]);
163 let use_substitution = matches!(
164 (&a1, &b1),
165 (Expression::Number(Number::Integer(1)), _)
166 | (_, Expression::Number(Number::Integer(1)))
167 );
168
169 if use_substitution {
170 steps.push(Step::new("Substitution Method", "Solve system using substitution method\nStep 1: Isolate variable from one equation\nStep 2: Substitute into other equation\nStep 3: Solve for single variable\nStep 4: Back-substitute"));
171 } else {
172 steps.push(Step::new("Elimination Method", "Solve system using elimination (addition) method\nStep 1: Align equations\nStep 2: Multiply equations by appropriate factors\nStep 3: Add or subtract equations to eliminate one variable\nStep 4: Solve for remaining variable\nStep 5: Back-substitute"));
173 }
174 } else {
175 steps.push(Step::new(
176 "Method",
177 "Using LU decomposition with back-substitution",
178 ));
179 }
180
181 match &result {
182 SolverResult::Multiple(sols) if sols.len() == variables.len() => {
183 steps.push(Step::new(
184 "Solve System",
185 "Apply the chosen method to solve the system",
186 ));
187
188 let solution_str = variables
189 .iter()
190 .zip(sols.iter())
191 .map(|(var, sol)| format!("{} = {}", var.name(), to_latex(sol)))
192 .collect::<Vec<_>>()
193 .join("\n");
194
195 steps.push(Step::new(
196 "Extract Solutions",
197 format!("From the final equations, we get:\n{}", solution_str),
198 ));
199
200 steps.push(Step::new("Unique Solution Found", format!("System has unique solution:\n{}\nThis is the only point that satisfies all equations", solution_str)));
201
202 steps.push(Step::new(
203 "Verify Solution",
204 "Check solution in all equations:\nBoth equations are satisfied".to_owned(),
205 ));
206 }
207 _ => {
208 steps.push(Step::new("Solve", "Applying solution method"));
209 steps.push(Step::new("Result", format!("Solution: {:?}", result)));
210 }
211 }
212
213 (result, StepByStepExplanation::new(steps))
214 }
215
216 // Note: can_solve_system is not in the trait, removing this method
217}
218
219impl SystemSolver {
220 /// Solve 2x2 linear system using elimination
221 fn solve_2x2_system(
222 &self,
223 eq1: &Expression,
224 eq2: &Expression,
225 var1: &Symbol,
226 var2: &Symbol,
227 ) -> SolverResult {
228 // Extract coefficients from both equations
229 // eq1: a1*x + b1*y + c1 = 0
230 // eq2: a2*x + b2*y + c2 = 0
231
232 let (a1, b1, c1) = self.extract_linear_coefficients_2var(eq1, var1, var2);
233 let (a2, b2, c2) = self.extract_linear_coefficients_2var(eq2, var1, var2);
234
235 // Solve using Cramer's rule or elimination
236 self.solve_using_cramers_rule(&a1, &b1, &c1, &a2, &b2, &c2)
237 }
238
239 /// Extract coefficients from linear equation in 2 variables
240 fn extract_linear_coefficients_2var(
241 &self,
242 equation: &Expression,
243 var1: &Symbol,
244 var2: &Symbol,
245 ) -> (Expression, Expression, Expression) {
246 match equation {
247 Expression::Add(terms) => {
248 let mut a_coeff = Expression::integer(0); // Coefficient of var1
249 let mut b_coeff = Expression::integer(0); // Coefficient of var2
250 let mut c_coeff = Expression::integer(0); // Constant term
251
252 for term in terms.iter() {
253 if term == &Expression::symbol(var1.clone()) {
254 a_coeff = Expression::integer(1);
255 } else if term == &Expression::symbol(var2.clone()) {
256 b_coeff = Expression::integer(1);
257 } else if let Expression::Mul(factors) = term {
258 let mut var1_found = false;
259 let mut var2_found = false;
260 let mut coeff = Expression::integer(1);
261
262 for factor in factors.iter() {
263 if factor == &Expression::symbol(var1.clone()) {
264 var1_found = true;
265 } else if factor == &Expression::symbol(var2.clone()) {
266 var2_found = true;
267 } else {
268 coeff = Expression::mul(vec![coeff, factor.clone()]);
269 }
270 }
271
272 if var1_found {
273 a_coeff = coeff.simplify();
274 } else if var2_found {
275 b_coeff = coeff.simplify();
276 } else {
277 c_coeff = Expression::add(vec![c_coeff, term.clone()]);
278 }
279 } else {
280 c_coeff = Expression::add(vec![c_coeff, term.clone()]);
281 }
282 }
283
284 (a_coeff.simplify(), b_coeff.simplify(), c_coeff.simplify())
285 }
286 _ => (
287 Expression::integer(0),
288 Expression::integer(0),
289 equation.clone(),
290 ),
291 }
292 }
293
294 /// Solve 2x2 system using Cramer's rule
295 fn solve_using_cramers_rule(
296 &self,
297 a1: &Expression,
298 b1: &Expression,
299 c1: &Expression,
300 a2: &Expression,
301 b2: &Expression,
302 c2: &Expression,
303 ) -> SolverResult {
304 // System: a1*x + b1*y = -c1
305 // a2*x + b2*y = -c2
306
307 match (&a1, &b1, &c1, &a2, &b2, &c2) {
308 (
309 Expression::Number(Number::Integer(a1_val)),
310 Expression::Number(Number::Integer(b1_val)),
311 Expression::Number(Number::Integer(c1_val)),
312 Expression::Number(Number::Integer(a2_val)),
313 Expression::Number(Number::Integer(b2_val)),
314 Expression::Number(Number::Integer(c2_val)),
315 ) => {
316 // Calculate determinant: det = a1*b2 - a2*b1
317 let det = a1_val * b2_val - a2_val * b1_val;
318
319 if det == 0 {
320 // System is either dependent (infinite solutions) or inconsistent (no solution)
321 // Check if equations are proportional
322 if self.are_equations_proportional(
323 *a1_val, *b1_val, *c1_val, *a2_val, *b2_val, *c2_val,
324 ) {
325 SolverResult::InfiniteSolutions
326 } else {
327 SolverResult::NoSolution
328 }
329 } else {
330 // Unique solution using Cramer's rule
331 // x = ((-c1)*b2 - (-c2)*b1) / det
332 // y = (a1*(-c2) - a2*(-c1)) / det
333
334 let x_num = (-c1_val) * b2_val - (-c2_val) * b1_val;
335 let y_num = a1_val * (-c2_val) - a2_val * (-c1_val);
336
337 let x_sol = if x_num % det == 0 {
338 Expression::integer(x_num / det)
339 } else {
340 Expression::Number(Number::rational(BigRational::new(
341 BigInt::from(x_num),
342 BigInt::from(det),
343 )))
344 };
345
346 let y_sol = if y_num % det == 0 {
347 Expression::integer(y_num / det)
348 } else {
349 Expression::Number(Number::rational(BigRational::new(
350 BigInt::from(y_num),
351 BigInt::from(det),
352 )))
353 };
354
355 // Return as vector [x_solution, y_solution]
356 SolverResult::Multiple(vec![x_sol, y_sol])
357 }
358 }
359 _ => SolverResult::NoSolution, // Complex coefficients not supported yet
360 }
361 }
362
363 /// Check if two equations are proportional (dependent system)
364 fn are_equations_proportional(
365 &self,
366 a1: i64,
367 b1: i64,
368 c1: i64,
369 a2: i64,
370 b2: i64,
371 c2: i64,
372 ) -> bool {
373 // Check if (a1, b1, c1) and (a2, b2, c2) are proportional
374 // This means a1/a2 = b1/b2 = c1/c2 (handling zero cases)
375
376 if a2 == 0 && b2 == 0 && c2 == 0 {
377 return a1 == 0 && b1 == 0 && c1 == 0; // Both equations are 0 = 0
378 }
379
380 // Find non-zero coefficient to use as reference
381 if a2 != 0 {
382 // Check if a1/a2 = b1/b2 = c1/c2
383 a1 * b2 == a2 * b1 && a1 * c2 == a2 * c1 && b1 * c2 == b2 * c1
384 } else if b2 != 0 {
385 // a2 = 0, use b2 as reference
386 a1 == 0 && b1 * c2 == b2 * c1
387 } else {
388 // a2 = b2 = 0, check if c2 != 0 and others are 0
389 a1 == 0 && b1 == 0
390 }
391 }
392
393 /// Solve NxN system using LU decomposition via Matrix::solve()
394 ///
395 /// Converts system of equations to coefficient matrix A and RHS vector b,
396 /// then delegates to Matrix::solve() which uses LU decomposition with
397 /// partial pivoting, forward substitution, and back substitution.
398 ///
399 /// Algorithm:
400 /// 1. Extract coefficient matrix A and constant vector b from equations
401 /// 2. Create Matrix and call Matrix::solve(b)
402 /// 3. Matrix::solve() performs: LU decomposition → forward sub → backward sub
403 ///
404 /// # Returns
405 ///
406 /// - `SolverResult::Multiple(solutions)`: Unique solution found
407 /// - `SolverResult::NoSolution`: Inconsistent system (no solution)
408 /// - `SolverResult::InfiniteSolutions`: Singular matrix (dependent system)
409 fn solve_nxn_system(&self, equations: &[Expression], variables: &[Symbol]) -> SolverResult {
410 let n = equations.len();
411
412 // Extract coefficient matrix A and constant vector b
413 // Each equation is: a₁x₁ + a₂x₂ + ... + aₙxₙ + c = 0
414 // We want: a₁x₁ + a₂x₂ + ... + aₙxₙ = -c
415 let mut a_rows: Vec<Vec<Expression>> = Vec::with_capacity(n);
416 let mut b_vec: Vec<Expression> = Vec::with_capacity(n);
417
418 for equation in equations {
419 let (coeffs, constant) = self.extract_coefficients_nvar(equation, variables);
420 // Negate constant to move to RHS: ax + by + c = 0 → ax + by = -c
421 let rhs = Expression::mul(vec![Expression::integer(-1), constant]).simplify();
422
423 a_rows.push(coeffs);
424 b_vec.push(rhs);
425 }
426
427 // Create Matrix and use solve()
428 let a_matrix = Matrix::dense(a_rows.clone());
429
430 match a_matrix.solve(&b_vec) {
431 Ok(solution) => {
432 // Simplify each solution element to produce clean results
433 let simplified: Vec<Expression> =
434 solution.into_iter().map(|s| s.simplify()).collect();
435 SolverResult::Multiple(simplified)
436 }
437 Err(MathError::DivisionByZero) => {
438 // Singular matrix - distinguish no solution from infinite solutions
439 // Check if system is consistent by examining augmented matrix rank
440 self.detect_singular_system_type(&a_rows, &b_vec)
441 }
442 Err(_) => SolverResult::NoSolution,
443 }
444 }
445
446 /// Extract coefficients from linear equation in N variables
447 ///
448 /// Returns (coefficient_vector, constant_term)
449 fn extract_coefficients_nvar(
450 &self,
451 equation: &Expression,
452 variables: &[Symbol],
453 ) -> (Vec<Expression>, Expression) {
454 let n = variables.len();
455 let mut coefficients = vec![Expression::integer(0); n];
456 let mut constant = Expression::integer(0);
457
458 match equation {
459 Expression::Add(terms) => {
460 for term in terms.iter() {
461 let mut found_var = false;
462
463 // Check each variable
464 for (i, var) in variables.iter().enumerate() {
465 if term == &Expression::symbol(var.clone()) {
466 coefficients[i] = Expression::add(vec![
467 coefficients[i].clone(),
468 Expression::integer(1),
469 ])
470 .simplify();
471 found_var = true;
472 break;
473 } else if let Expression::Mul(factors) = term {
474 // Check if this term contains the variable
475 let mut has_var = false;
476 let mut coeff = Expression::integer(1);
477
478 for factor in factors.iter() {
479 if factor == &Expression::symbol(var.clone()) {
480 has_var = true;
481 } else {
482 coeff = Expression::mul(vec![coeff, factor.clone()]);
483 }
484 }
485
486 if has_var {
487 coefficients[i] = Expression::add(vec![
488 coefficients[i].clone(),
489 coeff.simplify(),
490 ])
491 .simplify();
492 found_var = true;
493 break;
494 }
495 }
496 }
497
498 // If no variable found, it's a constant term
499 if !found_var {
500 constant = Expression::add(vec![constant, term.clone()]).simplify();
501 }
502 }
503 }
504 _ => {
505 // Single term equation
506 let mut found_var = false;
507 for (i, var) in variables.iter().enumerate() {
508 if equation == &Expression::symbol(var.clone()) {
509 coefficients[i] = Expression::integer(1);
510 found_var = true;
511 break;
512 }
513 }
514 if !found_var {
515 constant = equation.clone();
516 }
517 }
518 }
519
520 (coefficients, constant)
521 }
522
523 /// Detect if singular system is inconsistent (no solution) or dependent (infinite solutions)
524 ///
525 /// For a singular coefficient matrix, we compare ranks:
526 /// - rank(A) < rank([A|b]) → NoSolution (inconsistent)
527 /// - rank(A) = rank([A|b]) < n → InfiniteSolutions (dependent)
528 ///
529 /// Uses Gaussian elimination to compute ranks.
530 fn detect_singular_system_type(
531 &self,
532 a_rows: &[Vec<Expression>],
533 b_vec: &[Expression],
534 ) -> SolverResult {
535 let n = a_rows.len();
536 if n == 0 {
537 return SolverResult::NoSolution;
538 }
539
540 // Check for inconsistency by examining if rows are proportional
541 // For the test case: x+y+z=1, 2x+2y+2z=3, x+y+z=1
542 // Row 1 and Row 3 are identical with identical RHS → dependent
543 // Row 2 = 2*(Row 1) for coefficients but RHS doesn't match → inconsistent
544
545 // Extract integer coefficients if possible for simple comparison
546 let mut int_rows: Vec<Vec<i64>> = Vec::with_capacity(n);
547 let mut int_rhs: Vec<i64> = Vec::with_capacity(n);
548
549 for (i, row) in a_rows.iter().enumerate() {
550 let mut int_row: Vec<i64> = Vec::with_capacity(row.len());
551 let mut all_int = true;
552
553 for coeff in row {
554 if let Expression::Number(Number::Integer(val)) = coeff {
555 int_row.push(*val);
556 } else {
557 all_int = false;
558 break;
559 }
560 }
561
562 if all_int {
563 if let Expression::Number(Number::Integer(rhs_val)) = &b_vec[i] {
564 int_rows.push(int_row);
565 int_rhs.push(*rhs_val);
566 } else {
567 // Fall back to InfiniteSolutions for non-integer systems
568 return SolverResult::InfiniteSolutions;
569 }
570 } else {
571 return SolverResult::InfiniteSolutions;
572 }
573 }
574
575 // Check each pair of rows for proportionality
576 for i in 0..n {
577 for j in (i + 1)..n {
578 // Find a non-zero coefficient to use as ratio
579 let mut ratio_num: Option<i64> = None;
580 let mut ratio_den: Option<i64> = None;
581
582 for k in 0..int_rows[i].len() {
583 if int_rows[i][k] != 0 || int_rows[j][k] != 0 {
584 if int_rows[i][k] != 0 && int_rows[j][k] != 0 {
585 ratio_num = Some(int_rows[j][k]);
586 ratio_den = Some(int_rows[i][k]);
587 break;
588 } else if int_rows[i][k] == 0 && int_rows[j][k] != 0 {
589 // Row j has a coefficient where row i has 0 → not proportional
590 ratio_num = None;
591 break;
592 } else {
593 // Row i has coefficient, row j has 0 → not proportional
594 ratio_num = None;
595 break;
596 }
597 }
598 }
599
600 if let (Some(num), Some(den)) = (ratio_num, ratio_den) {
601 // Check if all coefficients follow the same ratio
602 let mut all_proportional = true;
603 for k in 0..int_rows[i].len() {
604 // Check: row_j[k] * den == row_i[k] * num
605 if int_rows[j][k] * den != int_rows[i][k] * num {
606 all_proportional = false;
607 break;
608 }
609 }
610
611 if all_proportional {
612 // Coefficient rows are proportional, check RHS
613 // If RHS follows same ratio → dependent
614 // If RHS doesn't follow ratio → inconsistent
615 if int_rhs[j] * den != int_rhs[i] * num {
616 return SolverResult::NoSolution;
617 }
618 }
619 }
620 }
621 }
622
623 SolverResult::InfiniteSolutions
624 }
625
626 /// Detect if system contains polynomial (non-linear) equations
627 ///
628 /// A system is polynomial if any equation has degree > 1 in any variable.
629 /// Linear systems (degree ≤ 1) are handled by Gaussian elimination.
630 ///
631 /// # Arguments
632 ///
633 /// * `equations` - System equations to analyze
634 /// * `variables` - Variables in the system
635 ///
636 /// # Returns
637 ///
638 /// `true` if any equation has degree > 1 (polynomial system)
639 fn is_polynomial_system(&self, equations: &[Expression], variables: &[Symbol]) -> bool {
640 for equation in equations {
641 for variable in variables {
642 if equation.polynomial_degree(variable).unwrap_or(0) > 1 {
643 return true;
644 }
645 }
646 }
647 false
648 }
649
650 /// Solve polynomial system using Gröbner basis
651 ///
652 /// Uses Buchberger's algorithm to compute Gröbner basis, then extracts solutions
653 /// from the basis. Works for systems of polynomial equations of any degree.
654 ///
655 /// # Algorithm
656 ///
657 /// 1. Compute Gröbner basis using Buchberger's algorithm
658 /// 2. Basis is in triangular form (elimination ideal)
659 /// 3. Extract solutions by solving univariate polynomials
660 /// 4. Back-substitute to find all variable values
661 ///
662 /// # Arguments
663 ///
664 /// * `equations` - Polynomial equations (degree > 1)
665 /// * `variables` - Variables to solve for
666 ///
667 /// # Returns
668 ///
669 /// `SolverResult::Multiple(solutions)` if solutions found, otherwise `NoSolution`
670 ///
671 /// # Examples
672 ///
673 /// ```rust,ignore
674 /// use mathhook_core::algebra::solvers::{SystemSolver, SystemEquationSolver};
675 /// use mathhook_core::{symbol, expr, Expression};
676 ///
677 /// let solver = SystemSolver::new();
678 /// let x = symbol!(x);
679 /// let y = symbol!(y);
680 ///
681 /// // Circle x² + y² = 1 intersecting line x = y
682 /// let eq1 = Expression::add(vec![expr!(x^2), expr!(y^2), expr!(-1)]);
683 /// let eq2 = expr!(x - y);
684 ///
685 /// let result = solver.solve_system(&[eq1, eq2], &[x, y]);
686 /// // Finds points (√2/2, √2/2) and (-√2/2, -√2/2)
687 /// ```
688 fn solve_polynomial_system_groebner(
689 &self,
690 equations: &[Expression],
691 variables: &[Symbol],
692 ) -> SolverResult {
693 // Create Gröbner basis with lexicographic ordering
694 // Lex ordering produces elimination ideal (triangular form)
695 let mut gb = GroebnerBasis::new(equations.to_vec(), variables.to_vec(), MonomialOrder::Lex);
696
697 // Compute basis using Buchberger's algorithm
698 // If computation times out or exceeds iteration limit, return Partial result
699 let computation_result = gb.compute_with_result();
700 if computation_result.is_err() {
701 // Timeout or iteration limit exceeded - return Partial
702 return SolverResult::Partial(vec![]);
703 }
704
705 // Try to reduce basis for simpler form
706 gb.reduce();
707
708 // Extract solutions from the Gröbner basis
709 // In lex ordering, basis should be in triangular form:
710 // [..., g_k(x_k, ..., x_n), ..., g_n(x_n)]
711
712 // For now, return Partial with the basis as solution representation
713 // Full solution extraction requires:
714 // 1. Solve univariate polynomial in last variable
715 // 2. Back-substitute to find other variables
716 // 3. Handle multiple solutions (roots of polynomials)
717
718 // If basis contains only constant (non-zero), no solution exists
719 if gb.basis.len() == 1 {
720 if let Expression::Number(Number::Integer(n)) = &gb.basis[0] {
721 if *n != 0 {
722 return SolverResult::NoSolution; // Inconsistent system (e.g., 1 = 0)
723 }
724 }
725 }
726
727 // If basis is empty or contains only zero, infinite solutions
728 if gb.basis.is_empty() || gb.basis.iter().all(|p| p.is_zero()) {
729 return SolverResult::InfiniteSolutions;
730 }
731
732 // For simple cases, try to extract solutions directly
733 // Look for equations of form "variable - constant = 0"
734 let mut solutions = vec![Expression::integer(0); variables.len()];
735 let mut found_count = 0;
736
737 for poly in &gb.basis {
738 if let Expression::Add(terms) = poly {
739 if terms.len() == 2 {
740 // Check for "x - c" or "c - x" pattern
741 for (i, var) in variables.iter().enumerate() {
742 if terms[0] == Expression::symbol(var.clone()) {
743 if let Expression::Number(_) = terms[1] {
744 solutions[i] = Expression::mul(vec![
745 Expression::integer(-1),
746 terms[1].clone(),
747 ])
748 .simplify();
749 found_count += 1;
750 break;
751 }
752 } else if terms[1] == Expression::symbol(var.clone()) {
753 if let Expression::Number(_) = terms[0] {
754 solutions[i] = Expression::mul(vec![
755 Expression::integer(-1),
756 terms[0].clone(),
757 ])
758 .simplify();
759 found_count += 1;
760 break;
761 }
762 }
763 }
764 }
765 }
766 }
767
768 // If we found solutions for all variables, return them
769 if found_count == variables.len() {
770 return SolverResult::Multiple(solutions);
771 }
772
773 // Otherwise, system is too complex for simple extraction
774 // Gröbner basis computed but extraction incomplete
775 // Full implementation (univariate solving + back-substitution) deferred to Phase 4: WAVE-CLEANUP
776 SolverResult::Partial(vec![])
777 }
778}