xingo_sparse/solve.rs
1//! # Chapter 5.4 — FTRAN and BTRAN, dense and hypersparse
2//!
3//! ## Purpose
4//! The simplex method needs `α_q = B⁻¹ a_q` (FTRAN, forward transformation)
5//! and `ρ_r = B⁻ᵀ e_r`, `y = B⁻ᵀ c_B` (BTRAN, backward transformation).
6//! This chapter applies the factors of Chapter 5.3 and the row etas of
7//! Chapter 5.5 to a right-hand side, in one of two ways:
8//!
9//! * the *dense* Algorithms F and B run over every elimination step,
10//! skipping exact zeros: `O(m + nnz(L) + nnz(U) + nnz(etas))` whatever
11//! the right-hand side;
12//! * the *hypersparse* Algorithms HF and HB first compute, by depth-first
13//! search in the graphs of the factors, the set of entries that can
14//! become nonzero (the *reach* of the right-hand side, Gilbert & Peierls
15//! 1988), and then perform only the operations of the reach, in a
16//! topological order. Their cost is proportional to the number of
17//! entries and factor nonzeros actually involved — on the large sparse LPs
18//! (Kennington `ken-*`, `pds-*`) a handful out of `m ≈ 10⁵`, which is what
19//! Hall & McKinnon (2005) call *hypersparsity*.
20//!
21//! The choice between the two is automatic, per call (policy below).
22//!
23//! ## Notation
24//! After `t` updates the factor represents
25//! ```text
26//! B = Pᵀ L R₁⁻¹ ⋯ R_t⁻¹ U Qᵀ,
27//! ```
28//! where `R_e = I − e_{p} μ_eᵀ` is the row eta of update `e` (target row
29//! `p`, multipliers `μ_e` on rows pivoted after `p`). FTRAN maps a vector
30//! indexed by *rows* to one indexed by *positions*; BTRAN the reverse.
31//! For a row `p` let `q(p) = q_{step(p)}` be the position pivoted with it,
32//! and for a position `q` let `p(q) = p_{step(q)}`.
33//!
34//! The four *graphs* of the hypersparse solves have an arc `a → b` when
35//! the value at `b` is modified by an operation that reads the value at
36//! `a`:
37//!
38//! * `G_L` on rows: `p → i` for every multiplier `l_ip` of the `L` eta with
39//! pivot row `p` (column-wise `L`, as stored);
40//! * `G_U` on rows: `p → i` for every off-diagonal `u_{i q(p)}` of column
41//! `q(p)` of `U` (`U` by columns);
42//! * `G_Uᵀ` on positions: `q → j` for every off-diagonal `u_{p(q) j}` of row
43//! `p(q)` of `U` (`U` by rows);
44//! * `G_Lᵀ` on rows: `i → p` for every multiplier `l_ip` (`L` by *rows*, a
45//! transposed copy built once per factorization).
46//!
47//! The row etas are applied in creation order (FTRAN) or in reverse
48//! creation order (BTRAN); their dependencies are held by two lists per
49//! row: the etas that *read* row `k` (`k` among their `p_k`) and the etas
50//! that *target* row `p`.
51//!
52//! ## Algorithm F (dense FTRAN: solve `B x = b`)
53//! F1. [L.] For each `L` eta in pivot order (pivot row `p`, multipliers
54//! `l_ip`): if `y_p ≠ 0`, set `y_i ← y_i − l_ip y_p`.
55//! F2. [R.] For each row eta in creation order (target `p`): set
56//! `y_p ← y_p − Σ_k μ_k y_{p_k}`. The vector `y` now holds the *spike*
57//! `R L⁻¹ b` — recorded by [`crate::LuFactor::ftran_for_update`].
58//! F3. [U.] For the steps `s` in decreasing order, with pivot `(p_s, q_s)`:
59//! `x_{q_s} ← y_{p_s} / u_{p_s q_s}`; if nonzero, `y_i ← y_i − u_{i q_s}
60//! x_{q_s}` for the entries of column `q_s` of `U` (all in rows of earlier
61//! steps).
62//!
63//! ## Algorithm B (dense BTRAN: solve `Bᵀ y = c`)
64//! B1. [Uᵀ.] For the steps `s` in increasing order:
65//! `w_{p_s} ← c_{q_s} / u_{p_s q_s}`;
66//! if nonzero, `c_j ← c_j − u_{p_s j} w_{p_s}` for the entries of row
67//! `p_s` of `U` (all in positions of later steps).
68//! B2. [Rᵀ.] For each row eta in *reverse* creation order (target `p`): if
69//! `w_p ≠ 0`, `w_{p_k} ← w_{p_k} − μ_k w_p`.
70//! B3. [Lᵀ.] For each `L` eta in *reverse* pivot order (pivot row `p`):
71//! `w_p ← w_p − Σ_i l_ip w_i`. Then `y = w`.
72//!
73//! Correctness: F1–F3 apply `L⁻¹`, `R_t ⋯ R_1` and `U⁻¹` (with the
74//! permutations implicit in the indexing) to `b`; B1–B3 apply the
75//! transposes in the opposite order.
76//!
77//! ## Algorithm D (reach: depth-first search, Gilbert & Peierls)
78//! Given a graph `G` and a set `S` of start nodes, produce the nodes
79//! reachable from `S` in a *topological* order (every arc `a → b` between
80//! reached nodes has `a` before `b`).
81//!
82//! D1. [Start.] Take a fresh mark (a stamp: no clearing). Empty the output
83//! list `post`. For each `s ∈ S` not yet marked, mark it and push
84//! `(s, 0)` on the stack; then run D2 until the stack is empty.
85//! D2. [Step.] Let `(v, k)` be the top of the stack. If `v` has a `k`-th
86//! successor `w`, advance `k`; if `w` is unmarked, mark it and push
87//! `(w, 0)`. Otherwise pop `v` and append it to `post` (`v` is
88//! *finished*).
89//! D3. [Order.] A node finishes after every node reachable from it, so
90//! `post` read backwards is a topological order.
91//!
92//! The stack is explicit (no recursion: the paths in `G_L` can be `10⁵`
93//! long). Cost: `O(|reach| + arcs out of the reach)`.
94//!
95//! ## Algorithm HF (hypersparse FTRAN)
96//! The right-hand side `b` is given with its pattern.
97//! HF1. [Load.] Copy the nonzeros of `b` into the row work array `y`;
98//! empty `b` (it receives the result).
99//! HF2. [Reach of L.] Run Algorithm D on `G_L` from the rows of HF1.
100//! HF3. [Apply L.] For each reached row `p` in topological order: if `p`
101//! is the pivot of an `L` eta and `y_p ≠ 0`, set `y_i ← y_i − l_ip y_p`.
102//! The reached rows form the list `Λ` (marked).
103//! HF4. [Row etas.] Put in a min-priority queue every eta that reads a row
104//! of `Λ`. While the queue is not empty, take the smallest eta `e`
105//! (target `t`): `y_t ← y_t − Σ_k μ_k y_{p_k}`; if `t ∉ Λ`, add it to
106//! `Λ` and queue the etas after `e` that read `t`. (An eta before `e`
107//! that reads `t` was applied — or skipped — while `y_t` was still
108//! zero, exactly as in F2.) For [`crate::LuFactor::ftran_for_update`],
109//! record the spike `y` on `Λ`.
110//! HF5. [Reach of U.] Run Algorithm D on `G_U` from `Λ`.
111//! HF6. [Apply U.] For each reached row `p` in topological order:
112//! `x_{q(p)} ← y_p / u_{p q(p)}`, `y_p ← 0`; if `x_{q(p)} ≠ 0`, set
113//! `y_i ← y_i − u_{i q(p)} x_{q(p)}` along column `q(p)`. The pattern
114//! of the result is the set of positions `q(p)` of the reach.
115//!
116//! ## Algorithm HB (hypersparse BTRAN)
117//! HB1. [Load.] Copy the nonzeros of `c` into the position work array;
118//! empty `c`.
119//! HB2. [Reach of Uᵀ.] Run Algorithm D on `G_Uᵀ` from the positions of HB1.
120//! HB3. [Apply Uᵀ.] For each reached position `q` in topological order:
121//! `w_{p(q)} ← c_q / u_{p(q) q}`, `c_q ← 0`; if nonzero,
122//! `c_j ← c_j − u_{p(q) j} w_{p(q)}` along row `p(q)`. The rows `p(q)`
123//! form the list `Λ` (marked).
124//! HB4. [Row etas, transposed.] Put in a max-priority queue every eta whose
125//! target is in `Λ`. While the queue is not empty, take the largest eta
126//! `e` (target `t`): if `w_t ≠ 0`, `w_{p_k} ← w_{p_k} − μ_k w_t`; for
127//! every `p_k ∉ Λ`, add it to `Λ` and queue the etas *before* `e` that
128//! target `p_k`. (The etas after `e` that target `p_k` already ran,
129//! with `w_{p_k} = 0`: no-ops in B2 too.)
130//! HB5. [Reach of Lᵀ.] Run Algorithm D on `G_Lᵀ` from `Λ`.
131//! HB6. [Apply Lᵀ.] For each reached row `i` in topological order: if
132//! `w_i ≠ 0`, set `w_p ← w_p − l_ip w_i` for every multiplier of row
133//! `i` of `L`. The result `y = w` has the reach as pattern.
134//!
135//! The direction of the arcs is the delicate point. In FTRAN `L` and `U`
136//! are used by *columns* (scatter: a known value pushes into the rows it
137//! modifies), so the graphs are those of the stored columns. In BTRAN the
138//! dense algorithm uses `U` by rows (B1 is already a scatter, along row
139//! `p(q)`: arcs `q → j`) but `L` by columns as a *gather* (B3 sums over the
140//! column of eta `p`): a gather cannot be driven by a reach, so HB6 turns it
141//! into a scatter along the rows of `L` (`i → p`), which needs the
142//! transposed copy of `L`. The row etas are the opposite: F2 is a gather
143//! (driven by the "readers" lists) and B2 a scatter (driven by the
144//! "targets" lists).
145//!
146//! ## Choice of the algorithm (alternation policy)
147//! Following Hall & McKinnon (2005, §4) and HiGHS, the hypersparse
148//! algorithm is used for a call iff
149//! (a) the right-hand side is in sparse mode (it has a pattern);
150//! (b) its density `count/m` is at most
151//! [`LuOptions::hyper_rhs_density`](crate::LuOptions) (`0.10`);
152//! (c) the *expected* density of the result — an exponential moving
153//! average, with weight [`HYPER_HISTORY_WEIGHT`] for the newest
154//! result, of the result densities of the previous calls of the same
155//! kind (FTRAN, BTRAN, FTRAN for update; kept across factorizations,
156//! starting at 0) — is at most
157//! [`LuOptions::hyper_result_density`](crate::LuOptions)
158//! (`0.10`).
159//!
160//! When the result fills in, the reach costs more than the dense sweep
161//! (the search visits every arc once, then the numerical pass visits them
162//! again), so (c) switches a kind of operation to the dense algorithms
163//! after about three dense results, and back once results are sparse again
164//! (the dense algorithms measure their result density exactly). The
165//! per-kind statistics ([`SolveStats`]) are in [`crate::LuStats`].
166//!
167//! ## Division of labour (second edition)
168//! Steps F2, B2, HF4 and HB4 — the row etas — belong to the basis-update
169//! policy (Chapter 5.5): they are the methods
170//! [`BasisUpdate::apply_ftran`], [`BasisUpdate::apply_btran`],
171//! [`BasisUpdate::apply_ftran_hyper`] and [`BasisUpdate::apply_btran_hyper`]
172//! of [`crate::ForrestTomlin`], called at exactly the place where the first
173//! edition ran them inline, with the same work arrays. Everything else is
174//! here, on the stored factors.
175//!
176//! ## Invariants
177//! (V1) On return the [`SparseVec`] is in sparse mode. After a dense solve
178//! its pattern is the exact support of the result (in increasing
179//! order); after a hypersparse solve it is the *symbolic reach*, a
180//! superset of the support that may contain explicit zeros (allowed by
181//! the invariant (V2) of [`SparseVec`]; call [`SparseVec::tidy`] to
182//! drop zeros and tiny entries). Computing the exact support would
183//! cost one more pass for no benefit to the simplex method.
184//! (V2) Both work arrays are zero between calls.
185//! (V3) The dense and the hypersparse algorithms perform the same
186//! operations except for the order of independent updates (and
187//! HB6, which sums the terms of B3 one at a time): their results agree
188//! to rounding (property test: relative error ≤ 1e-12).
189//!
190//! ## Complexity
191//! Dense: `O(m + nnz(L) + nnz(U) + nnz(etas))`. Hypersparse:
192//! `O(r + f + e log e)` with `r` the reach sizes, `f` the factor nonzeros
193//! in the columns (or rows) of the reach and `e` the number of row etas
194//! involved.
195//!
196//! ## References
197//! J. R. Gilbert and T. Peierls, "Sparse partial pivoting in time
198//! proportional to arithmetic operations", SIAM J. Sci. Stat. Comput. 9(5),
199//! 1988 (the reach by depth-first search); J. A. J. Hall and K. I. M.
200//! McKinnon, "Hyper-sparsity in the revised simplex method and how to
201//! exploit it", Comput. Optim. Appl. 32, 2005 (hypersparse FTRAN/BTRAN,
202//! the density thresholds); T. A. Davis, *Direct Methods for Sparse Linear
203//! Systems* (2006), §3.2 (`cs_reach`, `cs_dfs`); Maros (2003), §5.4–5.5;
204//! Forrest & Tomlin (1972); Suhl & Suhl (1990), §4; Koberstein (2005),
205//! §5.3; HiGHS `HFactor` (studied, not copied).
206
207use crate::SparseVec;
208use crate::factor::{Factors, Lu, VACANT, idx32};
209use crate::real::{Real, is_exact_zero, lt};
210use crate::update::BasisUpdate;
211
212/// Weight of the newest result in the moving average of the result
213/// density of each kind of solve (policy (c) of the module documentation).
214pub const HYPER_HISTORY_WEIGHT: f64 = 0.05;
215
216/// "No `L` eta" in the map from rows to `L` etas of [`Factors`].
217pub(crate) const NO_ETA: u32 = u32::MAX;
218
219/// Counters of one kind of solve (FTRAN, BTRAN or FTRAN for update),
220/// accumulated over the life of the [`Lu`] (not reset by a
221/// factorization). The contract's mean densities are the methods
222/// [`SolveStats::mean_rhs_density`] and
223/// [`SolveStats::mean_result_density`] over exact sums (no division per
224/// call).
225#[derive(Clone, Copy, Debug, Default, PartialEq)]
226pub struct SolveStats {
227 /// Number of calls.
228 pub calls: usize,
229 /// Calls served by the hypersparse algorithm (HF or HB).
230 pub hyper_calls: usize,
231 /// Sum over the calls of the right-hand side density `count/m`.
232 pub rhs_density_sum: f64,
233 /// Sum over the calls of the result density `count/m` (pattern size:
234 /// the reach for hypersparse calls, the exact support otherwise).
235 pub result_density_sum: f64,
236}
237
238impl SolveStats {
239 /// Mean right-hand side density (0 without calls).
240 pub fn mean_rhs_density(&self) -> f64 {
241 self.mean(self.rhs_density_sum)
242 }
243
244 /// Mean result density (0 without calls).
245 pub fn mean_result_density(&self) -> f64 {
246 self.mean(self.result_density_sum)
247 }
248
249 /// Fraction of the calls served by the hypersparse algorithm.
250 pub fn hyper_fraction(&self) -> f64 {
251 #[allow(clippy::cast_precision_loss)] // counts far below 2⁵³
252 let h = self.hyper_calls as f64;
253 self.mean(h)
254 }
255
256 fn mean(&self, sum: f64) -> f64 {
257 if self.calls == 0 {
258 0.0
259 } else {
260 #[allow(clippy::cast_precision_loss)]
261 let c = self.calls as f64;
262 sum / c
263 }
264 }
265
266 fn record(&mut self, hyper: bool, rhs_density: f64, result_density: f64) {
267 self.calls += 1;
268 self.hyper_calls += usize::from(hyper);
269 self.rhs_density_sum += rhs_density;
270 self.result_density_sum += result_density;
271 }
272}
273
274/// The kinds of solve, for the statistics and the moving averages.
275#[derive(Clone, Copy, Debug, PartialEq, Eq)]
276pub(crate) enum Kind {
277 Ftran = 0,
278 Btran = 1,
279 FtranUpdate = 2,
280}
281
282/// Work arrays of the solves and of the update (invariant (V2): the value
283/// arrays are zero between calls).
284#[derive(Clone, Debug)]
285pub(crate) struct Workspace<T: Real> {
286 /// Value work array (rows in FTRAN, positions in BTRAN and in the
287 /// update's spike row).
288 pub work: Vec<T>,
289 /// Second value work array (rows, BTRAN).
290 pub work2: Vec<T>,
291 /// Node marks of Algorithm D (and of the lists `Λ`).
292 pub mark: Vec<u32>,
293 /// Current stamp: `mark[v] == stamp` means marked.
294 pub stamp: u32,
295 /// The explicit stack of Algorithm D: `(node, next arc)`.
296 pub stack: Vec<(u32, u32)>,
297 /// The list `Λ`.
298 pub list: Vec<u32>,
299 /// The postorder of Algorithm D.
300 pub post: Vec<u32>,
301 /// Row etas currently in the priority queue of HF4/HB4.
302 pub queued: Vec<bool>,
303 /// Flags by position for the update's elimination (Chapter 5.5).
304 pub flag: Vec<bool>,
305}
306
307impl<T: Real> Default for Workspace<T> {
308 fn default() -> Self {
309 Self {
310 work: Vec::new(),
311 work2: Vec::new(),
312 mark: Vec::new(),
313 stamp: 0,
314 stack: Vec::new(),
315 list: Vec::new(),
316 post: Vec::new(),
317 queued: Vec::new(),
318 flag: Vec::new(),
319 }
320 }
321}
322
323impl<T: Real> Workspace<T> {
324 /// Sizes the arrays for dimension `m` (all zero).
325 pub fn resize(&mut self, m: usize) {
326 if self.work.len() != m {
327 *self = Self {
328 work: vec![T::ZERO; m],
329 work2: vec![T::ZERO; m],
330 mark: vec![0; m],
331 flag: vec![false; m],
332 ..Self::default()
333 };
334 }
335 }
336
337 /// A fresh stamp for Algorithm D, step D1.
338 pub fn new_stamp(&mut self) -> u32 {
339 if self.stamp == u32::MAX {
340 self.mark.fill(0);
341 self.stamp = 0;
342 }
343 self.stamp += 1;
344 self.stamp
345 }
346}
347
348/// Algorithm D: the nodes reachable from `seeds` in the graph whose
349/// successors of `v` are `adj(v)` are appended to `post` in postorder
350/// (read it backwards for a topological order). Uses the current stamp:
351/// marked nodes are not entered.
352fn reach<'g>(
353 mark: &mut [u32],
354 stamp: u32,
355 stack: &mut Vec<(u32, u32)>,
356 seeds: &[u32],
357 post: &mut Vec<u32>,
358 adj: impl Fn(usize) -> &'g [u32],
359) {
360 // D1.
361 for &s in seeds {
362 if mark[s as usize] == stamp {
363 continue;
364 }
365 mark[s as usize] = stamp;
366 stack.push((s, 0));
367 // D2.
368 while let Some(top) = stack.last_mut() {
369 let (v, k) = *top;
370 let succ = adj(v as usize);
371 if let Some(&w) = succ.get(k as usize) {
372 top.1 = k + 1;
373 if mark[w as usize] != stamp {
374 mark[w as usize] = stamp;
375 stack.push((w, 0));
376 }
377 } else {
378 stack.pop();
379 post.push(v);
380 }
381 }
382 }
383}
384
385impl<T: Real, U: BasisUpdate<T>> Lu<T, U> {
386 /// Overwrites `rhs` (indexed by rows) with `x = B⁻¹ rhs` (indexed by
387 /// positions) — Algorithm F or HF (see the alternation policy) — and,
388 /// for [`Kind::FtranUpdate`], records the spike `R L⁻¹ a_q` (after step
389 /// F2/HF4) for the next update.
390 ///
391 /// # Panics
392 /// If the factor is not valid or `rhs.size() != m` — both are
393 /// violations of the calling contract.
394 pub(crate) fn ftran_kind(&mut self, rhs: &mut SparseVec<T>, kind: Kind) {
395 self.factors.assert_ready(rhs);
396 let rhs_density = rhs.density();
397 let hyper = self.use_hyper(rhs, kind);
398 let mut ws = std::mem::take(&mut self.factors.ws);
399 let for_update = kind == Kind::FtranUpdate;
400 if hyper {
401 self.factors
402 .ftran_hyper(&self.update, rhs, &mut ws, for_update);
403 } else {
404 self.factors
405 .ftran_dense(&self.update, rhs, &mut ws, for_update);
406 }
407 self.factors.ws = ws;
408 if for_update {
409 self.factors.spike_valid = true;
410 }
411 self.record(kind, hyper, rhs_density, rhs.density());
412 }
413
414 /// Overwrites `rhs` (indexed by positions) with `y = B⁻ᵀ rhs` (indexed
415 /// by rows), by Algorithm B or HB (see the alternation policy).
416 ///
417 /// # Panics
418 /// As [`Lu::ftran_kind`].
419 pub(crate) fn btran_impl(&mut self, rhs: &mut SparseVec<T>) {
420 self.factors.assert_ready(rhs);
421 let rhs_density = rhs.density();
422 let hyper = self.use_hyper(rhs, Kind::Btran);
423 let mut ws = std::mem::take(&mut self.factors.ws);
424 if hyper {
425 self.factors.btran_hyper(&self.update, rhs, &mut ws);
426 } else {
427 self.factors.btran_dense(&self.update, rhs, &mut ws);
428 }
429 self.factors.ws = ws;
430 self.record(Kind::Btran, hyper, rhs_density, rhs.density());
431 }
432
433 /// The alternation policy (a)–(c).
434 fn use_hyper(&self, rhs: &SparseVec<T>, kind: Kind) -> bool {
435 rhs.is_sparse()
436 && !lt(self.opts.hyper_rhs_density, rhs.density())
437 && !lt(
438 self.opts.hyper_result_density,
439 self.expected_density[kind as usize],
440 )
441 }
442
443 /// Statistics and moving average of one call.
444 fn record(&mut self, kind: Kind, hyper: bool, rhs_density: f64, result_density: f64) {
445 let ema = &mut self.expected_density[kind as usize];
446 *ema = (1.0 - HYPER_HISTORY_WEIGHT) * *ema + HYPER_HISTORY_WEIGHT * result_density;
447 let st = &mut self.factors.stats;
448 let s = match kind {
449 Kind::Ftran => &mut st.ftran,
450 Kind::Btran => &mut st.btran,
451 Kind::FtranUpdate => &mut st.ftran_update,
452 };
453 s.record(hyper, rhs_density, result_density);
454 }
455
456 /// Expected result density of each kind of solve (policy (c)), for
457 /// diagnostics: `[ftran, btran, ftran_for_update]`.
458 pub fn expected_densities(&self) -> [f64; 3] {
459 self.expected_density
460 }
461}
462
463impl<T: Real> Factors<T> {
464 fn assert_ready(&self, rhs: &SparseVec<T>) {
465 assert!(
466 self.valid,
467 "solve with a factor that holds no valid factorization"
468 );
469 assert_eq!(rhs.size(), self.m, "right-hand side has the wrong size");
470 }
471
472 /// Records the spike `y` (row-indexed) given on the rows of `rows`,
473 /// or on all rows when `rows` is `None`.
474 fn record_spike(&mut self, y: &[T], rows: Option<&[u32]>) {
475 for &i in &self.spike_idx {
476 self.spike[i as usize] = T::ZERO;
477 }
478 self.spike_idx.clear();
479 match rows {
480 Some(rows) => {
481 for &i in rows {
482 self.spike[i as usize] = y[i as usize];
483 }
484 self.spike_idx.extend_from_slice(rows);
485 }
486 None => {
487 for (i, (&v, s)) in y.iter().zip(self.spike.iter_mut()).enumerate() {
488 if !is_exact_zero(v) {
489 *s = v;
490 self.spike_idx.push(idx32(i));
491 }
492 }
493 }
494 }
495 }
496
497 // ----------------------------------------------------------------------
498 // Dense algorithms F and B.
499
500 /// Algorithm F.
501 fn ftran_dense<U: BasisUpdate<T>>(
502 &mut self,
503 upd: &U,
504 rhs: &mut SparseVec<T>,
505 ws: &mut Workspace<T>,
506 for_update: bool,
507 ) {
508 let y = rhs.values_mut();
509 self.apply_l(y);
510 upd.apply_ftran(y);
511 if for_update {
512 self.record_spike(y, None);
513 }
514 self.solve_u(y, &mut ws.work2);
515 rhs.rebuild_pattern();
516 }
517
518 /// Algorithm B.
519 fn btran_dense<U: BasisUpdate<T>>(
520 &self,
521 upd: &U,
522 rhs: &mut SparseVec<T>,
523 ws: &mut Workspace<T>,
524 ) {
525 let c = rhs.values_mut();
526 let w = &mut ws.work2;
527 self.solve_ut(c, w);
528 upd.apply_btran(w);
529 self.apply_lt(w);
530 // c is all zero after B1; swap the result in and keep (V2).
531 c.swap_with_slice(w);
532 rhs.rebuild_pattern();
533 }
534
535 /// Step F1.
536 pub(crate) fn apply_l(&self, y: &mut [T]) {
537 for (e, &p) in self.l_pivot.iter().enumerate() {
538 let y_p = y[p as usize];
539 if is_exact_zero(y_p) {
540 continue;
541 }
542 let r = self.l_start[e]..self.l_start[e + 1];
543 for (&i, &l) in self.l_index[r.clone()].iter().zip(&self.l_value[r]) {
544 y[i as usize] -= l * y_p;
545 }
546 }
547 }
548
549 /// Step F3: on entry `y` is indexed by rows, on exit by positions; `x`
550 /// is a zero scratch array of length `m`, zero again on exit.
551 fn solve_u(&self, y: &mut [T], x: &mut [T]) {
552 for s in (0..self.step_row.len()).rev() {
553 let (p, q) = (self.step_row[s], self.step_col[s]);
554 if p == VACANT {
555 continue;
556 }
557 let (p, q) = (p as usize, q as usize);
558 let x_q = y[p] / self.diag[p];
559 x[q] = x_q;
560 if is_exact_zero(x_q) {
561 continue;
562 }
563 let (idx, val) = self.u_cols.line(q);
564 for (&i, &u) in idx.iter().zip(val) {
565 y[i as usize] -= u * x_q;
566 }
567 }
568 // Every y_p was consumed (y is now zero up to the values x took).
569 y.copy_from_slice(x);
570 x.fill(T::ZERO);
571 }
572
573 /// Step B1: `c` is indexed by positions (and left zero); the result is
574 /// written into the zero row-indexed array `w`.
575 fn solve_ut(&self, c: &mut [T], w: &mut [T]) {
576 for s in 0..self.step_row.len() {
577 let (p, q) = (self.step_row[s], self.step_col[s]);
578 if p == VACANT {
579 continue;
580 }
581 let (p, q) = (p as usize, q as usize);
582 let w_p = c[q] / self.diag[p];
583 c[q] = T::ZERO;
584 w[p] = w_p;
585 if is_exact_zero(w_p) {
586 continue;
587 }
588 let (idx, val) = self.u_rows.line(p);
589 for (&j, &u) in idx.iter().zip(val) {
590 c[j as usize] -= u * w_p;
591 }
592 }
593 }
594
595 /// Step B3.
596 fn apply_lt(&self, w: &mut [T]) {
597 for (e, &p) in self.l_pivot.iter().enumerate().rev() {
598 let r = self.l_start[e]..self.l_start[e + 1];
599 let s: T = self.l_index[r.clone()]
600 .iter()
601 .zip(&self.l_value[r])
602 .map(|(&i, &l)| l * w[i as usize])
603 .sum();
604 w[p as usize] -= s;
605 }
606 }
607
608 // ----------------------------------------------------------------------
609 // Hypersparse algorithms HF and HB.
610
611 /// The successors of row `p` in `G_L`.
612 #[inline]
613 fn l_succ(&self, p: usize) -> &[u32] {
614 match self.l_eta_of_row[p] {
615 NO_ETA => &[],
616 e => &self.l_index[self.l_start[e as usize]..self.l_start[e as usize + 1]],
617 }
618 }
619
620 /// Position pivoted with row `p`.
621 #[inline]
622 fn q_of(&self, p: usize) -> usize {
623 self.step_col[self.row_step[p]] as usize
624 }
625
626 /// Row pivoted with position `q`.
627 #[inline]
628 fn p_of(&self, q: usize) -> usize {
629 self.step_row[self.col_step[q]] as usize
630 }
631
632 /// Algorithm HF.
633 fn ftran_hyper<U: BasisUpdate<T>>(
634 &mut self,
635 upd: &U,
636 rhs: &mut SparseVec<T>,
637 ws: &mut Workspace<T>,
638 for_update: bool,
639 ) {
640 // HF1. Load.
641 let mut seeds = std::mem::take(&mut ws.list);
642 seeds.clear();
643 for &i in rhs.pattern().unwrap_or(&[]) {
644 let v = rhs.get(i);
645 if !is_exact_zero(v) {
646 ws.work[i as usize] = v;
647 seeds.push(i);
648 }
649 }
650 rhs.clear();
651 // HF2. Reach of L.
652 let stamp = ws.new_stamp();
653 ws.post.clear();
654 reach(
655 &mut ws.mark,
656 stamp,
657 &mut ws.stack,
658 &seeds,
659 &mut ws.post,
660 |p| self.l_succ(p),
661 );
662 // HF3. Apply L in topological order; Λ = the reach.
663 let y = &mut ws.work;
664 for &p in ws.post.iter().rev() {
665 let e = self.l_eta_of_row[p as usize];
666 let y_p = y[p as usize];
667 if e == NO_ETA || is_exact_zero(y_p) {
668 continue;
669 }
670 let r = self.l_start[e as usize]..self.l_start[e as usize + 1];
671 for (&i, &l) in self.l_index[r.clone()].iter().zip(&self.l_value[r]) {
672 y[i as usize] -= l * y_p;
673 }
674 }
675 let mut list = seeds;
676 list.clear();
677 list.extend(ws.post.iter().rev());
678 // HF4. Row etas, in creation order through a min-queue
679 // (Chapter 5.5: the update policy owns them).
680 upd.apply_ftran_hyper(
681 &mut ws.work,
682 &mut list,
683 &mut ws.mark,
684 ws.stamp,
685 &mut ws.queued,
686 );
687 if for_update {
688 self.record_spike(&ws.work, Some(&list));
689 }
690 // HF5. Reach of U.
691 let stamp = ws.new_stamp();
692 ws.post.clear();
693 reach(
694 &mut ws.mark,
695 stamp,
696 &mut ws.stack,
697 &list,
698 &mut ws.post,
699 |p| self.u_cols.line(self.q_of(p)).0,
700 );
701 // HF6. Apply U in topological order.
702 let y = &mut ws.work;
703 for &p in ws.post.iter().rev() {
704 let p = p as usize;
705 let q = self.q_of(p);
706 let x_q = y[p] / self.diag[p];
707 y[p] = T::ZERO;
708 rhs.set(idx32(q), x_q);
709 if is_exact_zero(x_q) {
710 continue;
711 }
712 let (idx, val) = self.u_cols.line(q);
713 for (&i, &u) in idx.iter().zip(val) {
714 y[i as usize] -= u * x_q;
715 }
716 }
717 ws.list = list;
718 }
719
720 /// Algorithm HB.
721 fn btran_hyper<U: BasisUpdate<T>>(
722 &self,
723 upd: &U,
724 rhs: &mut SparseVec<T>,
725 ws: &mut Workspace<T>,
726 ) {
727 // HB1. Load (positions).
728 let mut list = std::mem::take(&mut ws.list);
729 list.clear();
730 for &q in rhs.pattern().unwrap_or(&[]) {
731 let v = rhs.get(q);
732 if !is_exact_zero(v) {
733 ws.work[q as usize] = v;
734 list.push(q);
735 }
736 }
737 rhs.clear();
738 // HB2. Reach of Uᵀ.
739 let stamp = ws.new_stamp();
740 ws.post.clear();
741 reach(
742 &mut ws.mark,
743 stamp,
744 &mut ws.stack,
745 &list,
746 &mut ws.post,
747 |q| self.u_rows.line(self.p_of(q)).0,
748 );
749 // HB3. Apply Uᵀ in topological order; Λ = the pivot rows reached.
750 list.clear();
751 for &q in ws.post.iter().rev() {
752 let q = q as usize;
753 let p = self.p_of(q);
754 let w_p = ws.work[q] / self.diag[p];
755 ws.work[q] = T::ZERO;
756 ws.work2[p] = w_p;
757 list.push(idx32(p));
758 if is_exact_zero(w_p) {
759 continue;
760 }
761 let (idx, val) = self.u_rows.line(p);
762 for (&j, &u) in idx.iter().zip(val) {
763 ws.work[j as usize] -= u * w_p;
764 }
765 }
766 let stamp = ws.new_stamp();
767 for &p in &list {
768 ws.mark[p as usize] = stamp;
769 }
770 // HB4. Row etas, in reverse creation order through a max-queue
771 // (Chapter 5.5: the update policy owns them).
772 upd.apply_btran_hyper(
773 &mut ws.work2,
774 &mut list,
775 &mut ws.mark,
776 ws.stamp,
777 &mut ws.queued,
778 );
779 // HB5. Reach of Lᵀ.
780 let stamp = ws.new_stamp();
781 ws.post.clear();
782 reach(
783 &mut ws.mark,
784 stamp,
785 &mut ws.stack,
786 &list,
787 &mut ws.post,
788 |i| &self.lt_pivot[self.lt_start[i]..self.lt_start[i + 1]],
789 );
790 // HB6. Apply Lᵀ in topological order, then store the result.
791 let w = &mut ws.work2;
792 for &i in ws.post.iter().rev() {
793 let i = i as usize;
794 let w_i = w[i];
795 if is_exact_zero(w_i) {
796 continue;
797 }
798 let r = self.lt_start[i]..self.lt_start[i + 1];
799 for (&p, &l) in self.lt_pivot[r.clone()].iter().zip(&self.lt_value[r]) {
800 w[p as usize] -= l * w_i;
801 }
802 }
803 for &i in ws.post.iter().rev() {
804 rhs.set(i, w[i as usize]);
805 w[i as usize] = T::ZERO;
806 }
807 ws.list = list;
808 }
809}