use super::dimer::DimerCovering;
use super::solver::{generalized_ground_state, RvbSolver};
use crate::error::{self, Error, Result};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpinonSeparationPoint {
pub site_a: usize,
pub site_b: usize,
pub graph_distance: usize,
pub delta_energy: f64,
}
pub fn spinon_pair_energy(solver: &RvbSolver, r1: usize, r2: usize) -> Result<f64> {
if r1 >= solver.num_sites || r2 >= solver.num_sites {
return Err(error::invalid_param(
"r1/r2",
"spinon site index out of range",
));
}
if r1 == r2 {
return Err(error::invalid_param(
"r1/r2",
"the two spinon sites must be distinct",
));
}
let coverings =
DimerCovering::enumerate_monomer_dimer_matchings(solver.num_sites, &solver.bonds, r1, r2)?;
if coverings.is_empty() {
return Err(error::invalid_param(
"r1/r2",
"no monomer-dimer matching exists leaving exactly these two sites unpaired",
));
}
if coverings.len() > super::MAX_VB_BASIS {
return Err(Error::InvalidParameter {
param: "r1/r2".to_string(),
reason: format!(
"monomer-dimer covering enumeration produced {} basis states, exceeding \
MAX_VB_BASIS={} (bounded by CMatrix::MAX_DIM=64)",
coverings.len(),
super::MAX_VB_BASIS
),
});
}
let filtered_bonds: Vec<(usize, usize)> = solver
.bonds
.iter()
.copied()
.filter(|&(i, j)| i != r1 && i != r2 && j != r1 && j != r2)
.collect();
let direct_bond_correction = if solver
.bonds
.iter()
.any(|&(i, j)| (i == r1 && j == r2) || (i == r2 && j == r1))
{
0.25 * solver.coupling_j
} else {
0.0
};
let ground = generalized_ground_state(
solver.num_sites,
&coverings,
&filtered_bonds,
solver.coupling_j,
)?;
Ok(ground.energy + direct_bond_correction)
}
pub fn deconfinement_diagnostic(
solver: &RvbSolver,
pairs: &[(usize, usize)],
) -> Result<Vec<SpinonSeparationPoint>> {
let ground = solver.ground_state()?;
let mut points = Vec::with_capacity(pairs.len());
for &(r1, r2) in pairs {
let e_pair = spinon_pair_energy(solver, r1, r2)?;
let distances = super::dimer::bfs_distances(solver.num_sites, &solver.bonds, r1)?;
let graph_distance = distances.get(r2).copied().flatten().ok_or_else(|| {
error::invalid_param(
"pairs",
"the two spinon sites must be connected in the bond graph",
)
})?;
points.push(SpinonSeparationPoint {
site_a: r1,
site_b: r2,
graph_distance,
delta_energy: e_pair - ground.energy,
});
}
Ok(points)
}
#[cfg(test)]
mod tests {
use super::*;
fn ring4_solver() -> RvbSolver {
RvbSolver::from_bonds(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)], 1.0)
.expect("valid ring4 solver")
}
#[test]
fn test_ring4_adjacent_spinon_pair_energy_exact() {
let solver = ring4_solver();
let e = spinon_pair_energy(&solver, 0, 1).expect("adjacent spinon pair should solve");
assert!(
(e - (-0.5)).abs() < 1e-9,
"expected exactly -0.5, got {}",
e
);
let e12 = spinon_pair_energy(&solver, 1, 2).expect("adjacent pair (1,2)");
let e23 = spinon_pair_energy(&solver, 2, 3).expect("adjacent pair (2,3)");
let e30 = spinon_pair_energy(&solver, 3, 0).expect("adjacent pair (3,0)");
for e_other in [e12, e23, e30] {
assert!(
(e_other - e).abs() < 1e-9,
"ring symmetry should give identical energies"
);
}
}
#[test]
fn test_ring4_opposite_corners_have_no_matching() {
let solver = ring4_solver();
let result = spinon_pair_energy(&solver, 0, 2);
assert!(
result.is_err(),
"opposite corners should have no valid monomer-dimer matching"
);
}
#[test]
fn test_spinon_same_site_errors() {
let solver = ring4_solver();
assert!(spinon_pair_energy(&solver, 1, 1).is_err());
}
#[test]
fn test_spinon_out_of_range_errors() {
let solver = ring4_solver();
assert!(spinon_pair_energy(&solver, 0, 99).is_err());
}
#[test]
fn test_deconfinement_diagnostic_ring4_adjacent_pairs() {
let solver = ring4_solver();
let pairs = [(0, 1), (1, 2), (2, 3), (3, 0)];
let points = deconfinement_diagnostic(&solver, &pairs).expect("diagnostic should solve");
assert_eq!(points.len(), 4);
for p in &points {
assert_eq!(p.graph_distance, 1);
assert!(p.delta_energy.is_finite());
assert!(
(p.delta_energy - 1.5).abs() < 1e-8,
"expected delta_energy = 1.5, got {}",
p.delta_energy
);
}
}
#[test]
fn test_deconfinement_diagnostic_ladder() {
let bonds = vec![
(0, 1),
(1, 2),
(2, 3),
(4, 5),
(5, 6),
(6, 7),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
let solver = RvbSolver::from_bonds(8, bonds, 1.0).expect("valid ladder solver");
let points =
deconfinement_diagnostic(&solver, &[(0, 1), (0, 3)]).expect("diagnostic should solve");
assert_eq!(points.len(), 2);
assert_eq!(points[0].graph_distance, 1);
assert_eq!(points[1].graph_distance, 3);
for p in &points {
assert!(p.delta_energy.is_finite());
}
}
}