1use super::eager_session::with_eager_session;
11use crate::defaults::idx_tensor::{unfold_index_sets, unfold_split_inner, unfold_split_inner_in};
12use crate::defaults::DynIndex;
13use crate::index_like::IndexLike;
14use crate::truncation::{
15 validate_svd_truncation_options, validate_svd_truncation_policy, SingularValueMeasure,
16 SvdTruncationOptionsError, SvdTruncationPolicy, ThresholdScale, TruncationRule,
17};
18use crate::{ExecutionContext, IdxTensor, TensorElement};
19use num_complex::{Complex32, Complex64, ComplexFloat};
20use std::sync::Mutex;
21use tenferro::{DType, TensorScalar};
22use tenferro_ad::EagerTensor;
23use tenferro_linalg::EagerSessionLinalgExt;
24#[cfg(test)]
25use tensor4all_tensorbackend::native_tensor_primal_to_dense_col_major;
26use thiserror::Error;
27
28#[derive(Debug, Error)]
30pub enum SvdError {
31 #[error("SVD computation failed: {0}")]
33 ComputationError(#[from] anyhow::Error),
34 #[error("Invalid SVD truncation threshold: {0}. Threshold must be finite and non-negative.")]
36 InvalidThreshold(f64),
37 #[error("max_bond_dim must be at least 1 when set")]
39 InvalidMaxBondDim,
40 #[error("Unsupported storage type: {0}")]
45 UnsupportedStorage(
46 &'static str,
48 ),
49}
50
51#[derive(Debug, Clone, Copy)]
72pub struct SvdOptions {
73 pub max_bond_dim: Option<usize>,
75 pub policy: Option<SvdTruncationPolicy>,
78 truncate: bool,
79}
80
81impl Default for SvdOptions {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87impl SvdOptions {
88 #[must_use]
90 pub fn new() -> Self {
91 Self {
92 max_bond_dim: None,
93 policy: None,
94 truncate: true,
95 }
96 }
97
98 #[must_use]
100 pub fn with_max_bond_dim(mut self, max_bond_dim: usize) -> Self {
101 self.max_bond_dim = Some(max_bond_dim);
102 self
103 }
104
105 #[must_use]
107 pub fn with_policy(mut self, policy: SvdTruncationPolicy) -> Self {
108 self.policy = Some(policy);
109 self
110 }
111
112 pub(crate) fn full_rank() -> Self {
113 Self {
114 max_bond_dim: None,
115 policy: None,
116 truncate: false,
117 }
118 }
119}
120
121fn default_policy_guard() -> std::sync::MutexGuard<'static, SvdTruncationPolicy> {
122 match DEFAULT_SVD_TRUNCATION_POLICY.lock() {
123 Ok(guard) => guard,
124 Err(poisoned) => poisoned.into_inner(),
125 }
126}
127
128static DEFAULT_SVD_TRUNCATION_POLICY: Mutex<SvdTruncationPolicy> =
130 Mutex::new(SvdTruncationPolicy::new(1e-12));
131
132#[must_use]
136pub fn default_svd_truncation_policy() -> SvdTruncationPolicy {
137 *default_policy_guard()
138}
139
140pub fn set_default_svd_truncation_policy(policy: SvdTruncationPolicy) -> Result<(), SvdError> {
148 validate_svd_truncation_policy(policy).map_err(|e| SvdError::InvalidThreshold(e.0))?;
149 *default_policy_guard() = policy;
150 Ok(())
151}
152
153fn singular_value_measure(value: f64, measure: SingularValueMeasure) -> f64 {
154 match measure {
155 SingularValueMeasure::Value => value,
156 SingularValueMeasure::SquaredValue => value * value,
157 }
158}
159
160pub(crate) fn compute_retained_rank(s_vec: &[f64], policy: &SvdTruncationPolicy) -> usize {
165 if s_vec.is_empty() {
166 return 1;
167 }
168
169 let measured: Vec<f64> = s_vec
170 .iter()
171 .map(|&value| singular_value_measure(value, policy.measure))
172 .collect();
173 if measured.iter().all(|&value| value == 0.0) {
174 return 1;
175 }
176
177 let retained = match (policy.scale, policy.rule) {
178 (ThresholdScale::Relative, TruncationRule::PerValue) => {
179 let reference = measured.iter().copied().fold(0.0_f64, f64::max);
180 measured
181 .iter()
182 .take_while(|&&value| reference > 0.0 && value / reference > policy.threshold)
183 .count()
184 }
185 (ThresholdScale::Absolute, TruncationRule::PerValue) => measured
186 .iter()
187 .take_while(|&&value| value > policy.threshold)
188 .count(),
189 (ThresholdScale::Relative, TruncationRule::DiscardedTailSum) => {
190 let total: f64 = measured.iter().sum();
191 if total == 0.0 {
192 1
193 } else {
194 let mut discarded = 0.0;
195 let mut keep = measured.len();
196 for (i, value) in measured.iter().enumerate().rev() {
197 if (discarded + value) / total <= policy.threshold {
198 discarded += value;
199 keep = i;
200 } else {
201 break;
202 }
203 }
204 keep
205 }
206 }
207 (ThresholdScale::Absolute, TruncationRule::DiscardedTailSum) => {
208 let mut discarded = 0.0;
209 let mut keep = measured.len();
210 for (i, value) in measured.iter().enumerate().rev() {
211 if discarded + value <= policy.threshold {
212 discarded += value;
213 keep = i;
214 } else {
215 break;
216 }
217 }
218 keep
219 }
220 };
221
222 retained.max(1)
223}
224
225#[cfg(test)]
226fn singular_values_from_native(tensor: &tenferro::Tensor) -> Result<Vec<f64>, SvdError> {
227 match tensor.dtype() {
228 DType::F32 => native_tensor_primal_to_dense_col_major::<f32>(tensor)
229 .map(|values| values.into_iter().map(f64::from).collect())
230 .map_err(|e| SvdError::ComputationError(e.source)),
231 DType::F64 => native_tensor_primal_to_dense_col_major::<f64>(tensor)
232 .map_err(|e| SvdError::ComputationError(e.source)),
233 DType::C32 => native_tensor_primal_to_dense_col_major::<Complex32>(tensor)
234 .map(|values| {
235 values
236 .into_iter()
237 .map(|value| f64::from(value.re))
238 .collect()
239 })
240 .map_err(|e| SvdError::ComputationError(e.source)),
241 DType::C64 => native_tensor_primal_to_dense_col_major::<Complex64>(tensor)
242 .map(|values| values.into_iter().map(|value| value.re).collect())
243 .map_err(|e| SvdError::ComputationError(e.source)),
244 other => Err(SvdError::ComputationError(anyhow::anyhow!(
245 "native SVD returned unsupported singular-value scalar type {other:?}"
246 ))),
247 }
248}
249
250fn singular_values_from_eager(tensor: &EagerTensor) -> Result<Vec<f64>, SvdError> {
251 let value = tensor
252 .value()
253 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source)))?;
254 match tensor.dtype() {
255 DType::F32 => value
256 .as_slice::<f32>()
257 .map(|values| values.iter().map(|&value| f64::from(value)).collect())
258 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
259 DType::F64 => value
260 .as_slice::<f64>()
261 .map(|values| values.to_vec())
262 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
263 DType::C32 => value
264 .as_slice::<Complex32>()
265 .map(|values| values.iter().map(|value| f64::from(value.re)).collect())
266 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
267 DType::C64 => value
268 .as_slice::<Complex64>()
269 .map(|values| values.iter().map(|value| value.re).collect())
270 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
271 other => Err(SvdError::ComputationError(anyhow::anyhow!(
272 "eager SVD returned unsupported singular-value scalar type {other:?}"
273 ))),
274 }
275}
276
277#[cfg(feature = "tenferro-cuda")]
282fn resident_singular_values(
283 s_inner: &EagerTensor,
284 context: &ExecutionContext,
285) -> Result<Vec<f64>, SvdError> {
286 let indices: Vec<DynIndex> = s_inner
287 .shape()
288 .iter()
289 .map(|&dim| DynIndex::new_dyn(dim))
290 .collect();
291 let decision =
292 IdxTensor::from_inner(indices, s_inner.clone()).map_err(SvdError::ComputationError)?;
293 decision
294 .read_decision_data(context)
295 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))
296}
297
298type SvdTruncatedEagerResult = (
299 EagerTensor,
300 EagerTensor,
301 EagerTensor,
302 Vec<f64>,
303 DynIndex,
304 Vec<DynIndex>,
305 Vec<DynIndex>,
306);
307
308fn svd_truncated_inner(
309 t: &IdxTensor,
310 left_inds: &[DynIndex],
311 options: &SvdOptions,
312) -> Result<SvdTruncatedEagerResult, SvdError> {
313 if t.is_cuda_resident() {
314 return Err(SvdError::ComputationError(anyhow::anyhow!(
315 "CUDA-resident input requires svd_with_in with the owning execution context"
316 )));
317 }
318 svd_truncated_inner_scoped(t, left_inds, options, None)
319}
320
321pub(crate) fn svd_truncated_inner_in(
334 t: &IdxTensor,
335 left_inds: &[DynIndex],
336 options: &SvdOptions,
337 context: &ExecutionContext,
338) -> Result<SvdTruncatedEagerResult, SvdError> {
339 t.validate_context(context)
340 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
341 svd_truncated_inner_scoped(t, left_inds, options, Some(context))
342}
343
344fn svd_truncated_inner_scoped(
345 t: &IdxTensor,
346 left_inds: &[DynIndex],
347 options: &SvdOptions,
348 context: Option<&ExecutionContext>,
349) -> Result<SvdTruncatedEagerResult, SvdError> {
350 let (matrix_inner, _, m, n, left_indices, right_indices) = match context {
351 Some(context) => unfold_split_inner_in(t, left_inds, context),
352 None => unfold_split_inner(t, left_inds),
353 }
354 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))
355 .map_err(SvdError::ComputationError)?;
356 let k = m.min(n);
357
358 let (mut u_inner, mut s_inner, mut vt_inner) =
359 with_eager_session(matrix_inner.runtime(), context, |session| {
360 session.svd(&matrix_inner)
361 })
362 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
363 #[cfg(feature = "tenferro-cuda")]
364 let s_full = if let Some(context) = context {
365 if matches!(context, ExecutionContext::Cuda(_)) {
366 resident_singular_values(&s_inner, context)?
367 } else {
368 singular_values_from_eager(&s_inner)?
369 }
370 } else {
371 singular_values_from_eager(&s_inner)?
372 };
373 #[cfg(not(feature = "tenferro-cuda"))]
374 let s_full = {
375 let _ = context;
376 singular_values_from_eager(&s_inner)?
377 };
378 let order = descending_order(&s_full);
379 let s_full = match &order {
380 Some(order) => order.iter().map(|&i| s_full[i]).collect(),
381 None => s_full,
382 };
383 let mut r = retained_svd_rank(&s_full, k, options)?;
384 r = r.min(s_full.len());
385 if r < k || order.is_some() {
386 let keep: Vec<usize> = match &order {
387 Some(order) => order[..r].to_vec(),
388 None => (0..r).collect(),
389 };
390 u_inner = with_eager_session(u_inner.runtime(), context, |session| {
391 session.take_axis(&u_inner, 1, &keep)
392 })
393 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
394 s_inner = with_eager_session(s_inner.runtime(), context, |session| {
395 session.take_axis(&s_inner, 0, &keep)
396 })
397 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
398 vt_inner = with_eager_session(vt_inner.runtime(), context, |session| {
399 session.take_axis(&vt_inner, 0, &keep)
400 })
401 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
402 }
403
404 let bond_index = DynIndex::new_bond(r)
405 .map_err(|e| anyhow::anyhow!("Failed to create Link index: {:?}", e))
406 .map_err(SvdError::ComputationError)?;
407 let singular_values = s_full[..r].to_vec();
408
409 Ok((
410 u_inner,
411 s_inner,
412 vt_inner,
413 singular_values,
414 bond_index,
415 left_indices,
416 right_indices,
417 ))
418}
419
420pub fn svd<T>(
449 t: &IdxTensor,
450 left_inds: &[DynIndex],
451) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
452 svd_with::<T>(t, left_inds, &SvdOptions::default())
453}
454
455pub fn svd_with<T>(
494 t: &IdxTensor,
495 left_inds: &[DynIndex],
496 options: &SvdOptions,
497) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
498 let (u_inner, s_inner, vt_inner, _singular_values, bond_index, left_indices, right_indices) =
499 svd_truncated_inner(t, left_inds, options)?;
500
501 svd_assemble(
502 u_inner,
503 s_inner,
504 vt_inner,
505 bond_index,
506 left_indices,
507 right_indices,
508 None,
509 )
510}
511
512pub fn svd_with_in<T>(
559 t: &IdxTensor,
560 left_inds: &[DynIndex],
561 options: &SvdOptions,
562 context: &ExecutionContext,
563) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
564 let (u_inner, s_inner, vt_inner, _singular_values, bond_index, left_indices, right_indices) =
565 svd_truncated_inner_in(t, left_inds, options, context)?;
566
567 svd_assemble(
568 u_inner,
569 s_inner,
570 vt_inner,
571 bond_index,
572 left_indices,
573 right_indices,
574 Some(context),
575 )
576}
577
578fn svd_assemble(
580 u_inner: EagerTensor,
581 s_inner: EagerTensor,
582 vt_inner: EagerTensor,
583 bond_index: DynIndex,
584 left_indices: Vec<DynIndex>,
585 right_indices: Vec<DynIndex>,
586 context: Option<&ExecutionContext>,
587) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
588 let mut u_indices = left_indices;
589 u_indices.push(bond_index.clone());
590 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
591 let u_reshaped = with_eager_session(u_inner.runtime(), context, |session| {
592 session.reshape(&u_inner, &u_dims)
593 })
594 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}")))?;
595 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
596
597 let sim_bond_index = bond_index.sim();
602 let s_indices = vec![bond_index.clone(), sim_bond_index.clone()];
603 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
604
605 let mut vh_indices = vec![sim_bond_index];
606 vh_indices.extend(right_indices);
607 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
608 let vt_reshaped = with_eager_session(vt_inner.runtime(), context, |session| {
609 session.reshape(&vt_inner, &vh_dims)
610 })
611 .map_err(|e| {
612 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
613 })?;
614 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
615 let perm: Vec<usize> = (1..vh.indices.len()).chain(std::iter::once(0)).collect();
616 let v = vh
617 .conj()
618 .permute(&perm)
619 .map_err(|e| SvdError::ComputationError(anyhow::Error::new(e)))?;
620
621 Ok((u, s, v))
622}
623
624pub(crate) struct SvdFactorizeResult {
626 pub u: IdxTensor,
627 pub s: IdxTensor,
628 pub vh: IdxTensor,
629 pub bond_index: DynIndex,
630 pub singular_values: Vec<f64>,
631 pub rank: usize,
632}
633
634fn descending_order(singular_values: &[f64]) -> Option<Vec<usize>> {
642 if singular_values.windows(2).all(|pair| pair[0] >= pair[1]) {
643 return None;
644 }
645 let mut order: Vec<usize> = (0..singular_values.len()).collect();
646 order.sort_by(|&left, &right| singular_values[right].total_cmp(&singular_values[left]));
647 Some(order)
648}
649
650fn retained_svd_rank(
656 singular_values: &[f64],
657 k: usize,
658 options: &SvdOptions,
659) -> Result<usize, SvdError> {
660 if !options.truncate {
661 return Ok(k.max(1));
662 }
663 validate_svd_truncation_options(options.max_bond_dim, options.policy).map_err(|error| {
667 match error {
668 SvdTruncationOptionsError::ZeroMaxBondDim => SvdError::InvalidMaxBondDim,
669 SvdTruncationOptionsError::InvalidThreshold(threshold) => {
670 SvdError::InvalidThreshold(threshold)
671 }
672 }
673 })?;
674 let policy = options.policy.unwrap_or_else(default_svd_truncation_policy);
675 validate_svd_truncation_policy(policy).map_err(|e| SvdError::InvalidThreshold(e.0))?;
676 let mut retained = compute_retained_rank(singular_values, &policy);
677 if let Some(max_bond_dim) = options.max_bond_dim {
678 retained = retained.min(max_bond_dim);
679 }
680 Ok(retained.max(1).min(k))
681}
682
683pub(crate) fn svd_for_factorize_on_session<T>(
697 session: &mut tensor4all_tensorbackend::explicit::Session<'_>,
698 t: &IdxTensor,
699 left_inds: &[DynIndex],
700 options: &SvdOptions,
701) -> Result<SvdFactorizeResult, SvdError>
702where
703 T: TensorElement + ComplexFloat + tensor4all_tensorbackend::StorageScalar,
704 <T as ComplexFloat>::Real: Into<f64>,
705 <T as TensorScalar>::Real: Into<f64>,
706 T: num_traits::NumCast,
707{
708 if t.tracks_grad() {
709 return Err(SvdError::UnsupportedStorage(
710 crate::defaults::idx_tensor::TRACKED_SESSION_OPERAND_REFUSAL,
711 ));
712 }
713 let (left_indices, right_indices, order) =
714 unfold_index_sets(t, left_inds).map_err(|error| {
715 SvdError::ComputationError(anyhow::anyhow!("Failed to unfold tensor: {error}"))
716 })?;
717 let (dims, data) = t
720 .host_dense_permuted::<T>(&order)
721 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
722 let left_len = left_indices.len();
723 let m = crate::defaults::idx_tensor::checked_product(&dims[..left_len])
724 .map_err(SvdError::ComputationError)?;
725 let n = crate::defaults::idx_tensor::checked_product(&dims[left_len..])
726 .map_err(SvdError::ComputationError)?;
727 let k = m.min(n);
728 let matrix = tenferro::Tensor::from_vec_col_major(vec![m, n], data)
729 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
730 let (u_value, s_value, vt_value) = session
731 .svd(&matrix)
732 .map_err(|error| SvdError::ComputationError(anyhow::anyhow!("{error}")))?;
733 let s_full: Vec<f64> = s_value
736 .as_slice::<<T as TensorScalar>::Real>()
737 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?
738 .iter()
739 .map(|&value| value.into())
740 .collect();
741 let order = descending_order(&s_full);
742 let s_full = match &order {
743 Some(order) => order.iter().map(|&i| s_full[i]).collect(),
744 None => s_full,
745 };
746 let source = |position: usize| order.as_ref().map_or(position, |order| order[position]);
748 let rank = retained_svd_rank(&s_full, k, options)?;
749 let u_payload = u_value
750 .as_slice::<T>()
751 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
752 let vt_payload = vt_value
753 .as_slice::<T>()
754 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
755
756 let mut u_data = Vec::with_capacity(m * rank);
758 for column in 0..rank {
759 let start = source(column).checked_mul(m).ok_or_else(|| {
760 SvdError::ComputationError(anyhow::anyhow!("SVD U column offset overflows usize"))
761 })?;
762 let end = start.checked_add(m).ok_or_else(|| {
763 SvdError::ComputationError(anyhow::anyhow!("SVD U column end overflows usize"))
764 })?;
765 u_data.extend_from_slice(u_payload.get(start..end).ok_or_else(|| {
766 SvdError::ComputationError(anyhow::anyhow!("SVD U column {column} is out of bounds"))
767 })?);
768 }
769 let mut s_data = Vec::with_capacity(rank);
771 for &value in s_full.iter().take(rank) {
772 s_data.push(<T as num_traits::NumCast>::from(value).ok_or_else(|| {
773 SvdError::ComputationError(anyhow::anyhow!(
774 "singular value {value} does not fit the factor scalar type"
775 ))
776 })?);
777 }
778 let mut vt_data = Vec::with_capacity(rank * n);
780 for column in 0..n {
781 let base = column.checked_mul(k).ok_or_else(|| {
782 SvdError::ComputationError(anyhow::anyhow!("SVD V^H column offset overflows usize"))
783 })?;
784 for row in 0..rank {
785 let value = vt_payload.get(base + source(row)).ok_or_else(|| {
786 SvdError::ComputationError(anyhow::anyhow!("SVD V^H row {row} is out of bounds"))
787 })?;
788 vt_data.push(*value);
789 }
790 }
791
792 let bond_index = DynIndex::new_bond(rank).map_err(|error| {
793 SvdError::ComputationError(anyhow::anyhow!("Failed to create Link index: {error:?}"))
794 })?;
795 let mut u_indices = left_indices;
796 u_indices.push(bond_index.clone());
797 let u = IdxTensor::from_host_dense(u_indices, u_data)
798 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
799 let mut s_dense = vec![
803 <T as num_traits::Zero>::zero();
804 rank.checked_mul(rank).ok_or_else(|| {
805 SvdError::ComputationError(anyhow::anyhow!("SVD S payload size overflows usize"))
806 })?
807 ];
808 for (position, value) in s_data.iter().enumerate() {
809 s_dense[position * (rank + 1)] = *value;
810 }
811 let s = IdxTensor::from_host_dense(vec![bond_index.clone(), bond_index.sim()], s_dense)
812 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
813 let mut vh_indices = vec![bond_index.clone()];
814 vh_indices.extend(right_indices);
815 let vh = IdxTensor::from_host_dense(vh_indices, vt_data)
816 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
817
818 Ok(SvdFactorizeResult {
819 u,
820 s,
821 vh,
822 bond_index,
823 singular_values: s_full[..rank].to_vec(),
824 rank,
825 })
826}
827
828pub(crate) fn svd_for_factorize(
830 t: &IdxTensor,
831 left_inds: &[DynIndex],
832 options: &SvdOptions,
833) -> Result<SvdFactorizeResult, SvdError> {
834 let (u_inner, s_inner, vt_inner, singular_values, bond_index, left_indices, right_indices) =
835 svd_truncated_inner(t, left_inds, options)?;
836 svd_factorize_assemble(
837 (
838 u_inner,
839 s_inner,
840 vt_inner,
841 singular_values,
842 bond_index,
843 left_indices,
844 right_indices,
845 ),
846 None,
847 )
848}
849
850pub(crate) fn svd_for_factorize_in(
862 t: &IdxTensor,
863 left_inds: &[DynIndex],
864 options: &SvdOptions,
865 context: &ExecutionContext,
866) -> Result<SvdFactorizeResult, SvdError> {
867 let (u_inner, s_inner, vt_inner, singular_values, bond_index, left_indices, right_indices) =
868 svd_truncated_inner_in(t, left_inds, options, context)?;
869 svd_factorize_assemble(
870 (
871 u_inner,
872 s_inner,
873 vt_inner,
874 singular_values,
875 bond_index,
876 left_indices,
877 right_indices,
878 ),
879 Some(context),
880 )
881}
882
883fn svd_factorize_assemble(
885 (
886 u_inner,
887 s_inner,
888 vt_inner,
889 singular_values,
890 bond_index,
891 left_indices,
892 right_indices,
893 ): SvdTruncatedEagerResult,
894 context: Option<&ExecutionContext>,
895) -> Result<SvdFactorizeResult, SvdError> {
896 let rank = singular_values.len();
897 let mut u_indices = left_indices;
898 u_indices.push(bond_index.clone());
899 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
900 let u_reshaped = with_eager_session(u_inner.runtime(), context, |session| {
901 session.reshape(&u_inner, &u_dims)
902 })
903 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}")))?;
904 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
905
906 let s_inner = if s_inner.dtype() != vt_inner.dtype() {
911 with_eager_session(s_inner.runtime(), context, |session| {
912 session.convert(&s_inner, vt_inner.dtype())
913 })
914 .map_err(|e| {
915 SvdError::ComputationError(anyhow::anyhow!("eager SVD S dtype conversion failed: {e}"))
916 })?
917 } else {
918 s_inner
919 };
920 let s_indices = vec![bond_index.clone(), bond_index.sim()];
921 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
927
928 let mut vh_indices = vec![bond_index.clone()];
929 vh_indices.extend(right_indices);
930 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
931 let vt_reshaped = with_eager_session(vt_inner.runtime(), context, |session| {
932 session.reshape(&vt_inner, &vh_dims)
933 })
934 .map_err(|e| {
935 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
936 })?;
937 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
938
939 Ok(SvdFactorizeResult {
940 u,
941 s,
942 vh,
943 bond_index,
944 singular_values,
945 rank,
946 })
947}
948
949#[cfg(test)]
950mod tests;