Coverage Report

Created: 2025-07-01 06:56

/rust/registry/src/index.crates.io-6f17d22bba15001f/smawk-0.3.2/src/lib.rs
Line
Count
Source (jump to first uncovered line)
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")]
95
pub mod brute_force;
96
pub mod monge;
97
#[cfg(feature = "ndarray")]
98
pub 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`.
109
pub 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`].
124
impl<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")]
141
impl<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
185
pub 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.")]
199
pub 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
232
pub 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.")]
244
pub 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.
250
21.7M
fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
251
21.7M
    matrix: &M,
252
21.7M
    rows: &[usize],
253
21.7M
    cols: &[usize],
254
21.7M
    minima: &mut [usize],
255
21.7M
) {
256
21.7M
    if cols.is_empty() {
257
7.05M
        return;
258
14.6M
    }
259
14.6M
260
14.6M
    let mut stack = Vec::with_capacity(cols.len());
261
84.7M
    for r in rows {
262
        // TODO: use stack.last() instead of stack.is_empty() etc
263
84.0M
        while !stack.is_empty()
264
62.0M
            && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
265
62.0M
                > matrix(*r, cols[stack.len() - 1])
266
13.9M
        {
267
13.9M
            stack.pop();
268
13.9M
        }
269
70.1M
        if stack.len() != cols.len() {
270
56.7M
            stack.push(*r);
271
56.7M
        }
272
    }
273
14.6M
    let rows = &stack;
274
14.6M
275
14.6M
    let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
276
53.0M
    for (idx, c) in cols.iter().enumerate() {
277
53.0M
        if idx % 2 == 1 {
278
22.9M
            odd_cols.push(*c);
279
30.0M
        }
280
    }
281
282
14.6M
    smawk_inner(matrix, rows, &odd_cols, minima);
283
14.6M
284
14.6M
    let mut r = 0;
285
53.0M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0}>::{closure#0}
Line
Count
Source
285
44.7M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0}>::{closure#0}
Line
Count
Source
285
4.10M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0}>::{closure#0}
Line
Count
Source
285
4.22M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
286
30.0M
        let mut row = rows[r];
287
30.0M
        let last_row = if c == cols.len() - 1 {
288
7.06M
            rows[rows.len() - 1]
289
        } else {
290
22.9M
            minima[cols[c + 1]]
291
        };
292
30.0M
        let mut pair = (matrix(row, col), row);
293
37.2M
        while row != last_row {
294
7.21M
            r += 1;
295
7.21M
            row = rows[r];
296
7.21M
            if (matrix(row, col), row) < pair {
297
1.20M
                pair = (matrix(row, col), row);
298
6.00M
            }
299
        }
300
30.0M
        minima[col] = pair.1;
301
    }
302
21.7M
}
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0}>
Line
Count
Source
250
19.4M
fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
251
19.4M
    matrix: &M,
252
19.4M
    rows: &[usize],
253
19.4M
    cols: &[usize],
254
19.4M
    minima: &mut [usize],
255
19.4M
) {
256
19.4M
    if cols.is_empty() {
257
6.38M
        return;
258
13.1M
    }
259
13.1M
260
13.1M
    let mut stack = Vec::with_capacity(cols.len());
261
71.1M
    for r in rows {
262
        // TODO: use stack.last() instead of stack.is_empty() etc
263
70.9M
        while !stack.is_empty()
264
51.2M
            && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
265
51.2M
                > matrix(*r, cols[stack.len() - 1])
266
12.9M
        {
267
12.9M
            stack.pop();
268
12.9M
        }
269
57.9M
        if stack.len() != cols.len() {
270
48.3M
            stack.push(*r);
271
48.3M
        }
272
    }
273
13.1M
    let rows = &stack;
274
13.1M
275
13.1M
    let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
276
44.7M
    for (idx, c) in cols.iter().enumerate() {
277
44.7M
        if idx % 2 == 1 {
278
19.1M
            odd_cols.push(*c);
279
25.5M
        }
280
    }
281
282
13.1M
    smawk_inner(matrix, rows, &odd_cols, minima);
283
13.1M
284
13.1M
    let mut r = 0;
285
25.5M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
286
25.5M
        let mut row = rows[r];
287
25.5M
        let last_row = if c == cols.len() - 1 {
288
6.39M
            rows[rows.len() - 1]
289
        } else {
290
19.1M
            minima[cols[c + 1]]
291
        };
292
25.5M
        let mut pair = (matrix(row, col), row);
293
31.9M
        while row != last_row {
294
6.35M
            r += 1;
295
6.35M
            row = rows[r];
296
6.35M
            if (matrix(row, col), row) < pair {
297
1.17M
                pair = (matrix(row, col), row);
298
5.17M
            }
299
        }
300
25.5M
        minima[col] = pair.1;
301
    }
