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, rrlu, MatrixLuciFactors, RrLUOptions, Scalar as MatrixScalar,
40};
41use num_complex::{Complex64, ComplexFloat};
42use tenferro_ad::EagerTensor;
43use tensor4all_tensorbackend::{Matrix, TensorElement};
44
45use crate::defaults::svd::{compute_retained_rank, svd_for_factorize};
46use crate::qr::{qr_with, QrOptions};
47use crate::svd::SvdOptions;
48use crate::truncation::{
49 validate_svd_truncation_policy, SingularValueMeasure, SvdTruncationPolicy, ThresholdScale,
50 TruncationRule,
51};
52
53pub use crate::tensor_like::{
55 Canonical, FactorizeAlg, FactorizeError, FactorizeOptions, FactorizeResult,
56};
57
58pub fn factorize(
87 t: &IdxTensor,
88 left_inds: &[DynIndex],
89 options: &FactorizeOptions,
90) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
91 options.validate()?;
92
93 if t.is_diag() {
94 return Err(FactorizeError::UnsupportedStorage(
95 "Diagonal storage not supported for factorize",
96 ));
97 }
98 if t.tracks_grad() && matches!(options.alg, FactorizeAlg::LU | FactorizeAlg::CI) {
99 return Err(FactorizeError::UnsupportedStorage(
100 "LU and CI factorization do not support tracked tensors yet",
101 ));
102 }
103
104 if t.is_f64() {
105 factorize_impl_f64(t, left_inds, options)
106 } else if t.is_complex() {
107 factorize_impl_c64(t, left_inds, options)
108 } else {
109 Err(FactorizeError::UnsupportedStorage(
110 "factorize currently supports only f64 and Complex64 tensors",
111 ))
112 }
113}
114
115pub(crate) fn factorize_auto(
120 t: &IdxTensor,
121 left_inds: &[DynIndex],
122 options: &FactorizeOptions,
123) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
124 if options.alg != FactorizeAlg::SVD {
125 return Err(FactorizeError::InvalidOptions(
126 "automatic factorization only supports SVD options",
127 ));
128 }
129 options.validate()?;
130
131 let Some(policy) = options.svd_policy else {
132 return factorize(t, left_inds, options);
133 };
134 validate_svd_truncation_policy(policy).map_err(|error| FactorizeError::InvalidRtol(error.0))?;
135
136 let effective_cutoff = match (policy.scale, policy.measure, policy.rule) {
137 (ThresholdScale::Relative, SingularValueMeasure::SquaredValue, _) => policy.threshold,
138 (ThresholdScale::Relative, SingularValueMeasure::Value, TruncationRule::PerValue) => {
139 policy.threshold * policy.threshold
140 }
141 _ => 0.0,
142 };
143 if effective_cutoff <= 1.0e-12 || t.tracks_grad() || t.is_diag() {
144 return factorize(t, left_inds, options);
145 }
146 if !(t.is_f64() || t.is_c64()) {
147 return factorize(t, left_inds, options);
148 }
149
150 factorize_gram(t, left_inds, options, policy).or_else(|_| factorize(t, left_inds, options))
151}
152
153fn factorize_gram(
154 t: &IdxTensor,
155 left_inds: &[DynIndex],
156 options: &FactorizeOptions,
157 policy: SvdTruncationPolicy,
158) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
159 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
160 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
161 if m == 0 || n == 0 {
162 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
163 "cannot factorize a matrix with an empty dimension"
164 )));
165 }
166
167 let row = DynIndex::new_dyn(m);
168 let column = DynIndex::new_dyn(n);
169 let matrix = IdxTensor::from_inner(vec![row.clone(), column.clone()], matrix_inner)
170 .map_err(FactorizeError::ComputationError)?;
171
172 let eigenvectors_left = m <= n;
175 let gram = if eigenvectors_left {
176 let sim_row = DynIndex::new_dyn(m);
177 let adjoint = matrix
178 .conj()
179 .replaceind(&row, &sim_row)
180 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
181 contract_pair(&matrix, &adjoint)
182 } else {
183 let sim_column = DynIndex::new_dyn(n);
184 let adjoint = matrix
185 .conj()
186 .replaceind(&column, &sim_column)
187 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
188 contract_pair(&adjoint, &matrix)
189 }
190 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
191 let decomposition = gram
192 .hermitian_eigendecomposition(1.0e-12)
193 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
194
195 let lambda_scale = decomposition
196 .eigenvalues
197 .iter()
198 .map(|lambda| lambda.abs())
199 .fold(0.0_f64, f64::max);
200 if lambda_scale == 0.0 {
201 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
202 "zero Gram matrix uses the SVD fallback"
203 )));
204 }
205 let negative_tolerance = 1.0e-12 * lambda_scale;
206 let mut eigenpairs: Vec<(f64, usize)> = decomposition
207 .eigenvalues
208 .iter()
209 .copied()
210 .enumerate()
211 .map(|(column, mut lambda)| {
212 if !lambda.is_finite() {
213 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
214 "Gram eigendecomposition returned a non-finite eigenvalue"
215 )));
216 }
217 if lambda < -negative_tolerance {
218 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
219 "Gram eigendecomposition returned a materially negative eigenvalue"
220 )));
221 }
222 if lambda < 0.0 {
223 lambda = 0.0;
224 }
225 Ok((lambda, column))
226 })
227 .collect::<Result<_, _>>()?;
228 eigenpairs.sort_by(|(left, _), (right, _)| right.total_cmp(left));
229
230 let all_singular_values: Vec<f64> =
231 eigenpairs.iter().map(|(lambda, _)| lambda.sqrt()).collect();
232 let mut rank = compute_retained_rank(&all_singular_values, &policy);
233 if let Some(max_bond_dim) = options.max_bond_dim {
234 rank = rank.min(max_bond_dim);
235 }
236 rank = rank.max(1).min(m.min(n));
237 let singular_values = all_singular_values[..rank].to_vec();
238 if singular_values.contains(&0.0) {
239 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
240 "zero retained singular value uses the SVD fallback"
241 )));
242 }
243
244 let retained_columns: Vec<usize> = eigenpairs
245 .iter()
246 .take(rank)
247 .map(|(_, column)| *column)
248 .collect();
249 let basis_inner = decomposition
250 .eigenvectors
251 .as_inner()
252 .map_err(FactorizeError::ComputationError)?
253 .take_cols(&retained_columns)
254 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
255 let bond_index = DynIndex::new_bond(rank)
256 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
257 let basis_index = if eigenvectors_left {
258 row.clone()
259 } else {
260 column.clone()
261 };
262 let basis = IdxTensor::from_inner(vec![basis_index, bond_index.clone()], basis_inner)
263 .map_err(FactorizeError::ComputationError)?;
264
265 let (left_matrix, right_matrix) = if eigenvectors_left {
266 let sigma_vh = contract_pair(&basis.conj(), &matrix)
267 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
268 .permute_indices(&[bond_index.clone(), column.clone()])
269 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
270 match options.canonical {
271 Canonical::Left => (basis, sigma_vh),
272 Canonical::Right => {
273 let inverse: Vec<f64> = singular_values.iter().map(|sigma| 1.0 / sigma).collect();
274 (
275 scale_bond(&basis, &bond_index, &singular_values)?,
276 scale_bond(&sigma_vh, &bond_index, &inverse)?,
277 )
278 }
279 }
280 } else {
281 let u_sigma = contract_pair(&matrix, &basis)
282 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
283 .permute_indices(&[row.clone(), bond_index.clone()])
284 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
285 let vh = basis
286 .conj()
287 .permute_indices(&[bond_index.clone(), column.clone()])
288 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
289 match options.canonical {
290 Canonical::Right => (u_sigma, vh),
291 Canonical::Left => {
292 let inverse: Vec<f64> = singular_values.iter().map(|sigma| 1.0 / sigma).collect();
293 (
294 scale_bond(&u_sigma, &bond_index, &inverse)?,
295 scale_bond(&vh, &bond_index, &singular_values)?,
296 )
297 }
298 }
299 };
300
301 let mut output_left_indices = left_indices;
302 output_left_indices.push(bond_index.clone());
303 let left = reshape_factor(left_matrix, output_left_indices)?;
304 let mut output_right_indices = vec![bond_index.clone()];
305 output_right_indices.extend(right_indices);
306 let right = reshape_factor(right_matrix, output_right_indices)?;
307
308 Ok(FactorizeResult {
309 left,
310 right,
311 bond_index,
312 singular_values: Some(singular_values),
313 rank,
314 })
315}
316
317fn scale_bond(
318 tensor: &IdxTensor,
319 bond: &DynIndex,
320 values: &[f64],
321) -> Result<IdxTensor, FactorizeError> {
322 let temporary = DynIndex::new_bond(values.len())
323 .map_err(|error| FactorizeError::ComputationError(anyhow::anyhow!(error)))?;
324 let diagonal_values = values
325 .iter()
326 .map(|value| {
327 if tensor.is_complex() {
328 AnyScalar::new_complex(*value, 0.0)
329 } else {
330 AnyScalar::new_real(*value)
331 }
332 })
333 .collect();
334 let diagonal = IdxTensor::from_diag_any(vec![bond.clone(), temporary.clone()], diagonal_values)
335 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
336 contract_pair(tensor, &diagonal)
337 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
338 .replaceind(&temporary, bond)
339 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?
340 .permute_indices(tensor.indices())
341 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))
342}
343
344fn reshape_factor(tensor: IdxTensor, indices: Vec<DynIndex>) -> Result<IdxTensor, FactorizeError> {
345 let dims: Vec<usize> = indices.iter().map(|index| index.dim).collect();
346 let inner = tensor
347 .as_inner()
348 .map_err(FactorizeError::ComputationError)?
349 .reshape(&dims)
350 .map_err(|error| FactorizeError::ComputationError(anyhow::Error::new(error)))?;
351 IdxTensor::from_inner(indices, inner).map_err(FactorizeError::ComputationError)
352}
353
354pub fn factorize_full_rank(
400 t: &IdxTensor,
401 left_inds: &[DynIndex],
402 alg: FactorizeAlg,
403 canonical: Canonical,
404) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
405 if t.is_diag() {
406 return Err(FactorizeError::UnsupportedStorage(
407 "Diagonal storage not supported for factorize",
408 ));
409 }
410 if t.tracks_grad() && matches!(alg, FactorizeAlg::LU | FactorizeAlg::CI) {
411 return Err(FactorizeError::UnsupportedStorage(
412 "LU and CI factorization do not support tracked tensors yet",
413 ));
414 }
415
416 if t.is_f64() {
417 factorize_impl_f64_full_rank(t, left_inds, alg, canonical)
418 } else if t.is_complex() {
419 factorize_impl_c64_full_rank(t, left_inds, alg, canonical)
420 } else {
421 Err(FactorizeError::UnsupportedStorage(
422 "factorize currently supports only f64 and Complex64 tensors",
423 ))
424 }
425}
426
427fn factorize_impl_f64(
428 t: &IdxTensor,
429 left_inds: &[DynIndex],
430 options: &FactorizeOptions,
431) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
432 match options.alg {
433 FactorizeAlg::SVD => factorize_svd(t, left_inds, options),
434 FactorizeAlg::QR => factorize_qr(t, left_inds, options),
435 FactorizeAlg::LU => factorize_lu::<f64>(t, left_inds, options),
436 FactorizeAlg::CI => factorize_ci::<f64>(t, left_inds, options),
437 }
438}
439
440fn factorize_impl_f64_full_rank(
441 t: &IdxTensor,
442 left_inds: &[DynIndex],
443 alg: FactorizeAlg,
444 canonical: Canonical,
445) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
446 match alg {
447 FactorizeAlg::SVD => factorize_svd_full_rank(t, left_inds, canonical),
448 FactorizeAlg::QR => factorize_qr_full_rank(t, left_inds, canonical),
449 FactorizeAlg::LU => factorize_lu_full_rank::<f64>(t, left_inds, canonical),
450 FactorizeAlg::CI => factorize_ci_full_rank::<f64>(t, left_inds, canonical),
451 }
452}
453
454fn factorize_impl_c64(
455 t: &IdxTensor,
456 left_inds: &[DynIndex],
457 options: &FactorizeOptions,
458) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
459 match options.alg {
460 FactorizeAlg::SVD => factorize_svd(t, left_inds, options),
461 FactorizeAlg::QR => factorize_qr(t, left_inds, options),
462 FactorizeAlg::LU => factorize_lu::<Complex64>(t, left_inds, options),
463 FactorizeAlg::CI => factorize_ci::<Complex64>(t, left_inds, options),
464 }
465}
466
467fn factorize_impl_c64_full_rank(
468 t: &IdxTensor,
469 left_inds: &[DynIndex],
470 alg: FactorizeAlg,
471 canonical: Canonical,
472) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
473 match alg {
474 FactorizeAlg::SVD => factorize_svd_full_rank(t, left_inds, canonical),
475 FactorizeAlg::QR => factorize_qr_full_rank(t, left_inds, canonical),
476 FactorizeAlg::LU => factorize_lu_full_rank::<Complex64>(t, left_inds, canonical),
477 FactorizeAlg::CI => factorize_ci_full_rank::<Complex64>(t, left_inds, canonical),
478 }
479}
480
481fn factorize_svd(
483 t: &IdxTensor,
484 left_inds: &[DynIndex],
485 options: &FactorizeOptions,
486) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
487 let mut svd_options = SvdOptions::new();
488 if let Some(policy) = options.svd_policy {
489 svd_options = svd_options.with_policy(policy);
490 }
491 if let Some(max_bond_dim) = options.max_bond_dim {
492 svd_options = svd_options.with_max_bond_dim(max_bond_dim);
493 }
494
495 factorize_svd_with_options(t, left_inds, options.canonical, &svd_options)
496}
497
498fn factorize_svd_full_rank(
499 t: &IdxTensor,
500 left_inds: &[DynIndex],
501 canonical: Canonical,
502) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
503 let svd_options = SvdOptions::full_rank();
504 factorize_svd_with_options(t, left_inds, canonical, &svd_options)
505}
506
507fn factorize_svd_with_options(
508 t: &IdxTensor,
509 left_inds: &[DynIndex],
510 canonical: Canonical,
511 svd_options: &SvdOptions,
512) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
513 let result = svd_for_factorize(t, left_inds, svd_options)?;
514 let u = result.u;
515 let s = result.s;
516 let vh = result.vh;
517 let bond_index = result.bond_index;
518 let singular_values = result.singular_values;
519 let rank = result.rank;
520 let sim_bond_index = s.indices[1].clone();
525
526 match canonical {
527 Canonical::Left => {
528 let right_contracted = contract_pair(&s, &vh)
530 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
531 let right = right_contracted
532 .replaceind(&sim_bond_index, &bond_index)
533 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
534 Ok(FactorizeResult {
535 left: u,
536 right,
537 bond_index,
538 singular_values: Some(singular_values),
539 rank,
540 })
541 }
542 Canonical::Right => {
543 let left_contracted = contract_pair(&u, &s)
545 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
546 let left = left_contracted
547 .replaceind(&sim_bond_index, &bond_index)
548 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
549 Ok(FactorizeResult {
550 left,
551 right: vh,
552 bond_index,
553 singular_values: Some(singular_values),
554 rank,
555 })
556 }
557 }
558}
559
560fn factorize_qr(
562 t: &IdxTensor,
563 left_inds: &[DynIndex],
564 options: &FactorizeOptions,
565) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
566 if options.canonical == Canonical::Right {
567 return Err(FactorizeError::UnsupportedCanonical(
568 "QR only supports Canonical::Left (would need LQ for right)",
569 ));
570 }
571
572 let qr_options = if let Some(rtol) = options.qr_rtol {
573 QrOptions::new().with_rtol(rtol)
574 } else {
575 QrOptions::new()
576 };
577
578 factorize_qr_with_options(t, left_inds, &qr_options)
579}
580
581fn factorize_qr_full_rank(
582 t: &IdxTensor,
583 left_inds: &[DynIndex],
584 canonical: Canonical,
585) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
586 if canonical == Canonical::Right {
587 return Err(FactorizeError::UnsupportedCanonical(
588 "QR only supports Canonical::Left (would need LQ for right)",
589 ));
590 }
591
592 factorize_qr_with_options(t, left_inds, &QrOptions::full_rank())
593}
594
595fn factorize_qr_with_options(
596 t: &IdxTensor,
597 left_inds: &[DynIndex],
598 qr_options: &QrOptions,
599) -> Result<FactorizeResult<IdxTensor>, FactorizeError> {
600 let (q, r) = qr_with::<f64>(t, left_inds, qr_options)?;
601
602 let bond_index = q
604 .indices
605 .last()
606 .cloned()
607 .ok_or_else(|| anyhow::anyhow!("QR factorization returned a rank-0 Q tensor"))?;
608 let q_dims = q.dims();
610 let rank = *q_dims
611 .last()
612 .ok_or_else(|| anyhow::anyhow!("QR factorization returned Q with no dimensions"))?;
613
614 Ok(FactorizeResult {
615 left: q,
616 right: r,
617 bond_index,
618 singular_values: None,
619 rank,
620 })
621}
622
623fn factorize_lu<T>(
625 t: &IdxTensor,
626 left_inds: &[DynIndex],
627 options: &FactorizeOptions,
628) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
629where
630 T: TensorElement
631 + ComplexFloat
632 + Default
633 + From<<T as ComplexFloat>::Real>
634 + MatrixScalar
635 + crate::MatrixLuciScalar
636 + 'static,
637 <T as ComplexFloat>::Real: Into<f64> + 'static,
638{
639 factorize_lu_with_options::<T>(
640 t,
641 left_inds,
642 options.canonical,
643 options.max_bond_dim.unwrap_or(usize::MAX),
644 1e-14,
645 )
646}
647
648fn factorize_lu_full_rank<T>(
649 t: &IdxTensor,
650 left_inds: &[DynIndex],
651 canonical: Canonical,
652) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
653where
654 T: TensorElement
655 + ComplexFloat
656 + Default
657 + From<<T as ComplexFloat>::Real>
658 + MatrixScalar
659 + crate::MatrixLuciScalar
660 + 'static,
661 <T as ComplexFloat>::Real: Into<f64> + 'static,
662{
663 factorize_lu_with_options::<T>(t, left_inds, canonical, usize::MAX, 0.0)
664}
665
666fn factorize_lu_with_options<T>(
667 t: &IdxTensor,
668 left_inds: &[DynIndex],
669 canonical: Canonical,
670 max_bond_dim: usize,
671 rel_tol: f64,
672) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
673where
674 T: TensorElement
675 + ComplexFloat
676 + Default
677 + From<<T as ComplexFloat>::Real>
678 + MatrixScalar
679 + crate::MatrixLuciScalar
680 + 'static,
681 <T as ComplexFloat>::Real: Into<f64> + 'static,
682{
683 let (a_tensor, _, m, n, left_indices, right_indices) = unfold_split(t, left_inds)
685 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))?;
686
687 let a_matrix = native_tensor_to_matrix::<T>(&a_tensor, m, n)?;
689
690 let left_orthogonal = canonical == Canonical::Left;
692 let lu_options = RrLUOptions {
693 max_bond_dim,
694 rel_tol,
695 abs_tol: 0.0,
696 left_orthogonal,
697 };
698
699 let lu = rrlu(&a_matrix, Some(lu_options))?;
701 let rank = lu.npivots();
702
703 let l_matrix = lu.left(true);
705 let u_matrix = lu.right(true);
706
707 let bond_index = DynIndex::new_bond(rank)
709 .map_err(|e| anyhow::anyhow!("Failed to create bond index: {:?}", e))?;
710
711 let l_vec = matrix_to_vec(&l_matrix)?;
713 let mut l_indices = left_indices.clone();
714 l_indices.push(bond_index.clone());
715 let left = IdxTensor::from_dense(l_indices, l_vec)
716 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
717
718 let u_vec = matrix_to_vec(&u_matrix)?;
720 let mut r_indices = vec![bond_index.clone()];
721 r_indices.extend_from_slice(&right_indices);
722 let right = IdxTensor::from_dense(r_indices, u_vec)
723 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
724
725 Ok(FactorizeResult {
726 left,
727 right,
728 bond_index,
729 singular_values: None,
730 rank,
731 })
732}
733
734fn factorize_ci<T>(
736 t: &IdxTensor,
737 left_inds: &[DynIndex],
738 options: &FactorizeOptions,
739) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
740where
741 T: TensorElement
742 + ComplexFloat
743 + Default
744 + From<<T as ComplexFloat>::Real>
745 + MatrixScalar
746 + crate::MatrixLuciScalar
747 + 'static,
748 <T as ComplexFloat>::Real: Into<f64> + 'static,
749{
750 factorize_ci_with_options::<T>(
751 t,
752 left_inds,
753 options.canonical,
754 options.max_bond_dim.unwrap_or(usize::MAX),
755 1e-14,
756 )
757}
758
759fn factorize_ci_full_rank<T>(
760 t: &IdxTensor,
761 left_inds: &[DynIndex],
762 canonical: Canonical,
763) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
764where
765 T: TensorElement
766 + ComplexFloat
767 + Default
768 + From<<T as ComplexFloat>::Real>
769 + MatrixScalar
770 + crate::MatrixLuciScalar
771 + 'static,
772 <T as ComplexFloat>::Real: Into<f64> + 'static,
773{
774 factorize_ci_with_options::<T>(t, left_inds, canonical, usize::MAX, 0.0)
775}
776
777fn factorize_ci_with_options<T>(
788 t: &IdxTensor,
789 left_inds: &[DynIndex],
790 canonical: Canonical,
791 max_bond_dim: usize,
792 rel_tol: f64,
793) -> Result<FactorizeResult<IdxTensor>, FactorizeError>
794where
795 T: TensorElement
796 + ComplexFloat
797 + Default
798 + From<<T as ComplexFloat>::Real>
799 + MatrixScalar
800 + crate::MatrixLuciScalar
801 + 'static,
802 <T as ComplexFloat>::Real: Into<f64> + 'static,
803{
804 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
807 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))?;
808
809 let a_matrix = eager_tensor_to_matrix::<T>(&matrix_inner, m, n)?;
811
812 let left_orthogonal = canonical == Canonical::Left;
814 let lu_options = RrLUOptions {
815 max_bond_dim,
816 rel_tol,
817 abs_tol: 0.0,
818 left_orthogonal,
819 };
820
821 let factors = matrix_luci_factors_from_matrix(&a_matrix, Some(lu_options))?;
826 let rank = factors.rank;
827 let (left, right, bond_index) =
828 matrix_luci_factors_to_idx_tensors(factors, &left_indices, &right_indices)?;
829
830 Ok(FactorizeResult {
831 left,
832 right,
833 bond_index,
834 singular_values: None,
835 rank,
836 })
837}
838
839fn matrix_luci_factors_to_idx_tensors<T>(
840 factors: MatrixLuciFactors<T>,
841 left_indices: &[DynIndex],
842 right_indices: &[DynIndex],
843) -> Result<(IdxTensor, IdxTensor, DynIndex), FactorizeError>
844where
845 T: TensorElement + Clone,
846{
847 let bond_index = DynIndex::new_bond(factors.rank)
848 .map_err(|e| anyhow::anyhow!("Failed to create bond index: {:?}", e))?;
849
850 let mut l_indices = left_indices.to_vec();
851 l_indices.push(bond_index.clone());
852 let left = IdxTensor::from_dense(l_indices, matrix_to_vec(&factors.left)?)
853 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
854
855 let mut r_indices = vec![bond_index.clone()];
856 r_indices.extend_from_slice(right_indices);
857 let right = IdxTensor::from_dense(r_indices, matrix_to_vec(&factors.right)?)
858 .map_err(|e| FactorizeError::ComputationError(anyhow::Error::new(e)))?;
859
860 Ok((left, right, bond_index))
861}
862
863fn native_tensor_to_matrix<T>(
865 tensor: &tenferro::Tensor,
866 m: usize,
867 n: usize,
868) -> Result<Matrix<T>, FactorizeError>
869where
870 T: TensorElement + MatrixScalar + Copy,
871{
872 let data = T::dense_values_from_native_col_major(tensor).map_err(|e| {
873 FactorizeError::ComputationError(anyhow::anyhow!(
874 "failed to extract dense matrix entries from native tensor: {e}"
875 ))
876 })?;
877 matrix_from_col_major_values(data, m, n, "native")
878}
879
880fn eager_tensor_to_matrix<T>(
881 tensor: &EagerTensor,
882 m: usize,
883 n: usize,
884) -> Result<Matrix<T>, FactorizeError>
885where
886 T: TensorElement + MatrixScalar + Copy,
887{
888 let values = tensor
889 .value()
890 .map_err(|source| FactorizeError::ComputationError(anyhow::Error::new(source)))?
891 .as_slice::<T>()
892 .map_err(|source| FactorizeError::ComputationError(anyhow::Error::new(source)))?
893 .to_vec();
894 matrix_from_col_major_values(values, m, n, "eager")
895}
896
897fn matrix_from_col_major_values<T>(
898 data: Vec<T>,
899 m: usize,
900 n: usize,
901 source: &str,
902) -> Result<Matrix<T>, FactorizeError>
903where
904 T: MatrixScalar + Copy,
905{
906 let expected_len = checked_matrix_len(m, n, source)?;
907 if data.len() != expected_len {
908 return Err(FactorizeError::ComputationError(anyhow::anyhow!(
909 "{source} matrix materialization produced {} entries for shape ({m}, {n})",
910 data.len()
911 )));
912 }
913
914 let mut matrix = Matrix::zeros(m, n);
915 for i in 0..m {
916 for j in 0..n {
917 matrix[[i, j]] = data[j * m + i];
918 }
919 }
920 Ok(matrix)
921}
922
923fn matrix_to_vec<T>(matrix: &Matrix<T>) -> Result<Vec<T>, FactorizeError>
925where
926 T: Clone,
927{
928 let m = matrix.nrows();
929 let n = matrix.ncols();
930 let len = checked_matrix_len(m, n, "factorize output")?;
931 let mut vec = Vec::with_capacity(len);
932 for j in 0..n {
933 for i in 0..m {
934 vec.push(matrix[[i, j]].clone());
935 }
936 }
937 Ok(vec)
938}
939
940fn checked_matrix_len(m: usize, n: usize, source: &str) -> Result<usize, FactorizeError> {
941 m.checked_mul(n).ok_or_else(|| {
942 FactorizeError::ComputationError(anyhow::anyhow!(
943 "{source} matrix shape ({m}, {n}) overflows usize"
944 ))
945 })
946}
947
948#[cfg(test)]
949mod tests;