affinitree/linalg/
affine.rs

1//   Copyright 2025 affinitree developers
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15//! Structs to store linear functions and polytopes
16
17use 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
47// types going forward
48type 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
53// for compatibility
54pub 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
78/// # General constructor
79impl<I, D: Data<Elem = A>, A: Float> AffFuncBase<I, D> {
80    /// Create a new instance of an affine combination consisting of a matrix mat: R^{m x n} and a vector bias: R^m.
81    ///
82    /// When interpreted as a function, it is equivalent to f(x) = mat @ x + bias.
83    /// When interpreted as a polytope, it encodes the set P = {x | mat @ x <= bias}
84    #[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
166/// # AffFunc specific constructors
167impl<A: Float> AffFuncG<A> {
168    /// Creates an affine function that implements the identity function f(x)=x.
169    #[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    /// Creates an affine function that implements the zero function f(x)=0.
179    #[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    /// Creates an affine function that always returns the specified value.
189    #[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    /// Creates an affine function that returns the element in the given index of its input.
199    #[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    /// Creates an affine function that sets the element at the given index to zero
212    /// and leaves all other elements unchanged.
213    #[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    /// Creates an affine function R^dim -> R that returns the sum
226    /// over all its inputs.
227    #[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    /// Creates an affine function that subtracts the right index from the left index.
237    #[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    /// Creates an affine function that rotates the space as specified
252    /// by the given orthogonal matrix ``rotator``.
253    #[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    /// Creates an affine function that scales vectors uniformly along
266    /// all axis.
267    #[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    /// Creates an affine function that scales vectors.
273    #[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    /// Creates an affine function that slices inputs along specified axes.
282    /// For each axis were ``reference_point`` is NaN, the corresponding axis will be kept.
283    /// For each other axis, the axis is fixed with the value specified in ``reference_point``.
284    #[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    /// Creates an affine function that translates vectors by the given ``offset``.
293    #[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
303/// # Polytope specific constructors
304impl<A: Float> PolytopeG<A> {
305    /// Creates a polytope that contains the complete `dim`-dimensional ambient space.
306    #[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    /// Creates a `dim`-dimensional polytope that contains no point of the ambient space.
312    #[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    /// Creates a polytope from a set of halfspaces described by ``normal_vectors`` and ``points`` in the plane (hesse normal form).
318    #[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    /// Creates a ``dim``-dimensional hypercube centered at the origin.
325    /// In each dimension two hyperplanes are placed with distance +/- ``radius`` from the origin.
326    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    /// Creates a ``dim``-dimensional hyperrectangle centered at the origin.
333    /// The distances from the origin to the faces of the rectangle are given by ``intervals`` in order of the axes.
334    /// For interpretation of axis bounds see also [``Self::axis_bounds``].
335    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    /// Creates a ``dim``-dimensional regular simplex with edge length sqrt(2) containing
346    /// the origin.
347    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    /// Creates a ``dim``-dimensional cross polytope centered at the origin.
361    /// It generalizes the octahedron in that all its vertices are of the form
362    /// +/- e_i where e_i is the i-th unit vector.
363    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    /// Creates a polytope that restricts the value of the specified `axis` to be bounded by
381    /// the values of `lower_bound` and `upper_bound` (inclusive).
382    /// A value of `neg_inf` (resp. `inf`) results in an unbounded lower (resp. upper) bound.
383    /// The value of `lower_bound` must be less than or equal to `upper_bound`.
384    #[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
423/// # General methods
424impl<I, D: Data<Elem = A>, A: Float> AffFuncBase<I, D> {
425    /// Returns the dimension of the input space.
426    #[inline(always)]
427    pub fn indim(&self) -> usize {
428        self.mat.shape()[1]
429    }
430
431    /// Returns the dimension of the image space.
432    #[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    /// Returns the affine function that acts on component ``row``, which is
448    /// the ``row``-th row of this function.
449    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    /// Returns an iterator over the rows of this AffFuncBase instance.
463    /// Elements are returned as views.
464    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    // /// Iterate over the columns of this AffFuncBase instance.
480    // /// Elements are returned as views.
481    // pub fn column_iter(&self) -> impl Iterator<Item = AffFuncBase<I, ViewRepr>> + '_ {
482    //     self.mat.axis_iter(Axis(1))
483    //         .map(|column| {
484    //             AffFuncBase::<I, ViewRepr>::from_mats(
485    //                 column.insert_axis(Axis(1)),
486    //                 ArrayView1::zeros(1),
487    //             )
488    //         })
489    // }
490
491    /// Removes the rows given by ``indices``.
492    /// The iterator must return the rows to remove in ascending order.
493    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    /// Returns the number of inequalities (constraints) of this polytope.
556    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    /// Normalizes every row of this affine function / polytope with respect to Euclidean norm (l2).
563    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    /// Removes row constraints which are always satisfied on their own.
577    /// When a row constraint is encountered that is always false the
578    /// whole polytope is replaced by [``PolytopeG::empty()``].
579    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                        // superfluous bound
588                        None
589                    } else {
590                        // infeasible
591                        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    /// Removes all duplicate rows of this polytope from back to front.
616    /// Time complexity is O(n m^2) where n is the number of columns and m the
617    /// number of rows.
618    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
647/// # Evaluation on inputs
648impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<FunctionT, D> {
649    /// Evaluates this function under the given input.
650    /// Mathematically, this corresponds to calculating mat @ input + bias
651    pub fn apply<S: Data<Elem = A>>(&self, input: &ArrayBase<S, Ix1>) -> Array1<A> {
652        self.mat.dot(input) + &self.bias
653    }
654
655    /// Evaluates the transposed of this function under the given input.
656    /// For orthogonal functions this corresponds to the inverse.
657    /// Mathematically, this corresponds to calculating mat.T @ (input - bias)
658    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
663/// # Distances
664impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<PolytopeT, D> {
665    /// Calculates the distance from a point to the hyperplanes defined by the
666    /// rows of this polytope.
667    ///
668    /// # Warning
669    ///
670    /// Distance is not normalized.
671    #[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    /// Calculates the distances from multiple points to the hyperplanes defined by the
677    /// rows of this polytope.
678    ///
679    /// # Warning
680    ///
681    /// Distance is not normalized.
682    #[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    /// Calculates the (normalized) distance from a point to the hyperplanes defined by the
691    /// rows of this polytope.
692    ///
693    /// Precisely, it returns a vector where each element is the distance from the given point to the hyperplane in order.
694    /// Distance is positive if the point is inside the halfspace of that inequality and negative otherwise.
695    /// Returns f64::INFINITY if the corresponding halfspace includes all points.
696    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    /// Tests whether the input ``point`` lies inside this polytope or not.
708    #[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
716/// # Combination of two AffFunc instances
717impl<D: Data<Elem = A>, A: Float + LinalgScalar> AffFuncBase<FunctionT, D> {
718    /// Composes self with other. The resulting function will have the same effect as first applying other and then self.
719    ///
720    /// # Example
721    ///
722    /// ``` rust
723    /// use affinitree::linalg::affine::AffFunc;
724    /// use ndarray::arr1;
725    ///
726    /// let f1 = AffFunc::sum(6);
727    /// let f2 = AffFunc::zero_idx(6, 5);
728    ///
729    /// assert_eq!(
730    ///     f1.compose(&f2).apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])),
731    ///     f1.apply(&f2.apply(&arr1(&[0.3, -0.2, 0., -20., 300., -4000.])))
732    /// );
733    /// ```
734    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    /// Stacks this function on top of ``other`` vertically.
752    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
775/// # Combination of Polytopes
776impl<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    /// Constructs a new polytope representing the intersection of `polys`.
791    /// That is, the resulting polytope contains all points that are contained in each polytope of `polys`.
792    #[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    /// Applies the given function to the input space of this polytope.
827    pub fn apply_pre<D2: Data<Elem = A>>(&self, func: &AffFuncBase<FunctionT, D2>) -> PolytopeG<A>
828// where
829    //     D2: Ownership
830    {
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    /// Applies the function f(x) = inverse_mat^-1 @ x + bias to the output space of this polytope.
846    /// This method does not compute the inverse function. Instead, the inverse must be provided.
847    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    /// Rotate this polytope (i.e., the points contained in it) by the given rotation matrix.
878    /// The matrix must represent a rotation, i.e., it must be orthogonal.
879    /// The center of rotation is the origin.
880    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    // pub fn slice<D2>(&self, reference_vec: &ArrayBase<D2, Ix1>, reduce_dim: bool, add_constraints: bool) -> AffFuncBase<PolytopeT, OwnedRepr<A>>
888    // where
889    //     D2: Data<Elem = A>
890    // {
891    //     let diag = reference_vec.map(|x| {
892    //         if *x == A::zero() {
893    //             A::from(1).unwrap()
894    //         } else {
895    //             A::zero()
896    //         }
897    //     });
898
899    //     let mut poly = self.apply_pre(&AffFuncG::<A>::from_mats(Array2::from_diag(&diag), reference_vec.to_owned()));
900
901    //     // remove any columns that are zero
902    //     if reduce_dim {
903    //         use itertools::Itertools;
904
905    //         let nonzero_columns = reference_vec.iter()
906    //             .zip(poly.mat.axis_iter(Axis(1)))
907    //             .filter(|(&x, _)| x == A::zero())
908    //             .map(|(_, column)| column.insert_axis(Axis(1)))
909    //             .collect_vec();
910
911    //         poly.mat = concatenate(Axis(1), nonzero_columns.as_slice()).unwrap();
912    //     }
913
914    //     if add_constraints {
915    //         use itertools::Itertools;
916
917    //         let mut constraints = reference_vec.iter()
918    //             .enumerate()
919    //             .filter(|(_, &x)| x != A::zero())
920    //             .map(|(idx, val)| Polytope::axis_bounds(self.indim(), idx, val - 0.0001, val + 0.0001))
921    //             .collect_vec();
922
923    //         constraints.push(poly);
924
925    //         poly = PolytopeG::<A>::intersection_n(self.indim(), constraints.as_slice());
926    //     }
927
928    //     poly
929    // }
930}
931
932// Switch between ownership
933impl<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
955// Switch between types
956impl<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    /// Returns the linear program that encodes the chebyshev center of this polytope.
1032    /// That is, the resulting polytope contains all points that are
1033    ///
1034    /// 1. contained in this polytope
1035    /// 2. whose last dimension is less than or equal to the minimal distance to the hyperplanes of this polytope
1036    ///
1037    /// The returned array encodes the coefficients of the cost function.
1038    /// Minimizing this function over the region of the returned polytope gives the center point and radius of the largest enclosed sphere inside this polytope.
1039    pub fn chebyshev_center(&self) -> (PolytopeG<A>, Array1<A>) {
1040        // distance to each hyperplane
1041        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        // radius must be positive
1049        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/// Creates a new ``AffFunc`` from the given matrix and bias.
1123///
1124/// See also ndarray's ``array`` macro
1125///
1126/// # Examples
1127///
1128/// ```rust
1129/// use affinitree::aff;
1130///
1131/// let func = aff!([[1, 2, 5, 7], [-2, -9, 7, 8]] + [1, -1]);
1132/// ```
1133#[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/// Creates a new ``Polytope`` from the given matrix and bias.
1150///
1151/// # Examples
1152///
1153/// ```rust
1154/// use affinitree::poly;
1155///
1156/// let poly = poly!([[1, 0], [0, 1]] < [2, 3]);
1157/// ```
1158#[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    /* AffFunc Tests */
1207
1208    #[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]
1340    // pub fn test_column_iter() {
1341    //     let f = AffFunc::from_mats(Array2::eye(4), arr1(&[1., 2., 3., 4.]));
1342
1343    //     let fs = f.column_iter().map(|x| x.to_owned()).collect_vec();
1344
1345    //     assert_eq!(fs, vec![
1346    //         aff!([1., 0., 0., 0.] + 0.),
1347    //         aff!([0., 1., 0., 0.] + 0.),
1348    //         aff!([0., 0., 1., 0.] + 0.),
1349    //         aff!([0., 0., 0., 1.] + 0.)
1350    //     ])
1351    // }
1352
1353    #[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    /* Polytope Tests */
1443
1444    #[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        // extreme points
1655        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        // inner point
1661        assert!(poly.contains(&arr1(&[3.75, 1.25])));
1662
1663        // outer points
1664        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        // depends on the implementation of hypercube
1681        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        // define triangle with vertices (1, 1), (1, 2.87), (4.71, 1)
1706        let poly = poly!([[0, -1], [-1, 0], [0.45, 0.89]] < [-1, -1, 3]);
1707
1708        // rotate by 45° counterclockwise
1709        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        // rotated points
1720        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        // points just outside the triangle
1727        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]
1735    // fn test_slice() {
1736    //     // define triangle with vertices (1, 1), (1, 2.87), (4.71, 1)
1737    //     let poly = poly!([[0, -1], [-1, 0], [0.45, 0.89]] < [-1, -1, 3]);
1738
1739    //     let poly2 = poly.slice(&array![0., 1.5], false, false);
1740
1741    //     // points just inside
1742    //     assert!(poly2.contains(&array![1.1, 0.]));
1743    //     assert!(poly2.contains(&array![3.6, 0.]));
1744
1745    //     // points just outside
1746    //     assert!(!poly2.contains(&array![0.9, 0.]));
1747    //     assert!(!poly2.contains(&array![3.8, 0.]));
1748    // }
1749
1750    // #[test]
1751    // fn test_slice_reduce() {
1752    //     // define triangle with vertices (1, 1), (1, 2.87), (4.71, 1)
1753    //     let poly = poly!([[0, -1], [-1, 0], [0.45, 0.89]] < [-1, -1, 3]);
1754
1755    //     let poly2 = poly.slice(&array![0., 1.5], true, false);
1756
1757    //     // points just inside
1758    //     assert!(poly2.contains(&array![1.1]));
1759    //     assert!(poly2.contains(&array![3.6]));
1760
1761    //     // points just outside
1762    //     assert!(!poly2.contains(&array![0.9]));
1763    //     assert!(!poly2.contains(&array![3.8]));
1764    // }
1765
1766    #[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}