use algorithms_fixtures::dense_fixtures::{
build_mpo_site_f64, heisenberg_mpo_f64, op_id, op_sz, random_mps_center_zero_f64,
};
use ariadnetor_algorithms::dmrg::{DmrgEnvs, DmrgSweepParams, LocalEigensolverParams, sweep_2site};
use ariadnetor_algorithms::krylov::LanczosParams;
use ariadnetor_linalg::{TruncSvdParams, eigh_with_backend};
use ariadnetor_mps::{CanonicalForm, Mpo, TensorChain};
use ariadnetor_native::NativeBackend;
use ariadnetor_tensor::{ComputeBackendTensorExt, DenseLayout, DenseStorage, DenseTensor, Host};
const D: usize = 2;
fn op_sx(k: usize, b: usize) -> f64 {
if k != b { 1.0 } else { 0.0 }
}
fn tfi_mpo_f64(n: usize, j: f64, h: f64) -> Mpo<DenseStorage<f64>, DenseLayout> {
assert!(n >= 2, "tfi_mpo_f64 requires n >= 2");
let mut sites = Vec::with_capacity(n);
sites.push(build_mpo_site_f64(
1,
3,
&[(0, 0, op_sx, -h), (0, 1, op_sz, -j), (0, 2, op_id, 1.0)],
));
for _ in 1..n - 1 {
sites.push(build_mpo_site_f64(
3,
3,
&[
(0, 0, op_id, 1.0),
(1, 0, op_sz, 1.0),
(2, 0, op_sx, -h),
(2, 1, op_sz, -j),
(2, 2, op_id, 1.0),
],
));
}
sites.push(build_mpo_site_f64(
3,
1,
&[(0, 0, op_id, 1.0), (1, 0, op_sz, 1.0), (2, 0, op_sx, -h)],
));
Mpo::from_sites(sites)
}
fn z_of_bit(bit: usize) -> f64 {
if bit == 0 { 1.0 } else { -1.0 }
}
fn write_diag(h: &mut [f64], dim: usize, b: usize, value: f64) {
h[b + dim * b] += value;
}
fn write_offdiag(h: &mut [f64], dim: usize, b_out: usize, b_in: usize, value: f64) {
h[b_out + dim * b_in] += value;
}
fn tfi_ed_dense_f64(n: usize, j: f64, h_field: f64) -> DenseTensor<f64> {
let dim = 1usize << n;
let mut data = vec![0.0_f64; dim * dim];
for b in 0..dim {
let mut diag = 0.0_f64;
for i in 0..n - 1 {
let zi = z_of_bit((b >> i) & 1);
let zi1 = z_of_bit((b >> (i + 1)) & 1);
diag += -j * zi * zi1;
}
write_diag(&mut data, dim, b, diag);
for i in 0..n {
let b_out = b ^ (1 << i);
write_offdiag(&mut data, dim, b_out, b, -h_field);
}
}
Host::shared().dense(data, vec![dim, dim])
}
fn heisenberg_ed_dense_f64(n: usize, j: f64) -> DenseTensor<f64> {
let dim = 1usize << n;
let mut data = vec![0.0_f64; dim * dim];
for b in 0..dim {
let mut diag = 0.0_f64;
for i in 0..n - 1 {
let zi = z_of_bit((b >> i) & 1);
let zi1 = z_of_bit((b >> (i + 1)) & 1);
diag += j * zi * zi1;
}
write_diag(&mut data, dim, b, diag);
for i in 0..n - 1 {
let bi = (b >> i) & 1;
let bi1 = (b >> (i + 1)) & 1;
if bi != bi1 {
let b_out = b ^ ((1 << i) | (1 << (i + 1)));
write_offdiag(&mut data, dim, b_out, b, 2.0 * j);
}
}
}
Host::shared().dense(data, vec![dim, dim])
}
fn dense_min_eig_f64(h: &DenseTensor<f64>) -> f64 {
let (eigvals, _v) = eigh_with_backend(&NativeBackend::new(), h, 1).expect("eigh");
eigvals
.data_slice()
.iter()
.copied()
.fold(f64::INFINITY, f64::min)
}
fn validation_params(chi_max: usize, lanczos_seed: u64) -> DmrgSweepParams {
DmrgSweepParams {
max_sweeps: 30,
min_sweeps: 4,
energy_tol: 1e-12,
eigensolver: LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: 1e-12,
seed: Some(lanczos_seed),
}),
trunc: TruncSvdParams {
chi_max: Some(chi_max),
target_trunc_err: None,
},
}
}
fn run_validation(
name: &str,
mpo: Mpo<DenseStorage<f64>, DenseLayout>,
e_ed: f64,
chi_max: usize,
init_seed: u64,
lanczos_seed: u64,
) {
let n = mpo.len();
let mut mps = random_mps_center_zero_f64(n, D, 4, init_seed);
let mut envs = DmrgEnvs::build(&mps, &mpo).expect("envs build");
let params = validation_params(chi_max, lanczos_seed);
let result = sweep_2site(&mut envs, &mut mps, &mpo, ¶ms)
.unwrap_or_else(|e| panic!("{name}: sweep_2site failed: {e:?}"));
let delta = (result.energy - e_ed).abs();
assert!(
delta <= 1e-8,
"{name}: |E_DMRG - E_ED| = {delta:.3e} > 1e-8 (E_DMRG={:.10}, E_ED={:.10})",
result.energy,
e_ed,
);
assert!(
result.converged,
"{name}: result.converged = false (n_sweeps={}, last sweep_energy={:?})",
result.n_sweeps,
result.sweeps.last().map(|s| s.sweep_energy),
);
assert_eq!(
*mps.canonical_form(),
CanonicalForm::Mixed { center: 0 },
"{name}: final canonical form mismatch"
);
}
#[test]
fn v1_tfi_n6_critical_vs_ed() {
let n = 6;
let j = 1.0;
let h = 1.0;
let mpo = tfi_mpo_f64(n, j, h);
let e_ed = dense_min_eig_f64(&tfi_ed_dense_f64(n, j, h));
run_validation("v1_tfi_n6_critical", mpo, e_ed, 16, 0xD3F1, 0xA101);
}
#[test]
fn v2_tfi_n8_fm_gapped_vs_ed() {
let n = 8;
let j = 1.0;
let h = 0.5;
let mpo = tfi_mpo_f64(n, j, h);
let e_ed = dense_min_eig_f64(&tfi_ed_dense_f64(n, j, h));
run_validation("v2_tfi_n8_fm_gapped", mpo, e_ed, 16, 0xD3F2, 0xA102);
}
#[test]
fn v3_tfi_n10_paramagnetic_vs_ed() {
let n = 10;
let j = 1.0;
let h = 2.0;
let mpo = tfi_mpo_f64(n, j, h);
let e_ed = dense_min_eig_f64(&tfi_ed_dense_f64(n, j, h));
run_validation("v3_tfi_n10_paramagnetic", mpo, e_ed, 16, 0xD3F3, 0xA103);
}
#[test]
fn v4_heisenberg_n6_vs_ed() {
let n = 6;
let j = 1.0;
let mpo = heisenberg_mpo_f64(n, j);
let e_ed = dense_min_eig_f64(&heisenberg_ed_dense_f64(n, j));
run_validation("v4_heisenberg_n6", mpo, e_ed, 32, 0xD3F4, 0xA104);
}
#[test]
fn v5_heisenberg_n8_vs_ed() {
let n = 8;
let j = 1.0;
let mpo = heisenberg_mpo_f64(n, j);
let e_ed = dense_min_eig_f64(&heisenberg_ed_dense_f64(n, j));
run_validation("v5_heisenberg_n8", mpo, e_ed, 32, 0xD3F5, 0xA105);
}
fn contract_2site_mpo_f64(mpo: &Mpo<DenseStorage<f64>, DenseLayout>) -> Vec<f64> {
assert_eq!(mpo.len(), 2, "contract_2site_mpo_f64 requires N=2");
let s0 = mpo.site(0);
let s1 = mpo.site(1);
let chi = s0.shape()[3];
assert_eq!(s1.shape()[0], chi);
let s0_data = s0.data_slice();
let s1_data = s1.data_slice();
let s0_idx = |k1: usize, b1: usize, w: usize| k1 + D * (b1 + D * w);
let s1_idx = |w: usize, k2: usize, b2: usize| w + chi * (k2 + D * b2);
let dim = D * D;
let mut out = vec![0.0_f64; dim * dim];
for b1 in 0..D {
for b2 in 0..D {
for k1 in 0..D {
for k2 in 0..D {
let mut acc = 0.0_f64;
for w in 0..chi {
acc += s0_data[s0_idx(k1, b1, w)] * s1_data[s1_idx(w, k2, b2)];
}
let row = b1 + D * b2;
let col = k1 + D * k2;
out[row + dim * col] = acc;
}
}
}
}
out
}
fn assert_dense_close(actual: &[f64], expected: &DenseTensor<f64>, name: &str, tol: f64) {
let exp = expected.data_slice();
assert_eq!(actual.len(), exp.len(), "{name}: length mismatch");
for (i, (&a, &e)) in actual.iter().zip(exp.iter()).enumerate() {
let diff = (a - e).abs();
assert!(
diff <= tol,
"{name}: entry {i} differs by {diff:.3e} (got {a}, expected {e})",
);
}
}
#[test]
fn v6_tfi_n2_mpo_matches_analytic() {
let j = 0.7;
let h = 1.3;
let mpo = tfi_mpo_f64(2, j, h);
let actual = contract_2site_mpo_f64(&mpo);
let expected = tfi_ed_dense_f64(2, j, h);
assert_dense_close(&actual, &expected, "v6_tfi_n2_mpo_matches_analytic", 1e-12);
}
#[test]
fn v7_heisenberg_n2_mpo_matches_analytic() {
let j = 1.5;
let mpo = heisenberg_mpo_f64(2, j);
let actual = contract_2site_mpo_f64(&mpo);
let expected = heisenberg_ed_dense_f64(2, j);
assert_dense_close(
&actual,
&expected,
"v7_heisenberg_n2_mpo_matches_analytic",
1e-12,
);
}