use super::magnetoelastic::{
magnetoelastic_energy_density_cubic, stress_induced_anisotropy, MagnetoelasticMaterial,
PiezoelectricSubstrate, StrainTensor,
};
use crate::constants::MU_0;
use crate::error::{self, Result};
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct StraintronicDevice {
pub free_layer: MagnetoelasticMaterial,
pub substrate: PiezoelectricSubstrate,
pub free_layer_thickness: f64,
pub free_layer_width: f64,
pub ku: f64,
pub strain_transfer_efficiency: f64,
pub vcma_coefficient: f64,
}
impl StraintronicDevice {
pub fn new(
free_layer: MagnetoelasticMaterial,
substrate: PiezoelectricSubstrate,
free_layer_thickness: f64,
free_layer_width: f64,
ku: f64,
) -> Result<Self> {
if free_layer_thickness <= 0.0 {
return Err(error::invalid_param(
"free_layer_thickness",
"must be positive",
));
}
if free_layer_width <= 0.0 {
return Err(error::invalid_param("free_layer_width", "must be positive"));
}
Ok(Self {
free_layer,
substrate,
free_layer_thickness,
free_layer_width,
ku,
strain_transfer_efficiency: 0.9,
vcma_coefficient: 0.0,
})
}
pub fn with_strain_transfer_efficiency(mut self, eta: f64) -> Result<Self> {
if !(0.0..=1.0).contains(&eta) {
return Err(error::invalid_param(
"strain_transfer_efficiency",
"must be between 0 and 1",
));
}
self.strain_transfer_efficiency = eta;
Ok(self)
}
pub fn with_vcma_coefficient(mut self, beta: f64) -> Self {
self.vcma_coefficient = beta;
self
}
pub fn free_layer_volume(&self) -> f64 {
self.free_layer_width * self.free_layer_width * self.free_layer_thickness
}
pub fn effective_strain(&self, voltage: f64, substrate_thickness: f64) -> Result<StrainTensor> {
let substrate_strain = self
.substrate
.strain_from_voltage(voltage, substrate_thickness)?;
let eta = self.strain_transfer_efficiency;
let mut transferred = [[0.0; 3]; 3];
for (i, row) in transferred.iter_mut().enumerate() {
for (j, val) in row.iter_mut().enumerate() {
*val = eta * substrate_strain.components[i][j];
}
}
Ok(StrainTensor {
components: transferred,
})
}
}
pub fn anisotropy_energy_barrier(ku: f64, volume: f64) -> f64 {
ku * volume
}
pub fn strain_switching_energy(
device: &StraintronicDevice,
strain: &StrainTensor,
initial_dir: &Vector3<f64>,
final_dir: &Vector3<f64>,
) -> Result<f64> {
let e_initial = magnetoelastic_energy_density_cubic(&device.free_layer, strain, initial_dir)?;
let e_final = magnetoelastic_energy_density_cubic(&device.free_layer, strain, final_dir)?;
let volume = device.free_layer_volume();
Ok((e_final - e_initial).abs() * volume)
}
pub fn stt_switching_energy(critical_current: f64, resistance: f64, pulse_duration: f64) -> f64 {
critical_current * critical_current * resistance * pulse_duration
}
pub fn sot_switching_energy(
current_density: f64,
resistivity: f64,
channel_volume: f64,
pulse_duration: f64,
) -> f64 {
current_density * current_density * resistivity * channel_volume * pulse_duration
}
pub fn typical_switching_energy_comparison() -> (f64, f64, f64) {
let strain_energy = 1.0e-18;
let sot_jc = 1.0e11;
let sot_rho = 1.0e-6;
let sot_vol = 100.0e-9 * 300.0e-9 * 5.0e-9;
let sot_tau = 1.0e-9;
let sot_energy = sot_switching_energy(sot_jc, sot_rho, sot_vol, sot_tau);
let stt_ic = 50.0e-6;
let stt_r = 5.0e3;
let stt_tau = 10.0e-9;
let stt_energy = stt_switching_energy(stt_ic, stt_r, stt_tau);
(strain_energy, sot_energy, stt_energy)
}
pub fn critical_strain_uniaxial(ku: f64, lambda_s: f64, youngs_modulus: f64) -> Result<f64> {
let denominator = (1.5 * lambda_s * youngs_modulus).abs();
if denominator < 1e-30 {
return Err(error::invalid_param(
"lambda_s * youngs_modulus",
"product must be non-zero for critical strain calculation",
));
}
Ok(ku.abs() / denominator)
}
pub fn critical_strain_from_b1(ku: f64, b1: f64) -> Result<f64> {
if b1.abs() < 1e-30 {
return Err(error::invalid_param(
"b1",
"coupling constant must be non-zero",
));
}
Ok(ku.abs() / b1.abs())
}
pub fn critical_voltage(
critical_strain: f64,
substrate: &PiezoelectricSubstrate,
substrate_thickness: f64,
transfer_efficiency: f64,
) -> Result<f64> {
let effective_d31 = substrate.d31.abs() * transfer_efficiency;
if effective_d31 < 1e-30 {
return Err(error::invalid_param(
"d31 * transfer_efficiency",
"must be non-zero",
));
}
Ok(critical_strain.abs() * substrate_thickness / effective_d31)
}
#[derive(Debug, Clone, Copy)]
pub struct VcmaParameters {
pub beta: f64,
pub ki_intrinsic: f64,
pub dielectric_thickness: f64,
}
impl VcmaParameters {
pub fn new(beta: f64, ki_intrinsic: f64, dielectric_thickness: f64) -> Result<Self> {
if dielectric_thickness <= 0.0 {
return Err(error::invalid_param(
"dielectric_thickness",
"must be positive",
));
}
Ok(Self {
beta,
ki_intrinsic,
dielectric_thickness,
})
}
pub fn cofeb_mgo_typical() -> Self {
Self {
beta: 100.0e-15, ki_intrinsic: 1.3e-3, dielectric_thickness: 1.5e-9, }
}
pub fn anisotropy_change(&self, voltage: f64) -> f64 {
let e_field = voltage / self.dielectric_thickness;
self.beta * e_field
}
pub fn effective_anisotropy(&self, voltage: f64) -> f64 {
self.ki_intrinsic + self.anisotropy_change(voltage)
}
pub fn effective_ku(&self, voltage: f64, magnetic_thickness: f64, ms: f64) -> Result<f64> {
if magnetic_thickness <= 0.0 {
return Err(error::invalid_param(
"magnetic_thickness",
"must be positive",
));
}
let ki_eff = self.effective_anisotropy(voltage);
let demag = 0.5 * MU_0 * ms * ms;
Ok(ki_eff / magnetic_thickness - demag)
}
pub fn critical_voltage_pma_transition(&self, magnetic_thickness: f64, ms: f64) -> Result<f64> {
if magnetic_thickness <= 0.0 {
return Err(error::invalid_param(
"magnetic_thickness",
"must be positive",
));
}
if self.beta.abs() < 1e-30 {
return Err(error::invalid_param(
"beta",
"VCMA coefficient must be non-zero",
));
}
let demag_energy = 0.5 * MU_0 * ms * ms * magnetic_thickness;
let numerator = demag_energy - self.ki_intrinsic;
Ok(numerator * self.dielectric_thickness / self.beta)
}
}
pub fn combined_effective_anisotropy(
ku_intrinsic: f64,
lambda_s: f64,
stress: f64,
vcma_change: f64,
magnetic_thickness: f64,
) -> Result<f64> {
if magnetic_thickness <= 0.0 {
return Err(error::invalid_param(
"magnetic_thickness",
"must be positive",
));
}
let k_stress = stress_induced_anisotropy(lambda_s, stress);
let k_vcma = vcma_change / magnetic_thickness;
Ok(ku_intrinsic + k_stress + k_vcma)
}
pub fn can_switch_combined(
ku_intrinsic: f64,
lambda_s: f64,
stress: f64,
vcma_change: f64,
magnetic_thickness: f64,
) -> Result<bool> {
let k_eff = combined_effective_anisotropy(
ku_intrinsic,
lambda_s,
stress,
vcma_change,
magnetic_thickness,
)?;
Ok(k_eff * ku_intrinsic < 0.0)
}
pub fn estimate_switching_time(k_sigma: f64, ms: f64, gamma: f64) -> Result<f64> {
if ms.abs() < 1e-30 {
return Err(error::invalid_param("ms", "must be non-zero"));
}
if gamma <= 0.0 {
return Err(error::invalid_param("gamma", "must be positive"));
}
let h_eff = k_sigma.abs() / (MU_0 * ms);
if h_eff < 1e-30 {
return Err(error::numerical_error(
"effective field too small for switching time estimate",
));
}
Ok(1.0 / (gamma * h_eff))
}
pub fn energy_delay_product(switching_energy: f64, switching_time: f64) -> f64 {
switching_energy * switching_time
}
pub fn thermal_stability_factor(energy_barrier: f64, temperature: f64) -> Result<f64> {
if temperature <= 0.0 {
return Err(error::invalid_param("temperature", "must be positive"));
}
Ok(energy_barrier / (crate::constants::KB * temperature))
}
#[derive(Debug, Clone)]
pub struct MultiferroicSwitchingResult {
pub voltage: f64,
pub strain_in_plane: f64,
pub k_sigma: f64,
pub delta_k_vcma: f64,
pub k_eff: f64,
pub switching_possible: bool,
pub switching_energy: f64,
}
pub fn analyze_multiferroic_switching(
device: &StraintronicDevice,
vcma: Option<&VcmaParameters>,
voltage: f64,
substrate_thickness: f64,
) -> Result<MultiferroicSwitchingResult> {
let strain = device.effective_strain(voltage, substrate_thickness)?;
let strain_in_plane = strain.components[0][0];
let stress = strain_in_plane * device.free_layer.youngs_modulus;
let k_sigma = stress_induced_anisotropy(device.free_layer.lambda_s, stress);
let delta_k_vcma = match vcma {
Some(v) => v.anisotropy_change(voltage) / device.free_layer_thickness,
None => 0.0,
};
let k_eff = device.ku + k_sigma + delta_k_vcma;
let switching_possible = k_eff * device.ku < 0.0;
let area = device.free_layer_width * device.free_layer_width;
let epsilon_r = 1000.0; let capacitance = crate::constants::EPSILON_0 * epsilon_r * area / substrate_thickness;
let switching_energy = 0.5 * capacitance * voltage * voltage;
Ok(MultiferroicSwitchingResult {
voltage,
strain_in_plane,
k_sigma,
delta_k_vcma,
k_eff,
switching_possible,
switching_energy,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::GAMMA;
#[test]
fn test_switching_energy_comparison_strain_less_than_sot_less_than_stt() {
let (strain_e, sot_e, stt_e) = typical_switching_energy_comparison();
assert!(
strain_e < sot_e,
"Strain switching ({:.2e} J) should be less than SOT ({:.2e} J)",
strain_e,
sot_e
);
assert!(
sot_e < stt_e,
"SOT switching ({:.2e} J) should be less than STT ({:.2e} J)",
sot_e,
stt_e
);
assert!(
strain_e < 1.0e-15,
"Strain switching should be sub-fJ, got {:.2e} J",
strain_e
);
assert!(
stt_e > 1.0e-15,
"STT switching should be > fJ, got {:.2e} J",
stt_e
);
}
#[test]
fn test_critical_strain_is_positive() {
let ku = 5.0e5; let lambda_s = 25.0e-6; let youngs = 160.0e9;
let eps_crit =
critical_strain_uniaxial(ku, lambda_s, youngs).expect("should compute critical strain");
assert!(
eps_crit > 0.0,
"Critical strain must be positive, got {}",
eps_crit
);
assert!(
eps_crit > 1.0e-6 && eps_crit < 1.0,
"Critical strain should be in reasonable range, got {}",
eps_crit
);
}
#[test]
fn test_vcma_anisotropy_change_sign() {
let vcma = VcmaParameters::cofeb_mgo_typical();
let delta_k_pos = vcma.anisotropy_change(1.0);
let delta_k_neg = vcma.anisotropy_change(-1.0);
assert!(
delta_k_pos * delta_k_neg < 0.0,
"VCMA changes for +V and -V should have opposite signs: +V → {:.2e}, -V → {:.2e}",
delta_k_pos,
delta_k_neg
);
assert!(
(delta_k_pos.abs() - delta_k_neg.abs()).abs() / delta_k_pos.abs() < 1e-10,
"Magnitude of VCMA change should be symmetric"
);
}
#[test]
fn test_straintronic_device_creation() {
let cofeb = MagnetoelasticMaterial::cofeb();
let pmn_pt = PiezoelectricSubstrate::pmn_pt();
let device = StraintronicDevice::new(cofeb, pmn_pt, 2.0e-9, 100.0e-9, 5.0e5)
.expect("should create straintronic device");
let vol = device.free_layer_volume();
let expected_vol = 100.0e-9 * 100.0e-9 * 2.0e-9;
assert!(
(vol - expected_vol).abs() / expected_vol < 1e-10,
"Volume calculation incorrect"
);
}
#[test]
fn test_critical_voltage_finite() {
let eps_crit = 1.0e-3;
let pmn_pt = PiezoelectricSubstrate::pmn_pt();
let t_sub = 500.0e-6; let eta = 0.9;
let v_crit = critical_voltage(eps_crit, &pmn_pt, t_sub, eta)
.expect("should compute critical voltage");
assert!(
v_crit > 0.0,
"Critical voltage must be positive, got {}",
v_crit
);
assert!(
v_crit < 1000.0,
"Critical voltage should be reasonable, got {} V",
v_crit
);
}
#[test]
fn test_thermal_stability_factor() {
let ku = 5.0e5;
let volume = 100.0e-9 * 100.0e-9 * 2.0e-9;
let barrier = anisotropy_energy_barrier(ku, volume);
let temperature = 300.0;
let delta = thermal_stability_factor(barrier, temperature)
.expect("should compute stability factor");
assert!(
delta > 0.0,
"Thermal stability factor must be positive, got {}",
delta
);
}
#[test]
fn test_combined_effective_anisotropy() {
let ku = 5.0e5;
let lambda_s = 25.0e-6;
let stress = 200.0e6; let t_mag = 2.0e-9;
let k_eff = combined_effective_anisotropy(ku, lambda_s, stress, 0.0, t_mag)
.expect("should compute combined anisotropy");
assert!(
k_eff < ku,
"Tensile stress with positive λ_s should reduce K_eff: {} vs {}",
k_eff,
ku
);
}
#[test]
fn test_energy_delay_product() {
let e_switch = 1.0e-18; let tau = 1.0e-9;
let edp = energy_delay_product(e_switch, tau);
assert!(edp > 0.0, "EDP must be positive");
assert!(
(edp - 1.0e-27).abs() / 1.0e-27 < 1e-10,
"EDP should be 1e-27 J·s, got {:.2e}",
edp
);
}
#[test]
fn test_switching_time_estimate() {
let k_sigma = 1.0e5; let ms = 1.0e6;
let tau =
estimate_switching_time(k_sigma, ms, GAMMA).expect("should estimate switching time");
assert!(tau > 0.0, "Switching time must be positive");
assert!(
tau < 1.0e-6,
"Switching time should be < μs, got {:.2e} s",
tau
);
}
#[test]
fn test_analyze_multiferroic_switching() {
let cofeb = MagnetoelasticMaterial::cofeb();
let pmn_pt = PiezoelectricSubstrate::pmn_pt();
let device = StraintronicDevice::new(cofeb, pmn_pt, 2.0e-9, 100.0e-9, 5.0e5)
.expect("should create device");
let result = analyze_multiferroic_switching(&device, None, 10.0, 500.0e-6)
.expect("should analyze switching");
assert!(
result.strain_in_plane.abs() > 0.0,
"Strain should be non-zero for non-zero voltage"
);
assert!(
result.switching_energy > 0.0,
"Switching energy must be positive"
);
}
}