use crate::dmrg::heff::EffectiveHamiltonian2Site;
use crate::dmrg::heff_block_sparse::{
EffectiveHamiltonian2SiteBlockSparse, dmrg_2site_step_block_sparse,
};
use crate::dmrg::{DmrgEnvs, LocalEigensolverParams};
use crate::krylov::{LanczosParams, LinearOp};
use ariadnetor_linalg::{TruncSvdParams, eigh_with_backend};
use ariadnetor_mps::TensorChain;
use ariadnetor_native::NativeBackend;
use ariadnetor_tensor::MemoryOrder;
use ariadnetor_tensor::{ComputeBackendTensorExt, Host};
use ariadnetor_tensor::{Sector, U1Sector};
use super::fixtures::{make_n2_mpo_f64, make_n2_mps_f64, make_n3_mpo_f64, make_n3_mps_f64};
use ariadnetor_tensor::test_fixtures::{
build_dense_psi_from_flat, dense_to_template_flat, densify_bsp_f64, template_block_offsets,
template_from_mps_pair,
};
#[test]
fn bsp_heff_matvec_matches_dense_oracle() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let bsp_heff = EffectiveHamiltonian2SiteBlockSparse::new(
envs.left(0).expect("left env"),
mpo.site(0),
mpo.site(1),
envs.right(2).expect("right env"),
mps.site(0),
mps.site(1),
)
.expect("operands share backend preferred_order by construction");
let left_d = densify_bsp_f64(envs.left(0).expect("left"));
let right_d = densify_bsp_f64(envs.right(2).expect("right"));
let w0_d = densify_bsp_f64(mpo.site(0));
let w1_d = densify_bsp_f64(mpo.site(1));
let chi_l = left_d.shape()[0];
let d0 = mps.site(0).shape()[1];
let d1 = mps.site(1).shape()[1];
let chi_r = right_d.shape()[0];
let dense_heff =
EffectiveHamiltonian2Site::new(&left_d, &w0_d, &w1_d, &right_d, chi_l, d0, d1, chi_r);
let dim = bsp_heff.dim();
let test_inputs: Vec<Vec<f64>> = vec![
(0..dim).map(|i| (i + 1) as f64 * 0.13).collect(),
(0..dim).map(|i| ((i as i32 - 2) as f64) * 0.7).collect(),
vec![1.0; dim],
];
let template = template_from_mps_pair(mps.site(0), mps.site(1));
for (case, v) in test_inputs.iter().enumerate() {
let v_dense_flat = Host::shared().dense(v.clone(), vec![dim]);
let bsp_out = bsp_heff.apply(&v_dense_flat);
assert_eq!(bsp_out.shape(), &[dim], "BSP output shape");
let psi_dense = build_dense_psi_from_flat(v, &template);
let dense_out = dense_heff.apply(&psi_dense.reshape(vec![chi_l * d0 * d1 * chi_r]));
let dense_out_4d = dense_out.reshape(vec![chi_l, d0, d1, chi_r]);
let expected = dense_to_template_flat(&dense_out_4d, &template);
for (i, (bsp_val, exp_val)) in bsp_out.data_slice().iter().zip(expected.iter()).enumerate()
{
let diff = (bsp_val - exp_val).abs();
assert!(
diff <= 1e-10,
"case {case} idx {i}: bsp={bsp_val}, dense={exp_val}, |diff|={diff}"
);
}
}
}
#[test]
fn bsp_heff_matvec_matches_dense_oracle_n3_bulk() {
let mps = make_n3_mps_f64();
let mpo = make_n3_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let site = 0;
let bsp_heff = EffectiveHamiltonian2SiteBlockSparse::new(
envs.left(site).expect("left boundary"),
mpo.site(site),
mpo.site(site + 1),
envs.right(site + 2).expect("right ext"),
mps.site(site),
mps.site(site + 1),
)
.expect("operands share backend preferred_order by construction");
let right_total_dim = envs.right(site + 2).expect("right").shape()[0];
assert!(
right_total_dim > 1,
"n=3 site=0 must exercise an extended right env; \
got total_dim={right_total_dim} (single-sector dim-1 = boundary path, not bulk)"
);
let left_d = densify_bsp_f64(envs.left(site).expect("left"));
let right_d = densify_bsp_f64(envs.right(site + 2).expect("right"));
let w_i_d = densify_bsp_f64(mpo.site(site));
let w_ip1_d = densify_bsp_f64(mpo.site(site + 1));
let chi_l = left_d.shape()[0];
let d_i = mps.site(site).shape()[1];
let d_ip1 = mps.site(site + 1).shape()[1];
let chi_r = right_d.shape()[0];
let dense_heff = EffectiveHamiltonian2Site::new(
&left_d, &w_i_d, &w_ip1_d, &right_d, chi_l, d_i, d_ip1, chi_r,
);
let template = template_from_mps_pair(mps.site(site), mps.site(site + 1));
let dim = bsp_heff.dim();
assert_eq!(dim, *template_block_offsets(&template).last().unwrap());
let test_inputs: Vec<Vec<f64>> = vec![
(0..dim).map(|i| (i + 1) as f64 * 0.21).collect(),
(0..dim).map(|i| ((i as i32 - 1) as f64) * 0.3).collect(),
];
for (case, v) in test_inputs.iter().enumerate() {
let bsp_out = bsp_heff.apply(&Host::shared().dense(v.clone(), vec![dim]));
let psi_dense = build_dense_psi_from_flat(v, &template);
let dense_out = dense_heff.apply(&psi_dense.reshape(vec![chi_l * d_i * d_ip1 * chi_r]));
let dense_out_4d = dense_out.reshape(vec![chi_l, d_i, d_ip1, chi_r]);
let expected = dense_to_template_flat(&dense_out_4d, &template);
for (i, (bsp_val, exp_val)) in bsp_out.data_slice().iter().zip(expected.iter()).enumerate()
{
let diff = (bsp_val - exp_val).abs();
assert!(
diff <= 1e-10,
"n=3 bulk case {case} idx {i}: bsp={bsp_val}, dense={exp_val}, |diff|={diff}"
);
}
}
let params = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: 1e-12,
seed: Some(7),
});
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, site, ¶ms, &trunc)
.expect("step at site=0");
assert!(result.converged);
assert!(result.eigenvalue.is_finite());
}
#[test]
fn bsp_heff_step_eigenvalue_matches_eigh_on_bsp_flat() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let bsp_heff = EffectiveHamiltonian2SiteBlockSparse::new(
envs.left(0).expect("left"),
mpo.site(0),
mpo.site(1),
envs.right(2).expect("right"),
mps.site(0),
mps.site(1),
)
.expect("operands share backend preferred_order by construction");
let dim = bsp_heff.dim();
assert!(
dim >= 2,
"fixture dim should be >= 2 for a meaningful eigh oracle"
);
let mut h_data = vec![0.0_f64; dim * dim];
for j in 0..dim {
let mut e_j = vec![0.0_f64; dim];
e_j[j] = 1.0;
let out = bsp_heff.apply(&Host::shared().dense(e_j, vec![dim]));
for i in 0..dim {
h_data[i + dim * j] = out.data_slice()[i];
}
}
for i in 0..dim {
for j in (i + 1)..dim {
let a = h_data[i + dim * j];
let b = h_data[j + dim * i];
assert!(
(a - b).abs() <= 1e-10,
"H_bsp_flat not symmetric at ({i},{j}): H[{i},{j}]={a}, H[{j},{i}]={b}"
);
}
}
let h_dense = Host::shared().dense(h_data, vec![dim, dim]);
let (eigvals, _eigvecs) = eigh_with_backend(&NativeBackend::new(), &h_dense, 1).expect("eigh");
let eigh_smallest = eigvals.data_slice()[0];
let params = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: 1e-12,
seed: Some(42),
});
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, ¶ms, &trunc).expect("step");
assert!(result.converged, "Lanczos must converge");
let diff = (result.eigenvalue - eigh_smallest).abs();
assert!(
diff <= 1e-10,
"Lanczos eigenvalue {} vs eigh {} (|diff|={})",
result.eigenvalue,
eigh_smallest,
diff
);
}
#[test]
fn bsp_heff_step_uvt_canonical_form() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let params = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: 1e-12,
seed: Some(42),
});
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, ¶ms, &trunc).expect("step");
let u_d = densify_bsp_f64(&result.u);
let u_shape = u_d.shape().to_vec();
let m: usize = u_shape[0] * u_shape[1];
let n: usize = u_shape[2];
let u_2d = u_d.reshape(vec![m, n]);
let u_2d_rm = u_2d.reordered(MemoryOrder::RowMajor);
let u_data = u_2d_rm.data_slice();
for i in 0..n {
for j in 0..n {
let mut acc = 0.0_f64;
for k in 0..m {
acc += u_data[k * n + i] * u_data[k * n + j];
}
let expected = if i == j { 1.0 } else { 0.0 };
let diff = (acc - expected).abs();
assert!(
diff <= 1e-10,
"U^†U at ({i},{j}) = {acc}, expected {expected} (|diff|={diff})"
);
}
}
let vt_d = densify_bsp_f64(&result.vt);
let vt_shape = vt_d.shape().to_vec();
let p: usize = vt_shape[0];
let q: usize = vt_shape[1] * vt_shape[2];
let vt_2d = vt_d.reshape(vec![p, q]);
let vt_2d_rm = vt_2d.reordered(MemoryOrder::RowMajor);
let vt_data = vt_2d_rm.data_slice();
for i in 0..p {
for j in 0..p {
let mut acc = 0.0_f64;
for k in 0..q {
acc += vt_data[i * q + k] * vt_data[j * q + k];
}
let expected = if i == j { 1.0 } else { 0.0 };
let diff = (acc - expected).abs();
assert!(
diff <= 1e-10,
"Vt Vt^† at ({i},{j}) = {acc}, expected {expected} (|diff|={diff})"
);
}
}
}
#[test]
fn bsp_heff_step_flux_propagation() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let psi_flux = mps.site(0).flux().fuse(mps.site(1).flux());
assert_ne!(
psi_flux,
U1Sector::identity(),
"fixture must have non-identity 2-site flux"
);
let params = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: 1e-12,
seed: Some(42),
});
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, ¶ms, &trunc).expect("step");
assert_eq!(
result.u.flux(),
&U1Sector::identity(),
"U.flux must be identity"
);
assert_eq!(result.vt.flux(), &psi_flux, "Vt.flux must equal psi_flux");
}
#[test]
fn bsp_heff_step_n2_edge_case() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, ¶ms, &trunc);
assert!(result.is_ok(), "n=2 step must succeed at site=0");
let r = result.unwrap();
assert!(r.converged);
assert!(r.eigenvalue.is_finite());
}