geosets_rs/sets/
hpolytope.rs1#![allow(unused)]
2use super::*;
3use crate::VPolytope;
4use crate::cddlib_rs::compute_polytope_vertices;
5use crate::linalg_utils::{rank, vector_leq};
6use good_lp::{Expression, Solution, SolverModel, default_solver, variable, variables};
7use ndarray_linalg::Norm;
8use ndarray_rand::RandomExt;
9use ndarray_rand::rand_distr::{Normal, StandardNormal, Uniform};
10use plotly::box_plot;
11use thiserror::Error;
12
13#[derive(Clone, Debug)]
14#[allow(non_snake_case)]
15pub struct HPolytope {
16 A: Array2<f64>,
17 b: Array1<f64>,
18}
19
20#[derive(Error, Debug)]
21pub enum HPolytopeError {
22 #[error("Dimensions of A {a_dim:?} and b {b_dim:?} do not match")]
23 DimensionMismatch { a_dim: (usize, usize), b_dim: usize },
24}
25
26#[allow(non_snake_case)]
27impl HPolytope {
28 pub fn new(A: Array2<f64>, b: Array1<f64>) -> Result<HPolytope, HPolytopeError> {
29 if A.dim().0 != b.dim() {
30 Err(HPolytopeError::DimensionMismatch {
31 a_dim: A.dim(),
32 b_dim: b.dim(),
33 })
34 } else {
35 Ok(HPolytope { A, b })
36 }
37 }
38
39 pub fn from_random(dim: usize, n_constraints: usize) -> Result<HPolytope, HPolytopeError> {
40 let box_poly = HPolytope::from_unit_box(dim);
41
42 let mut random_A = Array2::random((n_constraints, dim), StandardNormal);
43 for mut row in random_A.rows_mut() {
45 let norm = row.norm_l2();
46 if norm > 0.0 {
47 row /= norm;
48 }
49 }
50
51 let interior_point = Array1::random(dim, Uniform::new(-0.8, 0.8));
52 let offsets = Array1::random(n_constraints, Uniform::new(0.1, 1.0));
53
54 let random_b = random_A.dot(&interior_point) + offsets;
55
56 let A = ndarray::concatenate(Axis(0), &[box_poly.A.view(), random_A.view()]).unwrap();
57 let b = ndarray::concatenate(Axis(0), &[box_poly.b.view(), random_b.view()]).unwrap();
58
59 Ok(HPolytope { A, b })
60 }
61
62 pub fn n_constraints(&self) -> usize {
63 self.A.nrows()
64 }
65}
66
67#[allow(non_snake_case)]
68impl GeoSet for HPolytope {
69 fn from_unit_box(dim: usize) -> Self {
70 let A = ndarray::concatenate(
71 Axis(0),
72 &[Array2::eye(dim).view(), (-Array2::eye(dim)).view()],
73 )
74 .unwrap();
75 let b = Array1::ones(dim * 2);
76 HPolytope::new(A, b).unwrap()
77 }
78
79 fn dim(&self) -> usize {
80 self.A.dim().1
81 }
82
83 fn empty(&self) -> Result<bool, SetOperationError> {
87 let mut vars = variables!();
89 let x: Vec<_> = (0..self.dim()).map(|_| vars.add(variable())).collect();
90
91 let mut problem = vars.minimise(0.0).using(default_solver);
93
94 for i in 0..self.n_constraints() {
96 let row = self.A.row(i);
97 let expr: Expression = row.iter().zip(&x).map(|(coef, xi)| *coef * *xi).sum();
98 problem = problem.with(expr.leq(self.b[i]));
99 }
100
101 match problem.solve() {
103 Ok(_) => Ok(false), Err(_) => Ok(true), }
106 }
107
108 fn to_vertices(&self) -> Result<Array2<f64>, SetOperationError> {
109 let empty = self.empty()?;
110 if empty {
111 return Err(SetOperationError::EmptySet);
112 }
113 compute_polytope_vertices(&self.A, &self.b)
114 }
115
116 fn center(&self) -> Result<Array1<f64>, SetOperationError> {
120 let mut vars = variables!();
121 let r = vars.add(variable().min(0.0));
122 let x: Vec<_> = (0..self.dim()).map(|_| vars.add(variable())).collect();
123
124 let mut problem = vars.maximise(r).using(default_solver);
126
127 for (i, row) in self.A.outer_iter().enumerate() {
129 let norm_ai = row.dot(&row).sqrt();
130 let lhs: Expression = row.iter().zip(&x).map(|(&aij, &xj)| aij * xj).sum();
131 problem = problem.with((lhs + norm_ai * r).leq(self.b[i]));
132 }
133
134 let solution = problem
135 .solve()
136 .map_err(|e| SetOperationError::InfeasibleOptimization {
137 source: Box::new(e),
138 })?;
139
140 let center =
141 Array1::from_shape_vec(self.dim(), x.iter().map(|&xi| solution.value(xi)).collect())
142 .map_err(|e| SetOperationError::DataConversionError {
143 source: Box::new(e),
144 })?;
145
146 Ok(center)
147 }
148
149 fn support_function(
153 &self,
154 direction: Array1<f64>,
155 ) -> Result<(Array1<f64>, f64), SetOperationError> {
156 self._check_operand_dim(direction.dim())?;
157
158 let mut vars = variables!();
160 let x: Vec<_> = (0..self.dim()).map(|_| vars.add(variable())).collect();
161
162 let objective: Expression = direction.iter().zip(&x).map(|(d_i, x_i)| *d_i * *x_i).sum();
164 let mut problem = vars.maximise(objective.clone()).using(default_solver);
165
166 for i in 0..self.n_constraints() {
168 let row = self.A.row(i);
169 let expr: Expression = row.iter().zip(&x).map(|(coef, xi)| *coef * *xi).sum();
170 problem = problem.with(expr.leq(self.b[i]));
171 }
172
173 let solution = problem
174 .solve()
175 .map_err(|e| SetOperationError::InfeasibleOptimization {
176 source: Box::new(e),
177 })?;
178
179 let support_vector =
180 Array1::from_shape_vec(self.dim(), x.iter().map(|&xi| solution.value(xi)).collect())
181 .map_err(|e| SetOperationError::DataConversionError {
182 source: Box::new(e),
183 })?;
184
185 let support_value = solution.eval(&objective);
186
187 Ok((support_vector, support_value))
188 }
189
190 fn volume(&self) -> Result<f64, SetOperationError> {
191 let vpoly = VPolytope::new(self.to_vertices()?).map_err(|_| SetOperationError::EmptySet)?;
192 vpoly.volume()
193 }
194
195 fn minkowski_sum_(&mut self, other: &Self) -> Result<(), SetOperationError> {
196 let dim = self.dim();
198
199 let directions = ndarray::concatenate![Axis(0), self.A.view(), other.A.view()];
201
202 let n_dirs = directions.nrows();
204 let mut new_A = Array2::<f64>::zeros((n_dirs, dim));
205 let mut new_b = Array1::<f64>::zeros(n_dirs);
206
207 for (i, dir) in directions.outer_iter().enumerate() {
208 let norm = dir.norm_l2();
210 if norm < 1e-9 {
211 continue;
212 }
213 let u = &dir / norm;
214
215 let (_, h1) = self.support_function(u.clone())?;
217 let (_, h2) = other.support_function(u.clone())?;
218
219 new_A.row_mut(i).assign(&u);
221 new_b[i] = h1 + h2;
222 }
223
224 self.A = new_A;
226 self.b = new_b;
227
228 Ok(())
229 }
230
231 fn matmul_(&mut self, mat: &Array2<f64>) -> Result<(), SetOperationError> {
232 let (m, n) = mat.dim();
233 self._check_operand_dim(m)?;
234 let mat_rank = rank(mat).unwrap();
235
236 if m == n && mat_rank == n {
237 self.A = self.A.dot(mat);
238 } else if m > n {
239 return Err(SetOperationError::NotImplemented);
240 }
241 Ok(())
242 }
243
244 fn translate_(&mut self, vector: &Array1<f64>) -> Result<(), SetOperationError> {
245 self._check_operand_dim(vector.dim())?;
246 self.b = &self.b + &self.A.dot(vector);
247 Ok(())
248 }
249
250 fn degenerate(&self) -> bool {
251 let c = match self.center() {
252 Ok(center) => center,
253 Err(SetOperationError::InfeasibleOptimization { .. }) => {
254 return true;
256 }
257 Err(_) => {
258 return true;
260 }
261 };
262
263 let residual = &self.b - &self.A.dot(&c);
264
265 residual.iter().any(|&x| (x - 0.0).abs() <= 1e-9)
267 }
268
269 fn contains_point(&self, point: &Array1<f64>) -> Result<bool, SetOperationError> {
270 self._check_operand_dim(point.dim())?;
271 Ok(vector_leq(&self.A.dot(point), &self.b))
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_polytope_new() {
281 let _ = HPolytope::new(Array::ones((2, 2)), Array::ones(2)).unwrap();
282 let _ = HPolytope::new(Array::ones((5, 2)), Array::ones(5)).unwrap();
283 }
284}