use volterra_fields::ScalarField3D;
const GRAD_THRESHOLD: f64 = 1e-6;
pub fn gauss_bonnet_chi(phi: &ScalarField3D, _epsilon: f64) -> f64 {
let nx = phi.nx;
let ny = phi.ny;
let nz = phi.nz;
let dx = phi.dx;
let mut chi_sum = 0.0_f64;
for i in 0..nx {
for j in 0..ny {
for k in 0..nz {
let ip = (i + 1) % nx;
let im = (i + nx - 1) % nx;
let jp = (j + 1) % ny;
let jm = (j + ny - 1) % ny;
let kp = (k + 1) % nz;
let km = (k + nz - 1) % nz;
let v_xp = phi.phi[phi.idx(ip, j, k )];
let v_xm = phi.phi[phi.idx(im, j, k )];
let v_yp = phi.phi[phi.idx(i, jp, k )];
let v_ym = phi.phi[phi.idx(i, jm, k )];
let v_zp = phi.phi[phi.idx(i, j, kp)];
let v_zm = phi.phi[phi.idx(i, j, km)];
let phi_x = (v_xp - v_xm) / (2.0*dx);
let phi_y = (v_yp - v_ym) / (2.0*dx);
let phi_z = (v_zp - v_zm) / (2.0*dx);
let grad2 = phi_x*phi_x + phi_y*phi_y + phi_z*phi_z;
let grad_mag = grad2.sqrt();
if grad_mag < GRAD_THRESHOLD {
continue;
}
let phi_c = phi.phi[phi.idx(i, j, k)];
let phi_xx = (v_xp - 2.0*phi_c + v_xm) / (dx*dx);
let phi_yy = (v_yp - 2.0*phi_c + v_ym) / (dx*dx);
let phi_zz = (v_zp - 2.0*phi_c + v_zm) / (dx*dx);
let phi_xy = (phi.phi[phi.idx(ip, jp, k)] - phi.phi[phi.idx(ip, jm, k)]
- phi.phi[phi.idx(im, jp, k)] + phi.phi[phi.idx(im, jm, k)])
/ (4.0*dx*dx);
let phi_xz = (phi.phi[phi.idx(ip, j, kp)] - phi.phi[phi.idx(ip, j, km)]
- phi.phi[phi.idx(im, j, kp)] + phi.phi[phi.idx(im, j, km)])
/ (4.0*dx*dx);
let phi_yz = (phi.phi[phi.idx(i, jp, kp)] - phi.phi[phi.idx(i, jp, km)]
- phi.phi[phi.idx(i, jm, kp)] + phi.phi[phi.idx(i, jm, km)])
/ (4.0*dx*dx);
let m01 = phi_x * (phi_yy*phi_zz - phi_yz*phi_yz)
- phi_xy * (phi_y*phi_zz - phi_yz*phi_z)
+ phi_xz * (phi_y*phi_yz - phi_yy*phi_z);
let m02 = phi_x * (phi_xy*phi_zz - phi_yz*phi_xz)
- phi_xx * (phi_y*phi_zz - phi_yz*phi_z)
+ phi_xz * (phi_y*phi_xz - phi_xy*phi_z);
let m03 = phi_x * (phi_xy*phi_yz - phi_yy*phi_xz)
- phi_xx * (phi_y*phi_yz - phi_yy*phi_z)
+ phi_xy * (phi_y*phi_xz - phi_xy*phi_z);
let bh_det = -phi_x * m01 + phi_y * m02 - phi_z * m03;
let k_g = -bh_det / (grad2 * grad2);
chi_sum += k_g * grad_mag * dx * dx * dx;
}
}
}
chi_sum / (2.0 * std::f64::consts::PI)
}
pub fn compute_kg_field(phi: &ScalarField3D) -> (Vec<f64>, Vec<f64>) {
let nx = phi.nx;
let ny = phi.ny;
let nz = phi.nz;
let dx = phi.dx;
let n = nx * ny * nz;
let mut kg_field = vec![0.0_f64; n];
let mut grad_field = vec![0.0_f64; n];
for i in 0..nx {
for j in 0..ny {
for k in 0..nz {
let ip = (i + 1) % nx; let im = (i + nx - 1) % nx;
let jp = (j + 1) % ny; let jm = (j + ny - 1) % ny;
let kp = (k + 1) % nz; let km = (k + nz - 1) % nz;
let v_xp = phi.phi[phi.idx(ip, j, k )];
let v_xm = phi.phi[phi.idx(im, j, k )];
let v_yp = phi.phi[phi.idx(i, jp, k )];
let v_ym = phi.phi[phi.idx(i, jm, k )];
let v_zp = phi.phi[phi.idx(i, j, kp)];
let v_zm = phi.phi[phi.idx(i, j, km)];
let phi_x = (v_xp - v_xm) / (2.0*dx);
let phi_y = (v_yp - v_ym) / (2.0*dx);
let phi_z = (v_zp - v_zm) / (2.0*dx);
let grad2 = phi_x*phi_x + phi_y*phi_y + phi_z*phi_z;
let grad_mag = grad2.sqrt();
grad_field[phi.idx(i,j,k)] = grad_mag;
if grad_mag < GRAD_THRESHOLD {
continue;
}
let phi_c = phi.phi[phi.idx(i,j,k)];
let phi_xx = (v_xp - 2.0*phi_c + v_xm) / (dx*dx);
let phi_yy = (v_yp - 2.0*phi_c + v_ym) / (dx*dx);
let phi_zz = (v_zp - 2.0*phi_c + v_zm) / (dx*dx);
let phi_xy = (phi.phi[phi.idx(ip,jp,k)] - phi.phi[phi.idx(ip,jm,k)]
- phi.phi[phi.idx(im,jp,k)] + phi.phi[phi.idx(im,jm,k)]) / (4.0*dx*dx);
let phi_xz = (phi.phi[phi.idx(ip,j,kp)] - phi.phi[phi.idx(ip,j,km)]
- phi.phi[phi.idx(im,j,kp)] + phi.phi[phi.idx(im,j,km)]) / (4.0*dx*dx);
let phi_yz = (phi.phi[phi.idx(i,jp,kp)] - phi.phi[phi.idx(i,jp,km)]
- phi.phi[phi.idx(i,jm,kp)] + phi.phi[phi.idx(i,jm,km)]) / (4.0*dx*dx);
let m01 = phi_x*(phi_yy*phi_zz - phi_yz*phi_yz)
- phi_xy*(phi_y*phi_zz - phi_yz*phi_z)
+ phi_xz*(phi_y*phi_yz - phi_yy*phi_z);
let m02 = phi_x*(phi_xy*phi_zz - phi_yz*phi_xz)
- phi_xx*(phi_y*phi_zz - phi_yz*phi_z)
+ phi_xz*(phi_y*phi_xz - phi_xy*phi_z);
let m03 = phi_x*(phi_xy*phi_yz - phi_yy*phi_xz)
- phi_xx*(phi_y*phi_yz - phi_yy*phi_z)
+ phi_xy*(phi_y*phi_xz - phi_xy*phi_z);
let bh_det = -phi_x*m01 + phi_y*m02 - phi_z*m03;
kg_field[phi.idx(i,j,k)] = -bh_det / (grad2 * grad2);
}
}
}
(kg_field, grad_field)
}
#[cfg(test)]
mod tests {
use super::*;
use volterra_fields::ScalarField3D;
#[test]
fn test_uniform_field_chi_zero() {
let phi = ScalarField3D::uniform(8, 8, 8, 1.0, 0.5);
let chi = gauss_bonnet_chi(&phi, 1.0);
assert!(chi.abs() < 1e-10, "uniform phi must give chi=0, got {}", chi);
}
#[test]
fn test_sphere_chi_approx_two() {
let n = 64usize;
let dx = 1.0_f64;
let r0 = 16.0_f64;
let epsilon = 3.0_f64;
let cx = n as f64 / 2.0;
let mut phi = ScalarField3D::zeros(n, n, n, dx);
for i in 0..n {
for j in 0..n {
for k in 0..n {
let x = i as f64 - cx;
let y = j as f64 - cx;
let z = k as f64 - cx;
let r = (x*x + y*y + z*z).sqrt();
let idx = phi.idx(i, j, k);
phi.phi[idx] = 0.5 * (1.0 + ((r - r0) / epsilon).tanh());
}
}
}
let chi = gauss_bonnet_chi(&phi, epsilon);
assert!(
(chi - 2.0).abs() < 0.15,
"sphere shell chi expected ≈ 2.0, got {:.4}",
chi
);
}
}