302
19.4M
}
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0}>
Line
Count
Source
250
1.45M
fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
251
1.45M
    matrix: &M,
252
1.45M
    rows: &[usize],
253
1.45M
    cols: &[usize],
254
1.45M
    minima: &mut [usize],
255
1.45M
) {
256
1.45M
    if cols.is_empty() {
257
435k
        return;
258
1.01M
    }
259
1.01M
260
1.01M
    let mut stack = Vec::with_capacity(cols.len());
261
6.82M
    for r in rows {
262
        // TODO: use stack.last() instead of stack.is_empty() etc
263
6.46M
        while !stack.is_empty()
264
4.89M
            && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
265
4.89M
                > matrix(*r, cols[stack.len() - 1])
266
647k
        {
267
647k
            stack.pop();
268
647k
        }
269
5.81M
        if stack.len() != cols.len() {
270
4.14M
            stack.push(*r);
271
4.14M
        }
272
    }
273
1.01M
    let rows = &stack;
274
1.01M
275
1.01M
    let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
276
4.10M
    for (idx, c) in cols.iter().enumerate() {
277
4.10M
        if idx % 2 == 1 {
278
1.83M
            odd_cols.push(*c);
279
2.27M
        }
280
    }
281
282
1.01M
    smawk_inner(matrix, rows, &odd_cols, minima);
283
1.01M
284
1.01M
    let mut r = 0;
285
2.27M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
286
2.27M
        let mut row = rows[r];
287
2.27M
        let last_row = if c == cols.len() - 1 {
288
436k
            rows[rows.len() - 1]
289
        } else {
290
1.83M
            minima[cols[c + 1]]
291
        };
292
2.27M
        let mut pair = (matrix(row, col), row);
293
2.64M
        while row != last_row {
294
373k
            r += 1;
295
373k
            row = rows[r];
296
373k
            if (matrix(row, col), row) < pair {
297
11.9k
                pair = (matrix(row, col), row);
298
361k
            }
299
        }
300
2.27M
        minima[col] = pair.1;
301
    }
302
1.45M
}
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0}>
Line
Count
Source
250
774k
fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
251
774k
    matrix: &M,
252
774k
    rows: &[usize],
253
774k
    cols: &[usize],
254
774k
    minima: &mut [usize],
255
774k
) {
256
774k
    if cols.is_empty() {
257
229k
        return;
258
545k
    }
259
545k
260
545k
    let mut stack = Vec::with_capacity(cols.len());
261
6.84M
    for r in rows {
262
        // TODO: use stack.last() instead of stack.is_empty() etc
263
6.64M
        while !stack.is_empty()
264
5.89M
            && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
265
5.89M
                > matrix(*r, cols[stack.len() - 1])
266
337k
        {
267
337k
            stack.pop();
268
337k
        }
269
6.30M
        if stack.len() != cols.len() {
270
4.25M
            stack.push(*r);
271
4.25M
        }
272
    }
273
545k
    let rows = &stack;
274
545k
275
545k
    let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
276
4.22M
    for (idx, c) in cols.iter().enumerate() {
277
4.22M
        if idx % 2 == 1 {
278
1.99M
            odd_cols.push(*c);
279
2.22M
        }
280
    }
281
282
545k
    smawk_inner(matrix, rows, &odd_cols, minima);
283
545k
284
545k
    let mut r = 0;
285
2.22M
    for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
286
2.22M
        let mut row = rows[r];
287
2.22M
        let last_row = if c == cols.len() - 1 {
288
230k
            rows[rows.len() - 1]
289
        } else {
290
1.99M
            minima[cols[c + 1]]
291
        };
292
2.22M
        let mut pair = (matrix(row, col), row);
293
2.71M
        while row != last_row {
294
481k
            r += 1;
295
481k
            row = rows[r];
296
481k
            if (matrix(row, col), row) < pair {
297
16.0k
                pair = (matrix(row, col), row);
298
465k
            }
299
        }
300
2.22M
        minima[col] = pair.1;
301
    }
302
774k
}
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.
330
451k
pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
331
451k
    initial: T,
332
451k
    size: usize,
333
451k
    matrix: M,
