1use crate::UnsafeRandom1DAccessByValue;
4use crate::base_types::{RlstError, RlstResult};
5use crate::dense::array::{Array, DynArray};
6use crate::dense::linalg::lapack::interface::trsm::{TrsmDiag, TrsmSide, TrsmTransA, TrsmUplo};
7use crate::traits::base_operations::Shape;
8use crate::traits::linalg::{decompositions::Lq, lapack::Lapack};
9
10pub use super::qr::QMode;
11pub use crate::dense::linalg::lapack::interface::mqr::{MqrSide, MqrTransMode};
12
13pub struct LqDecomposition<Item> {
21 a: DynArray<Item, 2>,
22 tau: Vec<Item>,
23}
24
25impl<Item, ArrayImpl> Lq for Array<ArrayImpl, 2>
26where
27 Item: Lapack,
28 ArrayImpl: UnsafeRandom1DAccessByValue<Item = Item> + Shape<2>,
29{
30 type Item = Item;
31
32 fn lq(&self) -> RlstResult<LqDecomposition<Item>> {
33 let mut a = DynArray::new_from(self);
34 let [m, n] = a.shape();
35 let mut tau = vec![Item::zero(); m.min(n)];
36 if m != 0 && n != 0 {
37 Item::gelqf(m, n, a.data_mut().unwrap(), m, &mut tau)?;
38 }
39 Ok(LqDecomposition { a, tau })
40 }
41}
42
43impl<Item> LqDecomposition<Item>
44where
45 Item: Lapack,
46{
47 pub fn q_mat(&self, mode: QMode) -> RlstResult<DynArray<Item, 2>> {
52 let [m, n] = self.a.shape();
53 let k = m.min(n);
54 let q_rows = match mode {
55 QMode::Compact => k,
56 QMode::Full => n,
57 };
58 let mut q = DynArray::<Item, 2>::from_shape([q_rows, n]);
59 for row in 0..k {
60 for col in row + 1..n {
61 q[[row, col]] = self.a[[row, col]];
62 }
63 }
64 if k == 0 {
65 if matches!(mode, QMode::Full) {
66 q.set_identity();
67 }
68 return Ok(q);
69 }
70 Item::orglq(
71 q_rows,
72 n,
73 k,
74 q.data_mut().unwrap(),
75 q_rows.max(1),
76 &self.tau,
77 )?;
78 Ok(q)
79 }
80
81 pub fn l_mat(&self) -> RlstResult<DynArray<Item, 2>> {
83 let [m, n] = self.a.shape();
84 let k = m.min(n);
85 let mut l = DynArray::<Item, 2>::from_shape([m, k]);
86 for col in 0..k {
87 for row in col..m {
88 l[[row, col]] = self.a[[row, col]];
89 }
90 }
91 Ok(l)
92 }
93
94 pub fn apply_q<ArrayImpl>(
103 &self,
104 side: MqrSide,
105 trans: MqrTransMode,
106 rhs: &Array<ArrayImpl, 2>,
107 ) -> RlstResult<DynArray<Item, 2>>
108 where
109 ArrayImpl: UnsafeRandom1DAccessByValue<Item = Item> + Shape<2>,
110 {
111 let [rows, cols] = rhs.shape();
112 let n = self.a.shape()[1];
113 let affected = match side {
114 MqrSide::Left => rows,
115 MqrSide::Right => cols,
116 };
117 if affected != n {
118 return Err(RlstError::GeneralError(format!(
119 "LQ Q application requires affected dimension {n}, got {affected}."
120 )));
121 }
122 let mut result = DynArray::new_from(rhs);
123 if rows == 0 || cols == 0 || self.tau.is_empty() {
124 return Ok(result);
125 }
126 Item::mlq(
127 side,
128 trans,
129 rows,
130 cols,
131 self.tau.len(),
132 self.a.data().unwrap(),
133 self.a.shape()[0].max(1),
134 &self.tau,
135 result.data_mut().unwrap(),
136 rows.max(1),
137 )?;
138 Ok(result)
139 }
140
141 pub fn solve<ArrayImpl, const NDIM: usize>(
153 &self,
154 rhs: &Array<ArrayImpl, NDIM>,
155 ) -> RlstResult<DynArray<Item, NDIM>>
156 where
157 ArrayImpl: UnsafeRandom1DAccessByValue<Item = Item> + Shape<NDIM>,
158 {
159 if NDIM != 1 && NDIM != 2 {
160 return Err(RlstError::GeneralError(
161 "LQ solve is only implemented for 1D and 2D arrays.".to_string(),
162 ));
163 }
164 let [m, n] = self.a.shape();
165 if m > n {
166 return Err(RlstError::GeneralError(
167 "LQ minimum-norm solve requires m <= n.".to_string(),
168 ));
169 }
170 if rhs.shape()[0] != m {
171 return Err(RlstError::GeneralError(format!(
172 "LQ solve requires {m} right-hand-side rows, got {}.",
173 rhs.shape()[0]
174 )));
175 }
176 for diagonal in 0..m {
177 if self.a[[diagonal, diagonal]] == Item::zero() {
178 return Err(RlstError::GeneralError(
179 "LQ minimum-norm solve requires full row rank.".to_string(),
180 ));
181 }
182 }
183
184 let nrhs = if NDIM == 1 { 1 } else { rhs.shape()[1] };
185 let mut shape = rhs.shape();
186 shape[0] = n;
187 let mut solution = DynArray::<Item, NDIM>::from_shape(shape);
188 let rhs_copy = DynArray::new_from(rhs);
189 for col in 0..nrhs {
190 for row in 0..m {
191 solution.data_mut().unwrap()[row + n * col] =
192 rhs_copy.data().unwrap()[row + m * col];
193 }
194 }
195 if m == 0 || nrhs == 0 {
196 return Ok(solution);
197 }
198
199 let mut l11 = DynArray::<Item, 2>::from_shape([m, m]);
200 for col in 0..m {
201 for row in col..m {
202 l11[[row, col]] = self.a[[row, col]];
203 }
204 }
205 Item::trsm(
206 TrsmSide::Left,
207 TrsmUplo::Lower,
208 TrsmTransA::NoTrans,
209 TrsmDiag::NonUnit,
210 m,
211 nrhs,
212 Item::one(),
213 l11.data_mut().unwrap(),
214 m,
215 solution.data_mut().unwrap(),
216 n,
217 )?;
218 Item::mlq(
219 MqrSide::Left,
220 MqrTransMode::ConjugateTranspose,
221 n,
222 nrhs,
223 m,
224 self.a.data().unwrap(),
225 m.max(1),
226 &self.tau,
227 solution.data_mut().unwrap(),
228 n,
229 )?;
230 Ok(solution)
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::{MqrSide, MqrTransMode, QMode};
237 use crate::base_types::{c32, c64};
238 use crate::dense::array::DynArray;
239 use crate::{Lq, MultIntoResize, RlstScalar, empty_array};
240 use paste::paste;
241
242 macro_rules! lq_scalar_tests {
243 ($scalar:ty, $tol:expr) => {
244 paste! {
245 #[test]
246 fn [<lq_reconstruct_apply_and_solve_ $scalar>]() {
247 let mut a = DynArray::<$scalar, 2>::from_shape([2, 3]);
248 a.fill_from_seed_equally_distributed(17);
249 a[[0, 0]] += <$scalar>::from_real(3.0);
250 a[[1, 1]] += <$scalar>::from_real(3.0);
251 let lq = a.lq().unwrap();
252 let l = lq.l_mat().unwrap();
253 let qc = lq.q_mat(QMode::Compact).unwrap();
254 let reconstructed = empty_array().simple_mult_into_resize(l.r(), qc.r());
255 crate::assert_array_relative_eq!(reconstructed, a, $tol);
256 let qch = DynArray::new_from(&qc.r().conj().transpose());
257 let qcqch = empty_array().simple_mult_into_resize(qc.r(), qch.r());
258 let mut compact_identity = DynArray::<$scalar, 2>::from_shape([2, 2]);
259 compact_identity.set_identity();
260 crate::assert_array_relative_eq!(qcqch, compact_identity, $tol);
261
262 for shape in [[2, 2], [3, 2]] {
263 let mut other = DynArray::<$scalar, 2>::from_shape(shape);
264 other.fill_from_seed_equally_distributed(31);
265 other[[0, 0]] += <$scalar>::from_real(3.0);
266 other[[1, 1]] += <$scalar>::from_real(3.0);
267 let other_lq = other.lq().unwrap();
268 let other_reconstructed = empty_array().simple_mult_into_resize(
269 other_lq.l_mat().unwrap().r(),
270 other_lq.q_mat(QMode::Compact).unwrap().r(),
271 );
272 crate::assert_array_relative_eq!(other_reconstructed, other, $tol);
273 }
274
275 let q = lq.q_mat(QMode::Full).unwrap();
276 let qh = DynArray::new_from(&q.r().conj().transpose());
277 let mut identity = DynArray::<$scalar, 2>::from_shape([3, 3]);
278 identity.set_identity();
279 let qqh = empty_array().simple_mult_into_resize(q.r(), qh.r());
280 crate::assert_array_relative_eq!(qqh, identity, $tol);
281
282 let mut left_rhs = DynArray::<$scalar, 2>::from_shape([3, 2]);
283 left_rhs.fill_from_seed_equally_distributed(41);
284 let left = lq.apply_q(MqrSide::Left, MqrTransMode::NoTranspose, &left_rhs).unwrap();
285 let explicit_left = empty_array().simple_mult_into_resize(q.r(), left_rhs.r());
286 crate::assert_array_relative_eq!(left, explicit_left, $tol);
287 let left_adjoint = lq.apply_q(MqrSide::Left, MqrTransMode::ConjugateTranspose, &left_rhs).unwrap();
288 let explicit_left_adjoint = empty_array().simple_mult_into_resize(qh.r(), left_rhs.r());
289 crate::assert_array_relative_eq!(left_adjoint, explicit_left_adjoint, $tol);
290 let mut right_rhs = DynArray::<$scalar, 2>::from_shape([2, 3]);
291 right_rhs.fill_from_seed_equally_distributed(43);
292 let right = lq.apply_q(MqrSide::Right, MqrTransMode::ConjugateTranspose, &right_rhs).unwrap();
293 let explicit_right = empty_array().simple_mult_into_resize(right_rhs.r(), qh.r());
294 crate::assert_array_relative_eq!(right, explicit_right, $tol);
295 let right_plain = lq.apply_q(MqrSide::Right, MqrTransMode::NoTranspose, &right_rhs).unwrap();
296 let explicit_right_plain = empty_array().simple_mult_into_resize(right_rhs.r(), q.r());
297 crate::assert_array_relative_eq!(right_plain, explicit_right_plain, $tol);
298 let q_again = lq.q_mat(QMode::Full).unwrap();
299 crate::assert_array_relative_eq!(q_again, q, $tol);
300
301 let mut expected = DynArray::<$scalar, 2>::from_shape([3, 2]);
302 expected.fill_from_seed_equally_distributed(23);
303 let rhs = empty_array().simple_mult_into_resize(a.r(), expected.r());
304 let x = lq.solve(&rhs).unwrap();
305 let residual = empty_array().simple_mult_into_resize(a.r(), x.r());
306 crate::assert_array_relative_eq!(residual, rhs, $tol);
307 let qx = empty_array().simple_mult_into_resize(q.r(), x.r());
308 for col in 0..2 {
309 assert!(qx[[2, col]].abs() < $tol * 10.0);
310 }
311
312 let mut rhs_vector = DynArray::<$scalar, 1>::from_shape([2]);
313 rhs_vector[[0]] = rhs[[0, 0]];
314 rhs_vector[[1]] = rhs[[1, 0]];
315 let vector_x = lq.solve(&rhs_vector).unwrap();
316 assert_eq!(vector_x.shape(), [3]);
317 for row in 0..3 {
318 assert!((vector_x[[row]] - x[[row, 0]]).abs() < $tol);
319 }
320 }
321 }
322 };
323 }
324
325 lq_scalar_tests!(f32, 2e-4);
326 lq_scalar_tests!(f64, 2e-12);
327 lq_scalar_tests!(c32, 3e-4);
328 lq_scalar_tests!(c64, 3e-12);
329
330 #[test]
331 fn lq_shapes_views_errors_and_empty_contracts() {
332 let mut source = DynArray::<f64, 2>::from_shape([3, 2]);
333 source.fill_from_seed_equally_distributed(5);
334 let view = source.transpose();
335 let lq = view.lq().unwrap();
336 assert_eq!(lq.l_mat().unwrap().shape(), [2, 2]);
337 assert_eq!(lq.q_mat(QMode::Compact).unwrap().shape(), [2, 3]);
338 assert_eq!(lq.q_mat(QMode::Full).unwrap().shape(), [3, 3]);
339 assert!(
340 lq.apply_q(
341 MqrSide::Left,
342 MqrTransMode::NoTranspose,
343 &DynArray::from_shape([2, 1])
344 )
345 .is_err()
346 );
347 assert!(lq.solve(&DynArray::<f64, 1>::from_shape([3])).is_err());
348 let rhs_storage = DynArray::<f64, 2>::from_shape([1, 2]);
349 assert_eq!(lq.solve(&rhs_storage.transpose()).unwrap().shape(), [3, 1]);
350 let apply_storage = DynArray::<f64, 2>::from_shape([1, 3]);
351 assert_eq!(
352 lq.apply_q(
353 MqrSide::Left,
354 MqrTransMode::NoTranspose,
355 &apply_storage.transpose(),
356 )
357 .unwrap()
358 .shape(),
359 [3, 1]
360 );
361
362 let tall = DynArray::<f64, 2>::from_shape([3, 2]).lq().unwrap();
363 assert!(tall.solve(&DynArray::<f64, 1>::from_shape([3])).is_err());
364 let singular = DynArray::<f64, 2>::from_shape([2, 3]).lq().unwrap();
365 assert!(
366 singular
367 .solve(&DynArray::<f64, 1>::from_shape([2]))
368 .is_err()
369 );
370
371 let empty = DynArray::<f64, 2>::from_shape([0, 3]).lq().unwrap();
372 let q = empty.q_mat(QMode::Full).unwrap();
373 let mut identity = DynArray::<f64, 2>::from_shape([3, 3]);
374 identity.set_identity();
375 crate::assert_array_abs_diff_eq!(q, identity, 0.0);
376 let rhs = DynArray::<f64, 2>::from_shape([0, 2]);
377 let x = empty.solve(&rhs).unwrap();
378 assert_eq!(x.shape(), [3, 2]);
379 assert!(x.iter_value().all(|value| value == 0.0));
380 let zero_columns = lq.solve(&DynArray::<f64, 2>::from_shape([2, 0])).unwrap();
381 assert_eq!(zero_columns.shape(), [3, 0]);
382 let applied = lq
383 .apply_q(
384 MqrSide::Left,
385 MqrTransMode::NoTranspose,
386 &DynArray::<f64, 2>::from_shape([3, 0]),
387 )
388 .unwrap();
389 assert_eq!(applied.shape(), [3, 0]);
390 assert!(
391 lq.solve(&DynArray::<f64, 3>::from_shape([2, 1, 1]))
392 .is_err()
393 );
394 }
395}