1#[allow(unused_imports)]
13use crate::prelude::*;
14use num_bigint::BigInt;
15use num_rational::BigRational;
16use num_traits::{One, Signed, Zero};
17
18pub type IntVector = Vec<BigInt>;
20
21pub type RatVector = Vec<BigRational>;
23
24#[derive(Debug, Clone)]
26pub struct Cone {
27 constraints: Vec<RatVector>,
29 dimension: usize,
31}
32
33impl Cone {
34 pub fn new(constraints: Vec<RatVector>) -> Self {
38 let dimension = if constraints.is_empty() {
39 0
40 } else {
41 constraints[0].len()
42 };
43
44 Self {
45 constraints,
46 dimension,
47 }
48 }
49
50 pub fn dimension(&self) -> usize {
52 self.dimension
53 }
54
55 pub fn contains(&self, point: &RatVector) -> bool {
57 if point.len() != self.dimension {
58 return false;
59 }
60
61 for constraint in &self.constraints {
62 let mut dot = BigRational::zero();
63 for i in 0..self.dimension {
64 dot += &constraint[i] * &point[i];
65 }
66 if dot < BigRational::zero() {
67 return false;
68 }
69 }
70
71 true
72 }
73}
74
75pub fn hilbert_basis(cone: &Cone) -> Vec<IntVector> {
81 if cone.dimension == 0 {
82 return vec![];
83 }
84
85 let mut basis = Vec::new();
87 let mut candidates = Vec::new();
88
89 for i in 0..cone.dimension {
91 let mut v = vec![BigInt::zero(); cone.dimension];
92 v[i] = BigInt::one();
93
94 let rat_v: RatVector = v
95 .iter()
96 .map(|x| BigRational::from_integer(x.clone()))
97 .collect();
98 if cone.contains(&rat_v) {
99 candidates.push(v);
100 }
101 }
102
103 let max_iterations = 100;
106 let mut iteration = 0;
107
108 while !candidates.is_empty() && iteration < max_iterations {
109 iteration += 1;
110
111 let candidate = candidates
112 .pop()
113 .expect("collection validated to be non-empty");
114
115 if is_primitive(&candidate) {
117 if !is_generated_by(&candidate, &basis) {
119 basis.push(candidate.clone());
120
121 for base_vec in &basis {
123 if base_vec != &candidate {
124 let sum = add_vectors(&candidate, base_vec);
125 let rat_sum: RatVector = sum
126 .iter()
127 .map(|x| BigRational::from_integer(x.clone()))
128 .collect();
129
130 if cone.contains(&rat_sum) && !candidates.contains(&sum) {
131 if candidates.len() < 1000 && vector_norm(&sum) < BigInt::from(100) {
133 candidates.push(sum);
134 }
135 }
136 }
137 }
138 }
139 }
140 }
141
142 minimize_basis(basis)
144}
145
146fn is_primitive(v: &IntVector) -> bool {
148 if v.is_empty() {
149 return false;
150 }
151
152 let mut g = v[0].clone();
153 for component in v.iter().skip(1) {
154 g = gcd(&g, component);
155 if g == BigInt::one() {
156 return true;
157 }
158 }
159
160 g == BigInt::one()
161}
162
163fn gcd(a: &BigInt, b: &BigInt) -> BigInt {
165 let mut a = a.abs();
166 let mut b = b.abs();
167
168 while !b.is_zero() {
169 let temp = b.clone();
170 b = &a % &b;
171 a = temp;
172 }
173
174 a
175}
176
177fn is_generated_by(v: &IntVector, generators: &[IntVector]) -> bool {
179 if generators.is_empty() {
180 return v.iter().all(|x| x.is_zero());
181 }
182
183 for generator in generators {
185 if is_multiple(v, generator) {
186 return true;
187 }
188 }
189
190 false
193}
194
195fn is_multiple(v1: &IntVector, v2: &IntVector) -> bool {
197 if v1.len() != v2.len() {
198 return false;
199 }
200
201 if v1.iter().all(|x| x.is_zero()) {
203 return true;
204 }
205
206 let mut ratio: Option<BigRational> = None;
208
209 for i in 0..v1.len() {
210 if !v2[i].is_zero() {
211 let r = BigRational::new(v1[i].clone(), v2[i].clone());
212 if let Some(ref existing_ratio) = ratio {
213 if &r != existing_ratio {
214 return false;
215 }
216 } else {
217 if r < BigRational::zero() || !r.is_integer() {
219 return false;
220 }
221 ratio = Some(r);
222 }
223 } else if !v1[i].is_zero() {
224 return false;
225 }
226 }
227
228 ratio.is_some()
229}
230
231fn add_vectors(v1: &IntVector, v2: &IntVector) -> IntVector {
233 assert_eq!(v1.len(), v2.len());
234 v1.iter().zip(v2.iter()).map(|(a, b)| a + b).collect()
235}
236
237fn vector_norm(v: &IntVector) -> BigInt {
239 v.iter().map(|x| x.abs()).sum()
240}
241
242fn minimize_basis(mut basis: Vec<IntVector>) -> Vec<IntVector> {
244 let mut minimal = Vec::new();
245
246 basis.sort_by(|a, b| {
248 let norm_a = vector_norm(a);
249 let norm_b = vector_norm(b);
250 norm_a.cmp(&norm_b)
251 });
252
253 for vec in basis {
254 if !is_generated_by(&vec, &minimal) {
256 minimal.push(vec);
257 }
258 }
259
260 minimal
261}
262
263pub struct IntCone {
265 generators: Vec<IntVector>,
267 dimension: usize,
269}
270
271impl IntCone {
272 pub fn from_hilbert_basis(generators: Vec<IntVector>) -> Self {
274 let dimension = if generators.is_empty() {
275 0
276 } else {
277 generators[0].len()
278 };
279
280 Self {
281 generators,
282 dimension,
283 }
284 }
285
286 pub fn generators(&self) -> &[IntVector] {
288 &self.generators
289 }
290
291 pub fn dimension(&self) -> usize {
293 self.dimension
294 }
295
296 pub fn contains_int(&self, point: &IntVector) -> bool {
298 if point.len() != self.dimension {
301 return false;
302 }
303
304 if point.iter().all(|x| x.is_zero()) {
306 return true;
307 }
308
309 for generator in &self.generators {
311 if is_multiple(point, generator) {
312 return true;
313 }
314 }
315
316 false
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 fn int_vec(values: &[i64]) -> IntVector {
326 values.iter().map(|&x| BigInt::from(x)).collect()
327 }
328
329 fn rat_vec(values: &[i64]) -> RatVector {
330 values
331 .iter()
332 .map(|&x| BigRational::from_integer(BigInt::from(x)))
333 .collect()
334 }
335
336 #[test]
337 fn test_cone_contains() {
338 let constraints = vec![rat_vec(&[1, 0]), rat_vec(&[0, 1])];
340 let cone = Cone::new(constraints);
341
342 assert!(cone.contains(&rat_vec(&[1, 1])));
343 assert!(cone.contains(&rat_vec(&[0, 0])));
344 assert!(!cone.contains(&rat_vec(&[-1, 1])));
345 assert!(!cone.contains(&rat_vec(&[1, -1])));
346 }
347
348 #[test]
349 fn test_is_primitive() {
350 assert!(is_primitive(&int_vec(&[1, 2, 3])));
351 assert!(is_primitive(&int_vec(&[1, 0, 0])));
352 assert!(!is_primitive(&int_vec(&[2, 4, 6])));
353 assert!(is_primitive(&int_vec(&[3, 5, 7])));
354 }
355
356 #[test]
357 fn test_gcd() {
358 assert_eq!(gcd(&BigInt::from(12), &BigInt::from(8)), BigInt::from(4));
359 assert_eq!(gcd(&BigInt::from(7), &BigInt::from(3)), BigInt::from(1));
360 assert_eq!(gcd(&BigInt::from(0), &BigInt::from(5)), BigInt::from(5));
361 }
362
363 #[test]
364 fn test_is_multiple() {
365 assert!(is_multiple(&int_vec(&[2, 4]), &int_vec(&[1, 2])));
366 assert!(!is_multiple(&int_vec(&[2, 5]), &int_vec(&[1, 2])));
367 assert!(is_multiple(&int_vec(&[0, 0]), &int_vec(&[1, 2])));
368 assert!(!is_multiple(&int_vec(&[1, 2]), &int_vec(&[0, 0])));
369 }
370
371 #[test]
372 fn test_add_vectors() {
373 let v1 = int_vec(&[1, 2, 3]);
374 let v2 = int_vec(&[4, 5, 6]);
375 let sum = add_vectors(&v1, &v2);
376 assert_eq!(sum, int_vec(&[5, 7, 9]));
377 }
378
379 #[test]
380 fn test_vector_norm() {
381 assert_eq!(vector_norm(&int_vec(&[1, -2, 3])), BigInt::from(6));
382 assert_eq!(vector_norm(&int_vec(&[0, 0, 0])), BigInt::from(0));
383 }
384
385 #[test]
386 fn test_hilbert_basis_simple() {
387 let constraints = vec![rat_vec(&[1, 0]), rat_vec(&[0, 1])];
389 let cone = Cone::new(constraints);
390
391 let basis = hilbert_basis(&cone);
392
393 assert!(!basis.is_empty());
395 assert!(basis.len() >= 2);
396 }
397
398 #[test]
399 fn test_int_cone() {
400 let generators = vec![int_vec(&[1, 0]), int_vec(&[0, 1])];
401 let cone = IntCone::from_hilbert_basis(generators);
402
403 assert_eq!(cone.dimension(), 2);
404 assert!(cone.contains_int(&int_vec(&[0, 0])));
405 assert!(cone.contains_int(&int_vec(&[1, 0])));
406 assert!(cone.contains_int(&int_vec(&[2, 0])));
407 }
408}