#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use std::f64::consts::PI;
pub const FLUX_QUANTUM: f64 = 2.067833848e-15;
const K_B: f64 = 1.380649e-23;
const KAPPA_THRESHOLD: f64 = std::f64::consts::FRAC_1_SQRT_2;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SuperconductorType {
TypeI,
TypeII,
HighTc,
}
#[derive(Clone, Debug)]
pub struct SuperconductorProps {
pub critical_temperature: f64,
pub critical_field_hc1: f64,
pub critical_field_hc2: f64,
pub london_penetration_depth: f64,
pub coherence_length: f64,
pub kappa: f64,
}
impl SuperconductorProps {
#[allow(clippy::too_many_arguments)]
pub fn new(
critical_temperature: f64,
critical_field_hc1: f64,
critical_field_hc2: f64,
london_penetration_depth: f64,
coherence_length: f64,
) -> Self {
let kappa = london_penetration_depth / coherence_length.max(1e-300);
Self {
critical_temperature,
critical_field_hc1,
critical_field_hc2,
london_penetration_depth,
coherence_length,
kappa,
}
}
pub fn niobium() -> Self {
Self::new(9.25, 1.58e5, 2.44e5, 39e-9, 38e-9)
}
pub fn lead() -> Self {
Self::new(7.19, 6.4e4, 6.4e4, 37e-9, 83e-9)
}
pub fn ybco() -> Self {
Self::new(93.0, 2.0e7, 1.2e8, 150e-9, 1.5e-9)
}
pub fn superconductor_type(&self) -> SuperconductorType {
if self.kappa > 10.0 {
SuperconductorType::HighTc
} else if is_type_ii(self.kappa) {
SuperconductorType::TypeII
} else {
SuperconductorType::TypeI
}
}
}
#[derive(Clone, Debug)]
pub struct GinzburgLandauModel {
pub order_parameter: Vec<f64>,
pub magnetic_field: Vec<f64>,
pub coherence_length: f64,
pub penetration_depth: f64,
pub kappa: f64,
}
impl GinzburgLandauModel {
pub fn new(n: usize, coherence_length: f64, penetration_depth: f64) -> Self {
let kappa = penetration_depth / coherence_length.max(1e-300);
Self {
order_parameter: vec![1.0; n],
magnetic_field: vec![0.0; n],
coherence_length,
penetration_depth,
kappa,
}
}
pub fn solve_gl(&mut self, n_iter: usize) -> Vec<f64> {
let n = self.order_parameter.len();
if n < 2 {
return self.order_parameter.clone();
}
self.order_parameter[0] = 0.0;
*self
.order_parameter
.last_mut()
.expect("collection should not be empty") = 1.0;
for _ in 0..n_iter {
for i in 1..n - 1 {
let psi_l = self.order_parameter[i - 1];
let psi_r = self.order_parameter[i + 1];
let psi = self.order_parameter[i];
let laplacian = psi_l - 2.0 * psi + psi_r;
let rhs = self.coherence_length.powi(2) * laplacian + psi * (1.0 - psi * psi);
self.order_parameter[i] = (psi + 0.1 * rhs).clamp(0.0, 1.0);
}
}
self.order_parameter.clone()
}
}
#[derive(Clone, Debug)]
pub struct BcsGap {
pub gap_energy: f64,
pub density_of_states: f64,
pub critical_temperature: f64,
}
impl BcsGap {
pub fn new(gap_energy: f64, density_of_states: f64, critical_temperature: f64) -> Self {
Self {
gap_energy,
density_of_states,
critical_temperature,
}
}
pub fn from_tc(critical_temperature: f64, density_of_states: f64) -> Self {
let gap = 1.764 * K_B * critical_temperature;
Self::new(gap, density_of_states, critical_temperature)
}
pub fn compute_gap(&self, temperature: f64) -> f64 {
if temperature <= 0.0 {
return self.gap_energy;
}
if temperature >= self.critical_temperature {
return 0.0;
}
let ratio = self.critical_temperature / temperature;
let arg = 1.74 * (ratio - 1.0).sqrt();
self.gap_energy * arg.tanh()
}
pub fn normalised_gap(&self, temperature: f64) -> f64 {
if self.gap_energy < 1e-300 {
return 0.0;
}
self.compute_gap(temperature) / self.gap_energy
}
}
#[derive(Clone, Debug)]
pub struct FluxVortex {
pub position: [f64; 2],
pub flux_quantum: f64,
}
impl FluxVortex {
pub fn new(position: [f64; 2]) -> Self {
Self {
position,
flux_quantum: FLUX_QUANTUM,
}
}
pub fn distance_to(&self, other: &FluxVortex) -> f64 {
let dx = self.position[0] - other.position[0];
let dy = self.position[1] - other.position[1];
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Clone, Debug)]
pub struct VortexLattice {
pub vortices: Vec<FluxVortex>,
pub penetration_depth: f64,
}
impl VortexLattice {
pub fn new(penetration_depth: f64) -> Self {
Self {
vortices: Vec::new(),
penetration_depth,
}
}
pub fn add_vortex(&mut self, vortex: FluxVortex) {
self.vortices.push(vortex);
}
pub fn triangular_lattice(nx: usize, ny: usize, spacing: f64, penetration_depth: f64) -> Self {
let mut lattice = Self::new(penetration_depth);
for j in 0..ny {
for i in 0..nx {
let x = i as f64 * spacing + if j % 2 == 1 { 0.5 * spacing } else { 0.0 };
let y = j as f64 * spacing * (3.0_f64.sqrt() / 2.0);
lattice.add_vortex(FluxVortex::new([x, y]));
}
}
lattice
}
pub fn compute_inter_vortex_force(&self) -> Vec<[f64; 2]> {
let n = self.vortices.len();
let mut forces = vec![[0.0_f64; 2]; n];
let mu0 = 4.0 * PI * 1e-7_f64;
let lambda = self.penetration_depth;
let prefactor = FLUX_QUANTUM.powi(2) / (2.0 * PI * mu0 * lambda.powi(2));
#[allow(clippy::needless_range_loop)]
for i in 0..n {
for j in 0..n {
if i == j {
continue;
}
let dx = self.vortices[i].position[0] - self.vortices[j].position[0];
let dy = self.vortices[i].position[1] - self.vortices[j].position[1];
let r = (dx * dx + dy * dy).sqrt().max(1e-15);
let xi = r / lambda;
let k1_approx = (1.0 / xi) * (-xi).exp();
let mag = prefactor * k1_approx / r;
forces[i][0] += mag * dx / r;
forces[i][1] += mag * dy / r;
}
}
forces
}
pub fn flux_density(&self, cell_area: f64) -> f64 {
(self.vortices.len() as f64) * FLUX_QUANTUM / cell_area.max(1e-300)
}
}
pub fn london_equation_factor(lambda: f64, field: f64) -> f64 {
if lambda <= 0.0 {
return 0.0;
}
(-field.abs() / lambda).exp()
}
pub fn is_type_ii(kappa: f64) -> bool {
kappa > KAPPA_THRESHOLD
}
pub fn upper_critical_field(coherence_length: f64) -> f64 {
let mu0 = 4.0 * PI * 1e-7_f64;
FLUX_QUANTUM / (2.0 * PI * mu0 * coherence_length.powi(2).max(1e-300))
}
pub fn thermodynamic_critical_field(lambda: f64, coherence_length: f64) -> f64 {
let mu0 = 4.0 * PI * 1e-7_f64;
FLUX_QUANTUM / (2.0 * PI * 2.0_f64.sqrt() * mu0 * lambda * coherence_length.max(1e-300))
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-10;
#[test]
fn test_flux_quantum_value() {
assert!((FLUX_QUANTUM - 2.067833848e-15).abs() < 1e-22);
}
#[test]
fn test_is_type_ii_false() {
assert!(!is_type_ii(0.3)); }
#[test]
fn test_is_type_ii_true() {
assert!(is_type_ii(1.0)); }
#[test]
fn test_is_type_ii_boundary() {
let kappa = 1.0 / 2.0_f64.sqrt();
assert!(!is_type_ii(kappa)); assert!(is_type_ii(kappa + 1e-9));
}
#[test]
fn test_props_kappa_computation() {
let props = SuperconductorProps::new(9.25, 1.58e5, 2.44e5, 40e-9, 20e-9);
assert!((props.kappa - 2.0).abs() < 1e-9);
}
#[test]
fn test_niobium_type_ii() {
let nb = SuperconductorProps::niobium();
assert_eq!(nb.superconductor_type(), SuperconductorType::TypeII);
}
#[test]
fn test_lead_type_i() {
let pb = SuperconductorProps::lead();
assert_eq!(pb.superconductor_type(), SuperconductorType::TypeI);
}
#[test]
fn test_ybco_high_tc() {
let y = SuperconductorProps::ybco();
assert_eq!(y.superconductor_type(), SuperconductorType::HighTc);
}
#[test]
fn test_gl_model_initial_order_parameter() {
let gl = GinzburgLandauModel::new(10, 10e-9, 40e-9);
assert!(gl.order_parameter.iter().all(|&v| (v - 1.0).abs() < EPS));
}
#[test]
fn test_gl_solve_boundary_conditions() {
let mut gl = GinzburgLandauModel::new(20, 0.1, 0.4);
let psi = gl.solve_gl(200);
assert!(psi[0].abs() < EPS, "Surface must be 0, got {}", psi[0]);
assert!(
(psi[19] - 1.0).abs() < EPS,
"Bulk must be 1, got {}",
psi[19]
);
}
#[test]
fn test_gl_solve_range() {
let mut gl = GinzburgLandauModel::new(30, 0.1, 0.4);
let psi = gl.solve_gl(100);
for &v in &psi {
assert!(
(0.0..=1.0).contains(&v),
"Order parameter out of range: {v}"
);
}
}
#[test]
fn test_gl_solve_monotone() {
let mut gl = GinzburgLandauModel::new(30, 0.1, 0.4);
let psi = gl.solve_gl(500);
for w in psi.windows(2) {
assert!(w[1] >= w[0] - 1e-8, "Not monotone: {} > {}", w[0], w[1]);
}
}
#[test]
fn test_bcs_gap_from_tc() {
let tc = 9.25;
let bcs = BcsGap::from_tc(tc, 1.0);
let expected = 1.764 * K_B * tc;
assert!((bcs.gap_energy - expected).abs() < 1e-28);
}
#[test]
fn test_bcs_gap_at_zero_temp() {
let bcs = BcsGap::from_tc(9.25, 1.0);
assert!((bcs.compute_gap(0.0) - bcs.gap_energy).abs() < EPS);
}
#[test]
fn test_bcs_gap_at_tc() {
let bcs = BcsGap::from_tc(9.25, 1.0);
assert!(bcs.compute_gap(9.25).abs() < EPS);
}
#[test]
fn test_bcs_gap_above_tc() {
let bcs = BcsGap::from_tc(9.25, 1.0);
assert!(bcs.compute_gap(100.0).abs() < EPS);
}
#[test]
fn test_bcs_gap_monotone() {
let bcs = BcsGap::from_tc(9.25, 1.0);
let temps: Vec<f64> = (0..=9).map(|i| i as f64).collect();
let gaps: Vec<f64> = temps.iter().map(|&t| bcs.compute_gap(t)).collect();
for w in gaps.windows(2) {
assert!(
w[1] <= w[0] + 1e-15,
"Gap not monotone: {} -> {}",
w[0],
w[1]
);
}
}
#[test]
fn test_bcs_normalised_gap_at_zero() {
let bcs = BcsGap::from_tc(9.25, 1.0);
assert!((bcs.normalised_gap(0.0) - 1.0).abs() < EPS);
}
#[test]
fn test_bcs_normalised_gap_at_tc() {
let bcs = BcsGap::from_tc(9.25, 1.0);
assert!(bcs.normalised_gap(9.25).abs() < EPS);
}
#[test]
fn test_flux_vortex_carries_one_quantum() {
let v = FluxVortex::new([0.0, 0.0]);
assert!((v.flux_quantum - FLUX_QUANTUM).abs() < 1e-25);
}
#[test]
fn test_flux_vortex_distance() {
let v1 = FluxVortex::new([0.0, 0.0]);
let v2 = FluxVortex::new([3.0, 4.0]);
assert!((v1.distance_to(&v2) - 5.0).abs() < EPS);
}
#[test]
fn test_vortex_lattice_empty() {
let lat = VortexLattice::new(40e-9);
assert!(lat.vortices.is_empty());
}
#[test]
fn test_vortex_lattice_add() {
let mut lat = VortexLattice::new(40e-9);
lat.add_vortex(FluxVortex::new([0.0, 0.0]));
lat.add_vortex(FluxVortex::new([1e-6, 0.0]));
assert_eq!(lat.vortices.len(), 2);
}
#[test]
fn test_triangular_lattice_count() {
let lat = VortexLattice::triangular_lattice(4, 3, 100e-9, 40e-9);
assert_eq!(lat.vortices.len(), 12);
}
#[test]
fn test_inter_vortex_force_length() {
let lat = VortexLattice::triangular_lattice(3, 3, 200e-9, 40e-9);
let forces = lat.compute_inter_vortex_force();
assert_eq!(forces.len(), 9);
}
#[test]
fn test_inter_vortex_force_newton_third() {
let mut lat = VortexLattice::new(40e-9);
lat.add_vortex(FluxVortex::new([0.0, 0.0]));
lat.add_vortex(FluxVortex::new([200e-9, 0.0]));
let f = lat.compute_inter_vortex_force();
assert!(
f[0][0] < 0.0,
"Vortex 0 should be pushed -x, got {}",
f[0][0]
);
assert!(
f[1][0] > 0.0,
"Vortex 1 should be pushed +x, got {}",
f[1][0]
);
assert!(
(f[0][0] + f[1][0]).abs() < 1e-3 * f[0][0].abs(),
"Newton's 3rd law violated: {} vs {}",
f[0][0],
f[1][0]
);
}
#[test]
fn test_london_factor_zero_field() {
let f = london_equation_factor(40e-9, 0.0);
assert!((f - 1.0).abs() < EPS);
}
#[test]
fn test_london_factor_decay() {
let lambda = 40e-9;
let f1 = london_equation_factor(lambda, lambda);
let f2 = london_equation_factor(lambda, 2.0 * lambda);
assert!(f2 < f1, "Factor should decay: f1={f1}, f2={f2}");
assert!((f1 - (-1.0_f64).exp()).abs() < 1e-12);
}
#[test]
fn test_london_factor_zero_lambda() {
assert!((london_equation_factor(0.0, 1.0)).abs() < EPS);
assert!((london_equation_factor(-1.0, 1.0)).abs() < EPS);
}
#[test]
fn test_upper_critical_field_scaling() {
let hc2_a = upper_critical_field(10e-9);
let hc2_b = upper_critical_field(20e-9); assert!(
(hc2_a / hc2_b - 4.0).abs() < 1e-8,
"H_c2(10nm)/H_c2(20nm) should be 4, got {}",
hc2_a / hc2_b
);
}
#[test]
fn test_thermodynamic_critical_field_positive() {
let hc = thermodynamic_critical_field(40e-9, 20e-9);
assert!(hc > 0.0);
}
#[test]
fn test_superconductor_type_eq() {
assert_eq!(SuperconductorType::TypeI, SuperconductorType::TypeI);
assert_ne!(SuperconductorType::TypeI, SuperconductorType::TypeII);
}
#[test]
fn test_flux_density_scaling() {
let mut lat1 = VortexLattice::new(40e-9);
let mut lat2 = VortexLattice::new(40e-9);
lat1.add_vortex(FluxVortex::new([0.0, 0.0]));
lat2.add_vortex(FluxVortex::new([0.0, 0.0]));
lat2.add_vortex(FluxVortex::new([1e-6, 0.0]));
let area = 1e-12;
assert!((lat2.flux_density(area) - 2.0 * lat1.flux_density(area)).abs() < 1e-25);
}
#[test]
fn test_bcs_gap_mid_temperature() {
let tc = 9.25;
let bcs = BcsGap::from_tc(tc, 1.0);
let gap_mid = bcs.compute_gap(tc / 2.0);
assert!(
gap_mid > 0.0 && gap_mid < bcs.gap_energy,
"Mid-T gap should be in (0, Δ₀): {gap_mid}"
);
}
}