use super::{identity_mpo, product_state_mps};
use crate::dmrg::heff::{EffectiveHamiltonian2Site, dmrg_2site_step};
use crate::dmrg::{DmrgHeffError, LocalEigensolverParams};
use crate::krylov::{LanczosError, LanczosParams, LinearOp};
use approx::assert_abs_diff_eq;
use ariadnetor_core::Scalar;
use ariadnetor_linalg::{TruncSvdParams, contract, diagonal_scale, eigh_with_backend};
use ariadnetor_mps::BraketEnvs;
use ariadnetor_mps::{Mpo, Mps};
use ariadnetor_native::NativeBackend;
use ariadnetor_tensor::{ComputeBackendTensorExt, DenseLayout, DenseStorage, DenseTensor, Host};
use num_complex::Complex;
use rand::RngExt;
use rand::SeedableRng;
use rand::rngs::StdRng;
fn random_mps_f64(
n: usize,
d: usize,
chi: usize,
seed: u64,
) -> Mps<DenseStorage<f64>, DenseLayout> {
let mut rng = StdRng::seed_from_u64(seed);
let storages: Vec<DenseTensor<f64>> = (0..n)
.map(|i| {
let l = if i == 0 { 1 } else { chi };
let r = if i + 1 == n { 1 } else { chi };
let len = l * d * r;
let data: Vec<f64> = (0..len).map(|_| rng.random_range(-0.5_f64..0.5)).collect();
Host::shared().dense(data, vec![l, d, r])
})
.collect();
Mps::from_sites(storages)
}
fn random_mps_c64(
n: usize,
d: usize,
chi: usize,
seed: u64,
) -> Mps<DenseStorage<Complex<f64>>, DenseLayout> {
let mut rng = StdRng::seed_from_u64(seed);
let storages: Vec<DenseTensor<Complex<f64>>> = (0..n)
.map(|i| {
let l = if i == 0 { 1 } else { chi };
let r = if i + 1 == n { 1 } else { chi };
let len = l * d * r;
let data: Vec<Complex<f64>> = (0..len)
.map(|_| {
let re = rng.random_range(-0.5_f64..0.5);
let im = rng.random_range(-0.5_f64..0.5);
Complex::new(re, im)
})
.collect();
Host::shared().dense(data, vec![l, d, r])
})
.collect();
Mps::from_sites(storages)
}
fn hermitian_local_mpo_f64(n: usize, d: usize, seed: u64) -> Mpo<DenseStorage<f64>, DenseLayout> {
let mut rng = StdRng::seed_from_u64(seed);
let storages: Vec<DenseTensor<f64>> = (0..n)
.map(|_| {
let mut m = vec![0.0_f64; d * d];
let mut r = vec![0.0_f64; d * d];
for entry in r.iter_mut() {
*entry = rng.random_range(-1.0_f64..1.0);
}
for s in 0..d {
for t in 0..d {
m[s * d + t] = 0.5 * (r[s * d + t] + r[t * d + s]);
}
}
Host::shared().dense(m, vec![1, d, d, 1])
})
.collect();
Mpo::from_sites(storages)
}
fn hermitian_local_mpo_c64(
n: usize,
d: usize,
seed: u64,
) -> Mpo<DenseStorage<Complex<f64>>, DenseLayout> {
let mut rng = StdRng::seed_from_u64(seed);
let storages: Vec<DenseTensor<Complex<f64>>> = (0..n)
.map(|_| {
let mut m = vec![Complex::new(0.0, 0.0); d * d];
let mut r = vec![Complex::new(0.0, 0.0); d * d];
for entry in r.iter_mut() {
let re = rng.random_range(-1.0_f64..1.0);
let im = rng.random_range(-1.0_f64..1.0);
*entry = Complex::new(re, im);
}
for s in 0..d {
for t in 0..d {
m[s * d + t] = (r[s * d + t] + r[t * d + s].conj()) * 0.5;
}
}
Host::shared().dense(m, vec![1, d, d, 1])
})
.collect();
Mpo::from_sites(storages)
}
fn build_heff_dense<T>(heff: &EffectiveHamiltonian2Site<'_, T>) -> DenseTensor<T>
where
T: Scalar,
{
let dim = heff.dim();
let mut data = vec![T::zero(); dim * dim];
for k in 0..dim {
let mut e = vec![T::zero(); dim];
e[k] = T::one();
let e_dense = Host::shared().dense(e, vec![dim]);
let col = heff.apply(&e_dense);
let col_data = col.data_slice();
for i in 0..dim {
data[i + dim * k] = col_data[i];
}
}
Host::shared().dense(data, vec![dim, dim])
}
fn make_heff<'a, T>(
envs: &'a BraketEnvs<DenseStorage<T>, DenseLayout>,
mps: &'a Mps<DenseStorage<T>, DenseLayout>,
mpo: &'a Mpo<DenseStorage<T>, DenseLayout>,
site: usize,
) -> EffectiveHamiltonian2Site<'a, T>
where
T: Scalar,
T::Real: Scalar<Real = T::Real>,
{
use ariadnetor_mps::TensorChain;
let left = envs.left(site).expect("left(site)");
let right = envs.right(site + 2).expect("right(site+2)");
let w_i = mpo.site(site);
let w_ip1 = mpo.site(site + 1);
let mps_i = mps.site(site);
let mps_ip1 = mps.site(site + 1);
let chi_l = left.shape()[0];
let chi_r = right.shape()[0];
let d_i = mps_i.shape()[1];
let d_ip1 = mps_ip1.shape()[1];
EffectiveHamiltonian2Site::new(left, w_i, w_ip1, right, chi_l, d_i, d_ip1, chi_r)
}
#[test]
fn heff_identity_smoke() {
let n = 4;
let mps = product_state_mps(n, 2);
let mpo = identity_mpo(n, 2);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let result = dmrg_2site_step(
&envs,
&mps,
&mpo,
1,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(7),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("step");
assert_abs_diff_eq!(result.eigenvalue, 1.0, epsilon = 1e-9);
assert!(result.converged, "Lanczos must converge on identity H_eff");
}
#[test]
fn heff_matvec_matches_dense_apply() {
let n = 4;
let d = 2;
let chi = 2;
let mps = random_mps_f64(n, d, chi, 0xCAFE_F00D);
let mpo = hermitian_local_mpo_f64(n, d, 0xBEEF_DEAD);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let site = 1;
let heff = make_heff(&envs, &mps, &mpo, site);
let h_dense = build_heff_dense(&heff);
let dim = heff.dim();
let h_data = h_dense.data_slice();
for i in 0..dim {
for k in 0..dim {
let aij = h_data[i + dim * k];
let aji = h_data[k + dim * i];
assert_abs_diff_eq!(aij, aji, epsilon = 1e-12);
}
}
let mut rng = StdRng::seed_from_u64(0xABCD_1234);
let v_data: Vec<f64> = (0..dim).map(|_| rng.random_range(-0.5_f64..0.5)).collect();
let v = Host::shared().dense(v_data.clone(), vec![dim]);
let apply_out = heff.apply(&v);
let apply_data = apply_out.data_slice();
let mut dense_out = vec![0.0_f64; dim];
for i in 0..dim {
for k in 0..dim {
dense_out[i] += h_data[i + dim * k] * v_data[k];
}
}
for i in 0..dim {
assert_abs_diff_eq!(apply_data[i], dense_out[i], epsilon = 1e-9);
}
}
#[test]
fn heff_lanczos_eigvalue_matches_eigh() {
let n = 4;
let d = 2;
let chi = 2;
let mps = random_mps_f64(n, d, chi, 0xCAFE_F00D);
let mpo = hermitian_local_mpo_f64(n, d, 0xBEEF_DEAD);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let site = 1;
let heff = make_heff(&envs, &mps, &mpo, site);
let h_dense = build_heff_dense(&heff);
let (eigvals, _) = eigh_with_backend(&NativeBackend::new(), &h_dense, 1).expect("eigh");
let reference = eigvals.data_slice()[0];
let result = dmrg_2site_step(
&envs,
&mps,
&mpo,
site,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(11),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("step");
assert_abs_diff_eq!(result.eigenvalue, reference, epsilon = 1e-9);
assert!(result.converged, "Lanczos must converge within budget");
assert!(result.iters < 200, "iters must stay within max_iter budget");
}
#[test]
fn heff_svd_split_is_canonical() {
let n = 4;
let d = 2;
let chi = 2;
let mps = random_mps_f64(n, d, chi, 0xCAFE_F00D);
let mpo = hermitian_local_mpo_f64(n, d, 0xBEEF_DEAD);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let result = dmrg_2site_step(
&envs,
&mps,
&mpo,
1,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(11),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("step");
let utu = contract(&NativeBackend::new(), &result.u, &result.u, "abc,abd->cd").expect("U^T U");
let chi_new = result.u.shape()[2];
assert_eq!(utu.shape(), &[chi_new, chi_new]);
let utu_data = utu.data_slice();
for i in 0..chi_new {
for j in 0..chi_new {
let expected = if i == j { 1.0 } else { 0.0 };
assert_abs_diff_eq!(utu_data[i * chi_new + j], expected, epsilon = 1e-10);
}
}
let vvt =
contract(&NativeBackend::new(), &result.vt, &result.vt, "abc,dbc->ad").expect("Vt Vt^T");
assert_eq!(vvt.shape(), &[chi_new, chi_new]);
let vvt_data = vvt.data_slice();
for i in 0..chi_new {
for j in 0..chi_new {
let expected = if i == j { 1.0 } else { 0.0 };
assert_abs_diff_eq!(vvt_data[i * chi_new + j], expected, epsilon = 1e-10);
}
}
}
#[test]
fn heff_svd_reconstruction_round_trips() {
let n = 4;
let d = 2;
let chi = 2;
let mps = random_mps_f64(n, d, chi, 0xCAFE_F00D);
let mpo = hermitian_local_mpo_f64(n, d, 0xBEEF_DEAD);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let site = 1;
let lan_params = LanczosParams {
seed: Some(11),
..LanczosParams::default()
};
let eigensolver = LocalEigensolverParams::Lanczos(lan_params.clone());
let result = dmrg_2site_step(
&envs,
&mps,
&mpo,
site,
&eigensolver,
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("step");
assert_abs_diff_eq!(result.trunc_err, 0.0, epsilon = 1e-10);
let s_sq_sum: f64 = result.s.data_slice().iter().map(|v| v * v).sum();
assert_abs_diff_eq!(s_sq_sum, 1.0, epsilon = 1e-9);
let s_data = result.s.data_slice().to_vec();
for window in s_data.windows(2) {
assert!(window[0] >= window[1] - 1e-12, "s descending");
}
for v in s_data.iter() {
assert!(*v >= -1e-12, "s non-negative");
}
let us = diagonal_scale(&NativeBackend::new(), &result.u, result.s.data_slice(), 2)
.expect("U·diag(S)");
let recon = contract(&NativeBackend::new(), &us, &result.vt, "aik,kjb->aijb").expect("U·S·Vt");
let heff = make_heff(&envs, &mps, &mpo, site);
let dim = heff.dim();
let lan = crate::krylov::lanczos_smallest::<f64, _>(&heff, dim, &lan_params).unwrap();
let psi_4d = lan
.eigenvector
.reshape(vec![heff.chi_l, heff.d_i, heff.d_ip1, heff.chi_r]);
let psi_data = psi_4d.data_slice();
let recon_data = recon.data_slice();
let frob_plus: f64 = psi_data
.iter()
.zip(recon_data.iter())
.map(|(p, r)| (p - r).powi(2))
.sum::<f64>()
.sqrt();
let frob_minus: f64 = psi_data
.iter()
.zip(recon_data.iter())
.map(|(p, r)| (p + r).powi(2))
.sum::<f64>()
.sqrt();
let frob = frob_plus.min(frob_minus);
let tol = result.trunc_err + 1e-9;
assert!(
frob <= tol,
"Frobenius residual {} exceeds trunc_err+slack {} (plus={}, minus={})",
frob,
tol,
frob_plus,
frob_minus
);
}
#[test]
fn heff_edge_sites_succeed() {
let n = 3;
let d = 2;
let chi = 2;
let mps = random_mps_f64(n, d, chi, 0xFEED_CAFE);
let mpo = hermitian_local_mpo_f64(n, d, 0x0BAD_F00D);
let envs0 = BraketEnvs::build(&mps, &mpo, &mps).expect("build site=0");
let r0 = dmrg_2site_step(
&envs0,
&mps,
&mpo,
0,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(3),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("site=0");
assert!(r0.converged);
assert_eq!(r0.u.shape()[0], 1);
let mut envs1 = BraketEnvs::build(&mps, &mpo, &mps).expect("build site=n-2");
for k in 0..(n - 2) {
envs1
.advance_left(&mps, &mpo, &mps, k)
.expect("advance_left");
}
let r1 = dmrg_2site_step(
&envs1,
&mps,
&mpo,
n - 2,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(5),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
)
.expect("site=n-2");
assert!(r1.converged);
assert_eq!(r1.vt.shape()[2], 1);
}
#[test]
fn heff_matvec_matches_dense_apply_complex() {
let n = 3;
let d = 2;
let chi = 2;
let mps = random_mps_c64(n, d, chi, 0x1357_9BDF);
let mpo = hermitian_local_mpo_c64(n, d, 0x2468_ACE0);
let envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
let site = 0;
let heff = make_heff(&envs, &mps, &mpo, site);
let h_dense = build_heff_dense(&heff);
let dim = heff.dim();
let h_data = h_dense.data_slice();
for i in 0..dim {
for k in 0..dim {
let aij = h_data[i + dim * k];
let aji = h_data[k + dim * i];
assert_abs_diff_eq!(aij.re, aji.conj().re, epsilon = 1e-12);
assert_abs_diff_eq!(aij.im, aji.conj().im, epsilon = 1e-12);
}
}
let mut rng = StdRng::seed_from_u64(0xFADE_D00D);
let v_data: Vec<Complex<f64>> = (0..dim)
.map(|_| {
let re = rng.random_range(-0.5_f64..0.5);
let im = rng.random_range(-0.5_f64..0.5);
Complex::new(re, im)
})
.collect();
let v = Host::shared().dense(v_data.clone(), vec![dim]);
let apply_out = heff.apply(&v);
let apply_data = apply_out.data_slice();
let mut dense_out = vec![Complex::new(0.0, 0.0); dim];
for i in 0..dim {
for k in 0..dim {
dense_out[i] += h_data[i + dim * k] * v_data[k];
}
}
for i in 0..dim {
assert_abs_diff_eq!(apply_data[i].re, dense_out[i].re, epsilon = 1e-9);
assert_abs_diff_eq!(apply_data[i].im, dense_out[i].im, epsilon = 1e-9);
}
}
fn nan_poisoned_mpo_f64(
n: usize,
d: usize,
poison_site: usize,
) -> Mpo<DenseStorage<f64>, DenseLayout> {
let storages: Vec<DenseTensor<f64>> = (0..n)
.map(|site| {
let mut data = vec![0.0_f64; d * d];
for k in 0..d {
data[k + d * k] = 1.0;
}
if site == poison_site {
data[0] = f64::NAN;
}
Host::shared().dense(data, vec![1, d, d, 1])
})
.collect();
Mpo::from_sites(storages)
}
#[test]
fn dmrg_step_surfaces_nonfinite_lanczos_as_error() {
let (n, d) = (4usize, 2usize);
let mps = product_state_mps(n, d);
let mpo = nan_poisoned_mpo_f64(n, d, 1);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("build");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
let result = dmrg_2site_step(
&envs,
&mps,
&mpo,
1,
&LocalEigensolverParams::Lanczos(LanczosParams {
seed: Some(7),
..LanczosParams::default()
}),
&TruncSvdParams {
chi_max: None,
target_trunc_err: None,
},
);
match result {
Err(DmrgHeffError::Lanczos(LanczosError::NonFinite { .. })) => {}
Err(other) => panic!("expected Lanczos(NonFinite), got a different error: {other:?}"),
Ok(_) => panic!("expected an error, got Ok — the NaN was not surfaced"),
}
}