use super::dimer::bfs_distances;
use super::solver::{build_hamiltonian_matrix, GroundState, RvbSolver};
use crate::error::{self, Result};
use crate::vector3::Vector3;
pub fn spin_correlation(
solver: &RvbSolver,
ground: &GroundState,
i: usize,
j: usize,
) -> Result<f64> {
if i >= solver.num_sites || j >= solver.num_sites {
return Err(error::invalid_param("i/j", "site index out of range"));
}
if ground.coefficients.len() != solver.dim() {
return Err(error::invalid_param(
"ground",
"ground-state coefficient length does not match the solver's basis dimension",
));
}
if i == j {
return Ok(0.75);
}
let single_bond = [(i, j)];
let m = build_hamiltonian_matrix(&solver.coverings, &single_bond, 1.0, solver.num_sites)?;
let s = solver.overlap_matrix()?;
let d = solver.dim();
let c = &ground.coefficients;
let mut numerator = 0.0_f64;
let mut denominator = 0.0_f64;
for a in 0..d {
for b in 0..d {
numerator += c[a] * m.get(a, b).re * c[b];
denominator += c[a] * s.get(a, b).re * c[b];
}
}
if denominator.abs() < 1e-14 {
return Err(error::numerical_error(
"ground state has (numerically) zero norm: c^T S c vanished",
));
}
Ok(numerator / denominator)
}
pub fn structure_factor(
solver: &RvbSolver,
ground: &GroundState,
positions: &[Vector3<f64>],
q: Vector3<f64>,
) -> Result<f64> {
if positions.len() != solver.num_sites {
return Err(error::invalid_param(
"positions",
"positions length must equal solver.num_sites",
));
}
let n = solver.num_sites;
let mut total = 0.0_f64;
for i in 0..n {
for j in 0..n {
let corr = spin_correlation(solver, ground, i, j)?;
let dr = positions[i] - positions[j];
let phase = q.dot(&dr);
total += corr * phase.cos();
}
}
Ok(total / n as f64)
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpinLiquidReport {
pub ground_energy: f64,
pub far_site_a: usize,
pub far_site_b: usize,
pub far_graph_distance: usize,
pub long_range_correlation: f64,
pub is_candidate: bool,
}
pub fn is_spin_liquid(solver: &RvbSolver, correlation_threshold: f64) -> Result<SpinLiquidReport> {
if correlation_threshold <= 0.0 {
return Err(error::invalid_param(
"correlation_threshold",
"correlation threshold must be positive",
));
}
let ground = solver.ground_state()?;
let n = solver.num_sites;
let mut far_pair = (0usize, 0usize);
let mut max_dist = 0usize;
for source in 0..n {
let distances = bfs_distances(n, &solver.bonds, source)?;
for (target, dist_opt) in distances.into_iter().enumerate() {
if let Some(dist) = dist_opt {
if dist > max_dist {
max_dist = dist;
far_pair = (source, target);
}
}
}
}
let long_range_correlation = if max_dist == 0 {
0.0
} else {
spin_correlation(solver, &ground, far_pair.0, far_pair.1)?.abs()
};
let is_candidate = long_range_correlation < correlation_threshold;
Ok(SpinLiquidReport {
ground_energy: ground.energy,
far_site_a: far_pair.0,
far_site_b: far_pair.1,
far_graph_distance: max_dist,
long_range_correlation,
is_candidate,
})
}
#[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_self_correlation_is_three_quarters() {
let solver = ring4_solver();
let ground = solver.ground_state().expect("ground state");
let c = spin_correlation(&solver, &ground, 2, 2).expect("self correlation");
assert!((c - 0.75).abs() < 1e-12);
}
#[test]
fn test_ring4_nearest_neighbor_correlation_exact() {
let solver = ring4_solver();
let ground = solver.ground_state().expect("ground state");
let corr = spin_correlation(&solver, &ground, 0, 1).expect("nn correlation");
assert!(
(corr - (-0.5)).abs() < 1e-9,
"expected <S_0.S_1> = -0.5, got {}",
corr
);
}
#[test]
fn test_ring4_diagonal_correlation_matches_singlet_constraint() {
let solver = ring4_solver();
let ground = solver.ground_state().expect("ground state");
let corr = spin_correlation(&solver, &ground, 0, 2).expect("diagonal correlation");
assert!(
(corr - 0.25).abs() < 1e-9,
"expected <S_0.S_2> = 0.25, got {}",
corr
);
}
#[test]
fn test_structure_factor_at_q_zero_matches_zero_total_spin() {
let solver = ring4_solver();
let ground = solver.ground_state().expect("ground state");
let positions = vec![Vector3::zero(); 4];
let s0 = structure_factor(&solver, &ground, &positions, Vector3::zero())
.expect("structure factor should solve");
assert!(
s0.abs() < 1e-9,
"expected S(q=0) = 0 for a total-spin-0 ground state, got {}",
s0
);
}
#[test]
fn test_structure_factor_rejects_wrong_position_count() {
let solver = ring4_solver();
let ground = solver.ground_state().expect("ground state");
let positions = vec![Vector3::zero(); 3];
assert!(structure_factor(&solver, &ground, &positions, Vector3::zero()).is_err());
}
#[test]
fn test_is_spin_liquid_report_ring4() {
let solver = ring4_solver();
let report = is_spin_liquid(&solver, 0.5).expect("report should solve");
assert_eq!(
report.far_graph_distance, 2,
"ring4 diameter is 2 (opposite corners)"
);
assert!((report.long_range_correlation - 0.25).abs() < 1e-9);
assert!((report.ground_energy - (-2.0)).abs() < 1e-9);
assert!(report.is_candidate);
}
#[test]
fn test_is_spin_liquid_rejects_non_positive_threshold() {
let solver = ring4_solver();
assert!(is_spin_liquid(&solver, 0.0).is_err());
assert!(is_spin_liquid(&solver, -0.1).is_err());
}
}