tensor4all_core/defaults/
svd.rs1use 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::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
245type SvdTruncatedEagerResult = (
246 EagerTensor,
247 EagerTensor,
248 EagerTensor,
249 Vec<f64>,
250 DynIndex,
251 Vec<DynIndex>,
252 Vec<DynIndex>,
253);
254
255fn svd_truncated_inner(
256 t: &IdxTensor,
257 left_inds: &[DynIndex],
258 options: &SvdOptions,
259) -> Result<SvdTruncatedEagerResult, SvdError> {
260 let (matrix_inner, _, m, n, left_indices, right_indices) = unfold_split_inner(t, left_inds)
261 .map_err(|e| anyhow::anyhow!("Failed to unfold tensor: {}", e))
262 .map_err(SvdError::ComputationError)?;
263 let k = m.min(n);
264
265 let (mut u_inner, mut s_inner, mut vt_inner) = matrix_inner
266 .svd()
267 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
268 let s_full = singular_values_from_eager(&s_inner)?;
269 let mut r = if options.truncate {
270 validate_svd_truncation_options(options.max_bond_dim, options.policy).map_err(|error| {
274 match error {
275 SvdTruncationOptionsError::ZeroMaxBondDim => SvdError::InvalidMaxBondDim,
276 SvdTruncationOptionsError::InvalidThreshold(threshold) => {
277 SvdError::InvalidThreshold(threshold)
278 }
279 }
280 })?;
281 let policy = options.policy.unwrap_or_else(default_svd_truncation_policy);
282 validate_svd_truncation_policy(policy).map_err(|e| SvdError::InvalidThreshold(e.0))?;
283
284 let mut retained = compute_retained_rank(&s_full, &policy);
285 if let Some(max_bond_dim) = options.max_bond_dim {
286 retained = retained.min(max_bond_dim);
287 }
288 retained.max(1)
289 } else {
290 k.max(1)
291 };
292 r = r.min(s_full.len());
293 if r < k {
294 let keep: Vec<usize> = (0..r).collect();
295 u_inner = u_inner
296 .take_axis(1, &keep)
297 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
298 s_inner = s_inner
299 .take_axis(0, &keep)
300 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
301 vt_inner = vt_inner
302 .take_axis(0, &keep)
303 .map_err(|e| SvdError::ComputationError(anyhow::anyhow!("{e}")))?;
304 }
305
306 let bond_index = DynIndex::new_bond(r)
307 .map_err(|e| anyhow::anyhow!("Failed to create Link index: {:?}", e))
308 .map_err(SvdError::ComputationError)?;
309 let singular_values = s_full[..r].to_vec();
310
311 Ok((
312 u_inner,
313 s_inner,
314 vt_inner,
315 singular_values,
316 bond_index,
317 left_indices,
318 right_indices,
319 ))
320}
321
322pub fn svd<T>(
350 t: &IdxTensor,
351 left_inds: &[DynIndex],
352) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
353 svd_with::<T>(t, left_inds, &SvdOptions::default())
354}
355
356pub fn svd_with<T>(
392 t: &IdxTensor,
393 left_inds: &[DynIndex],
394 options: &SvdOptions,
395) -> Result<(IdxTensor, IdxTensor, IdxTensor), SvdError> {
396 let (u_inner, s_inner, vt_inner, _singular_values, bond_index, left_indices, right_indices) =
397 svd_truncated_inner(t, left_inds, options)?;
398
399 let mut u_indices = left_indices;
400 u_indices.push(bond_index.clone());
401 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
402 let u_reshaped = u_inner.reshape(&u_dims).map_err(|e| {
403 SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}"))
404 })?;
405 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
406
407 let sim_bond_index = bond_index.sim();
412 let s_indices = vec![bond_index.clone(), sim_bond_index.clone()];
413 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
414
415 let mut vh_indices = vec![sim_bond_index];
416 vh_indices.extend(right_indices);
417 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
418 let vt_reshaped = vt_inner.reshape(&vh_dims).map_err(|e| {
419 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
420 })?;
421 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
422 let perm: Vec<usize> = (1..vh.indices.len()).chain(std::iter::once(0)).collect();
423 let v = vh
424 .conj()
425 .permute(&perm)
426 .map_err(|e| SvdError::ComputationError(anyhow::Error::new(e)))?;
427
428 Ok((u, s, v))
429}
430
431pub(crate) struct SvdFactorizeResult {
433 pub u: IdxTensor,
434 pub s: IdxTensor,
435 pub vh: IdxTensor,
436 pub bond_index: DynIndex,
437 pub singular_values: Vec<f64>,
438 pub rank: usize,
439}
440
441pub(crate) fn svd_for_factorize(
443 t: &IdxTensor,
444 left_inds: &[DynIndex],
445 options: &SvdOptions,
446) -> Result<SvdFactorizeResult, SvdError> {
447 let (u_inner, s_inner, vt_inner, singular_values, bond_index, left_indices, right_indices) =
448 svd_truncated_inner(t, left_inds, options)?;
449 let rank = singular_values.len();
450
451 let mut u_indices = left_indices;
452 u_indices.push(bond_index.clone());
453 let u_dims: Vec<usize> = u_indices.iter().map(|idx| idx.dim).collect();
454 let u_reshaped = u_inner.reshape(&u_dims).map_err(|e| {
455 SvdError::ComputationError(anyhow::anyhow!("eager SVD U reshape failed: {e}"))
456 })?;
457 let u = IdxTensor::from_inner(u_indices, u_reshaped).map_err(SvdError::ComputationError)?;
458
459 let s_indices = vec![bond_index.clone(), bond_index.sim()];
460 let s = IdxTensor::from_diag_inner(s_indices, s_inner).map_err(SvdError::ComputationError)?;
461
462 let mut vh_indices = vec![bond_index.clone()];
463 vh_indices.extend(right_indices);
464 let vh_dims: Vec<usize> = vh_indices.iter().map(|idx| idx.dim).collect();
465 let vt_reshaped = vt_inner.reshape(&vh_dims).map_err(|e| {
466 SvdError::ComputationError(anyhow::anyhow!("eager SVD V^T reshape failed: {e}"))
467 })?;
468 let vh = IdxTensor::from_inner(vh_indices, vt_reshaped).map_err(SvdError::ComputationError)?;
469
470 Ok(SvdFactorizeResult {
471 u,
472 s,
473 vh,
474 bond_index,
475 singular_values,
476 rank,
477 })
478}
479
480#[cfg(test)]
481mod tests;