QR
QR decomposition factors a rows x cols matrix a (with rows >= cols) as q * r,
where q has orthonormal columns and r is upper triangular. rustebra provides two
algorithms:
qr_householder— buildsqas a fullrows x rowsorthogonal matrix via Householder reflections, zeroing out the sub-diagonal of each column in turn.qr_gram_schmidt— modified Gram-Schmidt orthogonalization, producing arows x colsq(only as many orthonormal columns asahas, not a full square orthogonal matrix). Projecting each column against the running, already-orthogonalized vector (rather than the original column) is what keeps rounding error from compounding as badly as classical Gram-Schmidt does.
qr is the general-purpose entry point and currently delegates to qr_householder. Both
algorithms, plus the entry point, are demonstrated below, alongside the ergonomic
StaticMatrix/DynamicMatrix::qr() method used in
Matrix Operations.
#![allow(unused)]
fn main() {
use rustebra::algorithm::matrix::{qr, qr_gram_schmidt, qr_householder};
use rustebra::storage::StaticStorage;
pub(crate) fn run() {
println!("\n== QR decomposition ==");
// [[3, 5], [4, 0]].
let a = StaticStorage::new([3.0, 5.0, 4.0, 0.0]);
let mut scratch = [0.0; 2];
let mut q = [0.0; 4];
let mut r = [0.0; 4];
qr(&a, 2, 2, &mut q, &mut r, &mut scratch).unwrap();
println!("q = {q:?}, r = {r:?}");
let mut q_householder = [0.0; 4];
let mut r_householder = [0.0; 4];
qr_householder(
&a,
2,
2,
&mut q_householder,
&mut r_householder,
&mut scratch,
)
.unwrap();
println!("q (explicit householder) = {q_householder:?}, r = {r_householder:?}");
let mut q_gram_schmidt = [0.0; 4];
let mut r_gram_schmidt = [0.0; 4];
qr_gram_schmidt(
&a,
2,
2,
&mut q_gram_schmidt,
&mut r_gram_schmidt,
&mut scratch,
)
.unwrap();
println!("q (explicit gram-schmidt) = {q_gram_schmidt:?}, r = {r_gram_schmidt:?}");
}
}
Gotchas
qr_householder’sqisrows x rows;qr_gram_schmidt’sqis onlyrows x cols. Don’t assume both produce a same-shapedqif you swap between them.- If a column of
alies entirely in the span of the previous columns (or is zero), Gram- Schmidt has no direction left to normalize into that column ofq— rather than erroring, it leaves that column as0, since linear dependence is a property of the input, not a malformed call. - Both algorithms require
rows >= cols; this can’t be checked by the type system onStaticMatrix(stable Rust can’t bound one const generic against another), soqr()returnsResulton both matrix types even though most otherStaticMatrixoperations don’t need to.