1use crate::defaults::idx_tensor::unfold_split_inner;
36use crate::defaults::DynIndex;
37use crate::{contract_pair, unfold_split, AnyScalar, IdxTensor};
38use crate::{
39 matrix_luci_factors_from_matrix_owned, rrlu_mut, MatrixLuciFactors, RrLUOptions,
40 Scalar as MatrixScalar,
41};
42use num_complex::{Complex64, ComplexFloat};
43use tenferro_ad::EagerTensor;
44use tensor4all_tensorbackend::{Matrix, TensorElement};
45
46use crate::defaults::svd::{
47 compute_retained_rank, svd_for_factorize, svd_for_factorize_in, SvdFactorizeResult,
48};
49use crate::qr::{qr_with, qr_with_in, QrOptions};
50use crate::svd::SvdOptions;
51use crate::truncation::{
52 validate_svd_truncation_policy, SingularValueMeasure, SvdTruncationPolicy, ThresholdScale,
53 TruncationRule,
54};
55use crate::ExecutionContext;
56
57pub use crate::tensor_like::{
59 Canonical, FactorizeAlg, FactorizeError, FactorizeOptions, FactorizeResult,
60};
61
62pub fn factorize(
91 t: &IdxTensor,
92 left_inds: &[DynIndex],
93 options: &FactorizeOptions,
94) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
95 options.validate()?;
96
97 if t.is_diag() {
98 return Err(FactorizeError::UnsupportedStorage(
99 "Diagonal storage not supported for factorize",
100 ));
101 }
102 if t.tracks_grad() && matches!(options.alg, FactorizeAlg::LU | FactorizeAlg::CI) {
103 return Err(FactorizeError::UnsupportedStorage(
104 "LU and CI factorization do not support tracked tensors yet",
105 ));
106 }
107
108 if t.is_f64() {
109 factorize_impl_f64(t, left_inds, options)
110 } else if t.is_complex() {
111 factorize_impl_c64(t, left_inds, options)
112 } else {
113 Err(FactorizeError::UnsupportedStorage(
114 "factorize currently supports only f64 and Complex64 tensors",
115 ))
116 }
117}
118
119pub(crate) fn factorize_auto(
124 t: &IdxTensor,
125 left_inds: &[DynIndex],
126 options: &FactorizeOptions,
127) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
128 if options.alg != FactorizeAlg::SVD {
129 return Err(FactorizeError::InvalidOptions(
130 "automatic factorization only supports SVD options",
131 ));
132 }
133 options.validate()?;
134
135 let Some(policy) = options.svd_policy else {
136 return factorize(t, left_inds, options);
137 };
138 validate_svd_truncation_policy(policy).map_err(|error| FactorizeError::InvalidRtol(error.0))?;
139
140 let effective_cutoff = match (policy.scale, policy.measure, policy.rule) {
141 (ThresholdScale::Relative, SingularValueMeasure::SquaredValue, _) => policy.threshold,
142 (ThresholdScale::Relative, SingularValueMeasure::Value, TruncationRule::PerValue) => {
143 policy.threshold * policy.threshold
144 }
145 _ => 0.0,
146 };
147 if effective_cutoff <= 1.0e-12 || t.tracks_grad() || t.is_diag() {
148 return factorize(t, left_inds, options);
149 }
150 if !(t.is_f64() || t.is_c64()) {
151 return factorize(t, left_inds, options);
152 }
153
154 factorize_gram(t, left_inds, options, policy).or_else(|_| factorize(t, left_inds, options))
155}
156
157fn factorize_gram(
158 t: &IdxTensor,
159 left_inds: &[DynIndex],
160 options: &FactorizeOptions,
161 policy: SvdTruncationPolicy,
162) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
163 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
164 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
165 if m == 0 || n == 0 {
166 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
167 "cannot factorize a matrix with an empty dimension"
168 )));
169 }
170
171 let row = DynIndex::new_dyn(m);
172 let column = DynIndex::new_dyn(n);
173 let matrix = IdxTensor::from_inner(vec![row.clone(), column.clone()], matrix_inner)
174 .map_err(FactorizeError::ComputationError)?;
175
176 let eigenvectors_left = m <= n;
179 let gram = if eigenvectors_left {
180 let sim_row = DynIndex::new_dyn(m);
181 let adjoint = matrix
182 .conj()
183 .replaceind(&row, &sim_row)
184 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
185 contract_pair(&matrix, &adjoint)
186 } else {
187 let sim_column = DynIndex::new_dyn(n);
188 let adjoint = matrix
189 .conj()
190 .replaceind(&column, &sim_column)
191 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
192 contract_pair(&adjoint, &matrix)
193 }
194 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
195 let decomposition = gram
196 .hermitian_eigendecomposition(1.0e-12)
197 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
198
199 let lambda_scale = decomposition
200 .eigenvalues
201 .iter()
202 .map(|lambda| lambda.abs())
203 .fold(0.0_f64, f64::max);
204 if lambda_scale == 0.0 {
205 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
206 "zero Gram matrix uses the SVD fallback"
207 )));
208 }
209 let negative_tolerance = 1.0e-12 * lambda_scale;
210 let mut eigenpairs: Vec<(f64, usize)> = decomposition
211 .eigenvalues
212 .iter()
213 .copied()
214 .enumerate()
215 .map(|(column, mut lambda)| {
216 if !lambda.is_finite() {
217 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
218 "Gram eigendecomposition returned a non-finite eigenvalue"
219 )));
220 }
221 if lambda < -negative_tolerance {
222 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
223 "Gram eigendecomposition returned a materially negative eigenvalue"
224 )));
225 }
226 if lambda < 0.0 {
227 lambda = 0.0;
228 }
229 Ok((lambda, column))
230 })
231 .collect::<Result<_, _>>()?;
232 eigenpairs.sort_by(|(left, _), (right, _)| right.total_cmp(left));
233
234 let all_singular_values: Vec<f64> =
235 eigenpairs.iter().map(|(lambda, _)| lambda.sqrt()).collect();
236 let mut rank = compute_retained_rank(&all_singular_values, &policy);
237 if let Some(max_bond_dim) = options.max_bond_dim {
238 rank = rank.min(max_bond_dim);
239 }
240 rank = rank.max(1).min(m.min(n));
241 let singular_values = all_singular_values[..rank].to_vec();
242 if singular_values.contains(&0.0) {
243 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
244 "zero retained singular value uses the SVD fallback"
245 )));
246 }
247
248 let retained_columns: Vec<usize> = eigenpairs
249 .iter()
250 .take(rank)
251 .map(|(_, column)| *column)
252 .collect();
253 let basis_inner = decomposition
254 .eigenvectors
255 .as_inner()
256 .map_err(FactorizeError::ComputationError)?
257 .take_cols(&retained_columns)
258 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
259 let bond_index = DynIndex::new_bond(rank)
260 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
261 let basis_index = if eigenvectors_left {
262 row.clone()
263 } else {
264 column.clone()
265 };
266 let basis = IdxTensor::from_inner(vec![basis_index, bond_index.clone()], basis_inner)
267 .map_err(FactorizeError::ComputationError)?;
268
269 let (left_matrix, right_matrix) = if eigenvectors_left {
270 let sigma_vh = contract_pair(&basis.conj(), &matrix)
271 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
272 .permute_indices(&[bond_index.clone(), column.clone()])
273 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
274 match options.canonical {
275 Canonical::Left => (basis, sigma_vh),
276 Canonical::Right => {
277 let inverse: Vec<f64> = singular_values.iter().map(|sigma| 1.0 / sigma).collect();
278 (
279 scale_bond(&basis, &bond_index, &singular_values)?,
280 scale_bond(&sigma_vh, &bond_index, &inverse)?,
281 )
282 }
283 }
284 } else {
285 let u_sigma = contract_pair(&matrix, &basis)
286 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
287 .permute_indices(&[row.clone(), bond_index.clone()])
288 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
289 let vh = basis
290 .conj()
291 .permute_indices(&[bond_index.clone(), column.clone()])
292 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
293 match options.canonical {
294 Canonical::Right => (u_sigma, vh),
295 Canonical::Left => {
296 let inverse: Vec<f64> = singular_values.iter().map(|sigma| 1.0 / sigma).collect();
297 (
298 scale_bond(&u_sigma, &bond_index, &inverse)?,
299 scale_bond(&vh, &bond_index, &singular_values)?,
300 )
301 }
302 }
303 };
304
305 let mut output_left_indices = left_indices;
306 output_left_indices.push(bond_index.clone());
307 let left = reshape_factor(left_matrix, output_left_indices)?;
308 let mut output_right_indices = vec![bond_index.clone()];
309 output_right_indices.extend(right_indices);
310 let right = reshape_factor(right_matrix, output_right_indices)?;
311
312 Ok(FactorizeResult::new(
313 left,
314 right,
315 bond_index,
316 Some(singular_values),
317 rank,
318 ))
319}
320
321fn scale_bond(
322 tensor: &IdxTensor,
323 bond: &DynIndex,
324 values: &[f64],
325) -> Result<IdxTensor, FactorizeError> {
326 let temporary = DynIndex::new_bond(values.len())
327 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
328 let diagonal_values = values
329 .iter()
330 .map(|value| {
331 if tensor.is_complex() {
332 AnyScalar::new_complex(*value, 0.0)
333 } else {
334 AnyScalar::new_real(*value)
335 }
336 })
337 .collect();
338 let diagonal = IdxTensor::from_diag_any(vec![bond.clone(), temporary.clone()], diagonal_values)
339 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
340 contract_pair(tensor, &diagonal)
341 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
342 .replaceind(&temporary, bond)
343 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
344 .permute_indices(tensor.indices())
345 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))
346}
347
348fn reshape_factor(tensor: IdxTensor, indices: Vec<DynIndex>) -> Result<IdxTensor, FactorizeError> {
349 let dims: Vec<usize> = indices.iter().map(|index| index.dim).collect();
350 let inner = tensor
351 .as_inner()
352 .map_err(FactorizeError::ComputationError)?
353 .reshape(&dims)
354 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
355 IdxTensor::from_inner(indices, inner).map_err(FactorizeError::ComputationError)
356}
357
358pub fn factorize_full_rank(
404 t: &IdxTensor,
405 left_inds: &[DynIndex],
406 alg: FactorizeAlg,
407 canonical: Canonical,
408) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
409 if t.is_diag() {
410 return Err(FactorizeError::UnsupportedStorage(
411 "Diagonal storage not supported for factorize",
412 ));
413 }
414 if t.tracks_grad() && matches!(alg, FactorizeAlg::LU | FactorizeAlg::CI) {
415 return Err(FactorizeError::UnsupportedStorage(
416 "LU and CI factorization do not support tracked tensors yet",
417 ));
418 }
419
420 if t.is_f64() {
421 factorize_impl_f64_full_rank(t, left_inds, alg, canonical)
422 } else if t.is_complex() {
423 factorize_impl_c64_full_rank(t, left_inds, alg, canonical)
424 } else {
425 Err(FactorizeError::UnsupportedStorage(
426 "factorize currently supports only f64 and Complex64 tensors",
427 ))
428 }
429}
430
431pub fn factorize_in(
476 t: &IdxTensor,
477 left_inds: &[DynIndex],
478 options: &FactorizeOptions,
479 context: &ExecutionContext,
480) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
481 options.validate()?;
482 t.validate_context(context)
483 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
484
485 if t.is_diag() {
486 return Err(FactorizeError::UnsupportedStorage(
487 "Diagonal storage not supported for factorize",
488 ));
489 }
490
491 if !(t.is_f64() || t.is_complex()) {
492 return Err(FactorizeError::UnsupportedStorage(
493 "factorize currently supports only f64 and Complex64 tensors",
494 ));
495 }
496
497 match options.alg {
498 FactorizeAlg::SVD => factorize_svd_with_options_in(t, left_inds, options, context),
499 FactorizeAlg::QR => factorize_qr_with_options_in(t, left_inds, options, context),
500 FactorizeAlg::LU | FactorizeAlg::CI => Err(FactorizeError::UnsupportedStorage(
501 "LU and CI factorization have no context-scoped path; use factorize() on host inputs",
502 )),
503 }
504}
505
506pub fn factorize_full_rank_in(
517 t: &IdxTensor,
518 left_inds: &[DynIndex],
519 alg: FactorizeAlg,
520 canonical: Canonical,
521 context: &ExecutionContext,
522) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
523 t.validate_context(context)
524 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
525
526 if t.is_diag() {
527 return Err(FactorizeError::UnsupportedStorage(
528 "Diagonal storage not supported for factorize",
529 ));
530 }
531
532 if !(t.is_f64() || t.is_complex()) {
533 return Err(FactorizeError::UnsupportedStorage(
534 "factorize currently supports only f64 and Complex64 tensors",
535 ));
536 }
537
538 match alg {
539 FactorizeAlg::SVD => {
540 factorize_svd_with_options_in_full_rank(t, left_inds, canonical, context)
541 }
542 FactorizeAlg::QR => factorize_qr_full_rank_in(t, left_inds, canonical, context),
543 FactorizeAlg::LU | FactorizeAlg::CI => Err(FactorizeError::UnsupportedStorage(
544 "LU and CI factorization have no context-scoped path; use factorize_full_rank() on host inputs",
545 )),
546 }
547}
548
549fn factorize_svd_with_options_in(
550 t: &IdxTensor,
551 left_inds: &[DynIndex],
552 options: &FactorizeOptions,
553 context: &ExecutionContext,
554) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
555 let mut svd_options = SvdOptions::new();
556 if let Some(policy) = options.svd_policy {
557 svd_options = svd_options.with_policy(policy);
558 }
559 if let Some(max_bond_dim) = options.max_bond_dim {
560 svd_options = svd_options.with_max_bond_dim(max_bond_dim);
561 }
562
563 factorize_svd_with_eager_options_in(t, left_inds, options.canonical, &svd_options, context)
564}
565
566fn factorize_svd_with_options_in_full_rank(
567 t: &IdxTensor,
568 left_inds: &[DynIndex],
569 canonical: Canonical,
570 context: &ExecutionContext,
571) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
572 let svd_options = SvdOptions::full_rank();
573 factorize_svd_with_eager_options_in(t, left_inds, canonical, &svd_options, context)
574}
575
576fn factorize_svd_with_eager_options_in(
577 t: &IdxTensor,
578 left_inds: &[DynIndex],
579 canonical: Canonical,
580 svd_options: &SvdOptions,
581 context: &ExecutionContext,
582) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
583 let result = svd_for_factorize_in(t, left_inds, svd_options, context)
584 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
585 assemble_svd_factors(result, canonical)
586}
587
588fn factorize_qr_with_options_in(
589 t: &IdxTensor,
590 left_inds: &[DynIndex],
591 options: &FactorizeOptions,
592 context: &ExecutionContext,
593) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
594 if options.canonical == Canonical::Right {
595 return Err(FactorizeError::UnsupportedCanonical(
596 "QR only supports Canonical::Left (would need LQ for right)",
597 ));
598 }
599
600 let qr_options = if let Some(rtol) = options.qr_rtol {
601 QrOptions::new().with_rtol(rtol)
602 } else {
603 QrOptions::new()
604 };
605
606 factorize_qr_with_eager_options_in(t, left_inds, &qr_options, context)
607}
608
609fn factorize_qr_full_rank_in(
610 t: &IdxTensor,
611 left_inds: &[DynIndex],
612 canonical: Canonical,
613 context: &ExecutionContext,
614) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
615 if canonical == Canonical::Right {
616 return Err(FactorizeError::UnsupportedCanonical(
617 "QR only supports Canonical::Left (would need LQ for right)",
618 ));
619 }
620
621 factorize_qr_with_eager_options_in(t, left_inds, &QrOptions::full_rank(), context)
622}
623
624fn factorize_qr_with_eager_options_in(
625 t: &IdxTensor,
626 left_inds: &[DynIndex],
627 qr_options: &QrOptions,
628 context: &ExecutionContext,
629) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
630 let (q, r) = qr_with_in::<f64>(t, left_inds, qr_options, context)
631 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
632
633 let bond_index = q
634 .indices
635 .last()
636 .cloned()
637 .ok_or_else(|| anyhow::anyhow!("QR factorization returned a rank-0 Q tensor"))?;
638 let q_dims = q.dims();
639 let rank = *q_dims
640 .last()
641 .ok_or_else(|| anyhow::anyhow!("QR factorization returned Q with no dimensions"))?;
642
643 Ok(FactorizeResult::new(q, r, bond_index, None, rank))
644}
645
646fn factorize_impl_f64(
647 t: &IdxTensor,
648 left_inds: &[DynIndex],
649 options: &FactorizeOptions,
650) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
651 match options.alg {
652 FactorizeAlg::SVD => factorize_svd(t, left_inds, options),
653 FactorizeAlg::QR => factorize_qr(t, left_inds, options),
654 FactorizeAlg::LU => factorize_lu::<f64>(t, left_inds, options),
655 FactorizeAlg::CI => factorize_ci::<f64>(t, left_inds, options),
656 }
657}
658
659fn factorize_impl_f64_full_rank(
660 t: &IdxTensor,
661 left_inds: &[DynIndex],
662 alg: FactorizeAlg,
663 canonical: Canonical,
664) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
665 match alg {
666 FactorizeAlg::SVD => factorize_svd_full_rank(t, left_inds, canonical),
667 FactorizeAlg::QR => factorize_qr_full_rank(t, left_inds, canonical),
668 FactorizeAlg::LU => factorize_lu_full_rank::<f64>(t, left_inds, canonical),
669 FactorizeAlg::CI => factorize_ci_full_rank::<f64>(t, left_inds, canonical),
670 }
671}
672
673fn factorize_impl_c64(
674 t: &IdxTensor,
675 left_inds: &[DynIndex],
676 options: &FactorizeOptions,
677) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
678 match options.alg {
679 FactorizeAlg::SVD => factorize_svd(t, left_inds, options),
680 FactorizeAlg::QR => factorize_qr(t, left_inds, options),
681 FactorizeAlg::LU => factorize_lu::<Complex64>(t, left_inds, options),
682 FactorizeAlg::CI => factorize_ci::<Complex64>(t, left_inds, options),
683 }
684}
685
686fn factorize_impl_c64_full_rank(
687 t: &IdxTensor,
688 left_inds: &[DynIndex],
689 alg: FactorizeAlg,
690 canonical: Canonical,
691) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
692 match alg {
693 FactorizeAlg::SVD => factorize_svd_full_rank(t, left_inds, canonical),
694 FactorizeAlg::QR => factorize_qr_full_rank(t, left_inds, canonical),
695 FactorizeAlg::LU => factorize_lu_full_rank::<Complex64>(t, left_inds, canonical),
696 FactorizeAlg::CI => factorize_ci_full_rank::<Complex64>(t, left_inds, canonical),
697 }
698}
699
700fn factorize_svd(
702 t: &IdxTensor,
703 left_inds: &[DynIndex],
704 options: &FactorizeOptions,
705) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
706 let mut svd_options = SvdOptions::new();
707 if let Some(policy) = options.svd_policy {
708 svd_options = svd_options.with_policy(policy);
709 }
710 if let Some(max_bond_dim) = options.max_bond_dim {
711 svd_options = svd_options.with_max_bond_dim(max_bond_dim);
712 }
713
714 factorize_svd_with_options(t, left_inds, options.canonical, &svd_options)
715}
716
717fn factorize_svd_full_rank(
718 t: &IdxTensor,
719 left_inds: &[DynIndex],
720 canonical: Canonical,
721) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
722 let svd_options = SvdOptions::full_rank();
723 factorize_svd_with_options(t, left_inds, canonical, &svd_options)
724}
725
726fn factorize_svd_with_options(
727 t: &IdxTensor,
728 left_inds: &[DynIndex],
729 canonical: Canonical,
730 svd_options: &SvdOptions,
731) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
732 let result = svd_for_factorize(t, left_inds, svd_options)?;
733 assemble_svd_factors(result, canonical)
734}
735
736fn assemble_svd_factors(
741 result: SvdFactorizeResult,
742 canonical: Canonical,
743) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
744 let u = result.u;
745 let s = result.s;
746 let vh = result.vh;
747 let bond_index = result.bond_index;
748 let singular_values = result.singular_values;
749 let rank = result.rank;
750 let sim_bond_index = s.indices[1].clone();
755
756 match canonical {
757 Canonical::Left => {
758 let right_contracted = contract_pair(&s, &vh)
760 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
761 let right = right_contracted
762 .replaceind(&sim_bond_index, &bond_index)
763 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
764 Ok(FactorizeResult::new(
765 u,
766 right,
767 bond_index,
768 Some(singular_values),
769 rank,
770 ))
771 }
772 Canonical::Right => {
773 let left_contracted = contract_pair(&u, &s)
775 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
776 let left = left_contracted
777 .replaceind(&sim_bond_index, &bond_index)
778 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
779 Ok(FactorizeResult::new(
780 left,
781 vh,
782 bond_index,
783 Some(singular_values),
784 rank,
785 ))
786 }
787 }
788}
789
790fn factorize_qr(
792 t: &IdxTensor,
793 left_inds: &[DynIndex],
794 options: &FactorizeOptions,
795) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
796 if options.canonical == Canonical::Right {
797 return Err(FactorizeError::UnsupportedCanonical(
798 "QR only supports Canonical::Left (would need LQ for right)",
799 ));
800 }
801
802 let qr_options = if let Some(rtol) = options.qr_rtol {
803 QrOptions::new().with_rtol(rtol)
804 } else {
805 QrOptions::new()
806 };
807
808 factorize_qr_with_options(t, left_inds, &qr_options)
809}
810
811fn factorize_qr_full_rank(
812 t: &IdxTensor,
813 left_inds: &[DynIndex],
814 canonical: Canonical,
815) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
816 if canonical == Canonical::Right {
817 return Err(FactorizeError::UnsupportedCanonical(
818 "QR only supports Canonical::Left (would need LQ for right)",
819 ));
820 }
821
822 factorize_qr_with_options(t, left_inds, &QrOptions::full_rank())
823}
824
825fn factorize_qr_with_options(
826 t: &IdxTensor,
827 left_inds: &[DynIndex],
828 qr_options: &QrOptions,
829) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
830 let (q, r) = qr_with::<f64>(t, left_inds, qr_options)?;
831
832 let bond_index = q
834 .indices
835 .last()
836 .cloned()
837 .ok_or_else(|| anyhow::anyhow!("QR factorization returned a rank-0 Q tensor"))?;
838 let q_dims = q.dims();
840 let rank = *q_dims
841 .last()
842 .ok_or_else(|| anyhow::anyhow!("QR factorization returned Q with no dimensions"))?;
843
844 Ok(FactorizeResult::new(q, r, bond_index, None, rank))
845}
846
847fn factorize_lu<T>(
849 t: &IdxTensor,
850 left_inds: &[DynIndex],
851 options: &FactorizeOptions,
852) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
853where
854 T: TensorElement
855 + ComplexFloat
856 + Default
857 + From<<T as ComplexFloat>::Real>
858 + MatrixScalar
859 + crate::MatrixLuciScalar
860 + 'static,
861 <T as ComplexFloat>::Real: Into<f64> + 'static,
862{
863 factorize_lu_with_options::<T>(
864 t,
865 left_inds,
866 options.canonical,
867 options.max_bond_dim.unwrap_or(usize::MAX),
868 1e-14,
869 )
870}
871
872fn factorize_lu_full_rank<T>(
873 t: &IdxTensor,
874 left_inds: &[DynIndex],
875 canonical: Canonical,
876) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
877where
878 T: TensorElement
879 + ComplexFloat
880 + Default
881 + From<<T as ComplexFloat>::Real>
882 + MatrixScalar
883 + crate::MatrixLuciScalar
884 + 'static,
885 <T as ComplexFloat>::Real: Into<f64> + 'static,
886{
887 factorize_lu_with_options::<T>(t, left_inds, canonical, usize::MAX, 0.0)
888}
889
890fn factorize_lu_with_options<T>(
891 t: &IdxTensor,
892 left_inds: &[DynIndex],
893 canonical: Canonical,
894 max_bond_dim: usize,
895 rel_tol: f64,
896) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
897where
898 T: TensorElement
899 + ComplexFloat
900 + Default
901 + From<<T as ComplexFloat>::Real>
902 + MatrixScalar
903 + crate::MatrixLuciScalar
904 + 'static,
905 <T as ComplexFloat>::Real: Into<f64> + 'static,
906{
907 let (a_tensor, _, m, n, left_indices, right_indices) = unfold_split(t, left_inds)
909 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))?;
910
911 let a_matrix = native_tensor_to_matrix::<T>(&a_tensor, m, n)?;
913
914 let left_orthogonal = canonical == Canonical::Left;
916 let lu_options = RrLUOptions {
917 max_bond_dim,
918 rel_tol,
919 abs_tol: 0.0,
920 left_orthogonal,
921 };
922
923 let mut a_matrix = a_matrix;
925 let lu = rrlu_mut(&mut a_matrix, Some(lu_options))?;
926 let rank = lu.npivots();
927
928 let l_matrix = lu.left(true);
930 let u_matrix = lu.right(true);
931
932 let bond_index = DynIndex::new_bond(rank)
934 .map_err(|e| anyhow::anyhow!("Failed to create bond index: {:?}", e))?;
935
936 let l_vec = l_matrix.into_col_major_vec();
938 let mut l_indices = left_indices.clone();
939 l_indices.push(bond_index.clone());
940 let left = IdxTensor::from_dense(l_indices, l_vec)
941 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
942
943 let u_vec = u_matrix.into_col_major_vec();
945 let mut r_indices = vec![bond_index.clone()];
946 r_indices.extend_from_slice(&right_indices);
947 let right = IdxTensor::from_dense(r_indices, u_vec)
948 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
949
950 Ok(FactorizeResult::new(left, right, bond_index, None, rank))
951}
952
953fn factorize_ci<T>(
955 t: &IdxTensor,
956 left_inds: &[DynIndex],
957 options: &FactorizeOptions,
958) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
959where
960 T: TensorElement
961 + ComplexFloat
962 + Default
963 + From<<T as ComplexFloat>::Real>
964 + MatrixScalar
965 + crate::MatrixLuciScalar
966 + 'static,
967 <T as ComplexFloat>::Real: Into<f64> + 'static,
968{
969 factorize_ci_with_options::<T>(
970 t,
971 left_inds,
972 options.canonical,
973 options.max_bond_dim.unwrap_or(usize::MAX),
974 1e-14,
975 )
976}
977
978fn factorize_ci_full_rank<T>(
979 t: &IdxTensor,
980 left_inds: &[DynIndex],
981 canonical: Canonical,
982) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
983where
984 T: TensorElement
985 + ComplexFloat
986 + Default
987 + From<<T as ComplexFloat>::Real>
988 + MatrixScalar
989 + crate::MatrixLuciScalar
990 + 'static,
991 <T as ComplexFloat>::Real: Into<f64> + 'static,
992{
993 factorize_ci_with_options::<T>(t, left_inds, canonical, usize::MAX, 0.0)
994}
995
996fn factorize_ci_with_options<T>(
1007 t: &IdxTensor,
1008 left_inds: &[DynIndex],
1009 canonical: Canonical,
1010 max_bond_dim: usize,
1011 rel_tol: f64,
1012) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
1013where
1014 T: TensorElement
1015 + ComplexFloat
1016 + Default
1017 + From<<T as ComplexFloat>::Real>
1018 + MatrixScalar
1019 + crate::MatrixLuciScalar
1020 + 'static,
1021 <T as ComplexFloat>::Real: Into<f64> + 'static,
1022{
1023 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
1026 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))?;
1027
1028 let a_matrix = eager_tensor_to_matrix::<T>(&matrix_inner, m, n)?;
1030
1031 let left_orthogonal = canonical == Canonical::Left;
1033 let lu_options = RrLUOptions {
1034 max_bond_dim,
1035 rel_tol,
1036 abs_tol: 0.0,
1037 left_orthogonal,
1038 };
1039
1040 let factors = matrix_luci_factors_from_matrix_owned(a_matrix, Some(lu_options))?;
1045 let rank = factors.rank;
1046 let (left, right, bond_index) =
1047 matrix_luci_factors_to_idx_tensors(factors, &left_indices, &right_indices)?;
1048
1049 Ok(FactorizeResult::new(left, right, bond_index, None, rank))
1050}
1051
1052fn matrix_luci_factors_to_idx_tensors<T>(
1053 factors: MatrixLuciFactors<T>,
1054 left_indices: &[DynIndex],
1055 right_indices: &[DynIndex],
1056) -> Result<(IdxTensor, IdxTensor, DynIndex), FactorizeError>
1057where
1058 T: TensorElement + Clone,
1059{
1060 let bond_index = DynIndex::new_bond(factors.rank)
1061 .map_err(|e| anyhow::anyhow!("Failed to create bond index: {:?}", e))?;
1062
1063 let mut l_indices = left_indices.to_vec();
1064 l_indices.push(bond_index.clone());
1065 let left = IdxTensor::from_dense(l_indices, factors.left.into_col_major_vec())
1066 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
1067
1068 let mut r_indices = vec![bond_index.clone()];
1069 r_indices.extend_from_slice(right_indices);
1070 let right = IdxTensor::from_dense(r_indices, factors.right.into_col_major_vec())
1071 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
1072
1073 Ok((left, right, bond_index))
1074}
1075
1076fn native_tensor_to_matrix<T>(
1078 tensor: &tenferro::Tensor,
1079 m: usize,
1080 n: usize,
1081) -> Result<Matrix<T>, FactorizeError>
1082where
1083 T: TensorElement + MatrixScalar + Copy,
1084{
1085 let data = T::dense_values_from_native_col_major(tensor).map_err(|e| {
1086 FactorizeError::ComputationError(anyhow::anyhow!(
1087 "failed to extract dense matrix entries from native tensor: {e}"
1088 ))
1089 })?;
1090 matrix_from_col_major_values(data, m, n, "native")
1091}
1092
1093fn eager_tensor_to_matrix<T>(
1094 tensor: &EagerTensor,
1095 m: usize,
1096 n: usize,
1097) -> Result<Matrix<T>, FactorizeError>
1098where
1099 T: TensorElement + MatrixScalar + Copy,
1100{
1101 let values = tensor
1102 .value()
1103 .map_err(|source| FactorizeError::ComputationError(anyhow::Error::new(source)))?
1104 .as_slice::<T>()
1105 .map_err(|source| FactorizeError::ComputationError(anyhow::Error::new(source)))?
1106 .to_vec();
1107 matrix_from_col_major_values(values, m, n, "eager")
1108}
1109
1110fn matrix_from_col_major_values<T>(
1111 data: Vec<T>,
1112 m: usize,
1113 n: usize,
1114 source: &str,
1115) -> Result<Matrix<T>, FactorizeError>
1116where
1117 T: MatrixScalar + Copy,
1118{
1119 Matrix::try_from_col_major_vec(m, n, data).map_err(|error| {
1120 FactorizeError::ComputationError(anyhow::anyhow!(
1121 "{source} matrix conversion failed for shape ({m}, {n}): {error}"
1122 ))
1123 })
1124}
1125
1126#[cfg(test)]
1127fn checked_matrix_len(m: usize, n: usize, source: &str) -> Result<usize, FactorizeError> {
1128 m.checked_mul(n).ok_or_else(|| {
1129 FactorizeError::ComputationError(anyhow::anyhow!(
1130 "{source} matrix shape ({m}, {n}) overflows usize"
1131 ))
1132 })
1133}
1134
1135#[cfg(test)]
1136mod tests;