1use crate::defaults::idx_tensor::unfold_split_inner;
11use crate::defaults::DynIndex;
12use crate::index_like::IndexLike;
13use crate::truncation::{
14 validate_svd_truncation_options, validate_svd_truncation_policy, SingularValueMeasure,
15 SvdTruncationOptionsError, SvdTruncationPolicy, ThresholdScale, TruncationRule,
16};
17use crate::{ExecutionContext, IdxTensor};
18use num_complex::Complex64;
19use std::sync::Mutex;
20use tenferro::DType;
21use tenferro_ad::EagerTensor;
22use tenferro_linalg::EagerTensorLinalgExt;
23#[cfg(test)]
24use tensor4all_tensorbackend::native_tensor_primal_to_dense_col_major;
25use thiserror::Error;
26
27#[derive(Debug, Error)]
29pub enum SvdError {
30 #[error("SVD computation failed: {0}")]
32 ComputationError(#[from] anyhow::Error),
33 #[error("Invalid SVD truncation threshold: {0}. Threshold must be finite and non-negative.")]
35 InvalidThreshold(f64),
36 #[error("max_bond_dim must be at least 1 when set")]
38 InvalidMaxBondDim,
39}
40
41#[derive(Debug, Clone, Copy)]
62pub struct SvdOptions {
63 pub max_bond_dim: Option<usize>,
65 pub policy: Option<SvdTruncationPolicy>,
68 truncate: bool,
69}
70
71impl Default for SvdOptions {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl SvdOptions {
78 #[must_use]
80 pub fn new() -> Self {
81 Self {
82 max_bond_dim: None,
83 policy: None,
84 truncate: true,
85 }
86 }
87
88 #[must_use]
90 pub fn with_max_bond_dim(mut self, max_bond_dim: usize) -> Self {
91 self.max_bond_dim = Some(max_bond_dim);
92 self
93 }
94
95 #[must_use]
97 pub fn with_policy(mut self, policy: SvdTruncationPolicy) -> Self {
98 self.policy = Some(policy);
99 self
100 }
101
102 pub(crate) fn full_rank() -> Self {
103 Self {
104 max_bond_dim: None,
105 policy: None,
106 truncate: false,
107 }
108 }
109}
110
111fn default_policy_guard() -> std::sync::MutexGuard<'static, SvdTruncationPolicy> {
112 match DEFAULT_SVD_TRUNCATION_POLICY.lock() {
113 Ok(guard) => guard,
114 Err(poisoned) => poisoned.into_inner(),
115 }
116}
117
118static DEFAULT_SVD_TRUNCATION_POLICY: Mutex<SvdTruncationPolicy> =
120 Mutex::new(SvdTruncationPolicy::new(1e-12));
121
122#[must_use]
126pub fn default_svd_truncation_policy() -> SvdTruncationPolicy {
127 *default_policy_guard()
128}
129
130pub fn set_default_svd_truncation_policy(policy: SvdTruncationPolicy) -> Result<(), SvdError> {
138 validate_svd_truncation_policy(policy).map_err(|e| SvdError::InvalidThreshold(e.0))?;
139 *default_policy_guard() = policy;
140 Ok(())
141}
142
143fn singular_value_measure(value: f64, measure: SingularValueMeasure) -> f64 {
144 match measure {
145 SingularValueMeasure::Value => value,
146 SingularValueMeasure::SquaredValue => value * value,
147 }
148}
149
150pub(crate) fn compute_retained_rank(s_vec: &[f64], policy: &SvdTruncationPolicy) -> usize {
152 if s_vec.is_empty() {
153 return 1;
154 }
155
156 let measured: Vec<f64> = s_vec
157 .iter()
158 .map(|&value| singular_value_measure(value, policy.measure))
159 .collect();
160 if measured.iter().all(|&value| value == 0.0) {
161 return 1;
162 }
163
164 let retained = match (policy.scale, policy.rule) {
165 (ThresholdScale::Relative, TruncationRule::PerValue) => {
166 let reference = measured.iter().copied().fold(0.0_f64, f64::max);
167 measured
168 .iter()
169 .take_while(|&&value| reference > 0.0 && value / reference > policy.threshold)
170 .count()
171 }
172 (ThresholdScale::Absolute, TruncationRule::PerValue) => measured
173 .iter()
174 .take_while(|&&value| value > policy.threshold)
175 .count(),
176 (ThresholdScale::Relative, TruncationRule::DiscardedTailSum) => {
177 let total: f64 = measured.iter().sum();
178 if total == 0.0 {
179 1
180 } else {
181 let mut discarded = 0.0;
182 let mut keep = measured.len();
183 for (i, value) in measured.iter().enumerate().rev() {
184 if (discarded + value) / total <= policy.threshold {
185 discarded += value;
186 keep = i;
187 } else {
188 break;
189 }
190 }
191 keep
192 }
193 }
194 (ThresholdScale::Absolute, TruncationRule::DiscardedTailSum) => {
195 let mut discarded = 0.0;
196 let mut keep = measured.len();
197 for (i, value) in measured.iter().enumerate().rev() {
198 if discarded + value <= policy.threshold {
199 discarded += value;
200 keep = i;
201 } else {
202 break;
203 }
204 }
205 keep
206 }
207 };
208
209 retained.max(1)
210}
211
212#[cfg(test)]
213fn singular_values_from_native(tensor: &tenferro::Tensor) -> Result<Vec<f64>, SvdError> {
214 match tensor.dtype() {
215 DType::F64 => native_tensor_primal_to_dense_col_major::<f64>(tensor)
216 .map_err(|e| SvdError::ComputationError(e.source)),
217 DType::C64 => native_tensor_primal_to_dense_col_major::<Complex64>(tensor)
218 .map(|values| values.into_iter().map(|value| value.re).collect())
219 .map_err(|e| SvdError::ComputationError(e.source)),
220 other => Err(SvdError::ComputationError(anyhow::anyhow!(
221 "native SVD returned unsupported singular-value scalar type {other:?}"
222 ))),
223 }
224}
225
226fn singular_values_from_eager(tensor: &EagerTensor) -> Result<Vec<f64>, SvdError> {
227 let value = tensor
228 .value()
229 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source)))?;
230 match tensor.dtype() {
231 DType::F64 => value
232 .as_slice::<f64>()
233 .map(|values| values.to_vec())
234 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
235 DType::C64 => value
236 .as_slice::<Complex64>()
237 .map(|values| values.iter().map(|value| value.re).collect())
238 .map_err(|source| SvdError::ComputationError(anyhow::Error::new(source))),
239 other => Err(SvdError::ComputationError(anyhow::anyhow!(
240 "eager SVD returned unsupported singular-value scalar type {other:?}"
241 ))),
242 }
243}
244
245#[cfg(feature = "tenferro-cuda")]
250fn resident_singular_values(
251 s_inner: &EagerTensor,
252 context: &ExecutionContext,
253) -> Result<Vec<f64>, SvdError> {
254 let indices: Vec<DynIndex> = s_inner
255 .shape()
256 .iter()
257 .map(|&dim| DynIndex::new_dyn(dim))
258 .collect();
259 let decision =
260 IdxTensor::from_inner(indices, s_inner.clone()).map_err(SvdError::ComputationError)?;
261 decision
262 .read_decision_data(context)
263 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))
264}
265
266type SvdTruncatedEagerResult = (
267 EagerTensor,
268 EagerTensor,
269 EagerTensor,
270 Vec<f64>,
271 DynIndex,
272 Vec<DynIndex>,
273 Vec<DynIndex>,
274);
275
276fn svd_truncated_inner(
277 t: &IdxTensor,
278 left_inds: &[DynIndex],
279 options: &SvdOptions,
280) -> Result<SvdTruncatedEagerResult, SvdError> {
281 if t.is_cuda_resident() {
282 return Err(SvdError::ComputationError(anyhow::anyhow!(
283 "CUDA-resident input requires svd_with_in with the owning execution context"
284 )));
285 }
286 svd_truncated_inner_scoped(t, left_inds, options, None)
287}
288
289pub(crate) fn svd_truncated_inner_in(
302 t: &IdxTensor,
303 left_inds: &[DynIndex],
304 options: &SvdOptions,
305 context: &ExecutionContext,
306) -> Result<SvdTruncatedEagerResult, SvdError> {
307 t.validate_context(context)
308 .map_err(|error| SvdError::ComputationError(anyhow::Error::new(error)))?;
309 svd_truncated_inner_scoped(t, left_inds, options, Some(context))
310}
311
312fn svd_truncated_inner_scoped(
313 t: &IdxTensor,
314 left_inds: &[DynIndex],
315 options: &SvdOptions,
316 context: Option<&ExecutionContext>,
317) -> Result<SvdTruncatedEagerResult, SvdError> {
318 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
319 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))
320 .map_err(SvdError::ComputationError)?;
321 let k = m.min(n);
322
323 let (mut u_inner, mut s_inner, mut vt_inner) = matrix_inner
324 .svd()
325 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
326 #[cfg(feature = "tenferro-cuda")]
327 let s_full = if let Some(context) = context {
328 if matches!(context, ExecutionContext::Cuda(_)) {
329 resident_singular_values(&s_inner, context)?
330 } else {
331 singular_values_from_eager(&s_inner)?
332 }
333 } else {
334 singular_values_from_eager(&s_inner)?
335 };
336 #[cfg(not(feature = "tenferro-cuda"))]
337 let s_full = {
338 let _ = context;
339 singular_values_from_eager(&s_inner)?
340 };
341 let mut r = if options.truncate {
342 validate_svd_truncation_options(options.max_bond_dim, options.policy).map_err(|error| {
346 match error {
347 SvdTruncationOptionsError::ZeroMaxBondDim => SvdError::InvalidMaxBondDim,
348 SvdTruncationOptionsError::InvalidThreshold(threshold) => {
349 SvdError::InvalidThreshold(threshold)
350 }
351 }
352 })?;
353 let policy = options.policy.unwrap_or_else(default_svd_truncation_policy);
354 validate_svd_truncation_policy(policy).map_err(|e| SvdError::InvalidThreshold(e.0))?;
355
356 let mut retained = compute_retained_rank(&s_full, &policy);
357 if let Some(max_bond_dim) = options.max_bond_dim {
358 retained = retained.min(max_bond_dim);
359 }
360 retained.max(1)
361 } else {
362 k.max(1)
363 };
364 r = r.min(s_full.len());
365 if r < k {
366 let keep: Vec<usize> = (0..r).collect();
367 u_inner = u_inner
368 .take_axis(1, &keep)
369 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
370 s_inner = s_inner
371 .take_axis(0, &keep)
372 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
373 vt_inner = vt_inner
374 .take_axis(0, &keep)
375 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
376 }
377
378 let bond_index = DynIndex::new_bond(r)
379 .map_err(|e| anyhow::anyhow!("Failed to create Link index: {:?}", e))
380 .map_err(SvdError::ComputationError)?;
381 let singular_values = s_full[..r].to_vec();
382
383 Ok((
384 u_inner,
385 s_inner,
386 vt_inner,
387 singular_values,
388 bond_index,
389 left_indices,
390 right_indices,
391 ))
392}
393
394pub fn svd<T>(
422 t: &IdxTensor,
423 left_inds: &[DynIndex],
424) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
425 svd_with::<T>(t, left_inds, &SvdOptions::default())
426}
427
428pub fn svd_with<T>(
464 t: &IdxTensor,
465 left_inds: &[DynIndex],
466 options: &SvdOptions,
467) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
468 let (u_inner, s_inner, vt_inner, _singular_values, bond_index, left_indices, right_indices) =
469 svd_truncated_inner(t, left_inds, options)?;
470
471 svd_assemble(
472 u_inner,
473 s_inner,
474 vt_inner,
475 bond_index,
476 left_indices,
477 right_indices,
478 )
479}
480
481pub fn svd_with_in<T>(
525 t: &IdxTensor,
526 left_inds: &[DynIndex],
527 options: &SvdOptions,
528 context: &ExecutionContext,
529) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
530 let (u_inner, s_inner, vt_inner, _singular_values, bond_index, left_indices, right_indices) =
531 svd_truncated_inner_in(t, left_inds, options, context)?;
532
533 svd_assemble(
534 u_inner,
535 s_inner,
536 vt_inner,
537 bond_index,
538 left_indices,
539 right_indices,
540 )
541}
542
543fn svd_assemble(
545 u_inner: EagerTensor,
546 s_inner: EagerTensor,
547 vt_inner: EagerTensor,
548 bond_index: DynIndex,
549 left_indices: Vec<DynIndex>,
550 right_indices: Vec<DynIndex>,
551) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
552 let mut u_indices = left_indices;
553 u_indices.push(bond_index.clone());
554 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
555 let u_reshaped = u_inner.reshape(&u_dims).map_err(|e| {
556 SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}"))
557 })?;
558 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
559
560 let sim_bond_index = bond_index.sim();
565 let s_indices = vec![bond_index.clone(), sim_bond_index.clone()];
566 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
567
568 let mut vh_indices = vec![sim_bond_index];
569 vh_indices.extend(right_indices);
570 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
571 let vt_reshaped = vt_inner.reshape(&vh_dims).map_err(|e| {
572 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
573 })?;
574 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
575 let perm: Vec<usize> = (1..vh.indices.len()).chain(std::iter::once(0)).collect();
576 let v = vh
577 .conj()
578 .permute(&perm)
579 .map_err(|e| SvdError::ComputationError(anyhow::Error::new(e)))?;
580
581 Ok((u, s, v))
582}
583
584pub(crate) struct SvdFactorizeResult {
586 pub u: IdxTensor,
587 pub s: IdxTensor,
588 pub vh: IdxTensor,
589 pub bond_index: DynIndex,
590 pub singular_values: Vec<f64>,
591 pub rank: usize,
592}
593
594pub(crate) fn svd_for_factorize(
596 t: &IdxTensor,
597 left_inds: &[DynIndex],
598 options: &SvdOptions,
599) -> Result<SvdFactorizeResult, SvdError> {
600 let (u_inner, s_inner, vt_inner, singular_values, bond_index, left_indices, right_indices) =
601 svd_truncated_inner(t, left_inds, options)?;
602 svd_factorize_assemble(
603 u_inner,
604 s_inner,
605 vt_inner,
606 singular_values,
607 bond_index,
608 left_indices,
609 right_indices,
610 )
611}
612
613pub(crate) fn svd_for_factorize_in(
625 t: &IdxTensor,
626 left_inds: &[DynIndex],
627 options: &SvdOptions,
628 context: &ExecutionContext,
629) -> Result<SvdFactorizeResult, SvdError> {
630 let (u_inner, s_inner, vt_inner, singular_values, bond_index, left_indices, right_indices) =
631 svd_truncated_inner_in(t, left_inds, options, context)?;
632 svd_factorize_assemble(
633 u_inner,
634 s_inner,
635 vt_inner,
636 singular_values,
637 bond_index,
638 left_indices,
639 right_indices,
640 )
641}
642
643fn svd_factorize_assemble(
645 u_inner: EagerTensor,
646 s_inner: EagerTensor,
647 vt_inner: EagerTensor,
648 singular_values: Vec<f64>,
649 bond_index: DynIndex,
650 left_indices: Vec<DynIndex>,
651 right_indices: Vec<DynIndex>,
652) -> Result<SvdFactorizeResult, SvdError> {
653 let rank = singular_values.len();
654 let mut u_indices = left_indices;
655 u_indices.push(bond_index.clone());
656 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
657 let u_reshaped = u_inner.reshape(&u_dims).map_err(|e| {
658 SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}"))
659 })?;
660 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
661
662 let s_indices = vec![bond_index.clone(), bond_index.sim()];
663 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
664
665 let mut vh_indices = vec![bond_index.clone()];
666 vh_indices.extend(right_indices);
667 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
668 let vt_reshaped = vt_inner.reshape(&vh_dims).map_err(|e| {
669 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
670 })?;
671 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
672
673 Ok(SvdFactorizeResult {
674 u,
675 s,
676 vh,
677 bond_index,
678 singular_values,
679 rank,
680 })
681}
682
683#[cfg(test)]
684mod tests;