1#![allow(dead_code)]
13#![allow(clippy::too_many_arguments)]
14
15use std::f64::consts::PI;
16
17pub const FLUX_QUANTUM: f64 = 2.067833848e-15;
23const K_B: f64 = 1.380649e-23;
25const KAPPA_THRESHOLD: f64 = std::f64::consts::FRAC_1_SQRT_2;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum SuperconductorType {
35 TypeI,
37 TypeII,
39 HighTc,
41}
42
43#[derive(Clone, Debug)]
49pub struct SuperconductorProps {
50 pub critical_temperature: f64,
52 pub critical_field_hc1: f64,
54 pub critical_field_hc2: f64,
56 pub london_penetration_depth: f64,
58 pub coherence_length: f64,
60 pub kappa: f64,
62}
63
64impl SuperconductorProps {
65 #[allow(clippy::too_many_arguments)]
67 pub fn new(
68 critical_temperature: f64,
69 critical_field_hc1: f64,
70 critical_field_hc2: f64,
71 london_penetration_depth: f64,
72 coherence_length: f64,
73 ) -> Self {
74 let kappa = london_penetration_depth / coherence_length.max(1e-300);
75 Self {
76 critical_temperature,
77 critical_field_hc1,
78 critical_field_hc2,
79 london_penetration_depth,
80 coherence_length,
81 kappa,
82 }
83 }
84
85 pub fn niobium() -> Self {
87 Self::new(9.25, 1.58e5, 2.44e5, 39e-9, 38e-9)
88 }
89
90 pub fn lead() -> Self {
92 Self::new(7.19, 6.4e4, 6.4e4, 37e-9, 83e-9)
94 }
95
96 pub fn ybco() -> Self {
98 Self::new(93.0, 2.0e7, 1.2e8, 150e-9, 1.5e-9)
99 }
100
101 pub fn superconductor_type(&self) -> SuperconductorType {
103 if self.kappa > 10.0 {
104 SuperconductorType::HighTc
105 } else if is_type_ii(self.kappa) {
106 SuperconductorType::TypeII
107 } else {
108 SuperconductorType::TypeI
109 }
110 }
111}
112
113#[derive(Clone, Debug)]
122pub struct GinzburgLandauModel {
123 pub order_parameter: Vec<f64>,
125 pub magnetic_field: Vec<f64>,
127 pub coherence_length: f64,
129 pub penetration_depth: f64,
131 pub kappa: f64,
133}
134
135impl GinzburgLandauModel {
136 pub fn new(n: usize, coherence_length: f64, penetration_depth: f64) -> Self {
138 let kappa = penetration_depth / coherence_length.max(1e-300);
139 Self {
140 order_parameter: vec![1.0; n],
141 magnetic_field: vec![0.0; n],
142 coherence_length,
143 penetration_depth,
144 kappa,
145 }
146 }
147
148 pub fn solve_gl(&mut self, n_iter: usize) -> Vec<f64> {
154 let n = self.order_parameter.len();
155 if n < 2 {
156 return self.order_parameter.clone();
157 }
158 self.order_parameter[0] = 0.0;
160 *self
161 .order_parameter
162 .last_mut()
163 .expect("collection should not be empty") = 1.0;
164
165 for _ in 0..n_iter {
168 for i in 1..n - 1 {
169 let psi_l = self.order_parameter[i - 1];
170 let psi_r = self.order_parameter[i + 1];
171 let psi = self.order_parameter[i];
172 let laplacian = psi_l - 2.0 * psi + psi_r;
174 let rhs = self.coherence_length.powi(2) * laplacian + psi * (1.0 - psi * psi);
176 self.order_parameter[i] = (psi + 0.1 * rhs).clamp(0.0, 1.0);
177 }
178 }
179 self.order_parameter.clone()
180 }
181}
182
183#[derive(Clone, Debug)]
191pub struct BcsGap {
192 pub gap_energy: f64,
194 pub density_of_states: f64,
196 pub critical_temperature: f64,
198}
199
200impl BcsGap {
201 pub fn new(gap_energy: f64, density_of_states: f64, critical_temperature: f64) -> Self {
203 Self {
204 gap_energy,
205 density_of_states,
206 critical_temperature,
207 }
208 }
209
210 pub fn from_tc(critical_temperature: f64, density_of_states: f64) -> Self {
212 let gap = 1.764 * K_B * critical_temperature;
213 Self::new(gap, density_of_states, critical_temperature)
214 }
215
216 pub fn compute_gap(&self, temperature: f64) -> f64 {
222 if temperature <= 0.0 {
223 return self.gap_energy;
224 }
225 if temperature >= self.critical_temperature {
226 return 0.0;
227 }
228 let ratio = self.critical_temperature / temperature;
229 let arg = 1.74 * (ratio - 1.0).sqrt();
230 self.gap_energy * arg.tanh()
231 }
232
233 pub fn normalised_gap(&self, temperature: f64) -> f64 {
235 if self.gap_energy < 1e-300 {
236 return 0.0;
237 }
238 self.compute_gap(temperature) / self.gap_energy
239 }
240}
241
242#[derive(Clone, Debug)]
248pub struct FluxVortex {
249 pub position: [f64; 2],
251 pub flux_quantum: f64,
255}
256
257impl FluxVortex {
258 pub fn new(position: [f64; 2]) -> Self {
260 Self {
261 position,
262 flux_quantum: FLUX_QUANTUM,
263 }
264 }
265
266 pub fn distance_to(&self, other: &FluxVortex) -> f64 {
268 let dx = self.position[0] - other.position[0];
269 let dy = self.position[1] - other.position[1];
270 (dx * dx + dy * dy).sqrt()
271 }
272}
273
274#[derive(Clone, Debug)]
281pub struct VortexLattice {
282 pub vortices: Vec<FluxVortex>,
284 pub penetration_depth: f64,
286}
287
288impl VortexLattice {
289 pub fn new(penetration_depth: f64) -> Self {
291 Self {
292 vortices: Vec::new(),
293 penetration_depth,
294 }
295 }
296
297 pub fn add_vortex(&mut self, vortex: FluxVortex) {
299 self.vortices.push(vortex);
300 }
301
302 pub fn triangular_lattice(nx: usize, ny: usize, spacing: f64, penetration_depth: f64) -> Self {
305 let mut lattice = Self::new(penetration_depth);
306 for j in 0..ny {
307 for i in 0..nx {
308 let x = i as f64 * spacing + if j % 2 == 1 { 0.5 * spacing } else { 0.0 };
309 let y = j as f64 * spacing * (3.0_f64.sqrt() / 2.0);
310 lattice.add_vortex(FluxVortex::new([x, y]));
311 }
312 }
313 lattice
314 }
315
316 pub fn compute_inter_vortex_force(&self) -> Vec<[f64; 2]> {
326 let n = self.vortices.len();
327 let mut forces = vec![[0.0_f64; 2]; n];
328 let mu0 = 4.0 * PI * 1e-7_f64;
329 let lambda = self.penetration_depth;
330 let prefactor = FLUX_QUANTUM.powi(2) / (2.0 * PI * mu0 * lambda.powi(2));
332 #[allow(clippy::needless_range_loop)]
333 for i in 0..n {
334 for j in 0..n {
335 if i == j {
336 continue;
337 }
338 let dx = self.vortices[i].position[0] - self.vortices[j].position[0];
339 let dy = self.vortices[i].position[1] - self.vortices[j].position[1];
340 let r = (dx * dx + dy * dy).sqrt().max(1e-15);
341 let xi = r / lambda;
342 let k1_approx = (1.0 / xi) * (-xi).exp();
344 let mag = prefactor * k1_approx / r;
345 forces[i][0] += mag * dx / r;
346 forces[i][1] += mag * dy / r;
347 }
348 }
349 forces
350 }
351
352 pub fn flux_density(&self, cell_area: f64) -> f64 {
356 (self.vortices.len() as f64) * FLUX_QUANTUM / cell_area.max(1e-300)
357 }
358}
359
360pub fn london_equation_factor(lambda: f64, field: f64) -> f64 {
373 if lambda <= 0.0 {
374 return 0.0;
375 }
376 (-field.abs() / lambda).exp()
377}
378
379pub fn is_type_ii(kappa: f64) -> bool {
382 kappa > KAPPA_THRESHOLD
383}
384
385pub fn upper_critical_field(coherence_length: f64) -> f64 {
387 let mu0 = 4.0 * PI * 1e-7_f64;
388 FLUX_QUANTUM / (2.0 * PI * mu0 * coherence_length.powi(2).max(1e-300))
389}
390
391pub fn thermodynamic_critical_field(lambda: f64, coherence_length: f64) -> f64 {
393 let mu0 = 4.0 * PI * 1e-7_f64;
394 FLUX_QUANTUM / (2.0 * PI * 2.0_f64.sqrt() * mu0 * lambda * coherence_length.max(1e-300))
395}
396
397#[cfg(test)]
402mod tests {
403 use super::*;
404
405 const EPS: f64 = 1e-10;
406
407 #[test]
409 fn test_flux_quantum_value() {
410 assert!((FLUX_QUANTUM - 2.067833848e-15).abs() < 1e-22);
411 }
412
413 #[test]
415 fn test_is_type_ii_false() {
416 assert!(!is_type_ii(0.3)); }
418
419 #[test]
421 fn test_is_type_ii_true() {
422 assert!(is_type_ii(1.0)); }
424
425 #[test]
427 fn test_is_type_ii_boundary() {
428 let kappa = 1.0 / 2.0_f64.sqrt();
429 assert!(!is_type_ii(kappa)); assert!(is_type_ii(kappa + 1e-9));
431 }
432
433 #[test]
435 fn test_props_kappa_computation() {
436 let props = SuperconductorProps::new(9.25, 1.58e5, 2.44e5, 40e-9, 20e-9);
437 assert!((props.kappa - 2.0).abs() < 1e-9);
438 }
439
440 #[test]
442 fn test_niobium_type_ii() {
443 let nb = SuperconductorProps::niobium();
444 assert_eq!(nb.superconductor_type(), SuperconductorType::TypeII);
445 }
446
447 #[test]
449 fn test_lead_type_i() {
450 let pb = SuperconductorProps::lead();
451 assert_eq!(pb.superconductor_type(), SuperconductorType::TypeI);
452 }
453
454 #[test]
456 fn test_ybco_high_tc() {
457 let y = SuperconductorProps::ybco();
458 assert_eq!(y.superconductor_type(), SuperconductorType::HighTc);
459 }
460
461 #[test]
463 fn test_gl_model_initial_order_parameter() {
464 let gl = GinzburgLandauModel::new(10, 10e-9, 40e-9);
465 assert!(gl.order_parameter.iter().all(|&v| (v - 1.0).abs() < EPS));
466 }
467
468 #[test]
470 fn test_gl_solve_boundary_conditions() {
471 let mut gl = GinzburgLandauModel::new(20, 0.1, 0.4);
472 let psi = gl.solve_gl(200);
473 assert!(psi[0].abs() < EPS, "Surface must be 0, got {}", psi[0]);
474 assert!(
475 (psi[19] - 1.0).abs() < EPS,
476 "Bulk must be 1, got {}",
477 psi[19]
478 );
479 }
480
481 #[test]
483 fn test_gl_solve_range() {
484 let mut gl = GinzburgLandauModel::new(30, 0.1, 0.4);
485 let psi = gl.solve_gl(100);
486 for &v in &psi {
487 assert!(
488 (0.0..=1.0).contains(&v),
489 "Order parameter out of range: {v}"
490 );
491 }
492 }
493
494 #[test]
496 fn test_gl_solve_monotone() {
497 let mut gl = GinzburgLandauModel::new(30, 0.1, 0.4);
498 let psi = gl.solve_gl(500);
499 for w in psi.windows(2) {
500 assert!(w[1] >= w[0] - 1e-8, "Not monotone: {} > {}", w[0], w[1]);
501 }
502 }
503
504 #[test]
506 fn test_bcs_gap_from_tc() {
507 let tc = 9.25;
508 let bcs = BcsGap::from_tc(tc, 1.0);
509 let expected = 1.764 * K_B * tc;
510 assert!((bcs.gap_energy - expected).abs() < 1e-28);
511 }
512
513 #[test]
515 fn test_bcs_gap_at_zero_temp() {
516 let bcs = BcsGap::from_tc(9.25, 1.0);
517 assert!((bcs.compute_gap(0.0) - bcs.gap_energy).abs() < EPS);
518 }
519
520 #[test]
522 fn test_bcs_gap_at_tc() {
523 let bcs = BcsGap::from_tc(9.25, 1.0);
524 assert!(bcs.compute_gap(9.25).abs() < EPS);
525 }
526
527 #[test]
529 fn test_bcs_gap_above_tc() {
530 let bcs = BcsGap::from_tc(9.25, 1.0);
531 assert!(bcs.compute_gap(100.0).abs() < EPS);
532 }
533
534 #[test]
536 fn test_bcs_gap_monotone() {
537 let bcs = BcsGap::from_tc(9.25, 1.0);
538 let temps: Vec<f64> = (0..=9).map(|i| i as f64).collect();
539 let gaps: Vec<f64> = temps.iter().map(|&t| bcs.compute_gap(t)).collect();
540 for w in gaps.windows(2) {
541 assert!(
542 w[1] <= w[0] + 1e-15,
543 "Gap not monotone: {} -> {}",
544 w[0],
545 w[1]
546 );
547 }
548 }
549
550 #[test]
552 fn test_bcs_normalised_gap_at_zero() {
553 let bcs = BcsGap::from_tc(9.25, 1.0);
554 assert!((bcs.normalised_gap(0.0) - 1.0).abs() < EPS);
555 }
556
557 #[test]
559 fn test_bcs_normalised_gap_at_tc() {
560 let bcs = BcsGap::from_tc(9.25, 1.0);
561 assert!(bcs.normalised_gap(9.25).abs() < EPS);
562 }
563
564 #[test]
566 fn test_flux_vortex_carries_one_quantum() {
567 let v = FluxVortex::new([0.0, 0.0]);
568 assert!((v.flux_quantum - FLUX_QUANTUM).abs() < 1e-25);
569 }
570
571 #[test]
573 fn test_flux_vortex_distance() {
574 let v1 = FluxVortex::new([0.0, 0.0]);
575 let v2 = FluxVortex::new([3.0, 4.0]);
576 assert!((v1.distance_to(&v2) - 5.0).abs() < EPS);
577 }
578
579 #[test]
581 fn test_vortex_lattice_empty() {
582 let lat = VortexLattice::new(40e-9);
583 assert!(lat.vortices.is_empty());
584 }
585
586 #[test]
588 fn test_vortex_lattice_add() {
589 let mut lat = VortexLattice::new(40e-9);
590 lat.add_vortex(FluxVortex::new([0.0, 0.0]));
591 lat.add_vortex(FluxVortex::new([1e-6, 0.0]));
592 assert_eq!(lat.vortices.len(), 2);
593 }
594
595 #[test]
597 fn test_triangular_lattice_count() {
598 let lat = VortexLattice::triangular_lattice(4, 3, 100e-9, 40e-9);
599 assert_eq!(lat.vortices.len(), 12);
600 }
601
602 #[test]
604 fn test_inter_vortex_force_length() {
605 let lat = VortexLattice::triangular_lattice(3, 3, 200e-9, 40e-9);
606 let forces = lat.compute_inter_vortex_force();
607 assert_eq!(forces.len(), 9);
608 }
609
610 #[test]
615 fn test_inter_vortex_force_newton_third() {
616 let mut lat = VortexLattice::new(40e-9);
617 lat.add_vortex(FluxVortex::new([0.0, 0.0]));
618 lat.add_vortex(FluxVortex::new([200e-9, 0.0]));
619 let f = lat.compute_inter_vortex_force();
620 assert!(
622 f[0][0] < 0.0,
623 "Vortex 0 should be pushed -x, got {}",
624 f[0][0]
625 );
626 assert!(
628 f[1][0] > 0.0,
629 "Vortex 1 should be pushed +x, got {}",
630 f[1][0]
631 );
632 assert!(
634 (f[0][0] + f[1][0]).abs() < 1e-3 * f[0][0].abs(),
635 "Newton's 3rd law violated: {} vs {}",
636 f[0][0],
637 f[1][0]
638 );
639 }
640
641 #[test]
643 fn test_london_factor_zero_field() {
644 let f = london_equation_factor(40e-9, 0.0);
645 assert!((f - 1.0).abs() < EPS);
646 }
647
648 #[test]
650 fn test_london_factor_decay() {
651 let lambda = 40e-9;
652 let f1 = london_equation_factor(lambda, lambda);
653 let f2 = london_equation_factor(lambda, 2.0 * lambda);
654 assert!(f2 < f1, "Factor should decay: f1={f1}, f2={f2}");
655 assert!((f1 - (-1.0_f64).exp()).abs() < 1e-12);
657 }
658
659 #[test]
661 fn test_london_factor_zero_lambda() {
662 assert!((london_equation_factor(0.0, 1.0)).abs() < EPS);
663 assert!((london_equation_factor(-1.0, 1.0)).abs() < EPS);
664 }
665
666 #[test]
668 fn test_upper_critical_field_scaling() {
669 let hc2_a = upper_critical_field(10e-9);
670 let hc2_b = upper_critical_field(20e-9); assert!(
673 (hc2_a / hc2_b - 4.0).abs() < 1e-8,
674 "H_c2(10nm)/H_c2(20nm) should be 4, got {}",
675 hc2_a / hc2_b
676 );
677 }
678
679 #[test]
681 fn test_thermodynamic_critical_field_positive() {
682 let hc = thermodynamic_critical_field(40e-9, 20e-9);
683 assert!(hc > 0.0);
684 }
685
686 #[test]
688 fn test_superconductor_type_eq() {
689 assert_eq!(SuperconductorType::TypeI, SuperconductorType::TypeI);
690 assert_ne!(SuperconductorType::TypeI, SuperconductorType::TypeII);
691 }
692
693 #[test]
695 fn test_flux_density_scaling() {
696 let mut lat1 = VortexLattice::new(40e-9);
697 let mut lat2 = VortexLattice::new(40e-9);
698 lat1.add_vortex(FluxVortex::new([0.0, 0.0]));
699 lat2.add_vortex(FluxVortex::new([0.0, 0.0]));
700 lat2.add_vortex(FluxVortex::new([1e-6, 0.0]));
701 let area = 1e-12;
702 assert!((lat2.flux_density(area) - 2.0 * lat1.flux_density(area)).abs() < 1e-25);
703 }
704
705 #[test]
707 fn test_bcs_gap_mid_temperature() {
708 let tc = 9.25;
709 let bcs = BcsGap::from_tc(tc, 1.0);
710 let gap_mid = bcs.compute_gap(tc / 2.0);
711 assert!(
712 gap_mid > 0.0 && gap_mid < bcs.gap_energy,
713 "Mid-T gap should be in (0, Δ₀): {gap_mid}"
714 );
715 }
716}