1use std::fmt;
7
8use ocas_domain::{IntegerDomain, RationalDomain};
9use ocas_poly::matrix::{Matrix, MatrixError};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum SolveError {
14 EmptySystem,
16 NonLinear,
18 NonSquare,
20 Inconsistent,
22 Underdetermined {
24 rank: usize,
26 },
27 ResultNotInDomain,
29 Matrix(MatrixError),
31 Other(String),
33}
34
35impl fmt::Display for SolveError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 SolveError::EmptySystem => f.write_str("empty system"),
39 SolveError::NonLinear => f.write_str("system is not linear"),
40 SolveError::NonSquare => {
41 f.write_str("number of equations must match number of unknowns")
42 }
43 SolveError::Inconsistent => f.write_str("inconsistent system"),
44 SolveError::Underdetermined { rank } => {
45 write!(f, "underdetermined system (rank {})", rank)
46 }
47 SolveError::ResultNotInDomain => {
48 f.write_str("solution does not lie in the expected domain")
49 }
50 SolveError::Matrix(e) => write!(f, "matrix error: {}", e),
51 SolveError::Other(msg) => f.write_str(msg),
52 }
53 }
54}
55
56impl std::error::Error for SolveError {}
57
58impl From<MatrixError> for SolveError {
59 fn from(e: MatrixError) -> Self {
60 match e {
61 MatrixError::Inconsistent => SolveError::Inconsistent,
62 MatrixError::Underdetermined { rank } => SolveError::Underdetermined { rank },
63 MatrixError::ResultNotInDomain => SolveError::ResultNotInDomain,
64 MatrixError::ShapeMismatch => SolveError::NonSquare,
65 MatrixError::RightHandSideIsNotVector => {
66 SolveError::Other("right-hand side is not a vector".into())
67 }
68 }
69 }
70}
71
72pub fn solve_linear_rational(a: &[Vec<i64>], b: &[i64]) -> Result<Vec<(i64, i64)>, SolveError> {
91 if a.is_empty() || b.is_empty() {
92 return Err(SolveError::EmptySystem);
93 }
94 let n = a.len();
95 if b.len() != n {
96 return Err(SolveError::NonSquare);
97 }
98 for row in a {
99 if row.len() != n {
100 return Err(SolveError::NonSquare);
101 }
102 }
103
104 let d = RationalDomain;
105 use ocas_domain::Rational;
106
107 let rows: Vec<Vec<Rational>> = a
108 .iter()
109 .map(|row| row.iter().map(|&v| Rational::new(v, 1)).collect())
110 .collect();
111 let b_vec: Vec<Rational> = b.iter().map(|&v| Rational::new(v, 1)).collect();
112
113 let mat = Matrix::from_rows(rows, d);
114 let solution = mat.solve(&b_vec)?;
115
116 Ok(solution
117 .into_iter()
118 .map(|r| {
119 let numer = r.numer().to_i64().unwrap_or(0);
120 let denom = r.denom().to_i64().unwrap_or(1);
121 (numer, denom)
122 })
123 .collect())
124}
125
126pub fn solve_linear_integer(a: &[Vec<i64>], b: &[i64]) -> Result<Vec<i64>, SolveError> {
144 if a.is_empty() || b.is_empty() {
145 return Err(SolveError::EmptySystem);
146 }
147 let n = a.len();
148 if b.len() != n {
149 return Err(SolveError::NonSquare);
150 }
151 for row in a {
152 if row.len() != n {
153 return Err(SolveError::NonSquare);
154 }
155 }
156
157 let d = IntegerDomain;
158 use ocas_domain::Integer;
159
160 let rows: Vec<Vec<Integer>> = a
161 .iter()
162 .map(|row| row.iter().map(|&v| Integer::from(v)).collect())
163 .collect();
164 let b_vec: Vec<Integer> = b.iter().map(|&v| Integer::from(v)).collect();
165
166 let mat = Matrix::from_rows(rows, d);
167 let solution = mat.solve(&b_vec)?;
168
169 Ok(solution
170 .into_iter()
171 .map(|r| {
172 r.to_i64().unwrap_or(0)
174 })
175 .collect())
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct DiophantineSolution {
181 pub particular: (i64, i64),
183 pub general: (i64, i64),
185}
186
187pub fn solve_diophantine(a: i64, b: i64, c: i64) -> Option<DiophantineSolution> {
203 use ocas_domain::{EuclideanDomain, Integer, IntegerDomain};
204
205 if a == 0 && b == 0 {
206 return if c == 0 {
207 Some(DiophantineSolution {
208 particular: (0, 0),
209 general: (1, 0),
210 })
211 } else {
212 None
213 };
214 }
215
216 let d = IntegerDomain;
217 let ai = Integer::from(a);
218 let bi = Integer::from(b);
219
220 let (g, x, y) = d.extended_gcd(&ai, &bi);
221 let g_val: i64 = g.to_i64()?;
222 let x_val: i64 = x.to_i64()?;
223 let y_val: i64 = y.to_i64()?;
224
225 if c % g_val != 0 {
227 return None;
228 }
229
230 let scale = c / g_val;
231 let x0 = x_val * scale;
232 let y0 = y_val * scale;
233
234 Some(DiophantineSolution {
235 particular: (x0, y0),
236 general: (b / g_val, -(a / g_val)),
237 })
238}
239
240type RawPoly = Vec<(i64, Vec<usize>)>;
242
243pub fn solve_polynomial_system(
265 polys: &[RawPoly],
266 n_vars: usize,
267) -> Result<Vec<RawPoly>, SolveError> {
268 use ocas_domain::Rational;
269 use ocas_domain::RationalDomain;
270 use ocas_poly::SparseMultivariatePolynomial;
271 use ocas_poly::buchberger;
272 use ocas_poly::sparse::Lex;
273
274 if polys.is_empty() {
275 return Err(SolveError::EmptySystem);
276 }
277
278 let d = RationalDomain;
279
280 let ideal: Vec<SparseMultivariatePolynomial<RationalDomain, Lex>> = polys
281 .iter()
282 .map(|terms| {
283 let terms: Vec<(Vec<usize>, Rational)> = terms
284 .iter()
285 .map(|(c, exp)| (exp.clone(), Rational::new(*c, 1)))
286 .collect();
287 SparseMultivariatePolynomial::from_terms(d, n_vars, terms)
288 })
289 .collect();
290
291 let gb = buchberger(&ideal);
292
293 let result: Vec<RawPoly> = gb
294 .basis
295 .iter()
296 .map(|p| {
297 p.sorted_terms()
298 .into_iter()
299 .map(|(exp, coeff)| {
300 let numer = coeff.numer().to_i64().unwrap_or(0);
301 let _denom = coeff.denom().to_i64().unwrap_or(1);
302 (numer, exp.to_vec())
303 })
304 .collect()
305 })
306 .collect();
307
308 Ok(result)
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn diophantine_3x_5y_eq_1() {
317 let sol = solve_diophantine(3, 5, 1).unwrap();
318 let (x, y) = sol.particular;
320 assert_eq!(3 * x + 5 * y, 1);
321 }
322
323 #[test]
324 fn diophantine_no_solution() {
325 assert!(solve_diophantine(2, 4, 3).is_none());
327 }
328
329 #[test]
330 fn diophantine_zero_coeffs() {
331 assert!(solve_diophantine(0, 0, 1).is_none());
332 let sol = solve_diophantine(0, 0, 0).unwrap();
333 assert_eq!(sol.particular, (0, 0));
334 }
335
336 #[test]
337 fn solve_2x2_rational() {
338 let a = vec![vec![1, 2], vec![3, 4]];
339 let b = vec![5, 11];
340 let x = solve_linear_rational(&a, &b).unwrap();
341 assert_eq!(x, vec![(1, 1), (2, 1)]);
343 }
344
345 #[test]
346 fn solve_2x2_integer() {
347 let a = vec![vec![1, 1], vec![1, -1]];
348 let b = vec![3, 1];
349 let x = solve_linear_integer(&a, &b).unwrap();
350 assert_eq!(x, vec![2, 1]);
351 }
352
353 #[test]
354 fn solve_3x3_rational() {
355 let a = vec![vec![1, 1, 1], vec![2, -1, 1], vec![1, 2, -1]];
359 let b = vec![6, 3, 2];
360 let x = solve_linear_rational(&a, &b).unwrap();
361 assert_eq!(x, vec![(1, 1), (2, 1), (3, 1)]);
362 }
363
364 #[test]
365 fn inconsistent_system() {
366 let a = vec![vec![1, 1], vec![2, 2]];
367 let b = vec![1, 3];
368 assert!(matches!(
369 solve_linear_rational(&a, &b),
370 Err(SolveError::Inconsistent)
371 ));
372 }
373}