334
451k
) -> Vec<(usize, T)> {
335
451k
    let mut result = vec![(0, initial)];
336
451k
337
451k
    // State used by the algorithm.
338
451k
    let mut finished = 0;
339
451k
    let mut base = 0;
340
451k
    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
26.9M
    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
26.5M
        let i = finished + 1;
366
26.5M
        if i > tentative {
367
7.05M
            let rows = (base..finished + 1).collect::<Vec<_>>();
368
7.05M
            tentative = std::cmp::min(finished + rows.len(), size - 1);
369
7.05M
            let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
370
7.05M
            let mut minima = vec![0; tentative + 1];
371
162M
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0}
Line
Count
Source
371
135M
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0}
Line
Count
Source
371
12.4M
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0}
Line
Count
Source
371
14.5M
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
372
37.1M
            for col in cols {
373
30.0M
                let row = minima[col];
374
30.0M
                let v = m![row, col];
375
30.0M
                if col >= result.len() {
376
26.5M
                    result.push((row, v));
377
26.5M
                } else if v < result[col].1 {
378
1.76M
                    result[col] = (row, v);
379
1.76M
                }
380
            }
381
7.05M
            finished = i;
382
7.05M
            continue;
383
19.4M
        }
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
19.4M
        let diag = m![i - 1, i];
391
19.4M
        if diag < result[i].1 {
392
6.57M
            result[i] = (i - 1, diag);
393
6.57M
            base = i - 1;
394
6.57M
            tentative = i;
395
6.57M
            finished = i;
396
6.57M
            continue;
397
12.8M
        }
398
12.8M
399
12.8M
        // Third case: row i-1 does not supply a column minimum in any
400
12.8M
        // column up to tentative. We simply advance finished while
401
12.8M
        // maintaining the invariant.
402
12.8M
        if m![i - 1, tentative] >= result[tentative].1 {
403
12.8M
            finished = i;
404
12.8M
            continue;
405
61.4k
        }
406
61.4k
407
61.4k
        // Fourth and final case: a new column minimum at tentative.
408
61.4k
        // This allows us to make progress by incorporating rows prior
409
61.4k
        // to finished into the base. The base invariant holds because
410
61.4k
        // these rows cannot supply any later column minima. The work
411
61.4k
        // done when we last advanced tentative (and undone by this
412
61.4k
        // step) can be amortized against the increase in base.
413
61.4k
        base = i - 1;
414
61.4k
        tentative = i;
415
61.4k
        finished = i;
416
    }
417
418
451k
    result
419
451k
}
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>
Line
Count
Source
330
449k
pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
331
449k
    initial: T,
332
449k
    size: usize,
333
449k
    matrix: M,
334
449k
) -> Vec<(usize, T)> {
335
449k
    let mut result = vec![(0, initial)];
336
449k
337
449k
    // State used by the algorithm.
338
449k
    let mut finished = 0;
339
449k
    let mut base = 0;
340
449k
    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
23.1M
    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
22.6M
        let i = finished + 1;
366
22.6M
        if i > tentative {
367
6.38M
            let rows = (base..finished + 1).collect::<Vec<_>>();
368
6.38M
            tentative = std::cmp::min(finished + rows.len(), size - 1);
369
6.38M
            let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
370
6.38M
            let mut minima = vec![0; tentative + 1];
371
6.38M
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
372
31.9M
            for col in cols {
373
25.5M
                let row = minima[col];
374
25.5M
                let v = m![row, col];
375
25.5M
                if col >= result.len() {
376
22.6M
                    result.push((row, v));
377
22.6M
                } else if v < result[col].1 {
378
1.18M
                    result[col] = (row, v);
379
1.68M
                }
380
            }
381
6.38M
            finished = i;
382
6.38M
            continue;
383
16.2M
        }
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
16.2M
        let diag = m![i - 1, i];
391
16.2M
        if diag < result[i].1 {
392
6.12M
            result[i] = (i - 1, diag);
393
6.12M
            base = i - 1;
394
6.12M
            tentative = i;
395
6.12M
            finished = i;
396
6.12M
            continue;
397
10.1M
        }
398
10.1M
399
10.1M
        // Third case: row i-1 does not supply a column minimum in any
400
10.1M
        // column up to tentative. We simply advance finished while
401
10.1M
        // maintaining the invariant.
402
10.1M
        if m![i - 1, tentative] >= result[tentative].1 {
403
10.1M
            finished = i;
404
10.1M
            continue;
405
25.3k
        }
406
25.3k
407
25.3k
        // Fourth and final case: a new column minimum at tentative.
408
25.3k
        // This allows us to make progress by incorporating rows prior
409
25.3k
        // to finished into the base. The base invariant holds because
410
25.3k
        // these rows cannot supply any later column minima. The work
411
25.3k
        // done when we last advanced tentative (and undone by this
412
25.3k
        // step) can be amortized against the increase in base.
413
25.3k
        base = i - 1;
414
25.3k
        tentative = i;
415
25.3k
        finished = i;
416
    }
417
418
449k
    result
419
449k
}
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>
Line
Count
Source
330
883
pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
331
883
    initial: T,
332
883
    size: usize,
333
883
    matrix: M,
