use super::dimer::DimerCovering;
use crate::error::{self, Result};
const MAX_DENSE_CONSTRUCTION_SITES: usize = 20;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValenceBondState {
pub covering: DimerCovering,
pub num_sites: usize,
}
impl ValenceBondState {
pub fn new(covering: DimerCovering, num_sites: usize) -> Result<Self> {
if covering.num_sites() != num_sites {
return Err(error::invalid_param(
"covering",
"covering site count does not match num_sites",
));
}
Ok(Self {
covering,
num_sites,
})
}
pub fn overlap(&self, other: &Self) -> Result<f64> {
if self.num_sites != other.num_sites {
return Err(error::invalid_param(
"other",
"valence-bond states must share the same num_sites to compute an overlap",
));
}
overlap(&self.covering, &other.covering, self.num_sites)
}
pub fn to_dense_amplitudes(&self) -> Result<Vec<f64>> {
dense_amplitudes(&self.covering, self.num_sites)
}
}
#[derive(Debug, Clone)]
pub(crate) struct BondContribution {
pub diagonal: f64,
pub reconnection: Option<(DimerCovering, f64)>,
}
pub(crate) fn overlap(a: &DimerCovering, b: &DimerCovering, num_sites: usize) -> Result<f64> {
if a.num_sites() != num_sites || b.num_sites() != num_sites {
return Err(error::invalid_param(
"num_sites",
"covering site count does not match num_sites",
));
}
if a.monomers != b.monomers {
return Err(error::invalid_param(
"b",
"overlap requires both coverings to share the same (possibly empty) monomer set",
));
}
let n_paired = num_sites - a.monomers.len();
if n_paired % 2 != 0 {
return Err(error::invalid_param(
"num_sites",
"the number of paired (non-monomer) sites must be even",
));
}
let mut visited = vec![false; num_sites];
for &m in &a.monomers {
visited[m] = true;
}
let mut n_loops: i32 = 0;
let mut total_sign: f64 = 1.0;
for start in 0..num_sites {
if visited[start] {
continue;
}
let mut current = start;
let mut use_a = true;
let mut loop_sites: i32 = 0;
let mut edge_sign_product: f64 = 1.0;
loop {
visited[current] = true;
loop_sites += 1;
let next = if use_a {
a.partner_of(current)
} else {
b.partner_of(current)
}
.ok_or_else(|| {
error::invalid_param(
"a/b",
"encountered an unpaired site while tracing a transition-graph loop",
)
})?;
edge_sign_product *= signum_diff(next, current);
use_a = !use_a;
current = next;
if current == start {
break;
}
}
if loop_sites % 2 != 0 {
return Err(error::numerical_error(
"transition-graph loop has odd length; coverings are inconsistent",
));
}
let p = loop_sites / 2;
n_loops += 1;
let loop_sign = if p % 2 == 0 { 1.0 } else { -1.0 };
total_sign *= loop_sign * edge_sign_product;
}
let magnitude = 2.0_f64.powi(n_loops - (n_paired as i32) / 2);
Ok(total_sign * magnitude)
}
fn signum_diff(next: usize, current: usize) -> f64 {
if next > current {
1.0
} else {
-1.0
}
}
fn signum_i64(x: i64) -> f64 {
if x > 0 {
1.0
} else if x < 0 {
-1.0
} else {
0.0
}
}
pub(crate) fn reconnect(
covering: &DimerCovering,
i: usize,
j: usize,
) -> Result<Option<(DimerCovering, f64)>> {
let k = covering.partner_of(i).ok_or_else(|| {
error::invalid_param(
"i",
"site is out of range or a monomer (not part of any dimer)",
)
})?;
let l = covering.partner_of(j).ok_or_else(|| {
error::invalid_param(
"j",
"site is out of range or a monomer (not part of any dimer)",
)
})?;
if k == j {
return Ok(None);
}
let chi = signum_i64(k as i64 - i as i64)
* signum_i64(l as i64 - j as i64)
* signum_i64(l as i64 - i as i64)
* signum_i64(k as i64 - j as i64);
let num_sites = covering.num_sites();
let old_ik = (i.min(k), i.max(k));
let old_jl = (j.min(l), j.max(l));
let mut new_dimers: Vec<(usize, usize)> = covering
.dimers
.iter()
.copied()
.filter(|&d| d != old_ik && d != old_jl)
.collect();
new_dimers.push((i.min(l), i.max(l)));
new_dimers.push((j.min(k), j.max(k)));
let new_covering = DimerCovering::new(num_sites, new_dimers)?;
Ok(Some((new_covering, chi)))
}
pub(crate) fn bond_contribution(
covering: &DimerCovering,
i: usize,
j: usize,
coupling_j: f64,
) -> Result<BondContribution> {
match reconnect(covering, i, j)? {
None => Ok(BondContribution {
diagonal: -0.75 * coupling_j,
reconnection: None,
}),
Some((beta_prime, chi)) => Ok(BondContribution {
diagonal: -0.25 * coupling_j,
reconnection: Some((beta_prime, 0.5 * chi * coupling_j)),
}),
}
}
pub(crate) fn dense_amplitudes(covering: &DimerCovering, num_sites: usize) -> Result<Vec<f64>> {
if !covering.monomers.is_empty() {
return Err(error::invalid_param(
"covering",
"dense amplitude construction requires a perfect matching (no monomers)",
));
}
if num_sites > MAX_DENSE_CONSTRUCTION_SITES {
return Err(error::invalid_param(
"num_sites",
"dense amplitude construction is limited to a small number of sites (2^num_sites amplitudes)",
));
}
let dim = 1usize << num_sites;
let inv_sqrt2 = std::f64::consts::FRAC_1_SQRT_2;
let mut amplitudes = vec![0.0_f64; dim];
for (b, amp) in amplitudes.iter_mut().enumerate() {
let mut a = 1.0_f64;
for &(i, j) in &covering.dimers {
let bit_i = (b >> i) & 1;
let bit_j = (b >> j) & 1;
if bit_i == bit_j {
a = 0.0;
break;
} else if bit_i == 0 {
a *= inv_sqrt2;
} else {
a *= -inv_sqrt2;
}
}
*amp = a;
}
Ok(amplitudes)
}
#[cfg(test)]
mod tests {
use super::*;
fn ring4_coverings() -> (DimerCovering, DimerCovering) {
let a = DimerCovering::new(4, vec![(0, 1), (2, 3)]).expect("valid covering");
let b = DimerCovering::new(4, vec![(1, 2), (3, 0)]).expect("valid covering");
(a, b)
}
#[test]
fn test_self_overlap_is_one() {
let (a, _b) = ring4_coverings();
let s = overlap(&a, &a, 4).expect("overlap should succeed");
assert!(
(s - 1.0).abs() < 1e-12,
"self-overlap should be exactly 1, got {}",
s
);
}
#[test]
fn test_ring4_overlap_magnitude_half() {
let (a, b) = ring4_coverings();
let s = overlap(&a, &b, 4).expect("overlap should succeed");
assert!(
(s.abs() - 0.5).abs() < 1e-12,
"expected |S_AB| = 0.5, got {}",
s
);
}
#[test]
fn test_overlap_matches_direct_dense_inner_product_ring4() {
let (a, b) = overlap_direct_setup();
let loop_overlap = overlap(&a.covering, &b.covering, 4).expect("loop overlap");
let amp_a = a.to_dense_amplitudes().expect("dense a");
let amp_b = b.to_dense_amplitudes().expect("dense b");
let direct: f64 = amp_a.iter().zip(amp_b.iter()).map(|(x, y)| x * y).sum();
assert!(
(loop_overlap - direct).abs() < 1e-12,
"loop-counting overlap {} should match direct dense inner product {}",
loop_overlap,
direct
);
}
fn overlap_direct_setup() -> (ValenceBondState, ValenceBondState) {
let (a, b) = ring4_coverings();
(
ValenceBondState::new(a, 4).expect("valid VB state"),
ValenceBondState::new(b, 4).expect("valid VB state"),
)
}
fn rhombus_bonds() -> Vec<(usize, usize)> {
vec![(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
}
#[test]
fn test_rhombus_is_non_bipartite() {
let coloring = super::super::dimer::bipartite_coloring(4, &rhombus_bonds())
.expect("bipartite check should not error");
assert!(
coloring.is_none(),
"rhombus graph (contains a triangle) must be non-bipartite"
);
}
#[test]
fn test_non_bipartite_bond_contribution_matches_direct_computation() {
let a = DimerCovering::new(4, vec![(0, 1), (2, 3)]).expect("valid covering");
let b = DimerCovering::new(4, vec![(1, 2), (3, 0)]).expect("valid covering");
let bc = bond_contribution(&b, 0, 2, 1.0).expect("bond contribution");
let s_ab = overlap(&a, &b, 4).expect("overlap a,b");
let (beta_prime, recon_coeff) = bc
.reconnection
.expect("bond (0,2) must trigger reconnection (0 and 2 are not partners in B)");
let s_a_bp = overlap(&a, &beta_prime, 4).expect("overlap a,beta_prime");
let predicted = bc.diagonal * s_ab + recon_coeff * s_a_bp;
let amp_a = dense_amplitudes(&a, 4).expect("dense a");
let amp_b = dense_amplitudes(&b, 4).expect("dense b");
let mut applied = [0.0_f64; 16];
for (bstate, &) in amp_b.iter().enumerate() {
let bit0 = bstate & 1;
let bit2 = (bstate >> 2) & 1;
if bit0 == bit2 {
applied[bstate] += 0.25 * amp;
} else {
applied[bstate] -= 0.25 * amp;
let flipped = bstate ^ 1 ^ (1 << 2);
applied[flipped] += 0.5 * amp;
}
}
let direct: f64 = amp_a.iter().zip(applied.iter()).map(|(x, y)| x * y).sum();
assert!(
(predicted - direct).abs() < 1e-12,
"VB-basis bond_contribution prediction {} must match direct S_0.S_2 matrix element {}",
predicted,
direct
);
assert!(
(direct - (-0.375)).abs() < 1e-12,
"expected exact value -0.375, got {}",
direct
);
}
#[test]
fn test_reconnect_errors_on_monomer_site() {
let cov = DimerCovering::new(4, vec![(0, 1)]).expect("partial covering");
let result = reconnect(&cov, 2, 3);
assert!(result.is_err());
}
#[test]
fn test_reconnect_none_when_already_partners() {
let (a, _b) = ring4_coverings();
let result = reconnect(&a, 0, 1).expect("should not error");
assert!(result.is_none());
}
#[test]
fn test_overlap_rejects_mismatched_monomer_sets() {
let full = DimerCovering::new(4, vec![(0, 1), (2, 3)]).expect("full covering");
let partial = DimerCovering::new(4, vec![(0, 1)]).expect("partial covering");
let result = overlap(&full, &partial, 4);
assert!(result.is_err());
}
#[test]
fn test_dense_amplitudes_rejects_monomers() {
let partial = DimerCovering::new(4, vec![(0, 1)]).expect("partial covering");
let result = dense_amplitudes(&partial, 4);
assert!(result.is_err());
}
}