smawk/
lib.rs

1//! This crate implements various functions that help speed up dynamic
2//! programming, most importantly the SMAWK algorithm for finding row
3//! or column minima in a totally monotone matrix with *m* rows and
4//! *n* columns in time O(*m* + *n*). This is much better than the
5//! brute force solution which would take O(*mn*). When *m* and *n*
6//! are of the same order, this turns a quadratic function into a
7//! linear function.
8//!
9//! # Examples
10//!
11//! Computing the column minima of an *m* ✕ *n* Monge matrix can be
12//! done efficiently with `smawk::column_minima`:
13//!
14//! ```
15//! use smawk::Matrix;
16//!
17//! let matrix = vec![
18//!     vec![3, 2, 4, 5, 6],
19//!     vec![2, 1, 3, 3, 4],
20//!     vec![2, 1, 3, 3, 4],
21//!     vec![3, 2, 4, 3, 4],
22//!     vec![4, 3, 2, 1, 1],
23//! ];
24//! let minima = vec![1, 1, 4, 4, 4];
25//! assert_eq!(smawk::column_minima(&matrix), minima);
26//! ```
27//!
28//! The `minima` vector gives the index of the minimum value per
29//! column, so `minima[0] == 1` since the minimum value in the first
30//! column is 2 (row 1). Note that the smallest row index is returned.
31//!
32//! # Definitions
33//!
34//! Some of the functions in this crate only work on matrices that are
35//! *totally monotone*, which we will define below.
36//!
37//! ## Monotone Matrices
38//!
39//! We start with a helper definition. Given an *m* ✕ *n* matrix `M`,
40//! we say that `M` is *monotone* when the minimum value of row `i` is
41//! found to the left of the minimum value in row `i'` where `i < i'`.
42//!
43//! More formally, if we let `rm(i)` denote the column index of the
44//! left-most minimum value in row `i`, then we have
45//!
46//! ```text
47//! rm(0) ≤ rm(1) ≤ ... ≤ rm(m - 1)
48//! ```
49//!
50//! This means that as you go down the rows from top to bottom, the
51//! row-minima proceed from left to right.
52//!
53//! The algorithms in this crate deal with finding such row- and
54//! column-minima.
55//!
56//! ## Totally Monotone Matrices
57//!
58//! We say that a matrix `M` is *totally monotone* when every
59//! sub-matrix is monotone. A sub-matrix is formed by the intersection
60//! of any two rows `i < i'` and any two columns `j < j'`.
61//!
62//! This is often expressed as via this equivalent condition:
63//!
64//! ```text
65//! M[i, j] > M[i, j']  =>  M[i', j] > M[i', j']
66//! ```
67//!
68//! for all `i < i'` and `j < j'`.
69//!
70//! ## Monge Property for Matrices
71//!
72//! A matrix `M` is said to fulfill the *Monge property* if
73//!
74//! ```text
75//! M[i, j] + M[i', j'] ≤ M[i, j'] + M[i', j]
76//! ```
77//!
78//! for all `i < i'` and `j < j'`. This says that given any rectangle
79//! in the matrix, the sum of the top-left and bottom-right corners is
80//! less than or equal to the sum of the bottom-left and upper-right
81//! corners.
82//!
83//! All Monge matrices are totally monotone, so it is enough to
84//! establish that the Monge property holds in order to use a matrix
85//! with the functions in this crate. If your program is dealing with
86//! unknown inputs, it can use [`monge::is_monge`] to verify that a
87//! matrix is a Monge matrix.
88
89#![doc(html_root_url = "https://docs.rs/smawk/0.3.2")]
90// The s! macro from ndarray uses unsafe internally, so we can only
91// forbid unsafe code when building with the default features.
92#![cfg_attr(not(feature = "ndarray"), forbid(unsafe_code))]
93
94#[cfg(feature = "ndarray")]
95pub mod brute_force;
96pub mod monge;
97#[cfg(feature = "ndarray")]
98pub mod recursive;
99
100/// Minimal matrix trait for two-dimensional arrays.
101///
102/// This provides the functionality needed to represent a read-only
103/// numeric matrix. You can query the size of the matrix and access
104/// elements. Modeled after [`ndarray::Array2`] from the [ndarray
105/// crate](https://crates.io/crates/ndarray).
106///
107/// Enable the `ndarray` Cargo feature if you want to use it with
108/// `ndarray::Array2`.
109pub trait Matrix<T: Copy> {
110    /// Return the number of rows.
111    fn nrows(&self) -> usize;
112    /// Return the number of columns.
113    fn ncols(&self) -> usize;
114    /// Return a matrix element.
115    fn index(&self, row: usize, column: usize) -> T;
116}
117
118/// Simple and inefficient matrix representation used for doctest
119/// examples and simple unit tests.
120///
121/// You should prefer implementing it yourself, or you can enable the
122/// `ndarray` Cargo feature and use the provided implementation for
123/// [`ndarray::Array2`].
124impl<T: Copy> Matrix<T> for Vec<Vec<T>> {
125    fn nrows(&self) -> usize {
126        self.len()
127    }
128    fn ncols(&self) -> usize {
129        self[0].len()
130    }
131    fn index(&self, row: usize, column: usize) -> T {
132        self[row][column]
133    }
134}
135
136/// Adapting [`ndarray::Array2`] to the `Matrix` trait.
137///
138/// **Note: this implementation is only available if you enable the
139/// `ndarray` Cargo feature.**
140#[cfg(feature = "ndarray")]
141impl<T: Copy> Matrix<T> for ndarray::Array2<T> {
142    #[inline]
143    fn nrows(&self) -> usize {
144        self.nrows()
145    }
146    #[inline]
147    fn ncols(&self) -> usize {
148        self.ncols()
149    }
150    #[inline]
151    fn index(&self, row: usize, column: usize) -> T {
152        self[[row, column]]
153    }
154}
155
156/// Compute row minima in O(*m* + *n*) time.
157///
158/// This implements the [SMAWK algorithm] for efficiently finding row
159/// minima in a totally monotone matrix.
160///
161/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
162/// Wilbur, *Geometric applications of a matrix searching algorithm*,
163/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
164/// translation [David Eppstein's Python code][pads].
165///
166/// Running time on an *m* ✕ *n* matrix: O(*m* + *n*).
167///
168/// # Examples
169///
170/// ```
171/// use smawk::Matrix;
172/// let matrix = vec![vec![4, 2, 4, 3],
173///                   vec![5, 3, 5, 3],
174///                   vec![5, 3, 3, 1]];
175/// assert_eq!(smawk::row_minima(&matrix),
176///            vec![1, 1, 3]);
177/// ```
178///
179/// # Panics
180///
181/// It is an error to call this on a matrix with zero columns.
182///
183/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
184/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
185pub fn row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
186    // Benchmarking shows that SMAWK performs roughly the same on row-
187    // and column-major matrices.
188    let mut minima = vec![0; matrix.nrows()];
189    smawk_inner(
190        &|j, i| matrix.index(i, j),
191        &(0..matrix.ncols()).collect::<Vec<_>>(),
192        &(0..matrix.nrows()).collect::<Vec<_>>(),
193        &mut minima,
194    );
195    minima
196}
197
198#[deprecated(since = "0.3.2", note = "Please use `row_minima` instead.")]
199pub fn smawk_row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
200    row_minima(matrix)
201}
202
203/// Compute column minima in O(*m* + *n*) time.
204///
205/// This implements the [SMAWK algorithm] for efficiently finding
206/// column minima in a totally monotone matrix.
207///
208/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
209/// Wilbur, *Geometric applications of a matrix searching algorithm*,
210/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
211/// translation [David Eppstein's Python code][pads].
212///
213/// Running time on an *m* ✕ *n* matrix: O(*m* + *n*).
214///
215/// # Examples
216///
217/// ```
218/// use smawk::Matrix;
219/// let matrix = vec![vec![4, 2, 4, 3],
220///                   vec![5, 3, 5, 3],
221///                   vec![5, 3, 3, 1]];
222/// assert_eq!(smawk::column_minima(&matrix),
223///            vec![0, 0, 2, 2]);
224/// ```
225///
226/// # Panics
227///
228/// It is an error to call this on a matrix with zero rows.
229///
230/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
231/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
232pub fn column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
233    let mut minima = vec![0; matrix.ncols()];
234    smawk_inner(
235        &|i, j| matrix.index(i, j),
236        &(0..matrix.nrows()).collect::<Vec<_>>(),
237        &(0..matrix.ncols()).collect::<Vec<_>>(),
238        &mut minima,
239    );
240    minima
241}
242
243#[deprecated(since = "0.3.2", note = "Please use `column_minima` instead.")]
244pub fn smawk_column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
245    column_minima(matrix)
246}
247
248/// Compute column minima in the given area of the matrix. The
249/// `minima` slice is updated inplace.
250fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
251    matrix: &M,
252    rows: &[usize],
253    cols: &[usize],
254    minima: &mut [usize],
255) {
256    if cols.is_empty() {
257        return;
258    }
259
260    let mut stack = Vec::with_capacity(cols.len());
261    for r in rows {
262        // TODO: use stack.last() instead of stack.is_empty() etc
263        while !stack.is_empty()
264            && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
265                > matrix(*r, cols[stack.len() - 1])
266        {
267            stack.pop();
268        }
269        if stack.len() != cols.len() {
270            stack.push(*r);
271        }
272    }
273    let rows = &stack;
274
275    let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
276    for (idx, c) in cols.iter().enumerate() {
277        if idx % 2 == 1 {
278            odd_cols.push(*c);
279        }
280    }
281
282    smawk_inner(matrix, rows, &odd_cols, minima);
283
284    let mut r = 0;
285    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
286        let mut row = rows[r];
287        let last_row = if c == cols.len() - 1 {
288            rows[rows.len() - 1]
289        } else {
290            minima[cols[c + 1]]
291        };
292        let mut pair = (matrix(row, col), row);
293        while row != last_row {
294            r += 1;
295            row = rows[r];
296            if (matrix(row, col), row) < pair {
297                pair = (matrix(row, col), row);
298            }
299        }
300        minima[col] = pair.1;
301    }
302}
303
304/// Compute upper-right column minima in O(*m* + *n*) time.
305///
306/// The input matrix must be totally monotone.
307///
308/// The function returns a vector of `(usize, T)`. The `usize` in the
309/// tuple at index `j` tells you the row of the minimum value in
310/// column `j` and the `T` value is minimum value itself.
311///
312/// The algorithm only considers values above the main diagonal, which
313/// means that it computes values `v(j)` where:
314///
315/// ```text
316/// v(0) = initial
317/// v(j) = min { M[i, j] | i < j } for j > 0
318/// ```
319///
320/// If we let `r(j)` denote the row index of the minimum value in
321/// column `j`, the tuples in the result vector become `(r(j), M[r(j),
322/// j])`.
323///
324/// The algorithm is an *online* algorithm, in the sense that `matrix`
325/// function can refer back to previously computed column minima when
326/// determining an entry in the matrix. The guarantee is that we only
327/// call `matrix(i, j)` after having computed `v(i)`. This is
328/// reflected in the `&[(usize, T)]` argument to `matrix`, which grows
329/// as more and more values are computed.
330pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
331    initial: T,
332    size: usize,
333    matrix: M,
334) -> Vec<(usize, T)> {
335    let mut result = vec![(0, initial)];
336
337    // State used by the algorithm.
338    let mut finished = 0;
339    let mut base = 0;
340    let mut tentative = 0;
341
342    // Shorthand for evaluating the matrix. We need a macro here since
343    // we don't want to borrow the result vector.
344    macro_rules! m {
345        ($i:expr, $j:expr) => {{
346            assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j);
347            assert!(
348                $i < size && $j < size,
349                "(i, j) out of bounds: ({}, {}), size: {}",
350                $i,
351                $j,
352                size
353            );
354            matrix(&result[..finished + 1], $i, $j)
355        }};
356    }
357
358    // Keep going until we have finished all size columns. Since the
359    // columns are zero-indexed, we're done when finished == size - 1.
360    while finished < size - 1 {
361        // First case: we have already advanced past the previous
362        // tentative value. We make a new tentative value by applying
363        // smawk_inner to the largest square submatrix that fits under
364        // the base.
365        let i = finished + 1;
366        if i > tentative {
367            let rows = (base..finished + 1).collect::<Vec<_>>();
368            tentative = std::cmp::min(finished + rows.len(), size - 1);
369            let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
370            let mut minima = vec![0; tentative + 1];
371            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
372            for col in cols {
373                let row = minima[col];
374                let v = m![row, col];
375                if col >= result.len() {
376                    result.push((row, v));
377                } else if v < result[col].1 {
378                    result[col] = (row, v);
379                }
380            }
381            finished = i;
382            continue;
383        }
384
385        // Second case: the new column minimum is on the diagonal. All
386        // subsequent ones will be at least as low, so we can clear
387        // out all our work from higher rows. As in the fourth case,
388        // the loss of tentative is amortized against the increase in
389        // base.
390        let diag = m![i - 1, i];
391        if diag < result[i].1 {
392            result[i] = (i - 1, diag);
393            base = i - 1;
394            tentative = i;
395            finished = i;
396            continue;
397        }
398
399        // Third case: row i-1 does not supply a column minimum in any
400        // column up to tentative. We simply advance finished while
401        // maintaining the invariant.
402        if m![i - 1, tentative] >= result[tentative].1 {
403            finished = i;
404            continue;
405        }
406
407        // Fourth and final case: a new column minimum at tentative.
408        // This allows us to make progress by incorporating rows prior
409        // to finished into the base. The base invariant holds because
410        // these rows cannot supply any later column minima. The work
411        // done when we last advanced tentative (and undone by this
412        // step) can be amortized against the increase in base.
413        base = i - 1;
414        tentative = i;
415        finished = i;
416    }
417
418    result
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424
425    #[test]
426    fn smawk_1x1() {
427        let matrix = vec![vec![2]];
428        assert_eq!(row_minima(&matrix), vec![0]);
429        assert_eq!(column_minima(&matrix), vec![0]);
430    }
431
432    #[test]
433    fn smawk_2x1() {
434        let matrix = vec![
435            vec![3], //
436            vec![2],
437        ];
438        assert_eq!(row_minima(&matrix), vec![0, 0]);
439        assert_eq!(column_minima(&matrix), vec![1]);
440    }
441
442    #[test]
443    fn smawk_1x2() {
444        let matrix = vec![vec![2, 1]];
445        assert_eq!(row_minima(&matrix), vec![1]);
446        assert_eq!(column_minima(&matrix), vec![0, 0]);
447    }
448
449    #[test]
450    fn smawk_2x2() {
451        let matrix = vec![
452            vec![3, 2], //
453            vec![2, 1],
454        ];
455        assert_eq!(row_minima(&matrix), vec![1, 1]);
456        assert_eq!(column_minima(&matrix), vec![1, 1]);
457    }
458
459    #[test]
460    fn smawk_3x3() {
461        let matrix = vec![
462            vec![3, 4, 4], //
463            vec![3, 4, 4],
464            vec![2, 3, 3],
465        ];
466        assert_eq!(row_minima(&matrix), vec![0, 0, 0]);
467        assert_eq!(column_minima(&matrix), vec![2, 2, 2]);
468    }
469
470    #[test]
471    fn smawk_4x4() {
472        let matrix = vec![
473            vec![4, 5, 5, 5], //
474            vec![2, 3, 3, 3],
475            vec![2, 3, 3, 3],
476            vec![2, 2, 2, 2],
477        ];
478        assert_eq!(row_minima(&matrix), vec![0, 0, 0, 0]);
479        assert_eq!(column_minima(&matrix), vec![1, 3, 3, 3]);
480    }
481
482    #[test]
483    fn smawk_5x5() {
484        let matrix = vec![
485            vec![3, 2, 4, 5, 6],
486            vec![2, 1, 3, 3, 4],
487            vec![2, 1, 3, 3, 4],
488            vec![3, 2, 4, 3, 4],
489            vec![4, 3, 2, 1, 1],
490        ];
491        assert_eq!(row_minima(&matrix), vec![1, 1, 1, 1, 3]);
492        assert_eq!(column_minima(&matrix), vec![1, 1, 4, 4, 4]);
493    }
494
495    #[test]
496    fn online_1x1() {
497        let matrix = vec![vec![0]];
498        let minima = vec![(0, 0)];
499        assert_eq!(online_column_minima(0, 1, |_, i, j| matrix[i][j]), minima);
500    }
501
502    #[test]
503    fn online_2x2() {
504        let matrix = vec![
505            vec![0, 2], //
506            vec![0, 0],
507        ];
508        let minima = vec![(0, 0), (0, 2)];
509        assert_eq!(online_column_minima(0, 2, |_, i, j| matrix[i][j]), minima);
510    }
511
512    #[test]
513    fn online_3x3() {
514        let matrix = vec![
515            vec![0, 4, 4], //
516            vec![0, 0, 4],
517            vec![0, 0, 0],
518        ];
519        let minima = vec![(0, 0), (0, 4), (0, 4)];
520        assert_eq!(online_column_minima(0, 3, |_, i, j| matrix[i][j]), minima);
521    }
522
523    #[test]
524    fn online_4x4() {
525        let matrix = vec![
526            vec![0, 5, 5, 5], //
527            vec![0, 0, 3, 3],
528            vec![0, 0, 0, 3],
529            vec![0, 0, 0, 0],
530        ];
531        let minima = vec![(0, 0), (0, 5), (1, 3), (1, 3)];
532        assert_eq!(online_column_minima(0, 4, |_, i, j| matrix[i][j]), minima);
533    }
534
535    #[test]
536    fn online_5x5() {
537        let matrix = vec![
538            vec![0, 2, 4, 6, 7],
539            vec![0, 0, 3, 4, 5],
540            vec![0, 0, 0, 3, 4],
541            vec![0, 0, 0, 0, 4],
542            vec![0, 0, 0, 0, 0],
543        ];
544        let minima = vec![(0, 0), (0, 2), (1, 3), (2, 3), (2, 4)];
545        assert_eq!(online_column_minima(0, 5, |_, i, j| matrix[i][j]), minima);
546    }
547
548    #[test]
549    fn smawk_works_with_partial_ord() {
550        let matrix = vec![
551            vec![3.0, 2.0], //
552            vec![2.0, 1.0],
553        ];
554        assert_eq!(row_minima(&matrix), vec![1, 1]);
555        assert_eq!(column_minima(&matrix), vec![1, 1]);
556    }
557
558    #[test]
559    fn online_works_with_partial_ord() {
560        let matrix = vec![
561            vec![0.0, 2.0], //
562            vec![0.0, 0.0],
563        ];
564        let minima = vec![(0, 0.0), (0, 2.0)];
565        assert_eq!(
566            online_column_minima(0.0, 2, |_, i: usize, j: usize| matrix[i][j]),
567            minima
568        );
569    }
570}