1use core::fmt;
18use std::fmt::Debug;
19use std::iter::{Sum, zip};
20use std::marker::PhantomData;
21use std::ops::{BitAnd, DivAssign, Mul, Neg};
22
23use approx::{AbsDiffEq, RelativeEq};
24use itertools::{Itertools, enumerate};
25use ndarray::{
26 self, Array1, Array2, ArrayBase, ArrayView1, ArrayView2, Axis, Data, DataMut, DataOwned, Ix1,
27 Ix2, LinalgScalar, OwnedRepr, RawDataClone, ViewRepr, Zip, arr1, concatenate, s, stack,
28};
29use num_traits::float::Float;
30
31pub struct AffFuncBase<T, S>
32where
33 S: Data,
34 S::Elem: Float,
35{
36 pub mat: ArrayBase<S, Ix2>,
37 pub bias: ArrayBase<S, Ix1>,
38 pub _phantom: PhantomData<T>,
39}
40
41#[derive(Clone, Default, Debug)]
42pub struct FunctionT;
43
44#[derive(Clone, Default, Debug)]
45pub struct PolytopeT;
46
47type AffFuncG<A> = AffFuncBase<FunctionT, OwnedRepr<A>>;
49type AffFuncViewG<'a, A> = AffFuncBase<FunctionT, ViewRepr<&'a A>>;
50type PolytopeG<A> = AffFuncBase<PolytopeT, OwnedRepr<A>>;
51type PolytopeViewG<'a, A> = AffFuncBase<PolytopeT, ViewRepr<&'a A>>;
52
53pub type AffFunc = AffFuncG<f64>;
55pub type AffFuncView<'a> = AffFuncViewG<'a, f64>;
56pub type Polytope = PolytopeG<f64>;
57pub type PolytopeView<'a> = PolytopeViewG<'a, f64>;
58
59impl<I, D: Data<Elem = A>, A: Float + Debug> Debug for AffFuncBase<I, D> {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
61 f.debug_tuple("AffFuncBase")
62 .field(&self.mat)
63 .field(&self.bias)
64 .finish()
65 }
66}
67
68impl<I, D: Data<Elem = A> + RawDataClone, A: Float + Clone> Clone for AffFuncBase<I, D> {
69 fn clone(&self) -> Self {
70 AffFuncBase {
71 mat: self.mat.clone(),
72 bias: self.bias.clone(),
73 _phantom: self._phantom,
74 }
75 }
76}
77
78impl<I, D: Data<Elem = A>, A: Float> AffFuncBase<I, D> {
80 #[inline(always)]
85 pub fn from_mats(mat: ArrayBase<D, Ix2>, bias: ArrayBase<D, Ix1>) -> AffFuncBase<I, D> {
86 assert_eq!(
87 mat.len_of(Axis(0)),
88 bias.len_of(Axis(0)),
89 "Dimensions mismatch of matrix and bias: {} x {} and {}",
90 mat.len_of(Axis(0)),
91 mat.len_of(Axis(1)),
92 bias.len_of(Axis(0))
93 );
94 debug_assert!(
95 mat.iter().all(|x| x.is_normal() || x.is_zero()),
96 "Non-normal floats are deprecated"
97 );
98 debug_assert!(
99 bias.iter().all(|x| x.is_normal() || x.is_zero()),
100 "Non-normal floats are deprecated"
101 );
102
103 AffFuncBase {
104 mat,
105 bias,
106 _phantom: PhantomData,
107 }
108 }
109
110 #[cfg(test)]
111 pub fn from_random(dim_out: usize, dim_in: usize) -> AffFuncBase<I, OwnedRepr<f64>> {
112 use rand::Rng;
113
114 let mut rng = rand::thread_rng();
115 let mut mat = Array2::zeros((dim_out, dim_in));
116 let mut bias = Array1::zeros(dim_out);
117 for i in 0..dim_out {
118 for j in 0..dim_in {
119 mat[[i, j]] = rng.gen()
120 }
121 bias[i] = rng.gen();
122 }
123
124 AffFuncBase::<I, OwnedRepr<f64>> {
125 mat,
126 bias,
127 _phantom: PhantomData,
128 }
129 }
130}
131
132impl<I, A: Float> AffFuncBase<I, OwnedRepr<A>> {
133 pub fn from_row_iter<'a, D, Iter>(
134 indim: usize,
135 outdim: usize,
136 rows: Iter,
137 ) -> AffFuncBase<I, OwnedRepr<A>>
138 where
139 D: Data<Elem = A>,
140 Iter: IntoIterator<Item = (ArrayBase<D, Ix1>, &'a A)>,
141 A: 'a,
142 {
143 let mut mat = Array2::zeros((outdim, indim));
144 let mut bias = Array1::zeros(outdim);
145
146 let mut iter = rows.into_iter();
147
148 Zip::from(mat.axis_iter_mut(Axis(0)))
149 .and(&mut bias)
150 .for_each(|mut row, value| {
151 let (x, y) = iter.next().unwrap_or_else(|| {
152 panic!(
153 "Invalid number of elements in iterator: expected at least {}",
154 outdim
155 )
156 });
157
158 row.assign(&x);
159 *value = *y;
160 });
161
162 AffFuncBase::<I, OwnedRepr<A>>::from_mats(mat, bias)
163 }
164}
165
166impl<A: Float> AffFuncG<A> {
168 #[inline(always)]
170 #[rustfmt::skip]
171 pub fn identity(dim: usize) -> AffFuncG<A> {
172 AffFuncG::<A>::from_mats(
173 Array2::eye(dim),
174 Array1::zeros(dim)
175 )
176 }
177
178 #[inline(always)]
180 #[rustfmt::skip]
181 pub fn zeros(dim: usize) -> AffFuncG<A> {
182 AffFuncG::<A>::from_mats(
183 Array2::zeros((dim, dim)),
184 Array1::zeros(dim)
185 )
186 }
187
188 #[inline(always)]
190 #[rustfmt::skip]
191 pub fn constant(dim: usize, value: A) -> AffFuncG<A> {
192 AffFuncG::<A>::from_mats(
193 Array2::zeros((1, dim)),
194 arr1(&[value])
195 )
196 }
197
198 #[inline(always)]
200 #[rustfmt::skip]
201 pub fn unit(dim: usize, index: usize) -> AffFuncG<A> {
202 let mut mat = Array2::zeros((1, dim));
203 mat[[0, index]] = A::one();
204
205 AffFuncG::<A>::from_mats(
206 mat,
207 Array1::zeros(1)
208 )
209 }
210
211 #[inline(always)]
214 #[rustfmt::skip]
215 pub fn zero_idx(dim: usize, index: usize) -> AffFuncG<A> {
216 let mut mat = Array2::eye(dim);
217 mat[[index, index]] = A::zero();
218
219 AffFuncG::<A>::from_mats(
220 mat,
221 Array1::zeros(dim)
222 )
223 }
224
225 #[inline(always)]
228 #[rustfmt::skip]
229 pub fn sum(dim: usize) -> AffFuncG<A> {
230 AffFuncG::<A>::from_mats(
231 Array2::ones((1, dim)),
232 Array1::zeros(1)
233 )
234 }
235
236 #[inline(always)]
238 #[rustfmt::skip]
239 pub fn subtraction(dim: usize, left: usize, right: usize) -> AffFuncG<A> {
240 let mut matrix = Array2::zeros((1, dim));
241 matrix[[0, left]] = A::one();
242 matrix[[0, right]] = -A::one();
243 let bias = Array1::zeros(1);
244
245 AffFuncG::<A>::from_mats(
246 matrix,
247 bias
248 )
249 }
250
251 #[inline(always)]
254 #[rustfmt::skip]
255 pub fn rotation(rotator: Array2<A>) -> AffFuncG<A> {
256 assert_eq!(rotator.shape()[0], rotator.shape()[1]);
257 let dim = rotator.shape()[0];
258
259 AffFuncG::<A>::from_mats(
260 rotator,
261 Array1::zeros(dim)
262 )
263 }
264
265 #[inline(always)]
268 pub fn uniform_scaling(dim: usize, scalar: A) -> AffFuncG<A> {
269 AffFuncG::<A>::scaling(&Array1::from_elem(dim, scalar))
270 }
271
272 #[inline(always)]
274 pub fn scaling(scalars: &Array1<A>) -> AffFuncG<A> {
275 AffFuncG::<A>::from_mats(
276 Array2::from_diag(scalars),
277 Array1::zeros(scalars.shape()[0]),
278 )
279 }
280
281 #[inline(always)]
285 pub fn slice(reference_point: &Array1<A>) -> AffFuncG<A> {
286 let input_mask = reference_point.map(|x| if x.is_nan() { A::one() } else { A::zero() });
287 let fixed_values = reference_point.map(|x| if x.is_nan() { A::zero() } else { *x });
288
289 AffFuncG::<A>::from_mats(Array2::from_diag(&input_mask), fixed_values)
290 }
291
292 #[inline(always)]
294 #[rustfmt::skip]
295 pub fn translation(dim: usize, offset: Array1<A>) -> AffFuncG<A> {
296 AffFuncG::<A>::from_mats(
297 Array2::zeros((offset.shape()[0], dim)),
298 offset
299 )
300 }
301}
302
303impl<A: Float> PolytopeG<A> {
305 #[inline(always)]
307 pub fn unbounded(dim: usize) -> PolytopeG<A> {
308 PolytopeG::<A>::from_mats(Array2::zeros((1, dim)), Array1::ones(1))
309 }
310
311 #[inline(always)]
313 pub fn empty(dim: usize) -> PolytopeG<A> {
314 PolytopeG::<A>::from_mats(Array2::zeros((1, dim)), -Array1::ones(1))
315 }
316
317 #[inline(always)]
319 pub fn from_normal(normal_vectors: Array2<A>, points: Array2<A>) -> PolytopeG<A> {
320 let bias = (&normal_vectors).mul(&points).sum_axis(Axis(1));
321 PolytopeG::<A>::from_mats(-normal_vectors, -bias)
322 }
323
324 pub fn hypercube(dim: usize, radius: A) -> PolytopeG<A> {
327 let mat: Array2<A> = concatenate![Axis(0), Array2::eye(dim), -Array2::eye(dim)];
328 let bias: Array1<A> = Array1::from_elem(2 * dim, radius);
329 PolytopeG::<A>::from_mats(mat, bias)
330 }
331
332 pub fn hyperrectangle(intervals: &[(A, A)]) -> PolytopeG<A> {
336 let dim = intervals.len();
337 let mut mat: Array2<A> = Array2::zeros((2 * dim, dim));
338 let mut bias: Array1<A> = Array1::zeros(2 * dim);
339 for (idx, (lower, upper)) in intervals.iter().enumerate() {
340 Self::place_axis_bounds(2 * idx, &mut mat, &mut bias, idx, *lower, *upper);
341 }
342 PolytopeG::<A>::from_mats(mat, bias)
343 }
344
345 pub fn simplex(dim: usize) -> PolytopeG<A> {
348 let mut mat = Array2::ones((dim + 1, dim));
349 let bias = Array1::ones(dim + 1);
350
351 let dist = -(A::one() + (A::from(dim).unwrap() + A::one()).sqrt() + A::from(dim).unwrap());
352
353 for idx in 0..dim {
354 mat[[idx, idx]] = mat[[idx, idx]] + dist;
355 }
356
357 PolytopeG::<A>::from_mats(mat, bias)
358 }
359
360 pub fn cross_polytope(dim: usize) -> PolytopeG<A> {
364 let rows = usize::pow(2, dim as u32);
365
366 let mut mat = Array2::ones((rows, dim));
367 let bias = Array1::ones(rows);
368
369 for i in 0..rows {
370 for j in 0..dim {
371 if i.bitand(1 << j) != 0 {
372 mat[[i, j]] = -A::one();
373 }
374 }
375 }
376
377 PolytopeG::<A>::from_mats(mat, bias)
378 }
379
380 #[inline(always)]
385 pub fn axis_bounds(dim: usize, axis: usize, lower_bound: A, upper_bound: A) -> PolytopeG<A> {
386 assert!(
387 axis < dim,
388 "Invalid axis received: axis {} does not exist in a {}-dimensional space",
389 axis,
390 dim
391 );
392
393 let mut mat = Array2::zeros((2, dim));
394 let mut bias = Array1::zeros(2);
395 Self::place_axis_bounds(0, &mut mat, &mut bias, axis, lower_bound, upper_bound);
396 PolytopeG::<A>::from_mats(mat, bias)
397 }
398
399 fn place_axis_bounds<B: Float>(
400 idx: usize,
401 mat: &mut Array2<B>,
402 bias: &mut Array1<B>,
403 axis: usize,
404 lower: B,
405 upper: B,
406 ) {
407 assert!(lower <= upper);
408 if lower.is_infinite() {
409 bias[idx] = B::one();
410 } else {
411 mat[[idx, axis]] = -B::one();
412 bias[idx] = -lower;
413 }
414 if upper.is_infinite() {
415 bias[idx + 1] = B::one();
416 } else {
417 mat[[idx + 1, axis]] = B::one();
418 bias[idx + 1] = upper;
419 }
420 }
421}
422
423impl<I, D: Data<Elem = A>, A: Float> AffFuncBase<I, D> {
425 #[inline(always)]
427 pub fn indim(&self) -> usize {
428 self.mat.shape()[1]
429 }
430
431 #[inline(always)]
433 pub fn outdim(&self) -> usize {
434 self.mat.shape()[0]
435 }
436
437 #[inline(always)]
438 pub fn matrix_view(&self) -> ArrayView2<D::Elem> {
439 self.mat.view()
440 }
441
442 #[inline(always)]
443 pub fn bias_view(&self) -> ArrayView1<D::Elem> {
444 self.bias.view()
445 }
446
447 pub fn row<'a>(&'a self, row: usize) -> AffFuncBase<I, ViewRepr<&'a A>> {
450 assert!(
451 row < self.outdim(),
452 "Row outside range: got {} but only {} rows exist",
453 row,
454 self.outdim()
455 );
456 AffFuncBase::<I, ViewRepr<&'a A>>::from_mats(
457 self.mat.row(row).insert_axis(Axis(0)),
458 self.bias.slice(s![row]).insert_axis(Axis(0)),
459 )
460 }
461
462 pub fn row_iter<'a>(&'a self) -> impl Iterator<Item = AffFuncBase<I, ViewRepr<&'a A>>> + 'a
465 where
466 A: 'a,
467 {
468 self.mat
469 .outer_iter()
470 .zip(self.bias.outer_iter())
471 .map(|(row, bias)| {
472 AffFuncBase::<I, ViewRepr<&'a A>>::from_mats(
473 row.insert_axis(Axis(0)),
474 bias.insert_axis(Axis(0)),
475 )
476 })
477 }
478
479 pub fn remove_rows<Iter: IntoIterator<Item = usize>>(
494 &self,
495 indices: Iter,
496 ) -> AffFuncBase<I, OwnedRepr<A>> {
497 let mut indices = indices.into_iter();
498 let mut index = indices.next();
499
500 let rows = self
501 .mat
502 .axis_iter(Axis(0))
503 .zip(self.bias.iter())
504 .enumerate()
505 .filter(|(idx, _)| {
506 if let Some(val) = index {
507 if *idx == val {
508 index = indices.next();
509 false
510 } else {
511 true
512 }
513 } else {
514 true
515 }
516 })
517 .map(|(_, data)| data)
518 .collect_vec();
519
520 if index.is_some() {
521 panic!(
522 "iterator of indices for removal should have been completely consumed after checking each row"
523 );
524 }
525
526 AffFuncBase::<I, OwnedRepr<A>>::from_row_iter(self.indim(), rows.len(), rows)
527 }
528
529 pub fn remove_zero_rows(&self) -> AffFuncBase<I, OwnedRepr<A>> {
530 let rows = self
531 .mat
532 .axis_iter(Axis(0))
533 .zip(self.bias.iter())
534 .filter(|(r, &v)| r.iter().any(|&x| x != A::zero()) || v != A::zero())
535 .collect_vec();
536
537 AffFuncBase::<I, OwnedRepr<A>>::from_row_iter(self.indim(), rows.len(), rows)
538 }
539
540 pub fn remove_zero_columns(&self) -> AffFuncBase<I, OwnedRepr<A>> {
541 let rows = self
542 .mat
543 .axis_iter(Axis(1))
544 .filter(|r| r.iter().any(|&x| x != A::zero()))
545 .collect_vec();
546
547 AffFuncBase::<I, OwnedRepr<A>>::from_mats(
548 stack(Axis(1), rows.as_slice()).unwrap(),
549 self.bias.to_owned(),
550 )
551 }
552}
553
554impl<D: Data<Elem = A>, A: Float> AffFuncBase<PolytopeT, D> {
555 pub fn n_constraints(&self) -> usize {
557 self.mat.shape()[0]
558 }
559}
560
561impl<I, A: Float + DivAssign + Sum> AffFuncBase<I, OwnedRepr<A>> {
562 pub fn normalize(mut self) -> AffFuncBase<I, OwnedRepr<A>> {
564 for (mut row, mut bias) in zip(self.mat.outer_iter_mut(), self.bias.outer_iter_mut()) {
565 let norm: A = row.iter().map(|&x| x.powi(2)).sum::<A>().sqrt();
566 if norm > A::epsilon() {
567 row.map_inplace(|x| *x /= norm);
568 bias.map_inplace(|x| *x /= norm);
569 }
570 }
571 self
572 }
573}
574
575impl<A: Float> AffFuncBase<PolytopeT, OwnedRepr<A>> {
576 pub fn remove_tautologies(&self) -> AffFuncBase<PolytopeT, OwnedRepr<A>> {
580 let rows: Option<Vec<_>> = self
581 .mat
582 .axis_iter(Axis(0))
583 .zip(self.bias.iter())
584 .filter_map(|(r, v)| {
585 if r.iter().all(|&x| x == A::zero()) {
586 if *v >= A::zero() {
587 None
589 } else {
590 Some(None)
592 }
593 } else {
594 Some(Some((r, v)))
595 }
596 })
597 .collect();
598
599 let rows = match rows {
600 Some(rows) => rows,
601 None => return PolytopeG::<A>::empty(self.indim()),
602 };
603
604 if rows.is_empty() {
605 Self::unbounded(self.indim())
606 } else {
607 PolytopeG::<A>::from_row_iter(self.indim(), rows.len(), rows)
608 }
609 }
610}
611
612impl<A: Float + DivAssign + Sum + RelativeEq<A, Epsilon: Clone>>
613 AffFuncBase<PolytopeT, OwnedRepr<A>>
614{
615 pub fn remove_duplicate_rows(&self) -> AffFuncBase<PolytopeT, OwnedRepr<A>> {
619 let normal = self.clone().normalize();
620
621 let mut dups: Vec<usize> = Vec::with_capacity(self.n_constraints());
622 for i in (0..self.n_constraints()).rev() {
623 for j in (0..i).rev() {
624 let mat_eq = ArrayView1::relative_eq(
625 &normal.mat.row(i),
626 &normal.mat.row(j),
627 A::default_epsilon(),
628 A::default_max_relative(),
629 );
630 let bias_eq = A::relative_eq(
631 &normal.bias[i],
632 &normal.bias[j],
633 A::default_epsilon(),
634 A::default_max_relative(),
635 );
636 if mat_eq && bias_eq {
637 dups.push(i);
638 break;
639 }
640 }
641 }
642
643 self.remove_rows(dups.into_iter().rev())
644 }
645}
646
647impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<FunctionT, D> {
649 pub fn apply<S: Data<Elem = A>>(&self, input: &ArrayBase<S, Ix1>) -> Array1<A> {
652 self.mat.dot(input) + &self.bias
653 }
654
655 pub fn apply_transpose<S: Data<Elem = A>>(&self, input: &ArrayBase<S, Ix1>) -> Array1<A> {
659 self.mat.t().dot(&(input - &self.bias))
660 }
661}
662
663impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<PolytopeT, D> {
665 #[inline]
672 pub fn distance_raw<S: Data<Elem = A>>(&self, point: &ArrayBase<S, Ix1>) -> Array1<A> {
673 &self.bias - self.mat.dot(point)
674 }
675
676 #[inline]
683 pub fn distances_raw<S: Data<Elem = A>>(&self, point: &ArrayBase<S, Ix2>) -> Array2<A> {
684 let b_bias = self.bias.broadcast(point.dim()).unwrap();
685 &b_bias.t() - self.mat.dot(point)
686 }
687}
688
689impl<D: Data<Elem = A>, A: Float + LinalgScalar + DivAssign + Sum> AffFuncBase<PolytopeT, D> {
690 pub fn distance<S: Data<Elem = A>>(&self, point: &ArrayBase<S, Ix1>) -> Array1<A> {
697 let mut raw_dist = self.distance_raw(point);
698 for (row, mut dist) in zip(self.mat.outer_iter(), raw_dist.outer_iter_mut()) {
699 let norm: A = row.iter().map(|&x| x.powi(2)).sum::<A>().sqrt();
700 dist.map_inplace(|x| *x /= norm);
701 }
702 raw_dist
703 }
704}
705
706impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<PolytopeT, D> {
707 #[inline]
709 pub fn contains<S: Data<Elem = A>>(&self, point: &ArrayBase<S, Ix1>) -> bool {
710 self.distance_raw(point)
711 .into_iter()
712 .all(|x| x >= A::from(-1e-8).unwrap())
713 }
714}
715
716impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<FunctionT, D> {
718 pub fn compose(
735 &self,
736 other: &AffFuncBase<FunctionT, D>,
737 ) -> AffFuncBase<FunctionT, OwnedRepr<A>> {
738 assert_eq!(
739 self.indim(),
740 other.outdim(),
741 "Invalid shared dimensions for composition: {} and {}",
742 self.indim(),
743 other.outdim()
744 );
745 AffFuncBase::<FunctionT, OwnedRepr<A>>::from_mats(
746 self.mat.dot(&other.mat),
747 self.apply(&other.bias),
748 )
749 }
750
751 pub fn stack(&self, other: &AffFuncBase<FunctionT, D>) -> AffFuncBase<FunctionT, OwnedRepr<A>> {
753 assert_eq!(
754 self.indim(),
755 other.indim(),
756 "Invalid input dimensions for stacking: {} and {}",
757 self.indim(),
758 other.indim()
759 );
760 AffFuncBase::<FunctionT, OwnedRepr<A>>::from_mats(
761 concatenate![Axis(0), self.mat, other.mat],
762 concatenate![Axis(0), self.bias, other.bias],
763 )
764 }
765}
766
767impl<D: Data<Elem = A> + DataOwned + RawDataClone + DataMut, A: Float + LinalgScalar + Neg>
768 AffFuncBase<FunctionT, D>
769{
770 pub fn negate(self) -> AffFuncBase<FunctionT, D> {
771 AffFuncBase::<FunctionT, D>::from_mats(-self.mat.clone(), -self.bias)
772 }
773}
774
775impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<PolytopeT, D> {
777 pub fn translate(&self, direction: &Array1<A>) -> PolytopeG<A> {
778 PolytopeG::<A>::from_mats(self.mat.to_owned(), &self.bias + self.mat.dot(direction))
779 }
780
781 pub fn intersection(&self, other: &AffFuncBase<PolytopeT, D>) -> PolytopeG<A> {
782 assert!(self.indim() == other.indim());
783
784 let mat = concatenate![Axis(0), self.mat, other.mat];
785 let bias = concatenate![Axis(0), self.bias, other.bias];
786
787 PolytopeG::<A>::from_mats(mat, bias)
788 }
789
790 #[rustfmt::skip]
793 pub fn intersection_n(dim: usize, polys: &[AffFuncBase<PolytopeT, D>]) -> PolytopeG<A> {
794 if polys.is_empty() {
795 return PolytopeG::<A>::unbounded(dim);
796 }
797
798 let mat_view: Vec<ArrayView2<A>> = polys.iter()
799 .map(|poly| poly.mat.view())
800 .collect();
801 let mat_concat = ndarray::concatenate(Axis(0), mat_view.as_slice());
802 let mat = match mat_concat {
803 Ok(result) => result,
804 Err(error) => panic!(
805 "Error when concatenating matrices, probably caused by a mismatch in dimensions: {:?}",
806 error
807 )
808 };
809
810 let bias_view: Vec<ArrayView1<A>> = polys
811 .iter()
812 .map(|poly| poly.bias.view())
813 .collect();
814 let bias_concat = ndarray::concatenate(Axis(0), bias_view.as_slice());
815 let bias = match bias_concat {
816 Ok(result) => result,
817 Err(error) => panic!(
818 "Error when concatenating bias, probably caused by a mismatch in dimensions: {:?}",
819 error
820 )
821 };
822
823 PolytopeG::<A>::from_mats(mat, bias)
824 }
825
826 pub fn apply_pre<D2: Data<Elem = A>>(&self, func: &AffFuncBase<FunctionT, D2>) -> PolytopeG<A>
828{
831 assert_eq!(
832 self.indim(),
833 func.outdim(),
834 "Invalid shared dimensions for composition: {} and {}",
835 self.indim(),
836 func.outdim()
837 );
838
839 PolytopeG::<A>::from_mats(
840 self.mat.dot(&func.mat),
841 -self.mat.dot(&func.bias) + &self.bias,
842 )
843 }
844
845 pub fn apply_post<D2, D3>(
848 &self,
849 inverse_mat: &ArrayBase<D2, Ix2>,
850 bias: &ArrayBase<D3, Ix1>,
851 ) -> PolytopeG<A>
852 where
853 D2: Data<Elem = A>,
854 D3: Data<Elem = A>,
855 {
856 assert_eq!(
857 self.indim(),
858 inverse_mat.shape()[0],
859 "Invalid shared dimensions for composition: {} and {}",
860 self.indim(),
861 inverse_mat.shape()[0]
862 );
863 assert_eq!(
864 inverse_mat.shape()[0],
865 bias.shape()[0],
866 "Invalid dimensions of bias: expected {} but got {}",
867 inverse_mat.shape()[0],
868 bias.shape()[0]
869 );
870
871 PolytopeG::<A>::from_mats(
872 self.mat.dot(inverse_mat),
873 self.mat.dot(&inverse_mat.dot(bias)) + &self.bias,
874 )
875 }
876
877 pub fn rotate<D2>(&self, orthogonal_mat: &ArrayBase<D2, Ix2>) -> PolytopeG<A>
881 where
882 D2: Data<Elem = A>,
883 {
884 self.apply_post(&orthogonal_mat.t(), &Array1::zeros(self.indim()))
885 }
886
887 }
931
932impl<I, S: Data<Elem = A>, A: Float> AffFuncBase<I, S> {
934 #[inline]
935 pub fn view<'a>(&'a self) -> AffFuncBase<I, ViewRepr<&'a A>> {
936 AffFuncBase::<I, ViewRepr<&'a A>> {
937 mat: self.mat.view(),
938 bias: self.bias.view(),
939 _phantom: PhantomData,
940 }
941 }
942}
943
944impl<I, S: Data<Elem = A>, A: Float> AffFuncBase<I, S> {
945 #[inline]
946 pub fn to_owned(&self) -> AffFuncBase<I, OwnedRepr<A>> {
947 AffFuncBase::<I, OwnedRepr<A>> {
948 mat: self.mat.to_owned(),
949 bias: self.bias.to_owned(),
950 _phantom: PhantomData,
951 }
952 }
953}
954
955impl<D: Data<Elem = A> + RawDataClone, A: Float> AffFuncBase<FunctionT, D> {
957 #[inline]
958 pub fn as_polytope(&self) -> AffFuncBase<PolytopeT, D> {
959 AffFuncBase::<PolytopeT, D> {
960 mat: self.mat.clone(),
961 bias: self.bias.clone(),
962 _phantom: PhantomData,
963 }
964 }
965}
966
967#[derive(Clone, Copy, Debug, PartialEq)]
968pub enum PolyRepr {
969 MatrixLeqBias,
970 MatrixBiasLeqZero,
971 MatrixGeqBias,
972 MatrixBiasGeqZero,
973}
974
975impl<D: Data<Elem = A> + RawDataClone, A: Float> AffFuncBase<PolytopeT, D> {
976 #[inline]
977 pub fn as_function(&self) -> AffFuncBase<FunctionT, D> {
978 AffFuncBase::<FunctionT, D> {
979 mat: self.mat.clone(),
980 bias: self.bias.clone(),
981 _phantom: PhantomData,
982 }
983 }
984
985 #[inline]
986 pub fn new(aff: AffFuncBase<FunctionT, D>) -> AffFuncBase<PolytopeT, D> {
987 AffFuncBase::<PolytopeT, D> {
988 mat: aff.mat,
989 bias: aff.bias,
990 _phantom: PhantomData,
991 }
992 }
993}
994
995impl<A: Float> AffFuncBase<PolytopeT, OwnedRepr<A>> {
996 #[inline]
997 pub fn convert_to(self, repr: PolyRepr) -> AffFuncBase<FunctionT, OwnedRepr<A>> {
998 match repr {
999 PolyRepr::MatrixLeqBias => AffFuncBase::<FunctionT, OwnedRepr<A>> {
1000 mat: self.mat,
1001 bias: self.bias,
1002 _phantom: PhantomData,
1003 },
1004 PolyRepr::MatrixBiasLeqZero => AffFuncBase::<FunctionT, OwnedRepr<A>> {
1005 mat: self.mat,
1006 bias: -self.bias,
1007 _phantom: PhantomData,
1008 },
1009 PolyRepr::MatrixGeqBias => AffFuncBase::<FunctionT, OwnedRepr<A>> {
1010 mat: -self.mat,
1011 bias: -self.bias,
1012 _phantom: PhantomData,
1013 },
1014 PolyRepr::MatrixBiasGeqZero => AffFuncBase::<FunctionT, OwnedRepr<A>> {
1015 mat: -self.mat,
1016 bias: self.bias,
1017 _phantom: PhantomData,
1018 },
1019 }
1020 }
1021}
1022
1023impl AffFunc {
1024 pub fn reset_row(&mut self, row: usize) {
1025 self.mat.row_mut(row).fill(0.);
1026 self.bias[row] = 0.;
1027 }
1028}
1029
1030impl<D: Data<Elem = A> + RawDataClone, A: Float> AffFuncBase<PolytopeT, D> {
1031 pub fn chebyshev_center(&self) -> (PolytopeG<A>, Array1<A>) {
1040 let mut norm = Array2::<A>::zeros((self.mat.len_of(Axis(0)), 1));
1042
1043 for (idx, row) in enumerate(self.mat.outer_iter()) {
1044 norm[[idx, 0]] = row.map(|x: &A| x.powi(2)).sum().sqrt();
1045 }
1046 let mat = concatenate![Axis(1), self.mat, norm];
1047
1048 let mut radius = Array2::<A>::zeros((1, mat.len_of(Axis(1))));
1050 radius[[0, mat.len_of(Axis(1)) - 1]] = -A::one();
1051 let mat = concatenate![Axis(0), mat, radius];
1052 let bias = concatenate![Axis(0), self.bias.clone(), Array1::<A>::zeros(1)];
1053
1054 (
1055 PolytopeG::<A>::from_mats(mat, bias),
1056 Array1::from_iter(radius.iter().cloned()),
1057 )
1058 }
1059}
1060
1061impl<I, D: Data<Elem = A>, A: Float> core::cmp::PartialEq for AffFuncBase<I, D> {
1062 fn eq(&self, other: &Self) -> bool {
1063 let (
1064 AffFuncBase {
1065 mat: mat_self,
1066 bias: bias_self,
1067 _phantom: _,
1068 },
1069 AffFuncBase {
1070 mat: mat_other,
1071 bias: bias_other,
1072 _phantom: _,
1073 },
1074 ) = (self, other);
1075 mat_self.eq(mat_other) && bias_self.eq(bias_other)
1076 }
1077}
1078
1079impl<I, S, A> AbsDiffEq for AffFuncBase<I, S>
1080where
1081 S: Data<Elem = A>,
1082 A: Float + AbsDiffEq,
1083 A::Epsilon: Clone,
1084{
1085 type Epsilon = A::Epsilon;
1086
1087 fn default_epsilon() -> A::Epsilon {
1088 A::default_epsilon()
1089 }
1090
1091 fn abs_diff_eq(&self, other: &Self, epsilon: A::Epsilon) -> bool {
1092 <ArrayBase<S, Ix2> as AbsDiffEq<_>>::abs_diff_eq(&self.mat, &other.mat, epsilon.clone())
1093 && <ArrayBase<S, Ix1> as AbsDiffEq<_>>::abs_diff_eq(&self.bias, &other.bias, epsilon)
1094 }
1095}
1096
1097impl<I, S, A> RelativeEq for AffFuncBase<I, S>
1098where
1099 S: Data<Elem = A>,
1100 A: Float + RelativeEq,
1101 A::Epsilon: Clone,
1102{
1103 fn default_max_relative() -> Self::Epsilon {
1104 A::default_max_relative()
1105 }
1106
1107 fn relative_eq(&self, other: &Self, epsilon: A::Epsilon, max_relative: A::Epsilon) -> bool {
1108 <ArrayBase<S, Ix2> as RelativeEq<_>>::relative_eq(
1109 &self.mat,
1110 &other.mat,
1111 epsilon.clone(),
1112 max_relative.clone(),
1113 ) && <ArrayBase<S, Ix1> as RelativeEq<_>>::relative_eq(
1114 &self.bias,
1115 &other.bias,
1116 epsilon,
1117 max_relative,
1118 )
1119 }
1120}
1121
1122#[macro_export]
1134macro_rules! aff {
1135 ([ $([$($x:expr),* $(,)*]),+ $(,)* ] + [ $($y:expr),* $(,)* ]) => {{
1136 $crate::linalg::affine::AffFunc::from_mats(
1137 ndarray::Array2::<f64>::from(vec![$( [ $( ($x as f64), )* ], )*]),
1138 ndarray::Array1::<f64>::from(vec![$($y as f64,)*])
1139 )
1140 }};
1141 ([ $($x:expr),* $(,)* ] + $y:expr) => {{
1142 $crate::linalg::affine::AffFunc::from_mats(
1143 ndarray::Array2::<f64>::from(vec![ [ $( ($x as f64), )* ]]),
1144 ndarray::Array1::<f64>::from(vec![$y as f64])
1145 )
1146 }};
1147}
1148
1149#[macro_export]
1159macro_rules! poly {
1160 ([ $([$($x:expr),* $(,)*]),+ $(,)* ] < [ $($y:expr),* $(,)* ]) => {{
1161 $crate::linalg::affine::Polytope::from_mats(
1162 ndarray::Array2::<f64>::from(vec![$( [ $( ($x as f64), )* ], )*]),
1163 ndarray::Array1::<f64>::from(vec![$($y as f64,)*])
1164 )
1165 }};
1166 ([ $([$($x:expr),* $(,)*]),+ $(,)* ] + [ $($y:expr),* $(,)* ] < 0) => {{
1167 $crate::linalg::affine::Polytope::from_mats(
1168 ndarray::Array2::<f64>::from(vec![$( [ $( ($x as f64), )* ], )*]),
1169 ndarray::Array1::<f64>::from(vec![$(-$y as f64,)*])
1170 )
1171 }};
1172 ([ $([$($x:expr),* $(,)*]),+ $(,)* ] > [ $($y:expr),* $(,)* ]) => {{
1173 $crate::linalg::affine::Polytope::from_mats(
1174 ndarray::Array2::<f64>::from(vec![$( [ $( (-$x as f64), )* ], )*]),
1175 ndarray::Array1::<f64>::from(vec![$(-$y as f64,)*])
1176 )
1177 }};
1178 ([ $([$($x:expr),* $(,)*]),+ $(,)* ] + [ $($y:expr),* $(,)* ] > 0) => {{
1179 $crate::linalg::affine::Polytope::from_mats(
1180 ndarray::Array2::<f64>::from(vec![$( [ $( (-$x as f64), )* ], )*]),
1181 ndarray::Array1::<f64>::from(vec![$($y as f64,)*])
1182 )
1183 }};
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use approx::assert_relative_eq;
1189 use itertools::Itertools;
1190 use ndarray::{Array2, Axis, arr1, arr2, array, s};
1191
1192 use super::*;
1193
1194 fn init_logger() {
1195 use env_logger::Target;
1196 use log::LevelFilter;
1197
1198 let _ = env_logger::builder()
1199 .is_test(true)
1200 .filter_module("minilp", LevelFilter::Error)
1201 .target(Target::Stdout)
1202 .filter_level(LevelFilter::Warn)
1203 .try_init();
1204 }
1205
1206 #[test]
1209 pub fn test_from_mats() {
1210 let mat = Array2::from_diag(&arr1(&[1., 7., -2., 1e-4, 0.3]));
1211 let bias = arr1(&[0.1, -7., 1e-2, 1e+4, 0.3]);
1212 let f = AffFunc::from_mats(mat.clone(), bias.clone());
1213
1214 assert_eq!(f.mat, mat);
1215 assert_eq!(f.bias, bias);
1216 }
1217
1218 #[test]
1219 #[should_panic]
1220 pub fn test_from_mats_nan() {
1221 let mat = Array2::from_diag(&arr1(&[1., 7., f64::NAN, 1e-4, 0.3]));
1222 let bias = arr1(&[0.1, -7., 1e-2, 1e+4, 0.3]);
1223 AffFunc::from_mats(mat, bias);
1224 }
1225
1226 #[test]
1227 pub fn test_from_row_iter() {
1228 let mat = array![[1., 2.], [-3., 0.5], [0.3, 1e+4]];
1229 let bias = arr1(&[0.1, -7., 1e-2]);
1230
1231 let f = AffFunc::from_row_iter(2, 3, mat.axis_iter(Axis(0)).zip(bias.iter()));
1232
1233 assert_eq!(mat, f.mat);
1234 assert_eq!(bias, f.bias);
1235 }
1236
1237 #[test]
1238 pub fn test_identity() {
1239 let f = AffFunc::identity(6);
1240
1241 assert_eq!(
1242 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1243 arr1(&[0.3, -0.2, 0., -20., 300., -4000.])
1244 );
1245 }
1246
1247 #[test]
1248 pub fn test_constant() {
1249 let f = AffFunc::constant(6, 10.);
1250
1251 assert_eq!(
1252 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1253 arr1(&[10.])
1254 );
1255 }
1256
1257 #[test]
1258 pub fn test_unit() {
1259 let f = AffFunc::unit(6, 1);
1260
1261 assert_eq!(
1262 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1263 arr1(&[-0.2])
1264 );
1265 }
1266
1267 #[test]
1268 pub fn test_zero() {
1269 let f = AffFunc::zero_idx(6, 1);
1270
1271 assert_eq!(
1272 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1273 arr1(&[0.3, 0., 0., -20., 300., -4000.])
1274 );
1275 }
1276
1277 #[test]
1278 pub fn test_sum() {
1279 let f = AffFunc::sum(6);
1280
1281 assert_eq!(
1282 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1283 arr1(&[-3719.9])
1284 );
1285 }
1286
1287 #[test]
1288 pub fn test_subtraction() {
1289 let f = AffFunc::subtraction(6, 1, 4);
1290
1291 assert_eq!(
1292 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1293 arr1(&[-300.2])
1294 );
1295 }
1296
1297 #[test]
1298 fn test_slice_afffunc() {
1299 let res = AffFunc::slice(&arr1(&[2., f64::NAN, -4.]));
1300
1301 assert_eq!(res.apply(&arr1(&[5., -3., 7.])), arr1(&[2., -3., -4.]));
1302
1303 assert_eq!(res.apply(&arr1(&[0., 9., -0.3])), arr1(&[2., 9., -4.]));
1304 }
1305
1306 #[test]
1307 pub fn test_dim() {
1308 let f = aff!([[1, 2, 5, 7], [-2, -9, 7, 8]] + [1, -1]);
1309
1310 assert_eq!(f.indim(), 4);
1311 assert_eq!(f.outdim(), 2);
1312 }
1313
1314 #[test]
1315 pub fn test_row() {
1316 let f = aff!([[1, 2, 5, 7], [-2, -9, 7, 8]] + [1, -1]);
1317
1318 assert_eq!(f.row(0).to_owned(), aff!([[1, 2, 5, 7]] + [1]));
1319 assert_eq!(f.row(1).to_owned(), aff!([[-2, -9, 7, 8]] + [-1]));
1320 }
1321
1322 #[test]
1323 pub fn test_row_iter() {
1324 let f = AffFunc::from_mats(Array2::eye(4), arr1(&[1., 2., 3., 4.]));
1325
1326 let fs = f.row_iter().map(|x| x.to_owned()).collect_vec();
1327
1328 assert_eq!(
1329 fs,
1330 vec![
1331 aff!([1., 0., 0., 0.] + 1.),
1332 aff!([0., 1., 0., 0.] + 2.),
1333 aff!([0., 0., 1., 0.] + 3.),
1334 aff!([0., 0., 0., 1.] + 4.)
1335 ]
1336 )
1337 }
1338
1339 #[test]
1354 pub fn test_remove_rows() {
1355 let f = aff!([[1, 0, 2], [0, 3, -1], [2, 0.5, 0]] + [-7, 5, 1]);
1356
1357 let g = f.remove_rows(vec![0, 2]);
1358
1359 assert_eq!(g, aff!([[0, 3, -1]] + [5]));
1360 }
1361
1362 #[test]
1363 pub fn test_remove_zero_rows() {
1364 let f = aff!([[1, 0, 2], [0, 0, 0], [0, 0, 0]] + [0, 0, 1]);
1365
1366 let g = f.remove_zero_rows();
1367
1368 assert_eq!(g, aff!([[1, 0, 2], [0, 0, 0]] + [0, 1]));
1369 }
1370
1371 #[test]
1372 pub fn test_normalize() {
1373 let f = aff!([[1, 0, -1], [0, 1, 0]] + [2, 5]);
1374 let sqrt2 = 2.0f64.sqrt();
1375
1376 assert_eq!(
1377 f.normalize(),
1378 aff!([[1.0 / sqrt2, 0, -1.0 / sqrt2], [0, 1, 0]] + [2. / sqrt2, 5])
1379 );
1380 }
1381
1382 #[rustfmt::skip]
1383 #[test]
1384 pub fn test_normalize_zero() {
1385 let f = aff!([[0, 0, 0], [0, 1, 0]] + [2, 5]);
1386
1387 assert_eq!(
1388 f.normalize(),
1389 aff!([[0., 0, 0.], [0, 1, 0]] + [2, 5])
1390 );
1391 }
1392
1393 #[test]
1394 pub fn test_compose() {
1395 let f1 = AffFunc::zero_idx(6, 5);
1396 let f2 = AffFunc::sum(6);
1397 let f = f2.compose(&f1);
1398
1399 assert_eq!(
1400 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1401 arr1(&[280.1])
1402 );
1403 }
1404
1405 #[test]
1406 pub fn test_stack() {
1407 let f1 = AffFunc::unit(6, 5);
1408 let f2 = AffFunc::unit(6, 1);
1409 let f = f1.stack(&f2);
1410
1411 assert_eq!(
1412 f.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
1413 arr1(&[-4000., -0.2])
1414 );
1415 }
1416
1417 #[test]
1418 pub fn test_aff_macro() {
1419 assert_eq!(
1420 aff!([1, 0, 1] + 2),
1421 AffFunc::from_mats(arr2(&[[1., 0., 1.]]), arr1(&[2.]))
1422 );
1423 assert_eq!(
1424 aff!([[1, 0, 1]] + [2]),
1425 AffFunc::from_mats(arr2(&[[1., 0., 1.]]), arr1(&[2.]))
1426 );
1427 assert_eq!(
1428 aff!([[-2, 0, 3], [4, -5, 9.3]] + [2, -1]),
1429 AffFunc::from_mats(arr2(&[[-2., 0., 3.], [4., -5., 9.3]]), arr1(&[2., -1.]))
1430 );
1431 }
1432
1433 #[test]
1434 pub fn test_affine() {
1435 let a = AffFunc::from_random(4, 3);
1436 let b = AffFunc::identity(4);
1437 let c = b.compose(&a);
1438 assert_eq!(c.indim(), 3);
1439 assert_eq!(c.outdim(), 4);
1440 }
1441
1442 #[test]
1445 pub fn test_unbounded() {
1446 let poly = Polytope::unbounded(4);
1447
1448 assert_eq!(poly.indim(), 4);
1449 assert!(poly.contains(&arr1(&[1.5, 0.0, -2.3, 1.0])));
1450 assert!(poly.contains(&arr1(&[1.0, 0.9, 0.2, 0.3])));
1451 assert!(poly.contains(&arr1(&[2.0, -1.0, -7.6, 2.6])));
1452 }
1453
1454 #[test]
1455 pub fn test_hyperrectangle() {
1456 let ival = [(1., 2.), (-1., 1.)];
1457
1458 let poly = Polytope::hyperrectangle(&ival);
1459
1460 assert_eq!(poly.indim(), 2);
1461 assert!(poly.contains(&arr1(&[1.5, 0.0])));
1462 assert!(poly.contains(&arr1(&[1.0, 0.9])));
1463 assert!(poly.contains(&arr1(&[2.0, -1.0])));
1464 assert!(!poly.contains(&arr1(&[2.1, -0.2])));
1465 assert!(!poly.contains(&arr1(&[3.5, 4.0])));
1466 assert!(!poly.contains(&arr1(&[1.1, -2.0])));
1467 assert!(!poly.contains(&arr1(&[1.8, 1.2])));
1468 }
1469
1470 #[test]
1471 pub fn test_simplex() {
1472 let poly = Polytope::simplex(2);
1473
1474 assert_eq!(poly.indim(), 2);
1475 assert!(poly.contains(&arr1(&[1.0, 0.0])));
1476 assert!(poly.contains(&arr1(&[0.1, 0.9])));
1477 assert!(poly.contains(&arr1(&[-0.3, -0.3])));
1478 assert!(!poly.contains(&arr1(&[1.1, -0.2])));
1479 assert!(!poly.contains(&arr1(&[-0.4, 0.1])));
1480 assert!(!poly.contains(&arr1(&[-0.2, -0.5])));
1481 }
1482
1483 #[test]
1484 pub fn test_cross_polytope() {
1485 let poly = Polytope::cross_polytope(2);
1486
1487 assert_eq!(poly.indim(), 2);
1488 assert!(poly.contains(&arr1(&[1.0, 0.0])));
1489 assert!(poly.contains(&arr1(&[0.1, 0.9])));
1490 assert!(poly.contains(&arr1(&[-0.3, -0.3])));
1491 assert!(!poly.contains(&arr1(&[-1.1, -0.2])));
1492 assert!(!poly.contains(&arr1(&[-0.5, 0.6])));
1493 assert!(!poly.contains(&arr1(&[-0.2, -1.5])));
1494 }
1495
1496 #[test]
1497 pub fn test_poly_normal_constructor() {
1498 init_logger();
1499
1500 let normals = arr2(&[
1501 [1.0, 0.0],
1502 [0.0, 1.0],
1503 [-1.0, 0.0],
1504 [0.0, -1.0],
1505 [1.0, 1.0],
1506 [-1.0, -1.0],
1507 ]);
1508
1509 let points = arr2(&[
1510 [0.0, 0.0],
1511 [0.0, 0.0],
1512 [4.0, 4.0],
1513 [4.0, 4.0],
1514 [1.0, 0.0],
1515 [3.0, 0.0],
1516 ]);
1517
1518 let a = Polytope::from_normal(normals.to_owned(), points);
1519
1520 let b = Polytope::from_mats(-normals, arr1(&[-0.0, -0.0, 4.0, 4.0, -1.0, 3.0]));
1521
1522 assert_eq!(a, b);
1523 }
1524
1525 #[test]
1526 pub fn test_remove_tautologies() {
1527 let poly = poly!([[1, 0, -1], [0, 0, 0], [0, 1, 0]] < [2, 1, 5]);
1528
1529 assert_eq!(
1530 poly.remove_tautologies(),
1531 poly!([[1, 0, -1], [0, 1, 0]] < [2, 5])
1532 );
1533 }
1534
1535 #[rustfmt::skip]
1536 #[test]
1537 pub fn test_remove_tautologies_zero() {
1538 let poly = poly!([[1, 0, -1, 0], [0, 0, 0, 0]] < [-7, 0]);
1539
1540 assert_eq!(
1541 poly.remove_tautologies(),
1542 poly!([[1, 0, -1, 0]] < [-7])
1543 );
1544 }
1545
1546 #[test]
1547 pub fn test_remove_tautologies_all_zero() {
1548 let poly = Polytope::from_mats(Array2::zeros((4, 10)), Array1::zeros(4));
1549
1550 assert_eq!(
1551 poly.remove_tautologies(),
1552 Polytope::from_mats(Array2::zeros((1, 10)), Array1::ones(1))
1553 );
1554 }
1555
1556 #[rustfmt::skip]
1557 #[test]
1558 pub fn test_remove_tautologies_infeasible() {
1559 let poly = poly!([[1, 0, -1], [0, 0, 0], [0, 1, 0]] < [2, -2, 5]);
1560
1561 assert_eq!(
1562 poly.remove_tautologies(),
1563 poly!([[0, 0, 0]] < [-1])
1564 );
1565 }
1566
1567 #[rustfmt::skip]
1568 #[test]
1569 pub fn test_remove_tautologies_only() {
1570 let poly = poly!([[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] < [2, 7, 6]);
1571
1572 assert_eq!(
1573 poly.remove_tautologies(),
1574 poly!([[0, 0, 0, 0, 0]] < [1])
1575 );
1576 }
1577
1578 #[test]
1579 pub fn test_remove_duplicate_rows() {
1580 let poly = poly!([[1, 0, -1], [0, 2, 0], [1, 0, -1]] < [2, -2, 2]);
1581
1582 assert_eq!(
1583 poly.remove_duplicate_rows(),
1584 poly!([[1, 0, -1], [0, 2, 0]] < [2, -2])
1585 );
1586 }
1587
1588 #[test]
1589 pub fn test_remove_duplicate_rows_scaled() {
1590 let poly = poly!([[1, 0, -1], [-8, 2, 0], [1.5, 0, -1.5], [-4, 1, 0]] < [2, 10, 3, 5]);
1591
1592 assert_eq!(
1593 poly.remove_duplicate_rows(),
1594 poly!([[1, 0, -1], [-8, 2, 0]] < [2, 10])
1595 );
1596 }
1597
1598 #[test]
1599 pub fn test_remove_duplicate_rows_different_bias() {
1600 let poly = poly!([[1, 0, -1], [0, 2, 0], [1, 0, -1]] < [2, -2, 3]);
1601
1602 assert_eq!(
1603 poly.remove_duplicate_rows(),
1604 poly!([[1, 0, -1], [0, 2, 0], [1, 0, -1]] < [2, -2, 3])
1605 );
1606 }
1607
1608 #[test]
1609 pub fn test_remove_duplicate_rows_close() {
1610 let poly = poly!([[1, 0, -1], [0, 2, 0], [1, 0, -0.999]] < [2, -2, 2]);
1611
1612 assert_eq!(
1613 poly.remove_duplicate_rows(),
1614 poly!([[1, 0, -1], [0, 2, 0], [1, 0, -0.999]] < [2, -2, 2])
1615 );
1616 }
1617
1618 #[test]
1619 pub fn test_distance() {
1620 let poly = poly!([[2., 0., 0.]] < [2.]);
1621
1622 assert_eq!(poly.distance(&arr1(&[1., 0., 0.])), arr1(&[0.]));
1623 assert_eq!(poly.distance(&arr1(&[-7., 0., 0.])), arr1(&[8.]));
1624 assert_eq!(poly.distance(&arr1(&[7., 0., 0.])), arr1(&[-6.]));
1625 }
1626
1627 #[test]
1628 pub fn test_distance_unbounded() {
1629 let poly = Polytope::unbounded(4);
1630
1631 assert_eq!(
1632 poly.distance(&arr1(&[0., 1., 0., 0.])),
1633 arr1(&[f64::INFINITY])
1634 );
1635 }
1636
1637 #[test]
1638 pub fn test_contains() {
1639 init_logger();
1640
1641 let weights = arr2(&[
1642 [-1.0, 0.0],
1643 [0.0, -1.0],
1644 [1.0, 0.0],
1645 [0.0, 1.0],
1646 [-1.0, -1.0],
1647 [1.0, 1.0],
1648 [-1.0, 1.0],
1649 [-2.0, -1.0],
1650 ]);
1651 let bias = arr1(&[-1.0, -1.0, 4.0, 4.0, -3.0, 6.0, -2.0, -8.0]);
1652 let poly = Polytope::from_mats(weights, bias);
1653
1654 assert!(poly.contains(&arr1(&[3.5, 1.0])));
1656 assert!(poly.contains(&arr1(&[4.0, 1.0])));
1657 assert!(poly.contains(&arr1(&[4.0, 2.0])));
1658 assert!(poly.contains(&arr1(&[3.34, 1.33])));
1659
1660 assert!(poly.contains(&arr1(&[3.75, 1.25])));
1662
1663 assert!(!poly.contains(&arr1(&[3.25, 1.15])));
1665 assert!(!poly.contains(&arr1(&[3.5, 2.0])));
1666 assert!(!poly.contains(&arr1(&[4.5, 1.5])));
1667 assert!(!poly.contains(&arr1(&[4.0, 0.0])));
1668 }
1669
1670 #[test]
1671 pub fn test_translate() {
1672 let poly = Polytope::hypercube(3, 0.5);
1673
1674 assert!(!poly.contains(&arr1(&[4.0, 7.0, 0.0])));
1675
1676 let poly = poly.translate(&arr1(&[4.0, 6.8, 0.4]));
1677
1678 assert!(poly.contains(&arr1(&[4.0, 7.0, 0.0])));
1679
1680 assert_relative_eq!(
1682 poly.distance(&arr1(&[4.0, 7.0, 0.0])),
1683 arr1(&[0.5, 0.3, 0.9, 0.5, 0.7, 0.1])
1684 );
1685 }
1686
1687 #[test]
1688 pub fn test_intersection() {
1689 let poly1 = Polytope::hypercube(2, 0.5).translate(&arr1(&[-0.2, -0.2]));
1690 let poly2 = Polytope::hypercube(2, 0.5).translate(&arr1(&[0.2, 0.2]));
1691
1692 let poly = poly1.intersection(&poly2);
1693
1694 assert!(poly.contains(&arr1(&[0.2, 0.2])));
1695 assert!(poly.contains(&arr1(&[0.2, -0.2])));
1696 assert!(poly.contains(&arr1(&[-0.2, 0.2])));
1697 assert!(poly.contains(&arr1(&[-0.2, -0.2])));
1698
1699 assert!(!poly.contains(&arr1(&[0.4, 0.4])));
1700 assert!(!poly.contains(&arr1(&[-0.4, -0.4])));
1701 }
1702
1703 #[test]
1704 fn test_rotate() {
1705 let poly = poly!([[0, -1], [-1, 0], [0.45, 0.89]] < [-1, -1, 3]);
1707
1708 let rot = array![[0.71, -0.71], [0.71, 0.71]];
1710
1711 assert!(poly.contains(&array![1.1, 1.1]));
1712 assert!(poly.contains(&array![1.1, 2.6]));
1713 assert!(poly.contains(&array![2.0, 2.0]));
1714 assert!(poly.contains(&array![4.4, 1.1]));
1715 assert!(poly.contains(&array![3.0, 1.4]));
1716
1717 let poly2 = poly.rotate(&rot);
1718
1719 assert!(poly2.contains(&array![0.0, 1.562]));
1721 assert!(poly2.contains(&array![-1.065, 2.627]));
1722 assert!(poly2.contains(&array![0.0, 2.84]));
1723 assert!(poly2.contains(&array![2.343, 3.905]));
1724 assert!(poly2.contains(&array![1.136, 3.124]));
1725
1726 assert!(!poly2.contains(&array![-1.7, 2.8]));
1728 assert!(!poly2.contains(&array![3.1, 4.3]));
1729 assert!(!poly2.contains(&array![0.2, 3.6]));
1730 assert!(!poly2.contains(&array![-1., 2.]));
1731 assert!(!poly2.contains(&array![1.1, 2.1]));
1732 }
1733
1734 #[test]
1767 pub fn test_chebyshev_box() {
1768 let poly = Polytope::from_mats(
1769 array![[1., 0.], [-1., 0.], [0., 1.], [0., -1.]],
1770 array![1., 1., 1., 1.],
1771 );
1772
1773 let (p, _) = poly.chebyshev_center();
1774
1775 assert!(p.contains(&arr1(&[0.0, 0.0, 0.9])));
1776 assert!(!p.contains(&arr1(&[0.0, 0.0, 1.1])));
1777 }
1778
1779 #[test]
1780 pub fn test_chebyshev_box_2() {
1781 let poly = Polytope::from_mats(
1782 array![[1., 0.], [-1., 0.], [0., 1.], [0., -1.]],
1783 array![2., 1., 1., 1.],
1784 );
1785
1786 let (p, _) = poly.chebyshev_center();
1787
1788 assert!(p.contains(&arr1(&[0.1, 0.0, 0.9])));
1789 assert!(p.contains(&arr1(&[0.9, 0.0, 0.9])));
1790 }
1791
1792 #[test]
1793 pub fn test_chebyshev_triangle() {
1794 let poly = Polytope::from_mats(array![[1., 1.], [-1., 1.], [0., -1.]], array![0., 0., 2.4]);
1795
1796 let (p, _) = poly.chebyshev_center();
1797
1798 assert!(p.contains(&arr1(&[0., -1.414, 0.9])));
1799 }
1800
1801 #[test]
1802 pub fn test_intersection_0() {
1803 init_logger();
1804
1805 let weights = arr2(&[
1806 [-1.0, 0.0],
1807 [0.0, -1.0],
1808 [1.0, 0.0],
1809 [0.0, 1.0],
1810 [-1.0, -1.0],
1811 [1.0, 1.0],
1812 [-1.0, 1.0],
1813 [-2.0, -1.0],
1814 ]);
1815 let bias = arr1(&[-1.0, -1.0, 4.0, 4.0, -3.0, 6.0, -2.0, -11.0]);
1816 let poly = Polytope::from_mats(weights.clone(), bias.clone());
1817
1818 let poly0 = Polytope::from_mats(
1819 weights.slice(s![..4, ..]).to_owned(),
1820 bias.slice(s![..4]).to_owned(),
1821 );
1822 let poly1 = Polytope::from_mats(
1823 weights.slice(s![4.., ..]).to_owned(),
1824 bias.slice(s![4..]).to_owned(),
1825 );
1826
1827 assert_eq!(poly, poly0.intersection(&poly1));
1828 }
1829
1830 #[test]
1831 pub fn test_intersection_1() {
1832 init_logger();
1833
1834 let weights = arr2(&[
1835 [-1.0, 0.0],
1836 [0.0, -1.0],
1837 [1.0, 0.0],
1838 [0.0, 1.0],
1839 [-1.0, -1.0],
1840 [1.0, 1.0],
1841 [-1.0, 1.0],
1842 [-2.0, -1.0],
1843 ]);
1844 let bias = arr1(&[-1.0, -1.0, 4.0, 4.0, -3.0, 6.0, -2.0, -11.0]);
1845 let poly = Polytope::from_mats(weights.clone(), bias.clone());
1846
1847 let poly0 = Polytope::from_mats(
1848 weights.slice(s![..4, ..]).to_owned(),
1849 bias.slice(s![..4]).to_owned(),
1850 );
1851 let poly1 = Polytope::from_mats(
1852 weights.slice(s![5.., ..]).to_owned(),
1853 bias.slice(s![5..]).to_owned(),
1854 );
1855
1856 assert_ne!(poly, poly0.intersection(&poly1));
1857 }
1858
1859 #[test]
1860 pub fn test_intersection_n_0() {
1861 init_logger();
1862
1863 let weights = arr2(&[
1864 [-1.0, 0.0],
1865 [0.0, -1.0],
1866 [1.0, 0.0],
1867 [0.0, 1.0],
1868 [-1.0, -1.0],
1869 [1.0, 1.0],
1870 [-1.0, 1.0],
1871 [-2.0, -1.0],
1872 ]);
1873 let bias = arr1(&[-1.0, -1.0, 4.0, 4.0, -3.0, 6.0, -2.0, -11.0]);
1874 let poly = Polytope::from_mats(weights.clone(), bias.clone());
1875
1876 let poly0 = Polytope::from_mats(
1877 weights.slice(s![..2, ..]).to_owned(),
1878 bias.slice(s![..2]).to_owned(),
1879 );
1880 let poly1 = Polytope::from_mats(
1881 weights.slice(s![2..4, ..]).to_owned(),
1882 bias.slice(s![2..4]).to_owned(),
1883 );
1884 let poly2 = Polytope::from_mats(
1885 weights.slice(s![4..6, ..]).to_owned(),
1886 bias.slice(s![4..6]).to_owned(),
1887 );
1888 let poly3 = Polytope::from_mats(
1889 weights.slice(s![6.., ..]).to_owned(),
1890 bias.slice(s![6..]).to_owned(),
1891 );
1892
1893 assert_eq!(
1894 poly,
1895 Polytope::intersection_n(
1896 0,
1897 &[
1898 poly0.to_owned(),
1899 poly1.to_owned(),
1900 poly2.to_owned(),
1901 poly3.to_owned()
1902 ]
1903 )
1904 );
1905
1906 assert_ne!(
1907 poly,
1908 Polytope::intersection_n(
1909 0,
1910 &[
1911 poly0.to_owned(),
1912 poly2.to_owned(),
1913 poly1.to_owned(),
1914 poly3.to_owned()
1915 ]
1916 )
1917 );
1918 assert_ne!(poly, Polytope::intersection_n(0, &[poly0, poly1, poly2]));
1919 }
1920
1921 #[test]
1922 pub fn test_poly_macro() {
1923 assert_eq!(
1924 poly!([[1, 0, 1]] < [2]),
1925 Polytope::from_mats(arr2(&[[1., 0., 1.]]), arr1(&[2.]))
1926 );
1927 assert_eq!(
1928 poly!([[-2, 0, 3], [4, -5, 9.3]] < [2, -1]),
1929 Polytope::from_mats(arr2(&[[-2., 0., 3.], [4., -5., 9.3]]), arr1(&[2., -1.]))
1930 );
1931 }
1932}