use crate::dmrg::heff_block_sparse::{
EffectiveHamiltonian2SiteBlockSparse, dmrg_2site_step_block_sparse,
};
use crate::dmrg::{DmrgHeffError, LocalEigensolverParams};
use crate::krylov::{LanczosParams, LinearOp};
use ariadnetor_linalg::TruncSvdParams;
use ariadnetor_mps::BraketEnvs;
use ariadnetor_mps::{Mpo, TensorChain};
use ariadnetor_tensor::test_fixtures::legs;
use ariadnetor_tensor::{
BlockCoord, BlockSparseTensor, ComputeBackendTensorExt, Direction, Host, Sector, U1Sector,
};
use num_complex::Complex;
use ariadnetor_mps::Mps;
use super::fixtures::{
make_n2_mpo_c64, make_n2_mpo_f64, make_n2_mps_c64, make_n2_mps_f64, make_n3_mpo_f64,
make_n3_mps_f64,
};
use ariadnetor_tensor::test_fixtures::densify_bsp_c64;
#[test]
fn bsp_heff_step_error_paths_invalid_site() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = BraketEnvs::build(&mps, &mpo, &mps).expect("envs build");
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let r = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 1, ¶ms, &trunc);
assert!(matches!(r, Err(DmrgHeffError::InvalidSite { .. })));
let r2 = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, usize::MAX, ¶ms, &trunc);
assert!(matches!(r2, Err(DmrgHeffError::InvalidSite { .. })));
}
#[test]
fn bsp_heff_step_error_paths_invalid_eigensolver_params() {
let mps = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = BraketEnvs::build(&mps, &mpo, &mps).expect("envs build");
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let bad_iter = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 0,
tol: 1e-10,
seed: None,
});
let r = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, &bad_iter, &trunc);
assert!(matches!(
r,
Err(DmrgHeffError::InvalidEigensolverParams { .. })
));
let bad_nan = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: f64::NAN,
seed: None,
});
let r = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, &bad_nan, &trunc);
assert!(matches!(
r,
Err(DmrgHeffError::InvalidEigensolverParams { .. })
));
let bad_neg = LocalEigensolverParams::Lanczos(LanczosParams {
max_iter: 200,
tol: -1.0,
seed: None,
});
let r = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, &bad_neg, &trunc);
assert!(matches!(
r,
Err(DmrgHeffError::InvalidEigensolverParams { .. })
));
}
#[test]
fn bsp_heff_step_error_paths_qn_mismatch_mpo_flux() {
let mps = make_n2_mps_f64();
let mpo_good = make_n2_mpo_f64(1.5);
let envs = BraketEnvs::build(&mps, &mpo_good, &mps).expect("envs build");
let phys = vec![(U1Sector(0), 1), (U1Sector(1), 1)];
let trivial = vec![(U1Sector(0), 1)];
let xy_bond = vec![(U1Sector(-1), 1), (U1Sector(1), 1)];
let bad_w0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial, Direction::Out),
(phys.clone(), Direction::In),
(phys, Direction::Out),
(xy_bond, Direction::In),
]),
U1Sector(2),
);
let bad_mpo = Mpo::from_sites(vec![bad_w0, mpo_good.site(1).clone()]);
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let r = dmrg_2site_step_block_sparse(&envs, &mps, &bad_mpo, 0, ¶ms, &trunc);
assert!(
matches!(r, Err(DmrgHeffError::QnMismatch { field, .. }) if field.contains("flux")),
"expected QnMismatch on flux, got {:?}",
r.as_ref().err().map(|e| format!("{e}"))
);
}
#[test]
fn bsp_heff_step_error_paths_empty_psi_template() {
let phys = vec![(U1Sector(0), 1)];
let trivial = vec![(U1Sector(0), 1)];
let charged_right = vec![(U1Sector(2), 1)];
let mut mps0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial.clone(), Direction::Out),
(phys.clone(), Direction::Out),
(trivial.clone(), Direction::In),
]),
U1Sector::identity(),
);
mps0.block_data_mut(&BlockCoord(vec![0, 0, 0]))
.expect("(0,0,0)")[0] = 1.0;
let mps1 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial.clone(), Direction::Out),
(phys.clone(), Direction::Out),
(charged_right, Direction::In),
]),
U1Sector(2),
);
let mps = ariadnetor_mps::Mps::from_sites(vec![mps0, mps1]);
let mut w0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial.clone(), Direction::Out),
(phys.clone(), Direction::In),
(phys.clone(), Direction::Out),
(trivial.clone(), Direction::In),
]),
U1Sector::identity(),
);
w0.block_data_mut(&BlockCoord(vec![0, 0, 0, 0])).expect("I")[0] = 1.0;
let mut w1 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial.clone(), Direction::Out),
(phys.clone(), Direction::In),
(phys, Direction::Out),
(trivial, Direction::In),
]),
U1Sector::identity(),
);
w1.block_data_mut(&BlockCoord(vec![0, 0, 0, 0])).expect("I")[0] = 1.0;
let mpo = ariadnetor_mps::Mpo::from_sites(vec![w0, w1]);
let envs = BraketEnvs::build(&mps, &mpo, &mps).expect("envs build");
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let r = dmrg_2site_step_block_sparse(&envs, &mps, &mpo, 0, ¶ms, &trunc);
assert!(
matches!(r, Err(DmrgHeffError::QnMismatch { field, .. }) if field == "psi_template"),
"expected QnMismatch on psi_template (empty flux-allowed set), got {:?}",
r.as_ref().err().map(|e| format!("{e}"))
);
}
#[test]
fn bsp_heff_step_error_paths_qn_mismatch_mpo_bra_ket() {
let phys = vec![(U1Sector(0), 1), (U1Sector(1), 1)];
let trivial = vec![(U1Sector(0), 1)];
let xy_bond = vec![(U1Sector(-1), 1), (U1Sector(1), 1)];
let bad_w0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(trivial, Direction::Out),
(phys.clone(), Direction::In),
(phys, Direction::In), (xy_bond, Direction::In),
]),
U1Sector::identity(),
);
let mps = make_n2_mps_f64();
let mpo_good = make_n2_mpo_f64(1.5);
let envs = BraketEnvs::build(&mps, &mpo_good, &mps).expect("envs build");
let bad_mpo = Mpo::from_sites(vec![bad_w0, mpo_good.site(1).clone()]);
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let r = dmrg_2site_step_block_sparse(&envs, &mps, &bad_mpo, 0, ¶ms, &trunc);
assert!(
matches!(r, Err(DmrgHeffError::QnMismatch { .. })),
"expected QnMismatch, got {:?}",
r.as_ref().err().map(|e| format!("{e}"))
);
}
#[test]
fn bsp_heff_complex_path() {
let mps = make_n2_mps_c64();
let mpo = make_n2_mpo_c64(1.5);
let envs = BraketEnvs::build(&mps, &mpo, &mps).expect("c64 envs");
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();
let mut h_data = vec![Complex::new(0.0, 0.0); dim * dim];
for j in 0..dim {
let mut e_j = vec![Complex::new(0.0, 0.0); dim];
e_j[j] = Complex::new(1.0, 0.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 {
let diag_im = h_data[i + dim * i].im.abs();
assert!(
diag_im <= 1e-10,
"complex matvec not Hermitian: H[{i},{i}].im = {diag_im} (expected ≈ 0)"
);
for j in (i + 1)..dim {
let a = h_data[i + dim * j];
let b = h_data[j + dim * i].conj();
let diff = (a - b).norm();
assert!(
diff <= 1e-10,
"complex matvec not Hermitian at ({i},{j}): a={a:?}, conj(H[{j},{i}])={b:?}, |diff|={diff}"
);
}
}
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.eigenvalue.is_finite());
assert!(result.converged);
let _ = densify_bsp_c64(&result.u);
}
#[test]
fn bsp_validate_inputs_asymmetric_length_mismatch() {
let mps_n2 = make_n2_mps_f64();
let mpo_n2 = make_n2_mpo_f64(1.5);
let envs_n2 = BraketEnvs::build(&mps_n2, &mpo_n2, &mps_n2).expect("envs build");
let mps_n3 = make_n3_mps_f64();
let mpo_n3 = make_n3_mpo_f64(1.5);
let params = LocalEigensolverParams::Lanczos(LanczosParams::default());
let trunc = TruncSvdParams {
chi_max: None,
target_trunc_err: None,
};
let result = dmrg_2site_step_block_sparse(&envs_n2, &mps_n3, &mpo_n2, 0, ¶ms, &trunc);
assert!(
matches!(
result,
Err(DmrgHeffError::LengthMismatch {
mps: 3,
mpo: 2,
envs: 2,
})
),
"expected LengthMismatch {{ mps: 3, mpo: 2, envs: 2 }}, got {:?}",
result.as_ref().err().map(|e| format!("{e}")),
);
let result = dmrg_2site_step_block_sparse(&envs_n2, &mps_n2, &mpo_n3, 0, ¶ms, &trunc);
assert!(
matches!(
result,
Err(DmrgHeffError::LengthMismatch {
mps: 2,
mpo: 3,
envs: 2,
})
),
"expected LengthMismatch {{ mps: 2, mpo: 3, envs: 2 }}, got {:?}",
result.as_ref().err().map(|e| format!("{e}")),
);
}
#[test]
fn bsp_validate_inputs_stale_right_index_pinpoint() {
let mps = make_n3_mps_f64();
let mpo = make_n3_mpo_f64(1.5);
let mut envs = BraketEnvs::build(&mps, &mpo, &mps).expect("envs build n=3");
envs.advance_left(&mps, &mpo, &mps, 0)
.expect("advance_left(0)");
envs.advance_left(&mps, &mpo, &mps, 1)
.expect("advance_left(1)");
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!(
matches!(
result,
Err(DmrgHeffError::StaleEnv {
side: "right",
index: 2,
})
),
"expected StaleEnv {{ side: \"right\", index: 2 }}, got {:?}",
result.as_ref().err().map(|e| format!("{e}")),
);
}
#[test]
fn bsp_validate_inputs_qn_mismatch_contracted_axis_sectors() {
let mps_envs = make_n2_mps_f64();
let mpo = make_n2_mpo_f64(1.5);
let envs = BraketEnvs::build(&mps_envs, &mpo, &mps_envs).expect("envs build");
let phys = vec![(U1Sector(0), 1), (U1Sector(1), 1)];
let alt_left = vec![(U1Sector(2), 1)];
let alt_mid = vec![(U1Sector(2), 1), (U1Sector(3), 1)];
let trivial = vec![(U1Sector(0), 1)];
let mps_alt0 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(alt_left, Direction::Out),
(phys.clone(), Direction::Out),
(alt_mid.clone(), Direction::In),
]),
U1Sector::identity(),
);
let mps_alt1 = BlockSparseTensor::<f64, U1Sector>::zeros(
legs([
(alt_mid, Direction::Out),
(phys, Direction::Out),
(trivial, Direction::In),
]),
U1Sector(2),
);
let mps_alt = Mps::from_sites(vec![mps_alt0, mps_alt1]);
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_alt, &mpo, 0, ¶ms, &trunc);
assert!(
matches!(
&result,
Err(DmrgHeffError::QnMismatch { field, .. })
if *field == "left.bot_ket vs psi.axis 0 (MPS[i].left_bond)"
),
"expected QnMismatch on \"left.bot_ket vs psi.axis 0 (MPS[i].left_bond)\", got {:?}",
result.as_ref().err().map(|e| format!("{e}")),
);
}