334
883
) -> Vec<(usize, T)> {
335
883
    let mut result = vec![(0, initial)];
336
883
337
883
    // State used by the algorithm.
338
883
    let mut finished = 0;
339
883
    let mut base = 0;
340
883
    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
1.99M
    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
1.99M
        let i = finished + 1;
366
1.99M
        if i > tentative {
367
435k
            let rows = (base..finished + 1).collect::<Vec<_>>();
368
435k
            tentative = std::cmp::min(finished + rows.len(), size - 1);
369
435k
            let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
370
435k
            let mut minima = vec![0; tentative + 1];
371
435k
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
372
2.70M
            for col in cols {
373
2.27M
                let row = minima[col];
374
2.27M
                let v = m![row, col];
375
2.27M
                if col >= result.len() {
376
1.99M
                    result.push((row, v));
377
1.99M
                } else if v < result[col].1 {
378
248k
                    result[col] = (row, v);
379
248k
                }
380
            }
381
435k
            finished = i;
382
435k
            continue;
383
1.56M
        }
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
1.56M
        let diag = m![i - 1, i];
391
1.56M
        if diag < result[i].1 {
392
282k
            result[i] = (i - 1, diag);
393
282k
            base = i - 1;
394
282k
            tentative = i;
395
282k
            finished = i;
396
282k
            continue;
397
1.27M
        }
398
1.27M
399
1.27M
        // Third case: row i-1 does not supply a column minimum in any
400
1.27M
        // column up to tentative. We simply advance finished while
401
1.27M
        // maintaining the invariant.
402
1.27M
        if m![i - 1, tentative] >= result[tentative].1 {
403
1.25M
            finished = i;
404
1.25M
            continue;
405
22.0k
        }
406
22.0k
407
22.0k
        // Fourth and final case: a new column minimum at tentative.
408
22.0k
        // This allows us to make progress by incorporating rows prior
409
22.0k
        // to finished into the base. The base invariant holds because
410
22.0k
        // these rows cannot supply any later column minima. The work
411
22.0k
        // done when we last advanced tentative (and undone by this
412
22.0k
        // step) can be amortized against the increase in base.
413
22.0k
        base = i - 1;
414
22.0k
        tentative = i;
415
22.0k
        finished = i;
416
    }
417
418
883
    result
419
883
}
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>
Line
Count
Source
330
772
pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
331
772
    initial: T,
332
772
    size: usize,
333
772
    matrix: M,
334
772
) -> Vec<(usize, T)> {
335
772
    let mut result = vec![(0, initial)];
336
772
337
772
    // State used by the algorithm.
338
772
    let mut finished = 0;
339
772
    let mut base = 0;
340
772
    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
1.84M
    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
1.84M
        let i = finished + 1;
366
1.84M
        if i > tentative {
367
229k
            let rows = (base..finished + 1).collect::<Vec<_>>();
368
229k
            tentative = std::cmp::min(finished + rows.len(), size - 1);
369
229k
            let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
370
229k
            let mut minima = vec![0; tentative + 1];
371
229k
            smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
372
2.45M
            for col in cols {
373
2.22M
                let row = minima[col];
374
2.22M
                let v = m![row, col];
375
2.22M
                if col >= result.len() {
376
1.84M
                    result.push((row, v));
377
1.84M
                } else if v < result[col].1 {
378
330k
                    result[col] = (row, v);
379
330k
                }
380
            }
381
229k
            finished = i;
382
229k
            continue;
383
1.61M
        }
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
1.61M
        let diag = m![i - 1, i];
391
1.61M
        if diag < result[i].1 {
392
165k
            result[i] = (i - 1, diag);
393
165k
            base = i - 1;
394
165k
            tentative = i;
395
165k
            finished = i;
396
165k
            continue;
397
1.45M
        }
398
1.45M
399
1.45M
        // Third case: row i-1 does not supply a column minimum in any
400
1.45M
        // column up to tentative. We simply advance finished while
401
1.45M
        // maintaining the invariant.
402
1.45M
        if m![i - 1, tentative] >= result[tentative].1 {
403
1.43M
            finished = i;
404
1.43M
            continue;
405
13.9k
        }
406
13.9k
407
13.9k
        // Fourth and final case: a new column minimum at tentative.
408
13.9k
        // This allows us to make progress by incorporating rows prior
409
13.9k
        // to finished into the base. The base invariant holds because
410
13.9k
        // these rows cannot supply any later column minima. The work
411
13.9k
        // done when we last advanced tentative (and undone by this
412
13.9k
        // step) can be amortized against the increase in base.
413
13.9k
        base = i - 1;
414
13.9k
        tentative = i;
415
13.9k
        finished = i;
416
    }
417
418
772
    result
419
772
}
420
421
#[cfg(test)]
422
mod 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
}