use super::functions::*;
use super::functions::{GAUSS2_ABSCISSAE, GAUSS2_WEIGHTS};
pub struct TransducerResonance {
pub c33: f64,
pub e33: f64,
pub eps33: f64,
pub density: f64,
pub thickness: f64,
}
impl TransducerResonance {
pub fn new(c33: f64, e33: f64, eps33: f64, density: f64, thickness: f64) -> Self {
Self {
c33,
e33,
eps33,
density,
thickness,
}
}
pub fn resonance_frequency_uncoupled(&self) -> f64 {
if self.density <= 0.0 || self.c33 <= 0.0 {
return 0.0;
}
let v_sound = (self.c33 / self.density).sqrt();
v_sound / (2.0 * self.thickness)
}
pub fn resonance_frequency_stiffened(&self) -> f64 {
if self.density <= 0.0 || self.c33 <= 0.0 {
return 0.0;
}
let c33_stiffened = if self.eps33.abs() > 1e-60 {
self.c33 + self.e33 * self.e33 / self.eps33
} else {
self.c33
};
let v_sound = (c33_stiffened / self.density).sqrt();
v_sound / (2.0 * self.thickness)
}
pub fn kt_coupling_factor(&self) -> f64 {
if self.c33.abs() < 1e-60 || self.eps33.abs() < 1e-60 {
return 0.0;
}
let kt2 = self.e33 * self.e33 / (self.c33 * self.eps33);
kt2.sqrt()
}
pub fn anti_resonance_frequency(&self) -> f64 {
let fr = self.resonance_frequency_uncoupled();
let kt = self.kt_coupling_factor();
fr * (1.0 + kt * kt * std::f64::consts::PI * std::f64::consts::PI / 8.0).sqrt()
}
pub fn motional_impedance(&self, omega: f64, loss_factor: f64) -> f64 {
let fr = self.resonance_frequency_uncoupled();
let omega_r = 2.0 * std::f64::consts::PI * fr;
let area = 1e-4;
let mass = self.density * self.thickness * area;
let denominator_sq = (1.0 - (omega / omega_r).powi(2)).powi(2) + loss_factor * loss_factor;
if denominator_sq < 1e-60 {
return f64::INFINITY;
}
omega * mass / denominator_sq.sqrt()
}
}
pub struct EmFrfResult {
pub omega: f64,
pub mechanical_magnitude: f64,
pub electrical_magnitude: f64,
}
pub struct PiezoElement {
pub material: PiezoMaterial,
pub volume: f64,
}
impl PiezoElement {
pub fn new(material: PiezoMaterial, volume: f64) -> Self {
PiezoElement { material, volume }
}
pub fn coupling_force(&self, strain: &[f64; 6]) -> [f64; 3] {
e_times_strain(&self.material.e_matrix, strain)
}
pub fn induced_strain(&self, voltage: f64, thickness: f64) -> [f64; 6] {
const D33: f64 = 400e-12;
let eps33 = D33 * voltage / thickness;
[0.0, 0.0, eps33, 0.0, 0.0, 0.0]
}
pub fn element_coupling_matrix(&self) -> [[f64; 3]; 6] {
self.material.coupling_matrix(self.volume)
}
pub fn element_dielectric_stiffness(&self) -> [[f64; 3]; 3] {
self.material.dielectric_stiffness(self.volume)
}
pub fn compute_electromechanical_coupling(&self) -> f64 {
let e33 = self.material.e_matrix[2][2];
let c33 = self.material.c_matrix[2][2];
let eps33 = self.material.epsilon_matrix[2][2];
if c33 * eps33 < 1e-60 {
return 0.0;
}
let k33_sq = (e33 * e33) / (c33 * eps33);
k33_sq.sqrt()
}
pub fn compute_resonance_frequency(&self, length: f64, density: f64) -> f64 {
if length < 1e-20 || density < 1e-20 {
return 0.0;
}
let c33_e = self.material.c_matrix[2][2];
let e33 = self.material.e_matrix[2][2];
let eps33 = self.material.epsilon_matrix[2][2];
let c33_d = if eps33 > 1e-30 {
c33_e + e33 * e33 / eps33
} else {
c33_e
};
(1.0 / (2.0 * length)) * (c33_d / density).sqrt()
}
pub fn compute_effective_piezo_charge(&self) -> f64 {
let e33 = self.material.e_matrix[2][2];
let c33 = self.material.c_matrix[2][2];
if c33.abs() < 1e-60 {
return 0.0;
}
e33 / c33
}
pub fn compute_blocked_force(&self, voltage: f64, area: f64, thickness: f64) -> f64 {
let e33 = self.material.e_matrix[2][2];
if thickness.abs() < 1e-20 {
return 0.0;
}
e33 * voltage * area / thickness
}
pub fn compute_free_displacement(&self, voltage: f64) -> f64 {
let d33 = self.compute_effective_piezo_charge();
d33 * voltage
}
}
pub struct PiezoHarvester {
pub k_sq: f64,
pub q_m: f64,
pub omega_r: f64,
pub r_opt: f64,
}
impl PiezoHarvester {
pub fn new(k_sq: f64, q_m: f64, omega_r: f64, r_opt: f64) -> Self {
Self {
k_sq,
q_m,
omega_r,
r_opt,
}
}
pub fn max_efficiency(&self) -> f64 {
let x = self.k_sq * self.q_m;
if (1.0 + x).abs() < 1e-30 {
return 0.0;
}
x / (1.0 + x)
}
pub fn max_power_at_resonance(&self, mass: f64, accel_amplitude: f64) -> f64 {
if self.omega_r.abs() < 1e-30 {
return 0.0;
}
let f_sq = mass * mass * accel_amplitude * accel_amplitude;
f_sq * self.q_m / (2.0 * self.omega_r)
}
pub fn figure_of_merit(&self) -> f64 {
self.k_sq * self.q_m
}
pub fn bandwidth_hz(&self) -> f64 {
let f_r = self.omega_r / (2.0 * std::f64::consts::PI);
if self.q_m.abs() < 1e-30 {
return f64::INFINITY;
}
f_r / self.q_m
}
}
pub struct PiezoActuator {
pub material: PiezoMaterial,
pub area: f64,
pub thickness: f64,
}
impl PiezoActuator {
pub fn new(material: PiezoMaterial, area: f64, thickness: f64) -> Self {
Self {
material,
area,
thickness,
}
}
pub fn free_strain(&self, voltage: f64) -> [f64; 6] {
const D33: f64 = 400e-12;
const D31: f64 = -175e-12;
let e_field = voltage / self.thickness;
[D31 * e_field, D31 * e_field, D33 * e_field, 0.0, 0.0, 0.0]
}
pub fn blocking_force(&self, voltage: f64) -> f64 {
let strain = self.free_strain(voltage);
let c33 = self.material.c_matrix[2][2];
strain[2] * c33 * self.area
}
pub fn stored_energy(&self, voltage: f64) -> f64 {
let eps33 = self.material.epsilon_matrix[2][2];
let e_field = voltage / self.thickness;
0.5 * eps33 * e_field * e_field * self.area * self.thickness
}
}
pub struct PiezoHex8Element {
pub coords: [[f64; 3]; 8],
pub material: PiezoMaterial,
}
impl PiezoHex8Element {
pub fn new(coords: [[f64; 3]; 8], material: PiezoMaterial) -> Self {
Self { coords, material }
}
pub fn stiffness_matrix(&self) -> Vec<f64> {
let mut k = vec![0.0_f64; 24 * 24];
for &xi in &GAUSS2_ABSCISSAE {
for &eta in &GAUSS2_ABSCISSAE {
for &zeta in &GAUSS2_ABSCISSAE {
let wt = GAUSS2_WEIGHTS[0] * GAUSS2_WEIGHTS[0] * GAUSS2_WEIGHTS[0];
let j = hex8_jacobian(&self.coords, xi, eta, zeta);
let det_j = det3(&j);
if det_j.abs() < 1e-30 {
continue;
}
let j_inv = inv3(&j);
let dn_nat = hex8_shape_derivatives(xi, eta, zeta);
let dndx = hex8_dndx(&j_inv, &dn_nat);
let b = hex8_b_matrix(&dndx);
let factor = wt * det_j;
for i in 0..24 {
for l in 0..24 {
let mut val = 0.0;
for r in 0..6 {
for s in 0..6 {
val += b[r][i] * self.material.c_matrix[r][s] * b[s][l];
}
}
k[i * 24 + l] += factor * val;
}
}
}
}
}
k
}
pub fn coupling_matrix_full(&self) -> Vec<f64> {
let mut k_ue = vec![0.0_f64; 24 * 3];
for &xi in &GAUSS2_ABSCISSAE {
for &eta in &GAUSS2_ABSCISSAE {
for &zeta in &GAUSS2_ABSCISSAE {
let wt = GAUSS2_WEIGHTS[0].powi(3);
let j = hex8_jacobian(&self.coords, xi, eta, zeta);
let det_j = det3(&j);
if det_j.abs() < 1e-30 {
continue;
}
let j_inv = inv3(&j);
let dn_nat = hex8_shape_derivatives(xi, eta, zeta);
let dndx = hex8_dndx(&j_inv, &dn_nat);
let b = hex8_b_matrix(&dndx);
let factor = wt * det_j;
for i in 0..24 {
for q in 0..3 {
let mut val = 0.0;
for (r, b_row) in b.iter().enumerate() {
val += b_row[i] * self.material.e_matrix[r][q];
}
k_ue[i * 3 + q] += factor * val;
}
}
}
}
}
k_ue
}
pub fn volume(&self) -> f64 {
let mut vol = 0.0;
for &xi in &GAUSS2_ABSCISSAE {
for &eta in &GAUSS2_ABSCISSAE {
for &zeta in &GAUSS2_ABSCISSAE {
let wt = GAUSS2_WEIGHTS[0].powi(3);
let j = hex8_jacobian(&self.coords, xi, eta, zeta);
vol += wt * det3(&j);
}
}
}
vol
}
}
pub struct PiezoMaterialDForm {
pub s_matrix: [[f64; 6]; 6],
pub d_matrix: [[f64; 6]; 3],
pub epsilon_t: [[f64; 3]; 3],
pub name: String,
}
impl PiezoMaterialDForm {
pub fn pzt5a_d_form() -> Self {
let s11 = 16.4e-12_f64;
let s12 = -5.74e-12_f64;
let s13 = -7.22e-12_f64;
let s33 = 18.8e-12_f64;
let s44 = 47.5e-12_f64;
let s66 = 2.0 * (s11 - s12);
#[rustfmt::skip]
let s_matrix = [
[s11, s12, s13, 0.0, 0.0, 0.0],
[s12, s11, s13, 0.0, 0.0, 0.0],
[s13, s13, s33, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, s44, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, s44, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, s66],
];
let d31 = -171e-12_f64;
let d33 = 374e-12_f64;
let d15 = 584e-12_f64;
#[rustfmt::skip]
let d_matrix: [[f64; 6]; 3] = [
[0.0, 0.0, 0.0, d15, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, d15, 0.0],
[d31, d31, d33, 0.0, 0.0, 0.0],
];
let eps0 = 8.854187817e-12_f64;
let eps11 = 1700.0 * eps0;
let eps33 = 1470.0 * eps0;
let epsilon_t = [[eps11, 0.0, 0.0], [0.0, eps11, 0.0], [0.0, 0.0, eps33]];
PiezoMaterialDForm {
s_matrix,
d_matrix,
epsilon_t,
name: "PZT-5A (d-form)".to_string(),
}
}
pub fn strain(&self, stress: &[f64; 6], e_field: &[f64; 3]) -> [f64; 6] {
let s_sigma = mat6_vec6_mul(&self.s_matrix, stress);
let mut dt_e = [0.0_f64; 6];
for (j, dte_j) in dt_e.iter_mut().enumerate() {
for (k, &ek) in e_field.iter().enumerate() {
*dte_j += self.d_matrix[k][j] * ek;
}
}
let mut eps = [0.0_f64; 6];
for (eps_i, (&ss_i, &dte_i)) in eps.iter_mut().zip(s_sigma.iter().zip(dt_e.iter())) {
*eps_i = ss_i + dte_i;
}
eps
}
pub fn electric_displacement_d(&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() {
for (j, &sj) in stress.iter().enumerate() {
*di += self.d_matrix[i][j] * sj;
}
for (j, &ej) in e_field.iter().enumerate() {
*di += self.epsilon_t[i][j] * ej;
}
}
d
}
pub fn k33_coupling_factor(&self) -> f64 {
let d33 = self.d_matrix[2][2];
let s33 = self.s_matrix[2][2];
let eps33 = self.epsilon_t[2][2];
let denom = (s33 * eps33).sqrt();
if denom.abs() < 1e-60 {
return 0.0;
}
d33 / denom
}
}
pub struct PvdfFilm {
pub d31: f64,
pub d33: f64,
pub youngs_modulus: f64,
pub eps_r: f64,
pub thickness: f64,
pub area: f64,
}
impl PvdfFilm {
pub fn typical() -> Self {
Self {
d31: 23e-12,
d33: -33e-12,
youngs_modulus: 2.0e9,
eps_r: 12.0,
thickness: 28e-6,
area: 1e-4,
}
}
const EPS0: f64 = 8.854187817e-12;
pub fn permittivity(&self) -> f64 {
self.eps_r * Self::EPS0
}
pub fn capacitance(&self) -> f64 {
self.permittivity() * self.area / self.thickness
}
pub fn voltage_from_stress_d31(&self, sigma: f64) -> f64 {
let eps = self.permittivity();
if eps.abs() < 1e-60 {
return 0.0;
}
self.d31 * sigma * self.thickness / eps
}
pub fn voltage_from_stress_d33(&self, sigma: f64) -> f64 {
let eps = self.permittivity();
if eps.abs() < 1e-60 {
return 0.0;
}
self.d33 * sigma * self.thickness / eps
}
pub fn charge_from_force_d31(&self, force: f64) -> f64 {
let sigma = force / self.area;
self.d31 * sigma * self.area
}
pub fn actuation_strain_d31(&self, voltage: f64) -> f64 {
self.d31 * voltage / self.thickness
}
pub fn actuation_strain_d33(&self, voltage: f64) -> f64 {
self.d33 * voltage / self.thickness
}
pub fn k31_coupling_factor(&self) -> f64 {
let eps = self.permittivity();
if eps.abs() < 1e-60 || self.youngs_modulus.abs() < 1e-60 {
return 0.0;
}
let k31_sq = self.d31 * self.d31 * self.youngs_modulus / eps;
k31_sq.sqrt()
}
pub fn bending_stiffness_per_unit_width(&self) -> f64 {
self.youngs_modulus * self.thickness.powi(3) / 12.0
}
}
pub struct PiezoSensor {
pub material: PiezoMaterial,
pub area: f64,
pub thickness: f64,
}
impl PiezoSensor {
pub fn new(material: PiezoMaterial, area: f64, thickness: f64) -> Self {
Self {
material,
area,
thickness,
}
}
pub fn open_circuit_voltage(&self, strain: &[f64; 6]) -> f64 {
let _e33 = self.material.e_matrix[2][2];
let eps33 = self.material.epsilon_matrix[2][2];
if eps33.abs() < 1e-60 {
return 0.0;
}
let d3: f64 = (0..6)
.map(|j| self.material.e_matrix[j][2] * strain[j])
.sum();
-d3 * self.thickness / eps33
}
pub fn charge_output(&self, strain: &[f64; 6]) -> f64 {
let d = e_times_strain(&self.material.e_matrix, strain);
d[2] * self.area
}
}
pub struct PiezoMaterial {
pub c_matrix: [[f64; 6]; 6],
pub e_matrix: [[f64; 3]; 6],
pub epsilon_matrix: [[f64; 3]; 3],
pub name: String,
}
impl PiezoMaterial {
pub fn pzt5a() -> Self {
let c11 = 121.0e9_f64;
let c12 = 75.4e9_f64;
let c13 = 75.2e9_f64;
let c33 = 111.0e9_f64;
let c44 = 21.1e9_f64;
let c66 = (c11 - c12) / 2.0;
#[rustfmt::skip]
let c_matrix: [[f64; 6]; 6] = [
[c11, c12, c13, 0.0, 0.0, 0.0],
[c12, c11, c13, 0.0, 0.0, 0.0],
[c13, c13, c33, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, c44, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, c44, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, c66],
];
let e31 = -5.4_f64;
let e33 = 15.8_f64;
let e15 = 12.3_f64;
#[rustfmt::skip]
let e_matrix: [[f64; 3]; 6] = [
[0.0, 0.0, e31],
[0.0, 0.0, e31],
[0.0, 0.0, e33],
[e15, 0.0, 0.0],
[0.0, e15, 0.0],
[0.0, 0.0, 0.0],
];
let eps0 = 8.854187817e-12_f64;
let eps11 = 1700.0 * eps0;
let eps33 = 1470.0 * eps0;
#[rustfmt::skip]
let epsilon_matrix: [[f64; 3]; 3] = [
[eps11, 0.0, 0.0],
[0.0, eps11, 0.0],
[0.0, 0.0, eps33],
];
PiezoMaterial {
c_matrix,
e_matrix,
epsilon_matrix,
name: "PZT-5A".to_string(),
}
}
pub fn pzt5h() -> Self {
let c11 = 126.0e9_f64;
let c12 = 79.5e9_f64;
let c13 = 84.1e9_f64;
let c33 = 117.0e9_f64;
let c44 = 23.0e9_f64;
let c66 = (c11 - c12) / 2.0;
#[rustfmt::skip]
let c_matrix: [[f64; 6]; 6] = [
[c11, c12, c13, 0.0, 0.0, 0.0],
[c12, c11, c13, 0.0, 0.0, 0.0],
[c13, c13, c33, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, c44, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, c44, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, c66],
];
let e31 = -6.5_f64;
let e33 = 23.3_f64;
let e15 = 17.0_f64;
#[rustfmt::skip]
let e_matrix: [[f64; 3]; 6] = [
[0.0, 0.0, e31],
[0.0, 0.0, e31],
[0.0, 0.0, e33],
[e15, 0.0, 0.0],
[0.0, e15, 0.0],
[0.0, 0.0, 0.0],
];
let eps0 = 8.854187817e-12_f64;
let eps11 = 3130.0 * eps0;
let eps33 = 3400.0 * eps0;
#[rustfmt::skip]
let epsilon_matrix: [[f64; 3]; 3] = [
[eps11, 0.0, 0.0],
[0.0, eps11, 0.0],
[0.0, 0.0, eps33],
];
PiezoMaterial {
c_matrix,
e_matrix,
epsilon_matrix,
name: "PZT-5H".to_string(),
}
}
pub fn mechanical_stress(&self, strain: &[f64; 6], e_field: &[f64; 3]) -> [f64; 6] {
let c_eps = mat6_vec6_mul(&self.c_matrix, strain);
let mut et_e = [0.0_f64; 6];
for (j, ete_j) in et_e.iter_mut().enumerate() {
for (i, &ei) in e_field.iter().enumerate() {
*ete_j += self.e_matrix[j][i] * ei;
}
}
let mut sigma = [0.0_f64; 6];
for (sig_k, (&ceps_k, &ete_k)) in sigma.iter_mut().zip(c_eps.iter().zip(et_e.iter())) {
*sig_k = ceps_k - ete_k;
}
sigma
}
pub fn electric_displacement(&self, strain: &[f64; 6], e_field: &[f64; 3]) -> [f64; 3] {
let e_eps = e_times_strain(&self.e_matrix, strain);
let eps_e = mat3_vec3_mul(&self.epsilon_matrix, e_field);
let mut d = [0.0_f64; 3];
for (di, (&ee_i, &eps_i)) in d.iter_mut().zip(e_eps.iter().zip(eps_e.iter())) {
*di = ee_i + eps_i;
}
d
}
pub fn coupling_matrix(&self, volume: f64) -> [[f64; 3]; 6] {
let mut k_ue = [[0.0; 3]; 6];
for (j, kue_j) in k_ue.iter_mut().enumerate() {
for (i, kue_ji) in kue_j.iter_mut().enumerate() {
*kue_ji = self.e_matrix[j][i] * volume;
}
}
k_ue
}
pub fn dielectric_stiffness(&self, volume: f64) -> [[f64; 3]; 3] {
let mut k_ee = [[0.0; 3]; 3];
for (i, kee_i) in k_ee.iter_mut().enumerate() {
for (j, kee_ij) in kee_i.iter_mut().enumerate() {
*kee_ij = self.epsilon_matrix[i][j] * volume;
}
}
k_ee
}
}
pub struct CoupledPiezoSystem {
pub n_mech_dofs: usize,
pub n_elec_dofs: usize,
pub k_uu: Vec<f64>,
pub k_ue: Vec<f64>,
pub k_ee: Vec<f64>,
}
impl CoupledPiezoSystem {
pub fn new(n_mech: usize, n_elec: usize) -> Self {
Self {
n_mech_dofs: n_mech,
n_elec_dofs: n_elec,
k_uu: vec![0.0; n_mech * n_mech],
k_ue: vec![0.0; n_mech * n_elec],
k_ee: vec![0.0; n_elec * n_elec],
}
}
pub fn add_k_uu(&mut self, i: usize, j: usize, val: f64) {
self.k_uu[i * self.n_mech_dofs + j] += val;
}
pub fn add_k_ue(&mut self, i: usize, j: usize, val: f64) {
self.k_ue[i * self.n_elec_dofs + j] += val;
}
pub fn add_k_ee(&mut self, i: usize, j: usize, val: f64) {
self.k_ee[i * self.n_elec_dofs + j] += val;
}
pub fn total_dofs(&self) -> usize {
self.n_mech_dofs + self.n_elec_dofs
}
pub fn static_condensation_1dof(&self, f_mech: f64, phi: f64) -> f64 {
if self.n_mech_dofs != 1 || self.n_elec_dofs != 1 {
return 0.0;
}
let k_uu = self.k_uu[0];
let k_ue = self.k_ue[0];
if k_uu.abs() < 1e-60 {
return 0.0;
}
(f_mech - k_ue * phi) / k_uu
}
}