1use anyhow::{anyhow, Result};
2use good_lp::{
3 constraint, default_solver, variables, Expression, ResolutionError, Solution, SolverModel,
4};
5use itertools::Itertools;
6use log::{debug, info};
7use ndarray::{concatenate, prelude::*};
8use ndarray_rand::rand_distr::Uniform;
9use ndarray_rand::RandomExt;
10use ndarray_stats::QuantileExt;
11use serde::{Deserialize, Serialize};
12use std::cmp::Ordering;
13use std::collections::{BTreeMap, BTreeSet, HashMap};
14use std::io::Write;
15use std::rc::Rc;
16use std::usize;
17
18type Vector = [f64];
19type Matrix = Vec<Rc<Vector>>;
20
21const EMPTY_MATRIX: Matrix = Vec::new();
22
23const EPSILON: f64 = 1e-6;
26
27enum EssentialSolution {
28 Essential(Vec<f64>), Redundant,
30}
31
32enum InteriorPointSolution {
33 Feasible(Array1<f64>), Infeasible,
35}
36
37fn l2_norm(to_norm: &[f64]) -> f64 {
39 to_norm
40 .iter()
41 .map(|v| v.powi(2))
42 .fold(0., |l, r| l + r)
43 .sqrt()
44}
45
46fn inner_product_lp(coefficients: &Vector, variables: &[good_lp::Variable]) -> Expression {
47 return coefficients
48 .iter()
49 .zip_eq(variables)
50 .map(|item| *item.0 * *item.1)
51 .sum();
52}
53
54fn inner_product_lp_ndarray(
58 coefficients: &ArrayView1<f64>,
59 variables: &[good_lp::Variable],
60) -> Expression {
61 return coefficients
62 .iter()
63 .zip_eq(variables)
64 .map(|item| *item.0 * *item.1)
65 .sum();
66}
67
68fn lp_separation(
70 to_test: &Vector,
71 vertices: &Matrix,
72 linearity: &Matrix,
73) -> Result<EssentialSolution, ResolutionError> {
74 let mut vars = variables!();
75 let dim = to_test.len();
76 let mut x: Vec<good_lp::Variable> = Vec::with_capacity(dim);
77 for _i in 0..dim {
78 x.push(vars.add_variable());
79 }
80 let d = vars.add_variable();
81
82 let objective = d + inner_product_lp(&to_test, &x);
83 let mut problem = vars
84 .maximise(&objective)
85 .using(default_solver)
86 .with(constraint!(d + inner_product_lp(&to_test, &x) <= 1.));
87
88 for vertex in vertices {
89 problem = problem.with(constraint!(d + inner_product_lp(&vertex, &x) <= 0.));
90 }
91 for generator in linearity {
92 problem = problem.with(constraint!(d + inner_product_lp(&generator, &x) == 0.));
93 }
94
95 let solution = problem.solve()?;
96 let maxima = solution.eval(&objective);
97 let sep_solution = if maxima > EPSILON {
98 let mut cert: Vec<f64> = Vec::with_capacity(x.len());
99 cert.push(solution.value(d));
100 cert.extend(x.iter().map(|variable| solution.value(*variable)));
101 EssentialSolution::Essential(cert)
102 } else {
103 EssentialSolution::Redundant
104 };
105 return Ok(sep_solution);
106}
107
108fn lp_interior_point(
110 matrix_a: &Array2<f64>,
111 vector_b: &Array1<f64>,
112) -> Result<InteriorPointSolution> {
113 debug_assert_eq!(matrix_a.shape()[0], vector_b.shape()[0]);
114 let mut vars = variables!();
115 let num_rows = matrix_a.shape()[0];
116 let ones: Array2<f64> = Array2::ones((num_rows, 1));
117 let constraints = ndarray::concatenate(Axis(1), &[ones.view(), matrix_a.view()])?;
118 let dim = constraints.shape()[1];
119 let mut x: Vec<good_lp::Variable> = Vec::with_capacity(dim);
120 for _i in 0..dim {
121 x.push(vars.add_variable());
122 }
123
124 let objective = x.first().ok_or(anyhow!("No variables!"))?;
125 let mut problem = vars.maximise(objective).using(default_solver);
126 for (row, b_val) in constraints.axis_iter(Axis(0)).zip_eq(vector_b) {
127 let constraint = inner_product_lp_ndarray(&row, &x);
128 problem = problem.with(constraint!(constraint <= (*b_val - EPSILON)));
129 }
130
131 let b_ceil = 1f64.max(*vector_b.max()?) * 2.;
134 problem = problem.with(constraint!(*objective <= b_ceil));
135
136 let solution = problem.solve()?;
137 if solution.value(*objective) < EPSILON {
138 return Ok(InteriorPointSolution::Infeasible);
139 }
140 let maximiser = x[1..]
141 .iter()
142 .map(|v| solution.value(*v))
143 .collect::<Vec<_>>();
144 return Ok(InteriorPointSolution::Feasible(arr1(&maximiser)));
145}
146
147fn lp_normal_cone(edges: &Array2<f64>) -> Result<Array1<f64>> {
149 let vector_b: Array1<f64> = Array1::ones(edges.shape()[0]);
151 let solution = lp_interior_point(&(-1.0 * edges), &vector_b)?;
152 let res = match solution {
153 InteriorPointSolution::Feasible(cert) => Ok(cert),
154 InteriorPointSolution::Infeasible =>Result::Err(anyhow!("We should always get a feasible solution because the adjacency oracle will return feasible cones"))
155 };
156 return res;
157}
158
159fn lp_adjacency(edges: &Array2<f64>, to_test: usize) -> Result<InteriorPointSolution> {
161 let dim = edges.shape()[1];
162 let test_vec = edges
163 .select(Axis(0), &[to_test])
164 .into_shape(edges.shape()[1])?;
165 let objective_vec = &test_vec * 1.;
166
167 let parallel = test_vec
169 .dot(&edges.t())
170 .map(|d| (d.abs() - 1.).abs() < EPSILON);
171 let negated = -1. * edges;
172 let edges_view = negated.view();
173 let mut filtered = edges_view
174 .axis_iter(Axis(0))
175 .zip(parallel)
176 .filter_map(|(e, is_parallel)| {
177 if is_parallel {
178 None
179 } else {
180 Some(e.into_shape((1, dim)))
181 }
182 })
183 .collect::<core::result::Result<Vec<_>, _>>()?;
184 filtered.push(objective_vec.view().into_shape((1, dim))?);
185 let filtered_array = concatenate(Axis(0), &filtered)?;
186 let vector_b = Array1::zeros(filtered.len());
187 let solution = lp_interior_point(&filtered_array, &vector_b)?;
188 let result = match solution {
189 InteriorPointSolution::Feasible(cert) => {
190 let score = cert.dot(&test_vec);
191 if score < EPSILON {
192 InteriorPointSolution::Feasible(cert)
193 } else {
194 InteriorPointSolution::Infeasible
195 }
196 }
197 InteriorPointSolution::Infeasible => InteriorPointSolution::Infeasible,
198 };
199 return Ok(result);
200}
201
202fn make_other_matrix(vertices: &Matrix, to_remove: usize) -> Matrix {
203 let mut other: Matrix = Vec::with_capacity(vertices.len() - 1);
204 other.extend_from_slice(&vertices[0..to_remove]);
205 other.extend_from_slice(&vertices[to_remove + 1..vertices.len()]);
206 return other;
207}
208
209fn adjacency(
210 essential_vertices: &Matrix,
211 indices: &BTreeSet<usize>,
212) -> Result<HashMap<usize, Vec<usize>>> {
213 let index_map = Vec::from_iter(indices); let mut res: HashMap<usize, Vec<usize>> = HashMap::with_capacity(index_map.len());
215
216 for (index, _vertex) in essential_vertices.iter().enumerate() {
217 let linearity = Vec::from([Rc::clone(&essential_vertices[index])]);
218 eprintln!("Finding adjacent");
219 let adjacent = find_essential(essential_vertices, &linearity)?;
220 res.insert(
221 *index_map[index],
222 Vec::from_iter(adjacent.0.iter().map(|adj| *index_map[*adj])),
223 );
224 }
225 eprintln!("{:?}", res);
226 return Ok(res);
227}
228
229fn find_essential(
230 vertices: &Matrix,
231 linearity: &Matrix,
232) -> Result<(BTreeSet<usize>, HashMap<usize, Vec<f64>>)> {
233 let mut essential: BTreeSet<usize> = BTreeSet::new();
234 let mut certs: HashMap<usize, Vec<f64>> = HashMap::new();
235 for (index, vertex) in (vertices).iter().enumerate() {
236 let other = make_other_matrix(&vertices, index);
237 let lp_solution = lp_separation(vertex, &other, linearity)?;
238 let is_essential = match lp_solution {
239 EssentialSolution::Essential(cert) => {
240 certs.insert(index, cert);
241 essential.insert(index)
242 }
243 EssentialSolution::Redundant => false,
244 };
245 if is_essential {
246 eprint!("+");
247 } else {
248 eprint!("-");
249 }
250 std::io::stdout().flush()?;
251 }
253 eprintln!();
254 eprintln!("{:?}", essential);
255 return Ok((essential, certs));
256}
257
258#[derive(Serialize, Deserialize, Debug)]
259pub struct FullPolytope {
260 pub vertices: Matrix,
261 essential_indices: Option<BTreeSet<usize>>,
262 adjacency: Option<HashMap<usize, Vec<usize>>>,
263 essential_certs: Option<HashMap<usize, Vec<f64>>>,
264}
265
266struct Polytope {
268 vertices: Array2<f64>, flattened_adjacency: Vec<(usize, usize)>,
270 edges: Array2<f64>,
271 adjacency: Vec<Vec<usize>>,
272}
273
274impl Polytope {
275 fn neighbouring_edges<'a>(
276 self: &'a Polytope,
277 vertex: usize,
278 ) -> Box<dyn Iterator<Item = (&(usize, usize), ArrayView1<f64>)> + 'a> {
279 let iterator = self
280 .flattened_adjacency
281 .iter()
282 .zip_eq(self.edges.axis_iter(Axis(0)))
283 .filter(move |tup| tup.0 .0 == vertex);
284 return Box::new(iterator);
285 }
286
287 fn maximised_vertex(self: &Polytope, param: &Array1<f64>) -> Result<usize> {
288 return Ok(param.dot(&self.vertices).argmax()?);
289 }
290
291 fn _scores(self: &Polytope, param: &Array1<f64>) -> Vec<(usize, f64)> {
292 let scores = param.dot(&self.vertices);
293 return scores
294 .iter()
295 .enumerate()
296 .sorted_by(|(_ai, a_score), (_bi, b_score)| {
297 if *a_score - *b_score < 0. {
298 Ordering::Less
299 } else {
300 Ordering::Greater
301 }
302 })
303 .map(|(i, s)| (i, *s))
304 .collect::<Vec<_>>();
305 }
306}
307
308#[derive(Clone)]
309struct ReverseSearchState {
310 param: Array1<f64>,
311 previous_polytope_index: usize,
312 previous_vertex: usize,
313 neighbour_index: usize,
314 polytope_index: usize,
315 vertex: usize,
319 polys: Rc<Vec<Polytope>>,
320}
321
322#[derive(Serialize, Deserialize)]
323pub struct ReverseSearchOut {
324 pub param: Array1<f64>,
325 pub minkowski_decomp: Vec<usize>,
326}
327
328impl ReverseSearchState {
329 fn polytope(&self) -> &Polytope {
330 return &self.polys[self.polytope_index];
331 }
332
333 fn neighbours(&self) -> &Vec<usize> {
334 return &self.polytope().adjacency[self.vertex];
335 }
336
337 fn complete(&self) -> bool {
338 return self.polytope_index >= self.polys.len();
339 }
340
341 fn test_vertex(&self) -> usize {
342 self.neighbours()[self.neighbour_index]
343 }
344
345 fn incr_state(&mut self, minkowski_decomp: &Vec<usize>) -> () {
346 self.neighbour_index += 1;
347 if self.neighbour_index >= self.neighbours().len() {
348 self.polytope_index += 1;
349 self.neighbour_index = 0;
350 if !self.complete() {
351 self.vertex = minkowski_decomp[self.polytope_index];
352 }
353 }
354 }
355
356 fn make_child(&self, vertex: usize, param: Array1<f64>) -> ReverseSearchState {
357 ReverseSearchState {
358 previous_polytope_index: self.polytope_index,
359 previous_vertex: self.vertex,
360 param,
361 polys: self.polys.clone(),
362 neighbour_index: 0,
363 polytope_index: 0,
364 vertex,
365 }
366 }
367
368 fn make_minkowski_decomp(&self) -> Result<Vec<usize>> {
369 let decomp = self
370 .polys
371 .iter()
372 .map(|p| self.param.dot(&p.vertices).argmax())
373 .collect::<std::result::Result<Vec<_>, _>>()?;
374 debug!("Minkowski decomposition {:?}", &decomp);
375 return Ok(decomp);
376 }
377}
378
379fn make_state(param: Array1<f64>, polys: Rc<Vec<Polytope>>) -> Result<ReverseSearchState> {
380 let vertex = polys[0].maximised_vertex(¶m)?;
381 let state = ReverseSearchState {
382 param,
383 polytope_index: 0,
384 vertex,
385 neighbour_index: 0,
386 previous_polytope_index: 0,
387 previous_vertex: vertex,
388 polys: polys.clone(),
389 };
390 Ok(state)
391}
392
393#[derive(Debug)]
394struct AdjacencyTuple {
395 polytope_index: usize,
396 _vertex: usize,
397 neighbour: usize,
398}
399
400#[derive(Debug)]
401struct DecompositionIndex {
402 polytope_index: usize,
403 vertex: usize,
404}
405
406impl DecompositionIndex {
407 fn new(polytope_index: usize, vertex: usize) -> Self {
408 Self {
409 polytope_index,
410 vertex,
411 }
412 }
413}
414
415impl FullPolytope {
416 pub fn new(vertices: Matrix) -> FullPolytope {
417 FullPolytope {
418 vertices,
419 essential_indices: None,
420 essential_certs: None,
421 adjacency: None,
422 }
423 }
424
425 fn fill_essential(&mut self) -> Result<()> {
426 let essential = find_essential(&self.vertices, &EMPTY_MATRIX)?;
427 self.essential_indices = Option::Some(essential.0);
428 self.essential_certs = Option::Some(essential.1);
429 return Ok(());
430 }
431
432 fn essential_vertices(&self) -> Option<Matrix> {
433 return self.essential_indices.as_ref().map(|indices| {
434 Vec::from_iter(
435 indices
436 .iter()
437 .map(|index| Rc::clone(&(self.vertices[*index]))),
438 )
439 });
440 }
441
442 fn fill_adjacency(&mut self) -> Result<()> {
443 return self
444 .essential_indices
445 .as_ref()
446 .and_then(|essential| {
447 self.essential_vertices()
448 .map(|vertices| (vertices, essential))
449 })
450 .map_or(Ok(()), |(vertices, indices)| {
451 let adjacent = adjacency(&vertices, indices)?;
452 self.adjacency = Option::Some(adjacent);
453 Ok(())
454 });
455 }
456
457 fn essential_polytope(&self) -> Result<Option<Polytope>> {
458 let essential = self.essential_indices.as_ref().and_then(|indices| {
459 self.adjacency.as_ref().map(|adjacency| {
460 let mut mapping: HashMap<usize, usize> = HashMap::new();
461 for (essential, raw) in indices.iter().enumerate() {
462 mapping.insert(*raw, essential);
463 }
464 let dim = self.vertices.first().map_or(0, |f| f.len());
465 let mut essential_vertices = Array2::<f64>::zeros((dim, indices.len()));
466 for i in indices {
467 for j in 0..dim {
468 essential_vertices[[j, mapping[i]]] = self.vertices[*i][j];
469 }
470 }
471 let mut essential_adjacency: BTreeMap<usize, BTreeSet<usize>> = BTreeMap::new();
472 for (vertex, neighbours) in adjacency {
473 essential_adjacency.insert(
474 mapping[&vertex],
475 neighbours.iter().map(|i| mapping[i]).collect(),
476 );
477 }
478 (essential_vertices, essential_adjacency)
479 })
480 });
481 let poly: Option<Result<Polytope>> = essential.map(|(vertices, adjacency)| {
482 let mut edges: Vec<Array1<f64>> = Vec::new();
483 let mut flattened_adjacency: Vec<(usize, usize)> = Vec::new();
484 debug!("Vertices shape {:?}", vertices.shape());
485 for (current_vertex, neighbours) in &adjacency {
486 for neighbour in neighbours {
487 let edge = &vertices.index_axis(Axis(1), *current_vertex)
488 - &vertices.index_axis(Axis(1), *neighbour);
489 edges.push(edge);
490 flattened_adjacency.push((*current_vertex, *neighbour));
491 }
492 }
493 let expanded_dims = edges
494 .iter()
495 .map(|e| {
496 let dim = e.shape()[0];
497 return e.view().into_shape((1, dim));
498 })
499 .collect::<Result<Vec<_>, _>>()?;
500 let mut edge_array: Array2<f64> = ndarray::concatenate(Axis(0), &expanded_dims)?;
501 let vec_adjacency = adjacency
502 .iter()
503 .map(|(_vertex, neighbours)| neighbours.iter().map(|n| *n).collect::<Vec<_>>())
504 .collect::<Vec<_>>();
505 for mut row in edge_array.rows_mut() {
506 let row_slice = row
507 .as_slice()
508 .ok_or(anyhow!("Incorrect memory layout in edges"))?;
509 let norm = l2_norm(row_slice);
510 row.mapv_inplace(|v| v / norm);
511 }
512 let polytope = Polytope {
513 adjacency: vec_adjacency,
514 vertices: vertices,
515 flattened_adjacency,
516 edges: edge_array,
517 };
518
519 fn test_polytope(poly: &Polytope) -> bool {
520 let mut correct = true;
521 let mut flat_index: usize = 0;
522 for (vertex, neighbours) in poly.adjacency.iter().enumerate() {
523 for neighbour in neighbours {
524 let flat = &poly.flattened_adjacency[flat_index];
525 correct = correct && flat.0 == vertex && flat.1 == *neighbour;
526 flat_index += 1;
527 }
528 }
529 correct
530 }
531 debug_assert!(test_polytope(&polytope));
532 return Ok(polytope);
533 });
534 return poly.transpose();
535 }
536
537 fn map_essential_index(self: &FullPolytope, index: usize) -> Option<usize> {
539 return self.essential_indices.as_ref().and_then(|m| {
540 m.iter()
541 .enumerate()
542 .filter(|(essential, _r)| *essential == index)
543 .map(|(_e, raw)| *raw)
544 .take(1)
545 .collect::<Vec<_>>()
546 .first()
547 .map(|i| *i)
548 });
549 }
550}
551
552fn mink_sum_setup<'a>(poly_list: &mut [FullPolytope]) -> Result<ReverseSearchState> {
553 for poly in poly_list.as_mut() {
554 poly.fill_essential()?;
555 poly.fill_adjacency()?;
556 }
557
558 let essential_polys = poly_list
559 .iter()
560 .map(|p| p.essential_polytope())
561 .collect::<Result<Vec<_>, _>>()?
562 .into_iter()
563 .map(|p| p.ok_or(anyhow!("No item")))
564 .collect::<Result<Vec<_>, _>>()?;
565
566 let polys_ref = Rc::new(essential_polys);
567
568 let dim = polys_ref
569 .first()
570 .map(|p| p.vertices.shape()[0])
571 .ok_or(anyhow!("No polytopes!"))?;
572
573 let initial_param = Array1::<f64>::ones(dim);
574 let mut state: ReverseSearchState = make_state(initial_param, polys_ref)?;
575
576 let mut ties = true;
581 while ties {
582 ties = false;
583 for (i, polytope) in state.polys.iter().enumerate() {
584 debug!("Testing polytope {} for ties with initial param", i);
585 let scores = state.param.dot(&polytope.vertices);
586 let mut prev_best = *scores.first().unwrap_or(&0.0);
587 let mut best_count = 1;
588 for score in scores.to_vec()[1..].iter() {
589 if *score > prev_best {
590 prev_best = *score;
591 best_count = 1;
592 } else if *score == prev_best {
593 best_count += 1;
594 }
595 }
596 ties = best_count > 1;
597
598 if ties {
599 info!(
600 "Found tie: {} {}, perturbing starting parameter",
601 prev_best, best_count
602 );
603 debug! {"Scores\n {:?}", &scores};
604 debug!("Vertices\n {:?}", &polytope.vertices.t());
605 let perturb = Array::random(dim, Uniform::new(-1.0 * EPSILON, EPSILON));
606 state.param = state.param + perturb;
607 break;
608 }
609 }
610 }
611 info!("Setup done");
612 return Ok(state);
613}
614
615fn parent(
623 edges: &Array2<f64>,
624 interior_point: &Array1<f64>,
625 initial_point: &Array1<f64>,
626) -> Result<usize> {
627 debug_assert!(interior_point.len() > 0);
628 debug_assert_eq!(interior_point.len(), initial_point.len());
629 let direction = interior_point - initial_point;
630 let edge_t = edges.t();
631 let inner_product_point = interior_point.dot(&edge_t);
632 let inner_product_direction = direction.dot(&edge_t);
633 let alpha = &inner_product_direction / &inner_product_point;
634 let parent = alpha.argmax()?;
635 let min = alpha[parent];
636 debug_assert_eq!(
637 alpha.iter().filter(|a| (*a - min).abs() < EPSILON).count(),
638 1,
639 "Found ties when searching for a parent"
640 );
641 return Ok(parent);
642}
643
644fn decomp_iter<'a>(
645 decomp: impl Iterator<Item = &'a usize> + 'a,
646 decomp_index: &'a DecompositionIndex,
647) -> impl Iterator<Item = &'a usize> + 'a {
648 decomp.enumerate().map(|(index, vertex)| {
649 if decomp_index.polytope_index == index {
650 &decomp_index.vertex
651 } else {
652 vertex
653 }
654 })
655}
656
657pub fn reverse_search<'a>(
658 poly_list: &mut [FullPolytope],
659 mut writer: Box<dyn FnMut(ReverseSearchOut) -> Result<()> + 'a>,
660) -> Result<()> {
661 let initial_state = mink_sum_setup(poly_list)?;
662 let polys = initial_state.polys.clone();
663 let num_edges: usize = polys.iter().map(|p| p.edges.len()).sum();
664 info!("Total edges: {}", num_edges);
665 let mut edges: Vec<ArrayView2<f64>> = Vec::with_capacity(num_edges);
667 let mut edge_indices: Vec<AdjacencyTuple> = Vec::with_capacity(num_edges);
668
669 let initial_decomp = initial_state.make_minkowski_decomp()?;
671 let mut minkowski_decomp = initial_decomp.clone();
672
673 let mut stack: Vec<ReverseSearchState> = Vec::new();
674 stack.push(initial_state.clone());
675
676 while !stack.is_empty() {
677 let mut state = stack
678 .pop()
679 .ok_or(anyhow!("Empty stack! This should never happen"))?;
680 if state.complete() {
681 debug!(
682 "Popping, restoring element {} to {}",
683 state.previous_polytope_index, state.previous_vertex
684 );
685 minkowski_decomp[state.previous_polytope_index] = state.previous_vertex;
686 continue;
687 }
688 debug!(
689 "Starting state: polytope {} vertex {} neighbour counter {}",
690 state.polytope_index, state.vertex, state.neighbour_index
691 );
692 edges.clear();
693
694 let test_vertex = state.test_vertex();
695 let mut test_edge: Option<(usize, ArrayView1<f64>)> = None;
696 let mut edge_counter: usize = 0;
697 for (inner_polytope_index, (poly, inner_vertex)) in
698 polys.iter().zip(&minkowski_decomp).enumerate()
699 {
700 for ((_vertex, neighbouring_vertex), edge) in poly.neighbouring_edges(*inner_vertex) {
701 if inner_polytope_index == state.polytope_index
702 && test_vertex == *neighbouring_vertex
703 {
704 debug_assert_eq!(test_edge, None);
705 test_edge = Some((edge_counter, edge));
706 }
707 edges.push(edge.into_shape((1, edge.shape()[0]))?);
708 edge_counter += 1;
709 }
710 }
711 let (test_edge_index, _test_edge) = test_edge.ok_or(anyhow!("No edge found!"))?;
712
713 let edge_array: Array2<f64> = ndarray::concatenate(Axis(0), &edges)?;
714
715 let is_adjacent = match lp_adjacency(&edge_array, test_edge_index)? {
716 InteriorPointSolution::Feasible(_cert) => true,
717 InteriorPointSolution::Infeasible => false,
718 };
719 if !is_adjacent {
720 debug!("Skipping because edge is not adjacent");
721 state.incr_state(&minkowski_decomp);
722 stack.push(state);
723 continue;
724 }
725
726 let test_decomp_index = DecompositionIndex::new(state.polytope_index, test_vertex);
727 let test_iter = || decomp_iter(minkowski_decomp.iter(), &test_decomp_index);
728
729 if test_iter().eq(initial_decomp.iter()) {
730 debug!("Reached the initial state. Loop complete");
731 state.incr_state(&minkowski_decomp);
732 stack.push(state);
733 continue;
734 }
735
736 edges.clear();
738 edge_indices.clear();
739 for (inner_polytope_index, (poly, inner_vertex)) in
740 polys.iter().zip(test_iter()).enumerate()
741 {
742 for (adj, edge) in poly.neighbouring_edges(*inner_vertex) {
743 edges.push(edge.into_shape((1, edge.shape()[0]))?);
744 edge_indices.push(AdjacencyTuple {
745 polytope_index: inner_polytope_index,
746 _vertex: adj.0,
747 neighbour: adj.1,
748 });
749 }
750 }
751 debug_assert_eq!(edges.len(), edge_indices.len());
752 let test_edges_array: Array2<f64> = ndarray::concatenate(Axis(0), &edges)?;
753
754 let maximiser = lp_normal_cone(&test_edges_array)?;
755 let norm = l2_norm(maximiser.as_slice().ok_or(anyhow!("Incorrect memory"))?);
756 let maximiser_norm = &maximiser / norm;
757
758 debug!("Maximiser: {:?}", maximiser_norm);
759 fn test_edges(edges: &Array2<f64>, maximiser: &Array1<f64>) -> Result<bool> {
760 let scores = maximiser.dot(&edges.t()).map(|score| *score > EPSILON);
761 let test_op = scores.iter().map(|t| *t).reduce(|acc, test| acc && test);
762 let test = test_op.ok_or(anyhow!("No scores! - edge array must be empty"))?;
763 if !test {
764 for (edge_id, score) in maximiser.dot(&edges.t()).iter().enumerate() {
765 if *score < EPSILON {
766 debug!("Score {} for edge {} is below epsilon", *score, edge_id);
767 }
768 }
769 }
770 return Ok(test);
771 }
772
773 fn test_maximiser(
774 polys: &[Polytope],
775 decomp: impl Iterator<Item = usize>,
776 maximiser: &Array1<f64>,
777 ) -> Result<bool> {
778 let maximised = polys
779 .iter()
780 .map(|p| p.maximised_vertex(maximiser))
781 .collect::<Result<Vec<_>>>()?;
782 let equal_decomp = maximised
783 .iter()
784 .zip_eq(decomp)
785 .map(|(left, right)| *left == right)
786 .reduce(|accm, test| accm && test)
787 .ok_or(anyhow!("Empty decomp!"))?;
788 return Ok(equal_decomp);
803 }
804
805 debug_assert!(
806 test_edges(&test_edges_array, &maximiser)?,
808 "Because we have tested for adjacency, we should always get a maximiser"
809 );
810
811 debug_assert!(
812 test_maximiser(&polys, test_iter().map(|v| *v), &maximiser_norm)?,
813 "Maximiser does not retrieve the decomposition"
814 );
815
816 let parent_edge = parent(&test_edges_array, &maximiser_norm, &initial_state.param)?;
817 let parent_edge_index = &edge_indices[parent_edge];
818 let parent_decomp_index = DecompositionIndex::new(
819 parent_edge_index.polytope_index,
820 parent_edge_index.neighbour,
821 );
822
823 debug!(
824 "Parent decomp: {:?}",
825 decomp_iter(test_iter(), &parent_decomp_index).collect::<Vec<_>>()
826 );
827 debug!(" Test decomp: {:?}", test_iter().collect::<Vec<_>>());
828 debug!(" State decomp: {:?}", &minkowski_decomp);
829 if test_decomp_index.polytope_index == parent_decomp_index.polytope_index
830 && minkowski_decomp[parent_decomp_index.polytope_index] == parent_decomp_index.vertex
831 {
832 debug!("Reverse traverse");
834 let child_vertex = test_iter()
835 .next()
836 .ok_or(anyhow!("Test decomposition is empty"))?;
837 let child_state = state.make_child(*child_vertex, maximiser_norm);
838 state.incr_state(&minkowski_decomp);
839 minkowski_decomp[test_decomp_index.polytope_index] = test_decomp_index.vertex;
840 stack.push(state);
841
842 let output_decomp = minkowski_decomp.clone();
844 let mut output = ReverseSearchOut {
845 param: child_state.param.clone(),
846 minkowski_decomp: output_decomp,
847 };
848 for (vertex, full_poly) in output.minkowski_decomp.iter_mut().zip(poly_list.as_ref()) {
849 *vertex = full_poly
850 .map_essential_index(*vertex)
851 .ok_or(anyhow!("Essential vertex not found!"))?;
852 }
853
854 writer(output)?;
855
856 stack.push(child_state);
857 } else {
858 state.incr_state(&minkowski_decomp);
859 stack.push(state);
860 }
861 }
862 return Ok(());
863}