use ariadnetor_algorithms::dmrg::{DmrgSweepParams, LocalEigensolverParams, sweep_2site};
use ariadnetor_algorithms::krylov::LanczosParams;
use ariadnetor_linalg::{TruncSvdParams, eigh_with_backend};
use ariadnetor_mps::BraketEnvs;
use ariadnetor_mps::{CanonicalForm, Mpo, Mps, TensorChain};
use ariadnetor_native::NativeBackend;
use ariadnetor_tensor::test_fixtures::legs;
use ariadnetor_tensor::{
BlockCoord, BlockSparseLayout, BlockSparseStorage, BlockSparseTensor, ComputeBackendTensorExt,
DenseTensor, Direction, Host, Sector, U1Sector,
};
use rand::RngExt;
use rand::SeedableRng;
use rand::rngs::StdRng;
const D: usize = 2;
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 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)
}
const SEC_NEG: usize = 0; const SEC_ZERO: usize = 1; const SEC_POS: usize = 2;
const ZCHAN_ID_START: usize = 0;
const ZCHAN_SZ_PEND: usize = 1;
const ZCHAN_ID_FINISH: usize = 2;
fn bond5() -> Vec<(U1Sector, usize)> {
vec![(U1Sector(-1), 1), (U1Sector(0), 3), (U1Sector(1), 1)]
}
fn phys2() -> Vec<(U1Sector, usize)> {
vec![(U1Sector(0), 1), (U1Sector(1), 1)]
}
fn trivial1() -> Vec<(U1Sector, usize)> {
vec![(U1Sector(0), 1)]
}
fn flat_within_phys1(d_l: usize, chan_l: usize, chan_r: usize) -> usize {
chan_l + d_l * chan_r
}
fn heisenberg_mpo_bsp_f64(
n: usize,
j: f64,
) -> Mpo<BlockSparseStorage<f64>, BlockSparseLayout<U1Sector>> {
assert!(n >= 2, "heisenberg_mpo_bsp_f64 requires n >= 2");
let mut sites: Vec<BlockSparseTensor<f64, U1Sector>> = Vec::with_capacity(n);
let mut w0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial1(), Direction::Out),
(phys2(), Direction::In),
(phys2(), Direction::Out),
(bond5(), Direction::In),
]),
U1Sector::identity(),
);
w0.block_data_mut(&BlockCoord(vec![0, 0, 1, SEC_POS]))
.expect("site0 σ⁻")[0] = 2.0 * j;
w0.block_data_mut(&BlockCoord(vec![0, 1, 0, SEC_NEG]))
.expect("site0 σ⁺")[0] = 2.0 * j;
{
let blk = w0
.block_data_mut(&BlockCoord(vec![0, 0, 0, SEC_ZERO]))
.expect("site0 σᶻ k=0");
blk[ZCHAN_SZ_PEND] += j; blk[ZCHAN_ID_FINISH] += 1.0; }
{
let blk = w0
.block_data_mut(&BlockCoord(vec![0, 1, 1, SEC_ZERO]))
.expect("site0 σᶻ k=1");
blk[ZCHAN_SZ_PEND] += -j; blk[ZCHAN_ID_FINISH] += 1.0; }
sites.push(w0);
for _ in 1..n - 1 {
let mut w = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(bond5(), Direction::Out),
(phys2(), Direction::In),
(phys2(), Direction::Out),
(bond5(), Direction::In),
]),
U1Sector::identity(),
);
for k in 0..D {
let z = if k == 0 { 1.0 } else { -1.0 };
let blk = w
.block_data_mut(&BlockCoord(vec![SEC_ZERO, k, k, SEC_ZERO]))
.expect("bulk diag U1(0)");
blk[flat_within_phys1(3, ZCHAN_ID_START, ZCHAN_ID_START)] += 1.0;
blk[flat_within_phys1(3, ZCHAN_SZ_PEND, ZCHAN_ID_START)] += z;
blk[flat_within_phys1(3, ZCHAN_ID_FINISH, ZCHAN_SZ_PEND)] += j * z;
blk[flat_within_phys1(3, ZCHAN_ID_FINISH, ZCHAN_ID_FINISH)] += 1.0;
}
w.block_data_mut(&BlockCoord(vec![SEC_POS, 1, 0, SEC_ZERO]))
.expect("bulk σ⁺ close")[ZCHAN_ID_START] = 1.0;
w.block_data_mut(&BlockCoord(vec![SEC_NEG, 0, 1, SEC_ZERO]))
.expect("bulk σ⁻ close")[ZCHAN_ID_START] = 1.0;
w.block_data_mut(&BlockCoord(vec![SEC_ZERO, 0, 1, SEC_POS]))
.expect("bulk σ⁻ start")[ZCHAN_ID_FINISH] = 2.0 * j;
w.block_data_mut(&BlockCoord(vec![SEC_ZERO, 1, 0, SEC_NEG]))
.expect("bulk σ⁺ start")[ZCHAN_ID_FINISH] = 2.0 * j;
sites.push(w);
}
let mut wn = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(bond5(), Direction::Out),
(phys2(), Direction::In),
(phys2(), Direction::Out),
(trivial1(), Direction::In),
]),
U1Sector::identity(),
);
for k in 0..D {
let z = if k == 0 { 1.0 } else { -1.0 };
let blk = wn
.block_data_mut(&BlockCoord(vec![SEC_ZERO, k, k, 0]))
.expect("siteN diag U1(0)");
blk[ZCHAN_ID_START] += 1.0;
blk[ZCHAN_SZ_PEND] += z;
}
wn.block_data_mut(&BlockCoord(vec![SEC_POS, 1, 0, 0]))
.expect("siteN σ⁺ close")[0] = 1.0;
wn.block_data_mut(&BlockCoord(vec![SEC_NEG, 0, 1, 0]))
.expect("siteN σ⁻ close")[0] = 1.0;
sites.push(wn);
Mpo::from_sites(sites)
}
fn random_mps_bsp_center_zero_f64(
n: usize,
chi_internal: usize,
total_charge: i32,
seed: u64,
) -> Mps<BlockSparseStorage<f64>, BlockSparseLayout<U1Sector>> {
assert!(n >= 2, "random_mps_bsp_center_zero_f64 requires n >= 2");
assert!(
total_charge >= 0 && (total_charge as usize) <= n,
"total_charge {} out of range [0, {}]",
total_charge,
n
);
let mut rng = StdRng::seed_from_u64(seed);
let internal_legs: Vec<(U1Sector, usize)> = (0..=total_charge)
.map(|q| (U1Sector(q), chi_internal))
.collect();
let mut storages: Vec<BlockSparseTensor<f64, U1Sector>> = Vec::with_capacity(n);
for site in 0..n {
let left_legs: Vec<(U1Sector, usize)> = if site == 0 {
trivial1()
} else {
internal_legs.clone()
};
let right_legs: Vec<(U1Sector, usize)> = if site == n - 1 {
trivial1()
} else {
internal_legs.clone()
};
let flux = if site == n - 1 {
U1Sector(total_charge)
} else {
U1Sector::identity()
};
let mut s = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(left_legs, Direction::Out),
(phys2(), Direction::Out),
(right_legs, Direction::In),
]),
flux,
);
let coords: Vec<BlockCoord> = s.block_metas().iter().map(|m| m.coord.clone()).collect();
for coord in coords {
let blk = s.block_data_mut(&coord).expect("legal block");
for v in blk.iter_mut() {
*v = rng.random_range(-0.5_f64..0.5);
}
}
storages.push(s);
}
let mut mps = Mps::from_sites(storages);
mps.canonicalize(&NativeBackend::new(), 0);
assert_eq!(
*mps.canonical_form(),
CanonicalForm::Mixed { center: 0 },
"random_mps_bsp_center_zero_f64: post-canonicalize form mismatch"
);
mps
}
fn validation_params_bsp(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,
},
}
}
#[allow(clippy::too_many_arguments)]
fn run_validation_bsp(
name: &str,
mpo: Mpo<BlockSparseStorage<f64>, BlockSparseLayout<U1Sector>>,
e_ed: f64,
chi_max: usize,
chi_internal: usize,
init_seed: u64,
lanczos_seed: u64,
total_charge: i32,
tol: f64,
) {
let n = mpo.len();
let mut mps = random_mps_bsp_center_zero_f64(n, chi_internal, total_charge, init_seed);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("envs build");
let params = validation_params_bsp(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 <= tol,
"{name}: |E_BSP - E_ED| = {delta:.3e} > {tol:.0e} (E_BSP={:.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_bsp_heisenberg_n6_vs_ed() {
let n = 6;
let j = 1.0;
let mpo = heisenberg_mpo_bsp_f64(n, j);
let e_ed = dense_min_eig_f64(&heisenberg_ed_dense_f64(n, j));
run_validation_bsp(
"v1_bsp_heisenberg_n6",
mpo,
e_ed,
32,
4,
0xB6F1,
0xB101,
(n / 2) as i32,
1e-8,
);
}
#[test]
fn v2_bsp_heisenberg_n8_vs_ed() {
let n = 8;
let j = 1.0;
let mpo = heisenberg_mpo_bsp_f64(n, j);
let e_ed = dense_min_eig_f64(&heisenberg_ed_dense_f64(n, j));
run_validation_bsp(
"v2_bsp_heisenberg_n8",
mpo,
e_ed,
32,
4,
0xB6F2,
0xB102,
(n / 2) as i32,
1e-8,
);
}
#[test]
fn v3_bsp_heisenberg_n10_vs_ed() {
let n = 10;
let j = 1.0;
let mpo = heisenberg_mpo_bsp_f64(n, j);
let e_ed = dense_min_eig_f64(&heisenberg_ed_dense_f64(n, j));
run_validation_bsp(
"v3_bsp_heisenberg_n10",
mpo,
e_ed,
32,
4,
0xB6F3,
0xB103,
(n / 2) as i32,
1e-8,
);
}