pub struct PiezoCoupling {
pub e33: f64,
pub e31: f64,
pub e15: f64,
pub e_mod: f64,
pub nu: f64,
}
impl PiezoCoupling {
pub fn new(e_mod: f64, nu: f64, e33: f64, e31: f64, e15: f64) -> Self {
Self {
e33,
e31,
e15,
e_mod,
nu,
}
}
pub fn d33(&self) -> f64 {
let c33 = self.e_mod * (1.0 - self.nu) / ((1.0 + self.nu) * (1.0 - 2.0 * self.nu));
if c33.abs() > 1e-30 {
self.e33 / c33
} else {
0.0
}
}
pub fn d31(&self) -> f64 {
let lam = self.e_mod * self.nu / ((1.0 + self.nu) * (1.0 - 2.0 * self.nu));
let c33 = lam + 2.0 * self.e_mod / (2.0 * (1.0 + self.nu));
if c33.abs() > 1e-30 {
self.e31 / c33
} else {
0.0
}
}
pub fn k33(&self, eps33: f64) -> f64 {
let c33 = self.e_mod * (1.0 - self.nu) / ((1.0 + self.nu) * (1.0 - 2.0 * self.nu));
let denom = (c33 * eps33).max(0.0).sqrt();
if denom < 1e-30 {
return 0.0;
}
self.e33 / denom
}
pub fn induced_strain_33(&self, e_field: f64) -> f64 {
self.d33() * e_field
}
pub fn open_circuit_voltage(&self, stress: f64, length: f64, eps33: f64) -> f64 {
-self.d33() * stress * length / eps33.max(1e-30)
}
}
pub struct ElectromechanicalNode {
pub x: f64,
pub y: f64,
pub z: f64,
pub u: f64,
pub v: f64,
pub w: f64,
pub phi: f64,
}
impl ElectromechanicalNode {
pub fn new(x: f64, y: f64, z: f64) -> Self {
ElectromechanicalNode {
x,
y,
z,
u: 0.0,
v: 0.0,
w: 0.0,
phi: 0.0,
}
}
}
pub struct ElectrostaticSolver {
pub nx: usize,
pub ny: usize,
pub phi: Vec<f64>,
pub charge_density: Vec<f64>,
pub dx: f64,
}
impl ElectrostaticSolver {
pub fn new(nx: usize, ny: usize, dx: f64) -> Self {
let n = nx * ny;
ElectrostaticSolver {
nx,
ny,
phi: vec![0.0; n],
charge_density: vec![0.0; n],
dx,
}
}
pub fn solve_laplace_gauss_seidel(&mut self, max_iter: usize, tol: f64) -> usize {
let nx = self.nx;
let ny = self.ny;
let dx2 = self.dx * self.dx;
const EPS0: f64 = 8.854_187_817e-12;
for iter in 0..max_iter {
let mut max_change = 0.0_f64;
for iy in 1..ny - 1 {
for ix in 1..nx - 1 {
let idx = iy * nx + ix;
let rhs = self.charge_density[idx] / EPS0 * dx2;
let new_phi = 0.25
* (self.phi[idx - 1]
+ self.phi[idx + 1]
+ self.phi[idx - nx]
+ self.phi[idx + nx]
+ rhs);
let change = (new_phi - self.phi[idx]).abs();
if change > max_change {
max_change = change;
}
self.phi[idx] = new_phi;
}
}
if max_change < tol {
return iter + 1;
}
}
max_iter
}
pub fn electric_field_magnitude(&self) -> Vec<f64> {
let nx = self.nx;
let ny = self.ny;
let inv2dx = 1.0 / (2.0 * self.dx);
let mut mag = vec![0.0_f64; nx * ny];
for iy in 1..ny - 1 {
for ix in 1..nx - 1 {
let idx = iy * nx + ix;
let ex = -(self.phi[idx + 1] - self.phi[idx - 1]) * inv2dx;
let ey = -(self.phi[idx + nx] - self.phi[idx - nx]) * inv2dx;
mag[idx] = (ex * ex + ey * ey).sqrt();
}
}
mag
}
}
pub struct PiezoSensor {
pub material: PiezoCoupling,
pub area: f64,
pub thickness: f64,
pub permittivity: f64,
pub load_resistance: f64,
}
impl PiezoSensor {
pub fn new(
e_mod: f64,
nu: f64,
e33: f64,
e31: f64,
e15: f64,
area: f64,
thickness: f64,
eps_r: f64,
r_load: f64,
) -> Self {
const EPS0: f64 = 8.854_187_817e-12;
Self {
material: PiezoCoupling::new(e_mod, nu, e33, e31, e15),
area,
thickness,
permittivity: eps_r * EPS0,
load_resistance: r_load,
}
}
pub fn short_circuit_charge(&self, stress_33: f64) -> f64 {
self.material.d33() * stress_33 * self.area
}
pub fn open_circuit_voltage(&self, stress_33: f64) -> f64 {
self.material.d33() * stress_33 * self.thickness / self.permittivity.max(1e-60)
}
pub fn capacitance(&self) -> f64 {
self.permittivity * self.area / self.thickness.max(1e-30)
}
pub fn power_output(&self, stress_33_amplitude: f64, _f_hz: f64) -> f64 {
let v_oc = self.open_circuit_voltage(stress_33_amplitude);
let v_rms = v_oc / 2.0_f64.sqrt();
v_rms * v_rms / self.load_resistance.max(1e-30)
}
}
pub struct PiezoD {
pub compliance: [[f64; 6]; 6],
pub d_matrix: [[f64; 6]; 3],
pub permittivity_t: [[f64; 3]; 3],
}
impl PiezoD {
pub fn from_pzt(e_mod: f64, nu: f64, eps_r: f64, d33: f64, d31: f64, d15: f64) -> Self {
let s11 = 1.0 / e_mod;
let s12 = -nu / e_mod;
let s44 = 2.0 * (1.0 + nu) / e_mod;
let mut s = [[0.0_f64; 6]; 6];
for (i, row) in s.iter_mut().enumerate().take(3) {
for (j, val) in row.iter_mut().enumerate().take(3) {
*val = if i == j { s11 } else { s12 };
}
}
s[3][3] = s44;
s[4][4] = s44;
s[5][5] = s44;
let mut d = [[0.0_f64; 6]; 3];
d[2][0] = d31;
d[2][1] = d31;
d[2][2] = d33;
d[1][3] = d15;
d[0][4] = d15;
const EPS0: f64 = 8.854_187_817e-12;
let eps_val = eps_r * EPS0;
let mut eps = [[0.0_f64; 3]; 3];
eps[0][0] = eps_val;
eps[1][1] = eps_val;
eps[2][2] = eps_val;
PiezoD {
compliance: s,
d_matrix: d,
permittivity_t: eps,
}
}
pub fn strain(&self, stress: &[f64; 6], e_field: &[f64; 3]) -> [f64; 6] {
let mut s = [0.0_f64; 6];
for (i, si) in s.iter_mut().enumerate() {
let s_comp: f64 = (0..6).map(|j| self.compliance[i][j] * stress[j]).sum();
let d_comp: f64 = (0..3).map(|k| self.d_matrix[k][i] * e_field[k]).sum();
*si = s_comp + d_comp;
}
s
}
pub fn electric_displacement(&self, stress: &[f64; 6], e_field: &[f64; 3]) -> [f64; 3] {
let mut d = [0.0_f64; 3];
for (i, di) in d.iter_mut().enumerate() {
let dt: f64 = (0..6).map(|j| self.d_matrix[i][j] * stress[j]).sum();
let de: f64 = (0..3).map(|j| self.permittivity_t[i][j] * e_field[j]).sum();
*di = dt + de;
}
d
}
}
pub struct ElectromechanicalElement {
pub nodes: [usize; 4],
pub material_id: usize,
}
pub struct ElectrostrictiveMaterial {
pub m1: f64,
pub m2: f64,
}
impl ElectrostrictiveMaterial {
pub fn new(m1: f64, m2: f64) -> Self {
Self { m1, m2 }
}
pub fn strain_tensor(&self, e_field: &[f64; 3]) -> [[f64; 3]; 3] {
let e_sq: f64 = e_field.iter().map(|e| e * e).sum();
let mut eps = [[0.0_f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
eps[i][j] = self.m1 * e_field[i] * e_field[j] + self.m2 * delta * e_sq;
}
}
eps
}
pub fn energy_density(&self, e_field: &[f64; 3]) -> f64 {
let e_sq: f64 = e_field.iter().map(|e| e * e).sum();
0.5 * (self.m1 + 3.0 * self.m2) * e_sq * e_sq
}
}
pub struct DielectricElastomer {
pub mu: f64,
pub kappa: f64,
pub permittivity: f64,
pub thickness: f64,
}
impl DielectricElastomer {
pub fn new(mu: f64, kappa: f64, eps_r: f64, thickness: f64) -> Self {
const EPS0: f64 = 8.854_187_817e-12;
Self {
mu,
kappa,
permittivity: eps_r * EPS0,
thickness,
}
}
pub fn maxwell_stress_tensor(&self, e_field: &[f64; 3]) -> [[f64; 3]; 3] {
let e_sq: f64 = e_field.iter().map(|e| e * e).sum();
let mut t = [[0.0_f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
t[i][j] = self.permittivity * (e_field[i] * e_field[j] - 0.5 * delta * e_sq);
}
}
t
}
pub fn maxwell_pressure(&self, voltage: f64) -> f64 {
let e_z = voltage / self.thickness.max(1e-30);
self.permittivity * e_z * e_z
}
pub fn actuation_strain(&self, voltage: f64) -> f64 {
let p = self.maxwell_pressure(voltage);
-p / (4.0 * self.mu.max(1e-30))
}
pub fn electrostatic_energy_density(&self, voltage: f64) -> f64 {
let e = voltage / self.thickness.max(1e-30);
0.5 * self.permittivity * e * e
}
}
pub struct PiezoMaterial {
pub elastic_stiffness: [[f64; 6]; 6],
pub piezo_coupling: [[f64; 3]; 6],
pub permittivity: [[f64; 3]; 3],
pub density: f64,
}
impl PiezoMaterial {
pub fn isotropic_with_piezo(
e_mod: f64,
nu: f64,
eps_r: f64,
e33: f64,
e31: f64,
e15: f64,
) -> Self {
let lam = e_mod * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
let mu = e_mod / (2.0 * (1.0 + nu));
let mut c = [[0.0_f64; 6]; 6];
for (i, row) in c.iter_mut().enumerate().take(3) {
for val in row.iter_mut().take(3) {
*val = lam;
}
row[i] = lam + 2.0 * mu;
}
c[3][3] = mu;
c[4][4] = mu;
c[5][5] = mu;
let mut e = [[0.0_f64; 3]; 6];
e[0][2] = e31;
e[1][2] = e31;
e[2][2] = e33;
e[3][1] = e15;
e[4][0] = e15;
const EPS0: f64 = 8.854_187_817e-12;
let eps_val = eps_r * EPS0;
let mut eps = [[0.0_f64; 3]; 3];
eps[0][0] = eps_val;
eps[1][1] = eps_val;
eps[2][2] = eps_val;
PiezoMaterial {
elastic_stiffness: c,
piezo_coupling: e,
permittivity: eps,
density: 7750.0,
}
}
}
pub struct PiezoActuator {
pub length: f64,
pub area: f64,
pub material: PiezoMaterial,
pub voltage: f64,
}
impl PiezoActuator {
pub fn blocked_force(&self) -> f64 {
let e33 = self.material.piezo_coupling[2][2];
let field = self.voltage / self.length;
e33 * field * self.area
}
pub fn free_strain(&self) -> f64 {
let e33 = self.material.piezo_coupling[2][2];
let c33 = self.material.elastic_stiffness[2][2];
let d33 = if c33.abs() > 1e-30 { e33 / c33 } else { 0.0 };
d33 * self.voltage / self.length
}
pub fn tip_displacement(&self) -> f64 {
self.free_strain() * self.length
}
}
pub struct PiezoFemAssembler {
pub nodes: Vec<ElectromechanicalNode>,
pub elements: Vec<ElectromechanicalElement>,
pub materials: Vec<PiezoMaterial>,
}
impl PiezoFemAssembler {
pub fn new() -> Self {
PiezoFemAssembler {
nodes: Vec::new(),
elements: Vec::new(),
materials: Vec::new(),
}
}
pub fn compute_b_matrix(nodes: &[[f64; 3]; 4]) -> [[f64; 12]; 6] {
let [p0, p1, p2, p3] = nodes;
let j = [
[p1[0] - p0[0], p2[0] - p0[0], p3[0] - p0[0]],
[p1[1] - p0[1], p2[1] - p0[1], p3[1] - p0[1]],
[p1[2] - p0[2], p2[2] - p0[2], p3[2] - p0[2]],
];
let det = j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1])
- j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0]);
let inv_det = 1.0 / det;
let ji = [
[
(j[1][1] * j[2][2] - j[1][2] * j[2][1]) * inv_det,
(j[0][2] * j[2][1] - j[0][1] * j[2][2]) * inv_det,
(j[0][1] * j[1][2] - j[0][2] * j[1][1]) * inv_det,
],
[
(j[1][2] * j[2][0] - j[1][0] * j[2][2]) * inv_det,
(j[0][0] * j[2][2] - j[0][2] * j[2][0]) * inv_det,
(j[0][2] * j[1][0] - j[0][0] * j[1][2]) * inv_det,
],
[
(j[1][0] * j[2][1] - j[1][1] * j[2][0]) * inv_det,
(j[0][1] * j[2][0] - j[0][0] * j[2][1]) * inv_det,
(j[0][0] * j[1][1] - j[0][1] * j[1][0]) * inv_det,
],
];
let grad_ref: [[f64; 3]; 4] = [
[-1.0, -1.0, -1.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let mut dn = [[0.0_f64; 3]; 4];
for i in 0..4 {
for row in 0..3 {
let mut s = 0.0;
for col in 0..3 {
s += ji[col][row] * grad_ref[i][col];
}
dn[i][row] = s;
}
}
let mut b = [[0.0_f64; 12]; 6];
for (i, &[dndx, dndy, dndz]) in dn.iter().enumerate() {
let col_u = 3 * i;
let col_v = 3 * i + 1;
let col_w = 3 * i + 2;
b[0][col_u] = dndx;
b[1][col_v] = dndy;
b[2][col_w] = dndz;
b[3][col_v] = dndz;
b[3][col_w] = dndy;
b[4][col_u] = dndz;
b[4][col_w] = dndx;
b[5][col_u] = dndy;
b[5][col_v] = dndx;
}
b
}
pub fn compute_element_volume(nodes: &[[f64; 3]; 4]) -> f64 {
let [p0, p1, p2, p3] = nodes;
let j = [
[p1[0] - p0[0], p2[0] - p0[0], p3[0] - p0[0]],
[p1[1] - p0[1], p2[1] - p0[1], p3[1] - p0[1]],
[p1[2] - p0[2], p2[2] - p0[2], p3[2] - p0[2]],
];
let det = j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1])
- j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0]);
det.abs() / 6.0
}
pub fn compute_element_stiffness(&self, elem_idx: usize) -> Vec<Vec<f64>> {
let elem = &self.elements[elem_idx];
let mat = &self.materials[elem.material_id];
let mut coords = [[0.0_f64; 3]; 4];
for (i, &ni) in elem.nodes.iter().enumerate() {
let n = &self.nodes[ni];
coords[i] = [n.x, n.y, n.z];
}
let vol = Self::compute_element_volume(&coords);
let b = Self::compute_b_matrix(&coords);
let [p0, p1, p2, p3] = &coords;
let jac = [
[p1[0] - p0[0], p2[0] - p0[0], p3[0] - p0[0]],
[p1[1] - p0[1], p2[1] - p0[1], p3[1] - p0[1]],
[p1[2] - p0[2], p2[2] - p0[2], p3[2] - p0[2]],
];
let det = jac[0][0] * (jac[1][1] * jac[2][2] - jac[1][2] * jac[2][1])
- jac[0][1] * (jac[1][0] * jac[2][2] - jac[1][2] * jac[2][0])
+ jac[0][2] * (jac[1][0] * jac[2][1] - jac[1][1] * jac[2][0]);
let inv_det = 1.0 / det;
let ji = [
[
(jac[1][1] * jac[2][2] - jac[1][2] * jac[2][1]) * inv_det,
(jac[0][2] * jac[2][1] - jac[0][1] * jac[2][2]) * inv_det,
(jac[0][1] * jac[1][2] - jac[0][2] * jac[1][1]) * inv_det,
],
[
(jac[1][2] * jac[2][0] - jac[1][0] * jac[2][2]) * inv_det,
(jac[0][0] * jac[2][2] - jac[0][2] * jac[2][0]) * inv_det,
(jac[0][2] * jac[1][0] - jac[0][0] * jac[1][2]) * inv_det,
],
[
(jac[1][0] * jac[2][1] - jac[1][1] * jac[2][0]) * inv_det,
(jac[0][1] * jac[2][0] - jac[0][0] * jac[2][1]) * inv_det,
(jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0]) * inv_det,
],
];
let grad_ref: [[f64; 3]; 4] = [
[-1.0, -1.0, -1.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let mut bp = [[0.0_f64; 4]; 3];
for i in 0..4 {
for row in 0..3 {
let mut s = 0.0;
for col in 0..3 {
s += ji[col][row] * grad_ref[i][col];
}
bp[row][i] = s;
}
}
let mut cb = [[0.0_f64; 12]; 6];
for (r, _) in (0..6usize).enumerate() {
for c in 0..12 {
let mut s = 0.0;
for (k, _) in (0..6usize).enumerate() {
s += mat.elastic_stiffness[r][k] * b[k][c];
}
cb[r][c] = s;
}
}
let mut kuu = vec![vec![0.0_f64; 12]; 12];
for r in 0..12 {
for c in 0..12 {
let mut s = 0.0;
for k in 0..6 {
s += b[k][r] * cb[k][c];
}
kuu[r][c] = s * vol;
}
}
let mut ebp = [[0.0_f64; 4]; 6];
for (r, _) in (0..6usize).enumerate() {
for c in 0..4 {
let mut s = 0.0;
for (k, _) in (0..3usize).enumerate() {
s += mat.piezo_coupling[r][k] * bp[k][c];
}
ebp[r][c] = s;
}
}
let mut kup = vec![vec![0.0_f64; 4]; 12];
for r in 0..12 {
for c in 0..4 {
let mut s = 0.0;
for k in 0..6 {
s += b[k][r] * ebp[k][c];
}
kup[r][c] = s * vol;
}
}
let mut eps_bp = [[0.0_f64; 4]; 3];
for (r, _) in (0..3usize).enumerate() {
for c in 0..4 {
let mut s = 0.0;
for (k, _) in (0..3usize).enumerate() {
s += mat.permittivity[r][k] * bp[k][c];
}
eps_bp[r][c] = s;
}
}
let mut kpp = vec![vec![0.0_f64; 4]; 4];
for r in 0..4 {
for c in 0..4 {
let mut s = 0.0;
for k in 0..3 {
s += bp[k][r] * eps_bp[k][c];
}
kpp[r][c] = s * vol;
}
}
let mut ke = vec![vec![0.0_f64; 16]; 16];
for r in 0..12 {
for c in 0..12 {
ke[r][c] = kuu[r][c];
}
}
let mech_to_global = |i: usize| -> usize { (i / 3) * 4 + (i % 3) };
let phi_to_global = |j: usize| -> usize { j * 4 + 3 };
let mut ke16 = vec![vec![0.0_f64; 16]; 16];
for r in 0..12 {
for c in 0..12 {
ke16[mech_to_global(r)][mech_to_global(c)] = kuu[r][c];
}
}
for r in 0..12 {
for c in 0..4 {
ke16[mech_to_global(r)][phi_to_global(c)] = kup[r][c];
ke16[phi_to_global(c)][mech_to_global(r)] = kup[r][c];
}
}
for r in 0..4 {
for c in 0..4 {
ke16[phi_to_global(r)][phi_to_global(c)] = -kpp[r][c];
}
}
let _ = ke;
ke16
}
}
impl Default for PiezoFemAssembler {
fn default() -> Self {
Self::new()
}
}
pub struct ElectrostaticElement {
pub coords: [[f64; 3]; 4],
pub permittivity: f64,
}
impl ElectrostaticElement {
pub fn element_stiffness(&self) -> [[f64; 4]; 4] {
let vol = PiezoFemAssembler::compute_element_volume(&self.coords);
let bp = self.b_phi_matrix();
let mut k = [[0.0f64; 4]; 4];
for r in 0..4 {
for c in 0..4 {
let mut s = 0.0;
for (k_idx, bprow) in bp.iter().enumerate().take(3) {
s += bprow[r] * bprow[c];
let _ = k_idx;
}
k[r][c] = self.permittivity * s * vol;
}
}
k
}
pub fn electric_field(&self, nodal_phi: &[f64; 4]) -> [f64; 3] {
let bp = self.b_phi_matrix();
let mut grad = [0.0f64; 3];
for row in 0..3 {
for col in 0..4 {
grad[row] += bp[row][col] * nodal_phi[col];
}
}
[-grad[0], -grad[1], -grad[2]]
}
fn b_phi_matrix(&self) -> [[f64; 4]; 3] {
let [p0, p1, p2, p3] = &self.coords;
let j = [
[p1[0] - p0[0], p2[0] - p0[0], p3[0] - p0[0]],
[p1[1] - p0[1], p2[1] - p0[1], p3[1] - p0[1]],
[p1[2] - p0[2], p2[2] - p0[2], p3[2] - p0[2]],
];
let det = j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1])
- j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0]);
let inv_det = 1.0 / det;
let ji = [
[
(j[1][1] * j[2][2] - j[1][2] * j[2][1]) * inv_det,
(j[0][2] * j[2][1] - j[0][1] * j[2][2]) * inv_det,
(j[0][1] * j[1][2] - j[0][2] * j[1][1]) * inv_det,
],
[
(j[1][2] * j[2][0] - j[1][0] * j[2][2]) * inv_det,
(j[0][0] * j[2][2] - j[0][2] * j[2][0]) * inv_det,
(j[0][2] * j[1][0] - j[0][0] * j[1][2]) * inv_det,
],
[
(j[1][0] * j[2][1] - j[1][1] * j[2][0]) * inv_det,
(j[0][1] * j[2][0] - j[0][0] * j[2][1]) * inv_det,
(j[0][0] * j[1][1] - j[0][1] * j[1][0]) * inv_det,
],
];
let grad_ref: [[f64; 3]; 4] = [
[-1.0, -1.0, -1.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let mut bp = [[0.0f64; 4]; 3];
for i in 0..4 {
for row in 0..3 {
let mut s = 0.0;
for col in 0..3 {
s += ji[col][row] * grad_ref[i][col];
}
bp[row][i] = s;
}
}
bp
}
}
pub struct ElectricField {
pub ex: Vec<f64>,
pub ey: Vec<f64>,
pub ez: Vec<f64>,
}
impl ElectricField {
pub fn zeros(n: usize) -> Self {
ElectricField {
ex: vec![0.0; n],
ey: vec![0.0; n],
ez: vec![0.0; n],
}
}
}
#[derive(Debug, Clone)]
pub struct DielectricBreakdown {
pub e_breakdown: f64,
pub eps_r: f64,
}
impl DielectricBreakdown {
pub fn new(e_breakdown: f64, eps_r: f64) -> Self {
Self { e_breakdown, eps_r }
}
pub fn is_broken_down(&self, e_magnitude: f64) -> bool {
e_magnitude >= self.e_breakdown
}
pub fn safety_margin(&self, e_magnitude: f64) -> f64 {
if e_magnitude < 1e-30 {
return f64::INFINITY;
}
self.e_breakdown / e_magnitude
}
pub fn energy_density_at_breakdown(&self) -> f64 {
const EPS0: f64 = 8.854_187_817e-12;
0.5 * EPS0 * self.eps_r * self.e_breakdown * self.e_breakdown
}
}
#[derive(Debug, Clone)]
pub struct MemsActuator {
pub area: f64,
pub rest_gap: f64,
pub spring_stiffness: f64,
pub permittivity: f64,
}
impl MemsActuator {
pub fn new(area: f64, rest_gap: f64, spring_stiffness: f64, eps_r: f64) -> Self {
const EPS0: f64 = 8.854_187_817e-12;
Self {
area,
rest_gap,
spring_stiffness,
permittivity: eps_r * EPS0,
}
}
pub fn electrostatic_force(&self, voltage: f64, gap: f64) -> f64 {
let g = gap.max(1e-20);
self.permittivity * self.area * voltage * voltage / (2.0 * g * g)
}
pub fn equilibrium_gap(&self, voltage: f64) -> Option<f64> {
let g0 = self.rest_gap;
let h = |g: f64| self.spring_stiffness * (g0 - g) - self.electrostatic_force(voltage, g);
let g_lo = g0 * 0.334;
let g_hi = g0 * (1.0 - 1e-9);
let h_lo = h(g_lo);
let h_hi = h(g_hi);
if h_lo * h_hi > 0.0 {
return None;
}
let (mut lo, mut hi) = if h_lo > 0.0 {
(g_lo, g_hi)
} else {
(g_hi, g_lo)
};
for _ in 0..80 {
let mid = 0.5 * (lo + hi);
if h(mid) > 0.0 {
lo = mid;
} else {
hi = mid;
}
}
let g_eq = 0.5 * (lo + hi);
if g_eq > 0.0 && g_eq < g0 {
Some(g_eq)
} else {
None
}
}
pub fn pull_in_voltage(&self) -> f64 {
let g0 = self.rest_gap;
let num = 8.0 * self.spring_stiffness * g0.powi(3);
let den = 27.0 * self.permittivity * self.area;
if den.abs() < 1e-60 {
return f64::INFINITY;
}
(num / den).sqrt()
}
pub fn capacitance(&self, gap: f64) -> f64 {
self.permittivity * self.area / gap.max(1e-20)
}
}
pub struct GlobalPiezoAssembler {
pub local: PiezoFemAssembler,
pub k_global: Vec<Vec<f64>>,
pub n_dof: usize,
}
impl GlobalPiezoAssembler {
pub fn new(local: PiezoFemAssembler) -> Self {
let n_nodes = local.nodes.len();
let n_dof = n_nodes * 4;
let k_global = vec![vec![0.0; n_dof]; n_dof];
Self {
local,
k_global,
n_dof,
}
}
pub fn assemble(&mut self) {
let n_elems = self.local.elements.len();
for ei in 0..n_elems {
let ke = self.local.compute_element_stiffness(ei);
let nodes = self.local.elements[ei].nodes;
for (a, &na) in nodes.iter().enumerate() {
for da in 0..4 {
let global_a = na * 4 + da;
let local_a = a * 4 + da;
for (b, &nb) in nodes.iter().enumerate() {
for db in 0..4 {
let global_b = nb * 4 + db;
let local_b = b * 4 + db;
if global_a < self.n_dof && global_b < self.n_dof {
self.k_global[global_a][global_b] += ke[local_a][local_b];
}
}
}
}
}
}
}
pub fn apply_dirichlet(&mut self, idx: usize, _val: f64) {
if idx >= self.n_dof {
return;
}
for j in 0..self.n_dof {
self.k_global[idx][j] = 0.0;
self.k_global[j][idx] = 0.0;
}
self.k_global[idx][idx] = 1.0;
}
pub fn extract_k_uu(&self) -> Vec<Vec<f64>> {
let n_nodes = self.local.nodes.len();
let n_mech = 3 * n_nodes;
let mut kuu = vec![vec![0.0; n_mech]; n_mech];
for na in 0..n_nodes {
for da in 0..3 {
let row_global = na * 4 + da;
let row_mech = na * 3 + da;
for nb in 0..n_nodes {
for db in 0..3 {
let col_global = nb * 4 + db;
let col_mech = nb * 3 + db;
kuu[row_mech][col_mech] = self.k_global[row_global][col_global];
}
}
}
}
kuu
}
pub fn extract_k_pp(&self) -> Vec<Vec<f64>> {
let n_nodes = self.local.nodes.len();
let mut kpp = vec![vec![0.0; n_nodes]; n_nodes];
for (na, kpp_row) in kpp.iter_mut().enumerate().take(n_nodes) {
let row_global = na * 4 + 3;
for (nb, kpp_val) in kpp_row.iter_mut().enumerate().take(n_nodes) {
let col_global = nb * 4 + 3;
*kpp_val = self.k_global[row_global][col_global];
}
}
kpp
}
}