algorithm/
algorithm.rs

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
23// Used for the LP separation problem. Due to the formalisation of the LP the number will always
24// be in the range [0,1]. Having a fixed epsilon is probably ok.
25const EPSILON: f64 = 1e-6;
26
27enum EssentialSolution {
28    Essential(Vec<f64>), // Contains the certificate of the solution
29    Redundant,
30}
31
32enum InteriorPointSolution {
33    Feasible(Array1<f64>), // Contains the certificate of the solution
34    Infeasible,
35}
36
37// Cannot use linalg on wasm because of BLAS. Build our own l2_norm function.
38fn 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
54// Frustratingly, I can't seem to find a way to make this generic with inner_product_lp
55// The issue is that into_iter for an native array needs a mutable reference :(
56// There must be a way...
57fn 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
68// Used to detecting extreme points in individual polytopes
69fn 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
108// Shamelessly copied from CDD for LP feasibility problems
109fn 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    // To be honest, I'm not completely sure why this is needed. I think
132    // it's to keep the solution bounded but using a 2 is a bit arbitrary.
133    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
147// Finds a maximiser for a normal cone
148fn lp_normal_cone(edges: &Array2<f64>) -> Result<Array1<f64>> {
149    // Copied from CDD, I think the 1 is to ensure that the point is interior
150    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
159// Identifies whether an edge identifies a boundary to an adjacent cone
160fn 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    // The edges have been normalised, making it easy to test for parallelism
168    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); // Maps the essential index back to the original indices
214    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        //eprintln!("{:#?}", essential);
252    }
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
266// A polytope with essential data only
267struct Polytope {
268    vertices: Array2<f64>, // Transposed - dim x vertices
269    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 is a function of polytope index and the minkowski decomp.
316    // We cache it in the state to make keeping track of diffs easier.
317    // It must be kept in sync with the minkowski decomp
318    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(&param)?;
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    // Map the essential indices back to full polytope raw indices before exporting
538    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    // Checking to see if any vertices in a polytope have the same score
577    // If they do the reverse search will fail. We break times by perturbing the parameter
578    // vector
579    // Probably a degenerate condition that will never happen.
580    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
615// Find the parent of a cone. We shoot a ray towards the initial
616// cone, and the first hyperplane intersected by the ray is the
617// boundary to the parent cone.
618// Following CDD convention, the interior point should have a 1 as its
619// first element, and the direction a 0. However, because all the edges
620// have no offset we can ignore the first element.
621//
622fn 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    // Allocate these arrays in advance
666    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    //Use a single decomposition vector. These could get quite large
670    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        //rebuild edges for new cone
737        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            // Commenting out at the moment because the decomp iterator can't be called twice.
789            // Will figure out later. Probably safe to delete this test
790            /* if !equal_decomp {
791                for ((decomp_val, maximised_val), poly) in
792                    decomp.iter().zip_eq(maximised).zip_eq(polys)
793                {
794                    if *decomp_val != maximised_val {
795                        let scores = poly.scores(maximiser);
796                        for (vertex, score) in scores {
797                            debug!("{}, {}", vertex, score);
798                        }
799                    }
800                }
801            }*/
802            return Ok(equal_decomp);
803        }
804
805        debug_assert!(
806            // Some distance from edges are very small. The normalisation pushes them below epsilon. Use the raw maximiser instead
807            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            // Reverse traverse
833            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            // TODO: Fix the writing code to take references to decompositions, allowing for deletion of this clone
843            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}