use core::f64::consts::PI;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tokamak {
pub minor_radius: f64,
pub major_radius: f64,
pub field: f64,
pub current: f64,
pub elongation: f64,
pub triangularity: f64,
}
pub const ITER: Tokamak = Tokamak { minor_radius: 2.0, major_radius: 6.2, field: 5.3, current: 15.0, elongation: 1.7, triangularity: 0.33 };
pub const SPARC: Tokamak = Tokamak { minor_radius: 0.57, major_radius: 1.85, field: 12.2, current: 8.7, elongation: 1.75, triangularity: 0.45 };
impl Tokamak {
pub fn inverse_aspect_ratio(&self) -> f64 {
self.minor_radius / self.major_radius
}
pub fn q_cylindrical(&self) -> f64 {
5.0 * self.minor_radius.powi(2) * self.field / (self.major_radius * self.current)
}
pub fn q_star(&self) -> f64 {
let (k, d) = (self.elongation, self.triangularity);
self.q_cylindrical() * (1.0 + k * k * (1.0 + 2.0 * d * d - 1.2 * d.powi(3))) / 2.0
}
pub fn q95(&self) -> f64 {
let e = self.inverse_aspect_ratio();
self.q_star() * (1.17 - 0.65 * e) / (1.0 - e * e).powi(2)
}
pub fn greenwald(&self) -> f64 {
self.current / (PI * self.minor_radius.powi(2)) * 1e20
}
pub fn troyon_beta(&self, beta_n: f64) -> f64 {
beta_n * self.current / (self.minor_radius * self.field) / 100.0
}
pub fn ipb98(&self, n19: f64, power: f64, mass: f64, kappa_a: f64, h: f64) -> f64 {
0.0562 * h * self.current.powf(0.93) * self.field.powf(0.15) * n19.powf(0.41) * power.powf(-0.69) * self.major_radius.powf(1.97)
* kappa_a.powf(0.78) * self.inverse_aspect_ratio().powf(0.58) * mass.powf(0.19)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn safety_factors() {
assert!(close(ITER.q95(), 2.996_339, 1e-6));
assert!(close(SPARC.q_star(), 3.058_679, 1e-6) && close(SPARC.q95(), 3.620_936, 1e-6));
assert!(ITER.q_cylindrical() > 1.0 && SPARC.q_cylindrical() > 1.0);
}
#[test]
fn density_and_beta_limits() {
assert!(close(ITER.greenwald(), 1.193_662e20, 1e-6) && close(0.85 * ITER.greenwald(), 1.01e20, 5e-3));
assert!(close(SPARC.greenwald(), 8.523_533e20, 1e-6) && close(0.37 * SPARC.greenwald(), 3.15e20, 2e-3));
assert!(close(ITER.troyon_beta(1.8), 0.025_472, 1e-4) && close(SPARC.troyon_beta(1.0), 0.012_511, 1e-4));
}
#[test]
fn confinement() {
assert!(close(ITER.ipb98(10.1, 83.815, 2.5, 1.7, 1.0), 3.7, 1e-4));
let n19 = 0.37 * SPARC.greenwald() / 1e19;
assert!(close(SPARC.ipb98(n19, 29.036, 2.5, 1.75, 1.0), 0.77, 1e-4));
assert!(close(ITER.ipb98(10.1, 2.0 * 83.815, 2.5, 1.7, 1.0) / 3.7, 2f64.powf(-0.69), 1e-4));
}
}