#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(clippy::too_many_arguments)]
use std::f64::consts::PI;
#[derive(Clone, Debug)]
pub struct CorticalBone {
pub e_long: f64,
pub e_trans: f64,
pub g_lt: f64,
pub nu_lt: f64,
pub nu_tt: f64,
pub density: f64,
pub uts: f64,
pub ucs: f64,
pub k_ic: f64,
}
impl CorticalBone {
pub fn femur_cortical() -> Self {
Self::human_femur()
}
pub fn human_femur() -> Self {
Self {
e_long: 20.0e9,
e_trans: 12.0e9,
g_lt: 4.5e9,
nu_lt: 0.29,
nu_tt: 0.63,
density: 1900.0,
uts: 120.0e6,
ucs: 180.0e6,
k_ic: 2.5e6,
}
}
pub fn longitudinal_wave_speed(&self) -> f64 {
(self.e_long / self.density).sqrt()
}
pub fn transverse_wave_speed(&self) -> f64 {
(self.g_lt / self.density).sqrt()
}
pub fn apparent_density(vf: f64) -> f64 {
vf * 1900.0
}
pub fn modulus_from_density_power_law(density_g_cm3: f64) -> f64 {
let a = 2.065e10;
let b = 3.09;
a * density_g_cm3.powf(b)
}
pub fn bending_stiffness(&self, outer_r: f64, inner_r: f64, length: f64) -> f64 {
let i = PI / 4.0 * (outer_r.powi(4) - inner_r.powi(4));
48.0 * self.e_long * i / length.powi(3)
}
pub fn three_point_bending_stress(
&self,
force: f64,
length: f64,
outer_r: f64,
inner_r: f64,
) -> f64 {
let i = PI / 4.0 * (outer_r.powi(4) - inner_r.powi(4));
force * length * outer_r / (4.0 * i)
}
}
#[derive(Clone, Debug)]
pub struct CancellousBone {
pub relative_density: f64,
pub apparent_density: f64,
pub apparent_modulus: f64,
pub apparent_strength: f64,
pub anisotropy_ratio: f64,
}
impl CancellousBone {
pub fn from_relative_density(rel_density: f64) -> Self {
let e_solid = 20.0e9;
let sigma_solid = 170.0e6;
let c1 = 1.0;
let c2 = 0.2;
let apparent_modulus = c1 * e_solid * rel_density.powi(2);
let apparent_strength = c2 * sigma_solid * rel_density.powf(1.5);
Self {
relative_density: rel_density,
apparent_density: rel_density * 1900.0,
apparent_modulus,
apparent_strength,
anisotropy_ratio: 1.5,
}
}
pub fn energy_absorption(&self) -> f64 {
let densification_strain = 1.0 - 1.4 * self.relative_density;
0.5 * self.apparent_strength * densification_strain.max(0.0)
}
pub fn failure_strain(&self) -> f64 {
0.07 * (self.relative_density / 0.2).powf(-0.3)
}
}
pub fn cortical_bone_stiffness_tensor(
e_l: f64,
e_t: f64,
g_lt: f64,
nu_lt: f64,
nu_tt: f64,
) -> [[f64; 6]; 6] {
let nu_tl = nu_lt * e_t / e_l;
let det = 1.0 - nu_tt * nu_tt - 2.0 * nu_lt * nu_tl;
let mut c = [[0.0f64; 6]; 6];
c[0][0] = e_l * (1.0 - nu_tt * nu_tt) / det;
c[1][1] = e_t * (1.0 - nu_lt * nu_tl) / det;
c[2][2] = c[1][1];
c[0][1] = e_l * (nu_tl + nu_tt * nu_tl) / det;
c[1][0] = c[0][1];
c[0][2] = c[0][1];
c[2][0] = c[0][2];
c[1][2] = e_t * (nu_tt + nu_lt * nu_tl) / det;
c[2][1] = c[1][2];
c[3][3] = g_lt;
c[4][4] = g_lt;
c[5][5] = e_t / (2.0 * (1.0 + nu_tt));
c
}
#[derive(Clone, Debug)]
pub struct CartilageBiphasic {
pub ha: f64,
pub mu_s: f64,
pub permeability: f64,
pub thickness: f64,
pub density: f64,
}
impl CartilageBiphasic {
pub fn superficial_zone() -> Self {
Self {
ha: 0.42e6,
mu_s: 0.10e6,
permeability: 4.0e-15,
thickness: 2.0e-3,
density: 1100.0,
}
}
pub fn middle_zone() -> Self {
Self {
ha: 0.60e6,
mu_s: 0.15e6,
permeability: 2.0e-15,
thickness: 2.5e-3,
density: 1100.0,
}
}
pub fn deep_zone() -> Self {
Self {
ha: 0.85e6,
mu_s: 0.20e6,
permeability: 1.0e-15,
thickness: 3.0e-3,
density: 1100.0,
}
}
pub fn knee() -> Self {
Self {
ha: 0.7e6,
mu_s: 0.15e6,
permeability: 2.17e-15,
thickness: 2.5e-3,
density: 1100.0,
}
}
pub fn creep_time_constant(&self) -> f64 {
self.thickness * self.thickness / (self.ha * self.permeability)
}
pub fn steady_state_strain(&self, sigma: f64) -> f64 {
sigma / self.ha
}
pub fn creep_displacement(&self, sigma: f64, t: f64) -> f64 {
let tau = self.creep_time_constant();
let eps_inf = self.steady_state_strain(sigma);
let correction = (1.0 - (-t / tau * PI * PI / 4.0).exp()).max(0.0);
eps_inf * self.thickness * correction
}
pub fn fluid_pressure(&self, sigma: f64, t: f64) -> f64 {
let tau = self.creep_time_constant();
sigma * (-t / tau * PI * PI / 4.0).exp()
}
pub fn poisson_ratio(&self) -> f64 {
(self.ha - 2.0 * self.mu_s) / (2.0 * (self.ha - self.mu_s))
}
pub fn confined_compression_stiffness(&self, area: f64) -> f64 {
self.ha * area / self.thickness
}
}
#[derive(Clone, Debug)]
pub struct TendonViscoelastic {
pub toe_strain: f64,
pub toe_modulus: f64,
pub linear_modulus: f64,
pub tau: f64,
pub relaxation_magnitude: f64,
pub area: f64,
pub resting_length: f64,
}
impl TendonViscoelastic {
pub fn achilles() -> Self {
Self {
toe_strain: 0.03,
toe_modulus: 50.0e6,
linear_modulus: 1.2e9,
tau: 20.0,
relaxation_magnitude: 0.15,
area: 80.0e-6,
resting_length: 0.25,
}
}
pub fn elastic_stress(&self, strain: f64) -> f64 {
if strain < 0.0 {
return 0.0;
}
if strain <= self.toe_strain {
let b = (self.linear_modulus / self.toe_modulus).ln() / self.toe_strain;
let a = self.toe_modulus / b;
a * ((b * strain).exp() - 1.0)
} else {
let toe_stress = self.elastic_stress(self.toe_strain);
toe_stress + self.linear_modulus * (strain - self.toe_strain)
}
}
pub fn reduced_relaxation(&self, t: f64) -> f64 {
1.0 - self.relaxation_magnitude * (1.0 - (-t / self.tau).exp())
}
pub fn stress_relaxation(&self, eps0: f64, t: f64) -> f64 {
self.elastic_stress(eps0) * self.reduced_relaxation(t)
}
pub fn force(&self, strain: f64) -> f64 {
self.elastic_stress(strain) * self.area
}
pub fn stiffness(&self, strain: f64) -> f64 {
let deps = 1e-6;
let df = self.force(strain + deps) - self.force(strain - deps);
df / (2.0 * deps * self.resting_length)
}
pub fn ultimate_force(&self) -> f64 {
self.elastic_stress(0.10) * self.area
}
}
#[derive(Clone, Debug)]
pub struct HydrogelFloryRehner {
pub chi: f64,
pub nu_crosslink: f64,
pub phi_ref: f64,
pub temperature: f64,
pub v_solvent: f64,
}
const R_GAS: f64 = 8.314;
impl HydrogelFloryRehner {
pub fn polyacrylamide() -> Self {
Self {
chi: 0.48,
nu_crosslink: 200.0,
phi_ref: 0.08,
temperature: 298.15,
v_solvent: 1.8e-5,
}
}
pub fn pegda() -> Self {
Self {
chi: 0.45,
nu_crosslink: 500.0,
phi_ref: 0.1,
temperature: 310.15,
v_solvent: 1.8e-5,
}
}
pub fn chemical_potential_mixing(&self, phi_p: f64) -> f64 {
let phi_s = 1.0 - phi_p;
R_GAS * self.temperature * ((phi_s).ln() + phi_p + self.chi * phi_p * phi_p)
}
pub fn chemical_potential_elastic(&self, phi_p: f64) -> f64 {
R_GAS
* self.temperature
* self.nu_crosslink
* self.v_solvent
* (phi_p / self.phi_ref).powf(1.0 / 3.0)
* (0.5 - phi_p / self.phi_ref)
}
pub fn total_chemical_potential(&self, phi_p: f64) -> f64 {
self.chemical_potential_mixing(phi_p) + self.chemical_potential_elastic(phi_p)
}
pub fn equilibrium_swelling_ratio(&self) -> f64 {
let mut lo = 0.001f64;
let mut hi = 0.999f64;
for _ in 0..100 {
let mid = (lo + hi) / 2.0;
let f_lo = self.total_chemical_potential(lo);
let f_mid = self.total_chemical_potential(mid);
if f_lo * f_mid < 0.0 {
hi = mid;
} else {
lo = mid;
}
}
let phi_eq = (lo + hi) / 2.0;
1.0 / phi_eq }
pub fn shear_modulus(&self, phi_p: f64) -> f64 {
R_GAS * self.temperature * self.nu_crosslink * phi_p.powf(1.0 / 3.0)
}
pub fn osmotic_pressure(&self, phi_p: f64) -> f64 {
-R_GAS * self.temperature / self.v_solvent
* (self.total_chemical_potential(phi_p) / (R_GAS * self.temperature))
}
}
#[derive(Clone, Debug)]
pub struct CellMechanics {
pub substrate_modulus: f64,
pub cell_stiffness: f64,
pub adhesion_energy: f64,
pub cell_radius: f64,
pub cortical_tension: f64,
}
impl CellMechanics {
pub fn fibroblast_soft() -> Self {
Self {
substrate_modulus: 1.0e3,
cell_stiffness: 500.0,
adhesion_energy: 1e-5,
cell_radius: 10.0e-6,
cortical_tension: 4e-4,
}
}
pub fn jkr_contact_radius(&self, f: f64) -> f64 {
let r = self.cell_radius;
let e_star = self.effective_modulus();
let w = self.adhesion_energy;
let p0 = f + 3.0 * PI * w * r + (6.0 * PI * w * r * f + (3.0 * PI * w * r).powi(2)).sqrt();
(r * p0 / (4.0 * e_star / 3.0)).cbrt()
}
pub fn effective_modulus(&self) -> f64 {
let e_cell = self.cell_stiffness;
let e_sub = self.substrate_modulus;
let nu = 0.5; 1.0 / (2.0 * (1.0 - nu * nu) / e_cell + 2.0 * (1.0 - nu * nu) / e_sub)
}
pub fn hertz_force(&self, indent_depth: f64) -> f64 {
let e_star = self.effective_modulus();
let r = self.cell_radius;
4.0 / 3.0 * e_star * r.sqrt() * indent_depth.powf(1.5)
}
pub fn spreading_area(&self) -> f64 {
let a_max = PI * (50e-6f64).powi(2);
let a_min = PI * (5e-6f64).powi(2);
let half_max = 10.0e3; let e = self.substrate_modulus;
a_min + (a_max - a_min) * e / (e + half_max)
}
pub fn traction_force(&self, n_bonds: usize) -> f64 {
let k_bond = 1e-3; let d_slip = 5e-9; n_bonds as f64 * k_bond * d_slip
}
}
#[derive(Clone, Debug)]
pub struct BiodegradationKinetics {
pub rate_constant: f64,
pub mw_initial: f64,
pub mw_critical: f64,
pub d_water: f64,
pub half_thickness: f64,
}
impl BiodegradationKinetics {
pub fn pla_scaffold() -> Self {
Self {
rate_constant: 1.0e-7,
mw_initial: 100_000.0,
mw_critical: 10_000.0,
d_water: 1.0e-14,
half_thickness: 1.0e-3,
}
}
pub fn molecular_weight(&self, t: f64) -> f64 {
self.mw_initial * (-self.rate_constant * t).exp()
}
pub fn time_to_failure(&self) -> f64 {
(self.mw_initial / self.mw_critical).ln() / self.rate_constant
}
pub fn mass_loss_fraction(&self, t: f64) -> f64 {
let t_half = 0.693 / self.rate_constant;
1.0 - (-0.693 * t / t_half).exp()
}
pub fn diffusion_time(&self) -> f64 {
self.half_thickness * self.half_thickness / self.d_water
}
pub fn is_bulk_degradation(&self) -> bool {
let t_reaction = 1.0 / self.rate_constant;
let t_diffusion = self.diffusion_time();
t_diffusion < t_reaction
}
pub fn plga_mass_loss(&self, t: f64, autocatalysis_factor: f64) -> f64 {
let k_eff = self.rate_constant * (1.0 + autocatalysis_factor * self.mass_loss_fraction(t));
1.0 - (-k_eff * t).exp()
}
}
#[derive(Clone, Debug)]
pub struct ScaffoldPermeability {
pub porosity: f64,
pub specific_surface: f64,
pub kozeny_const: f64,
pub viscosity: f64,
pub thickness: f64,
}
impl ScaffoldPermeability {
pub fn ha_scaffold() -> Self {
Self {
porosity: 0.65,
specific_surface: 2.0e6,
kozeny_const: 5.0,
viscosity: 1e-3,
thickness: 5.0e-3,
}
}
pub fn permeability(&self) -> f64 {
let phi = self.porosity;
let sv = self.specific_surface;
phi.powi(3) / (self.kozeny_const * sv * sv * (1.0 - phi).powi(2))
}
pub fn darcy_velocity(&self, pressure_gradient: f64) -> f64 {
self.permeability() * pressure_gradient / self.viscosity
}
pub fn hydraulic_conductivity(&self) -> f64 {
self.permeability() / self.viscosity
}
pub fn tortuosity(&self) -> f64 {
self.porosity.powf(-0.5)
}
pub fn effective_diffusivity(&self, d_bulk: f64) -> f64 {
d_bulk * self.porosity / self.tortuosity()
}
pub fn mean_pore_diameter(&self) -> f64 {
4.0 * self.porosity / self.specific_surface
}
}
#[derive(Clone, Debug)]
pub struct BiocompatibilityIndex {
pub cytotoxicity: f64,
pub inflammatory_response: f64,
pub haemocompatibility: f64,
pub genotoxicity: f64,
pub host_response: f64,
}
impl BiocompatibilityIndex {
pub fn composite_score(&self) -> f64 {
0.3 * self.cytotoxicity
+ 0.25 * self.inflammatory_response
+ 0.2 * self.haemocompatibility
+ 0.15 * self.genotoxicity
+ 0.1 * self.host_response
}
pub fn passes_iso_10993(&self) -> bool {
self.composite_score() < 0.3
}
pub fn ti6al4v() -> Self {
Self {
cytotoxicity: 0.05,
inflammatory_response: 0.1,
haemocompatibility: 0.08,
genotoxicity: 0.02,
host_response: 0.05,
}
}
pub fn uhmwpe() -> Self {
Self {
cytotoxicity: 0.04,
inflammatory_response: 0.08,
haemocompatibility: 0.05,
genotoxicity: 0.01,
host_response: 0.04,
}
}
}
#[derive(Clone, Debug)]
pub struct SutureMaterial {
pub name: String,
pub modulus: f64,
pub uts: f64,
pub failure_strain: f64,
pub knot_efficiency: f64,
pub knot_breaking_force: f64,
pub diameter: f64,
pub biodegradable: bool,
pub half_life_days: f64,
}
impl SutureMaterial {
pub fn vicryl(diameter_mm: f64) -> Self {
let d = diameter_mm * 1e-3;
Self {
name: "Vicryl (PGA/PLA)".into(),
modulus: 8.0e9,
uts: 550.0e6,
failure_strain: 0.20,
knot_efficiency: 0.55,
knot_breaking_force: 40.0 * d / 0.3e-3,
diameter: d,
biodegradable: true,
half_life_days: 28.0,
}
}
pub fn prolene(diameter_mm: f64) -> Self {
let d = diameter_mm * 1e-3;
Self {
name: "Prolene (PP)".into(),
modulus: 3.5e9,
uts: 450.0e6,
failure_strain: 0.40,
knot_efficiency: 0.50,
knot_breaking_force: 35.0 * d / 0.3e-3,
diameter: d,
biodegradable: false,
half_life_days: f64::INFINITY,
}
}
pub fn area(&self) -> f64 {
PI * (self.diameter / 2.0).powi(2)
}
pub fn breaking_strength(&self) -> f64 {
self.uts * self.area()
}
pub fn knot_strength(&self) -> f64 {
self.breaking_strength() * self.knot_efficiency
}
pub fn elongation_at_break(&self, gauge_length: f64) -> f64 {
self.failure_strain * gauge_length
}
pub fn strength_retention(&self, t_days: f64) -> f64 {
if !self.biodegradable {
return 1.0;
}
(-0.693 * t_days / self.half_life_days).exp()
}
}
#[derive(Clone, Debug)]
pub struct SurgicalMesh {
pub areal_density: f64,
pub pore_size: f64,
pub porosity: f64,
pub tensile_strength_per_width: f64,
pub stiffness: f64,
pub burst_strength: f64,
}
impl SurgicalMesh {
pub fn lightweight_pp() -> Self {
Self {
areal_density: 36.0,
pore_size: 2.0e-3,
porosity: 0.80,
tensile_strength_per_width: 60.0,
stiffness: 5.0,
burst_strength: 120.0,
}
}
pub fn safety_factor(&self, mesh_width: f64, max_pressure: f64) -> f64 {
let load = max_pressure * mesh_width;
self.tensile_strength_per_width / load
}
}
#[derive(Clone, Debug)]
pub struct DentalEnamel {
pub modulus: f64,
pub hardness: f64,
pub k_ic: f64,
pub density: f64,
pub mineral_fraction: f64,
}
impl DentalEnamel {
pub fn human() -> Self {
Self {
modulus: 75.0e9,
hardness: 3.5e9,
k_ic: 0.7e6,
density: 2940.0,
mineral_fraction: 0.92,
}
}
pub fn critical_flaw_size(&self, tensile_strength: f64) -> f64 {
(self.k_ic / (tensile_strength * PI.sqrt())).powi(2) / PI
}
pub fn vickers_hardness(&self) -> f64 {
self.hardness / (9.81 * 1e6 / 1e4) }
}
#[derive(Clone, Debug)]
pub struct Dentin {
pub modulus: f64,
pub hardness: f64,
pub k_ic: f64,
pub density: f64,
pub mineral_fraction: f64,
pub collagen_fraction: f64,
}
impl Dentin {
pub fn human() -> Self {
Self {
modulus: 20.0e9,
hardness: 0.5e9,
k_ic: 3.1e6,
density: 2100.0,
mineral_fraction: 0.50,
collagen_fraction: 0.30,
}
}
pub fn tubule_toughening_factor(&self, tubule_density: f64) -> f64 {
1.0 / (1.0 + 0.1 * tubule_density * 1e-9)
}
}
#[derive(Clone, Debug)]
pub struct DentalComposite {
pub filler_fraction: f64,
pub filler_modulus: f64,
pub matrix_modulus: f64,
pub particle_size: f64,
pub flexural_strength: f64,
pub poly_shrinkage: f64,
pub water_absorption: f64,
}
impl DentalComposite {
pub fn nano_hybrid() -> Self {
Self {
filler_fraction: 0.70,
filler_modulus: 70.0e9,
matrix_modulus: 3.5e9,
particle_size: 0.5e-6,
flexural_strength: 120.0e6,
poly_shrinkage: 0.02,
water_absorption: 1.5,
}
}
pub fn nano_hybrid_v2() -> Self {
Self::nano_hybrid()
}
pub fn reuss_modulus(&self) -> f64 {
let vf = self.filler_fraction;
1.0 / (vf / self.filler_modulus + (1.0 - vf) / self.matrix_modulus)
}
pub fn voigt_modulus(&self) -> f64 {
let vf = self.filler_fraction;
vf * self.filler_modulus + (1.0 - vf) * self.matrix_modulus
}
pub fn halpin_tsai_modulus(&self) -> f64 {
let eta = (self.filler_modulus / self.matrix_modulus - 1.0)
/ (self.filler_modulus / self.matrix_modulus + 2.0);
self.matrix_modulus * (1.0 + 2.0 * eta * self.filler_fraction)
/ (1.0 - eta * self.filler_fraction)
}
pub fn shrinkage_stress(&self, compliance: f64) -> f64 {
self.poly_shrinkage / compliance
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TissueType {
Bone,
Cartilage,
Tendon,
Skin,
Muscle,
Artery,
Custom(String),
}
#[derive(Clone, Debug)]
pub struct BoneMaterial {
pub is_cortical: bool,
pub density_g_cm3: f64,
pub porosity: f64,
pub bvf: f64,
pub power_law_a: f64,
pub power_law_b: f64,
}
impl BoneMaterial {
pub fn cortical() -> Self {
Self {
is_cortical: true,
density_g_cm3: 1.85,
porosity: 0.05,
bvf: 0.95,
power_law_a: 2.065e10,
power_law_b: 3.09,
}
}
pub fn trabecular() -> Self {
Self {
is_cortical: false,
density_g_cm3: 0.30,
porosity: 0.75,
bvf: 0.25,
power_law_a: 1.310e10,
power_law_b: 2.74,
}
}
pub fn young_modulus_from_density(&self) -> f64 {
self.power_law_a * self.density_g_cm3.powf(self.power_law_b)
}
pub fn modulus_at_density(&self, rho_g_cm3: f64) -> f64 {
self.power_law_a * rho_g_cm3.powf(self.power_law_b)
}
pub fn tissue_type(&self) -> TissueType {
TissueType::Bone
}
}
#[derive(Clone, Debug)]
pub struct CartilageMaterial {
pub aggregate_modulus: f64,
pub shear_modulus: f64,
pub permeability: f64,
pub fluid_fraction: f64,
pub thickness: f64,
}
impl CartilageMaterial {
pub fn knee() -> Self {
Self {
aggregate_modulus: 0.7e6,
shear_modulus: 0.15e6,
permeability: 2.17e-15,
fluid_fraction: 0.75,
thickness: 2.5e-3,
}
}
pub fn creep_time_constant(&self) -> f64 {
self.thickness * self.thickness / (self.aggregate_modulus * self.permeability)
}
pub fn steady_state_strain(&self, sigma: f64) -> f64 {
sigma / self.aggregate_modulus
}
pub fn fluid_pressure(&self, sigma: f64, t: f64) -> f64 {
let tau = self.creep_time_constant();
sigma * (-t / tau * PI * PI / 4.0).exp()
}
pub fn tissue_type(&self) -> TissueType {
TissueType::Cartilage
}
}
#[derive(Clone, Debug)]
pub struct TendonLigament {
pub fiber_angle_rad: f64,
pub toe_stiffness: f64,
pub linear_stiffness: f64,
pub toe_strain: f64,
pub failure_strain: f64,
}
impl TendonLigament {
pub fn achilles() -> Self {
Self {
fiber_angle_rad: 0.05,
toe_stiffness: 50.0e6,
linear_stiffness: 1.2e9,
toe_strain: 0.03,
failure_strain: 0.10,
}
}
pub fn stress(&self, eps: f64) -> f64 {
if eps <= 0.0 {
return 0.0;
}
if eps <= self.toe_strain {
let b = (self.linear_stiffness / self.toe_stiffness).ln() / self.toe_strain;
let a = self.toe_stiffness / b;
a * ((b * eps).exp() - 1.0)
} else {
let sigma_toe = self.stress(self.toe_strain);
sigma_toe + self.linear_stiffness * (eps - self.toe_strain)
}
}
pub fn is_failed(&self, eps: f64) -> bool {
eps >= self.failure_strain
}
pub fn tissue_type(&self) -> TissueType {
TissueType::Tendon
}
}
#[derive(Clone, Debug)]
pub struct ArterialWall {
pub c10: f64,
pub k1: f64,
pub k2: f64,
pub fiber_angle_rad: f64,
}
impl ArterialWall {
pub fn aorta_media() -> Self {
Self {
c10: 26.95e3,
k1: 15.56e3,
k2: 11.65,
fiber_angle_rad: 0.4869, }
}
pub fn neo_hookean_energy(&self, i1: f64) -> f64 {
self.c10 * (i1 - 3.0)
}
pub fn fiber_energy(&self, i4: f64) -> f64 {
if i4 <= 1.0 {
return 0.0;
}
self.k1 / (2.0 * self.k2) * ((self.k2 * (i4 - 1.0).powi(2)).exp() - 1.0)
}
pub fn strain_energy(&self, i1: f64, i4: f64) -> f64 {
self.neo_hookean_energy(i1) + self.fiber_energy(i4)
}
pub fn fiber_stress(&self, i4: f64) -> f64 {
if i4 <= 1.0 {
return 0.0;
}
2.0 * self.k1 * (i4 - 1.0) * (self.k2 * (i4 - 1.0).powi(2)).exp()
}
pub fn tissue_type(&self) -> TissueType {
TissueType::Artery
}
}
#[derive(Clone, Debug)]
pub struct MuscleMaterial {
pub max_isometric_force: f64,
pub optimal_fiber_length: f64,
pub pennation_angle: f64,
pub vmax_factor: f64,
pub passive_stiffness: f64,
}
impl MuscleMaterial {
pub fn biceps() -> Self {
Self {
max_isometric_force: 1200.0,
optimal_fiber_length: 0.116,
pennation_angle: 0.0,
vmax_factor: 10.0,
passive_stiffness: 1.0e4,
}
}
pub fn force_length_factor(&self, length: f64) -> f64 {
let ratio = length / self.optimal_fiber_length;
(-4.0 * (ratio - 1.0).powi(2)).exp()
}
pub fn active_force(&self, activation: f64, length: f64) -> f64 {
let a = activation.clamp(0.0, 1.0);
let pennation_cos = self.pennation_angle.cos();
a * self.max_isometric_force * self.force_length_factor(length) * pennation_cos
}
pub fn passive_force(&self, length: f64) -> f64 {
let stretch = (length - self.optimal_fiber_length).max(0.0);
self.passive_stiffness * stretch
}
pub fn total_force(&self, activation: f64, length: f64) -> f64 {
self.active_force(activation, length) + self.passive_force(length)
}
pub fn tissue_type(&self) -> TissueType {
TissueType::Muscle
}
}
pub fn reuss_modulus(e1: f64, e2: f64, vf1: f64) -> f64 {
let vf2 = 1.0 - vf1;
1.0 / (vf1 / e1 + vf2 / e2)
}
pub fn voigt_modulus(e1: f64, e2: f64, vf1: f64) -> f64 {
vf1 * e1 + (1.0 - vf1) * e2
}
pub fn strain_energy_density_neo_hookean(c1: f64, i1: f64) -> f64 {
c1 * (i1 - 3.0)
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-9;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_cortical_bone_wave_speed() {
let bone = CorticalBone::human_femur();
let v = bone.longitudinal_wave_speed();
assert!(v > 2000.0 && v < 5000.0, "Wave speed should be ~3200 m/s");
}
#[test]
fn test_cortical_bone_bending_stiffness() {
let bone = CorticalBone::human_femur();
let k = bone.bending_stiffness(0.015, 0.010, 0.3);
assert!(k > 0.0);
}
#[test]
fn test_cortical_bone_three_point_stress() {
let bone = CorticalBone::human_femur();
let sigma = bone.three_point_bending_stress(1000.0, 0.3, 0.015, 0.010);
assert!(sigma > 0.0);
}
#[test]
fn test_cortical_stiffness_tensor_symmetry() {
let bone = CorticalBone::human_femur();
let c = cortical_bone_stiffness_tensor(
bone.e_long,
bone.e_trans,
bone.g_lt,
bone.nu_lt,
bone.nu_tt,
);
assert!(approx_eq(c[0][1], c[1][0], 1e3));
assert!(approx_eq(c[0][2], c[2][0], 1e3));
}
#[test]
fn test_cancellous_bone_modulus() {
let bone = CancellousBone::from_relative_density(0.2);
assert!(bone.apparent_modulus > 0.0);
let expected = 1.0 * 20.0e9 * 0.04; assert!(approx_eq(bone.apparent_modulus, expected, 1.0));
}
#[test]
fn test_cancellous_energy_absorption() {
let bone = CancellousBone::from_relative_density(0.2);
let e = bone.energy_absorption();
assert!(e > 0.0);
}
#[test]
fn test_cartilage_creep_time_constant() {
let cart = CartilageBiphasic::knee();
let tau = cart.creep_time_constant();
assert!(tau > 0.0);
}
#[test]
fn test_cartilage_steady_state_strain() {
let cart = CartilageBiphasic::knee();
let eps = cart.steady_state_strain(1.0e4);
assert!(approx_eq(eps, 1.0e4 / 0.7e6, 1e-6));
}
#[test]
fn test_cartilage_creep_displacement_increases_with_time() {
let cart = CartilageBiphasic::knee();
let d1 = cart.creep_displacement(1.0e4, 10.0);
let d2 = cart.creep_displacement(1.0e4, 100.0);
assert!(d2 >= d1);
}
#[test]
fn test_cartilage_fluid_pressure_decays() {
let cart = CartilageBiphasic::knee();
let p0 = cart.fluid_pressure(1.0e4, 0.0);
let p1 = cart.fluid_pressure(1.0e4, 100.0);
assert!(p0 >= p1);
}
#[test]
fn test_tendon_elastic_stress_toe_region() {
let tendon = TendonViscoelastic::achilles();
let s = tendon.elastic_stress(0.01);
assert!(s > 0.0);
let s2 = tendon.elastic_stress(0.02);
assert!(s2 > s);
}
#[test]
fn test_tendon_elastic_stress_linear_region() {
let tendon = TendonViscoelastic::achilles();
let s1 = tendon.elastic_stress(0.05);
let s2 = tendon.elastic_stress(0.06);
let d = s2 - s1;
assert!(approx_eq(d, 1.2e7, 1.0e6));
}
#[test]
fn test_tendon_stress_relaxation_decreases() {
let tendon = TendonViscoelastic::achilles();
let s1 = tendon.stress_relaxation(0.05, 1.0);
let s2 = tendon.stress_relaxation(0.05, 100.0);
assert!(s1 > s2);
}
#[test]
fn test_tendon_reduced_relaxation_limits() {
let tendon = TendonViscoelastic::achilles();
let g0 = tendon.reduced_relaxation(0.0);
let g_inf = tendon.reduced_relaxation(1e9);
assert!(approx_eq(g0, 1.0, 1e-6));
assert!(approx_eq(g_inf, 1.0 - tendon.relaxation_magnitude, 1e-4));
}
#[test]
fn test_hydrogel_swelling_ratio_positive() {
let gel = HydrogelFloryRehner::pegda();
let q = gel.equilibrium_swelling_ratio();
assert!(q > 1.0, "Swelling ratio must exceed 1 for a swelling gel");
}
#[test]
fn test_hydrogel_shear_modulus_positive() {
let gel = HydrogelFloryRehner::pegda();
let g = gel.shear_modulus(0.1);
assert!(g > 0.0);
}
#[test]
fn test_scaffold_permeability_kozeny() {
let scaffold = ScaffoldPermeability::ha_scaffold();
let k = scaffold.permeability();
assert!(k > 0.0 && k < 1e-10);
}
#[test]
fn test_scaffold_darcy_velocity() {
let scaffold = ScaffoldPermeability::ha_scaffold();
let v = scaffold.darcy_velocity(1.0e3);
assert!(v > 0.0);
}
#[test]
fn test_scaffold_pore_diameter() {
let scaffold = ScaffoldPermeability::ha_scaffold();
let d = scaffold.mean_pore_diameter();
assert!(approx_eq(d, 4.0 * 0.65 / 2.0e6, 1e-8));
}
#[test]
fn test_biocompatibility_ti6al4v_passes() {
let mat = BiocompatibilityIndex::ti6al4v();
assert!(mat.passes_iso_10993());
}
#[test]
fn test_biocompatibility_score_range() {
let mat = BiocompatibilityIndex {
cytotoxicity: 0.5,
inflammatory_response: 0.5,
haemocompatibility: 0.5,
genotoxicity: 0.5,
host_response: 0.5,
};
let score = mat.composite_score();
assert!(approx_eq(score, 0.5, EPS));
}
#[test]
fn test_suture_breaking_strength() {
let suture = SutureMaterial::vicryl(0.3);
let f = suture.breaking_strength();
assert!(f > 0.0);
}
#[test]
fn test_suture_strength_retention_biodegradable() {
let suture = SutureMaterial::vicryl(0.3);
let r0 = suture.strength_retention(0.0);
let r28 = suture.strength_retention(28.0);
assert!(approx_eq(r0, 1.0, 1e-10));
assert!(approx_eq(r28, 0.5, 0.01));
}
#[test]
fn test_suture_prolene_non_degradable() {
let suture = SutureMaterial::prolene(0.3);
assert!(!suture.biodegradable);
assert!(approx_eq(suture.strength_retention(3650.0), 1.0, 1e-10));
}
#[test]
fn test_enamel_critical_flaw() {
let enamel = DentalEnamel::human();
let flaw = enamel.critical_flaw_size(50.0e6);
assert!(flaw > 0.0 && flaw < 1e-3);
}
#[test]
fn test_composite_voigt_reuss_bounds() {
let comp = DentalComposite::nano_hybrid_v2();
let voigt = comp.voigt_modulus();
let reuss = comp.reuss_modulus();
assert!(voigt >= reuss, "Voigt bound must be >= Reuss bound");
}
#[test]
fn test_composite_halpin_tsai_in_bounds() {
let comp = DentalComposite::nano_hybrid_v2();
let ht = comp.halpin_tsai_modulus();
let reuss = comp.reuss_modulus();
let voigt = comp.voigt_modulus();
assert!(ht >= reuss && ht <= voigt);
}
#[test]
fn test_cell_hertz_force_positive() {
let cell = CellMechanics::fibroblast_soft();
let f = cell.hertz_force(100e-9);
assert!(f > 0.0);
}
#[test]
fn test_cell_spreading_area_increases_with_stiffness() {
let soft = CellMechanics::fibroblast_soft();
let hard = CellMechanics {
substrate_modulus: 100e3,
..CellMechanics::fibroblast_soft()
};
assert!(hard.spreading_area() > soft.spreading_area());
}
#[test]
fn test_degradation_mw_decreases() {
let pla = BiodegradationKinetics::pla_scaffold();
let mw0 = pla.molecular_weight(0.0);
let mw1 = pla.molecular_weight(1e8);
assert!(approx_eq(mw0, pla.mw_initial, 1e-6));
assert!(mw1 < mw0);
}
#[test]
fn test_degradation_time_to_failure() {
let pla = BiodegradationKinetics::pla_scaffold();
let t = pla.time_to_failure();
assert!(approx_eq(
pla.molecular_weight(t),
pla.mw_critical,
pla.mw_critical * 0.01
));
}
#[test]
fn test_degradation_mass_loss_fraction() {
let pla = BiodegradationKinetics::pla_scaffold();
let ml = pla.mass_loss_fraction(0.0);
assert!(approx_eq(ml, 0.0, 1e-10));
let ml_inf = pla.mass_loss_fraction(1e12);
assert!((ml_inf - 1.0).abs() < 0.01);
}
#[test]
fn test_dentin_modulus_range() {
let d = Dentin::human();
assert!(d.modulus >= 15.0e9 && d.modulus <= 25.0e9);
}
#[test]
fn test_tissue_type_equality() {
assert_eq!(TissueType::Bone, TissueType::Bone);
assert_ne!(TissueType::Bone, TissueType::Cartilage);
}
#[test]
fn test_tissue_type_custom() {
let t = TissueType::Custom("Intervertebral Disc".into());
assert_ne!(t, TissueType::Bone);
}
#[test]
fn test_bone_cortical_modulus_range() {
let b = BoneMaterial::cortical();
let e = b.young_modulus_from_density();
assert!(
e > 1.0e9 && e < 500.0e9,
"Cortical E = {e:.3e} Pa out of range"
);
}
#[test]
fn test_bone_trabecular_modulus_range() {
let b = BoneMaterial::trabecular();
let e = b.young_modulus_from_density();
assert!(
e > 1.0e8 && e < 5.0e9,
"Trabecular E = {e:.3e} Pa out of range"
);
}
#[test]
fn test_bone_modulus_increases_with_density() {
let b = BoneMaterial::cortical();
let e_low = b.modulus_at_density(0.5);
let e_high = b.modulus_at_density(2.0);
assert!(e_high > e_low, "Higher density should give higher modulus");
}
#[test]
fn test_bone_power_law_exponent() {
let b = BoneMaterial::cortical();
let ratio = b.modulus_at_density(2.0) / b.modulus_at_density(1.0);
let expected = (2.0_f64).powf(b.power_law_b);
assert!(
approx_eq(ratio, expected, 1.0),
"Power-law scaling incorrect"
);
}
#[test]
fn test_bone_tissue_type() {
let b = BoneMaterial::cortical();
assert_eq!(b.tissue_type(), TissueType::Bone);
}
#[test]
fn test_bone_bvf_range() {
let c = BoneMaterial::cortical();
let t = BoneMaterial::trabecular();
assert!(c.bvf > 0.9 && c.bvf <= 1.0);
assert!(t.bvf < 0.5 && t.bvf > 0.0);
}
#[test]
fn test_cartilage_creep_constant_positive() {
let c = CartilageMaterial::knee();
let tau = c.creep_time_constant();
assert!(tau > 0.0, "Creep time constant must be positive");
}
#[test]
fn test_cartilage_material_steady_state_strain() {
let c = CartilageMaterial::knee();
let sigma = 1.0e4;
let eps = c.steady_state_strain(sigma);
let expected = sigma / c.aggregate_modulus;
assert!(approx_eq(eps, expected, 1e-12));
}
#[test]
fn test_cartilage_material_fluid_pressure_decays() {
let c = CartilageMaterial::knee();
let p0 = c.fluid_pressure(1.0e4, 0.0);
let p1 = c.fluid_pressure(1.0e4, 100.0);
assert!(p0 > p1, "Fluid pressure should decay over time");
}
#[test]
fn test_cartilage_fluid_fraction_range() {
let c = CartilageMaterial::knee();
assert!(c.fluid_fraction > 0.5 && c.fluid_fraction < 1.0);
}
#[test]
fn test_cartilage_tissue_type() {
assert_eq!(
CartilageMaterial::knee().tissue_type(),
TissueType::Cartilage
);
}
#[test]
fn test_tendon_zero_stress_at_zero_strain() {
let t = TendonLigament::achilles();
assert!(approx_eq(t.stress(0.0), 0.0, EPS));
}
#[test]
fn test_tendon_stress_increases_with_strain() {
let t = TendonLigament::achilles();
let s1 = t.stress(0.01);
let s2 = t.stress(0.02);
let s3 = t.stress(0.05);
assert!(
s1 < s2 && s2 < s3,
"Stress must be monotonically increasing"
);
}
#[test]
fn test_tendon_linear_region() {
let t = TendonLigament::achilles();
let ds = t.stress(0.06) - t.stress(0.05);
let de = 0.01;
let tangent = ds / de;
let tol = t.linear_stiffness * 0.20;
assert!(
(tangent - t.linear_stiffness).abs() < tol,
"Linear tangent {tangent:.3e} should be near {:.3e}",
t.linear_stiffness
);
}
#[test]
fn test_tendon_failure_strain() {
let t = TendonLigament::achilles();
assert!(!t.is_failed(0.05));
assert!(t.is_failed(0.10));
}
#[test]
fn test_tendon_tissue_type() {
assert_eq!(TendonLigament::achilles().tissue_type(), TissueType::Tendon);
}
#[test]
fn test_artery_neo_hookean_energy_zero_at_rest() {
let a = ArterialWall::aorta_media();
assert!(approx_eq(a.neo_hookean_energy(3.0), 0.0, EPS));
}
#[test]
fn test_artery_fiber_energy_zero_in_compression() {
let a = ArterialWall::aorta_media();
assert!(approx_eq(a.fiber_energy(0.8), 0.0, EPS));
assert!(approx_eq(a.fiber_energy(1.0), 0.0, EPS));
}
#[test]
fn test_artery_fiber_energy_positive_in_tension() {
let a = ArterialWall::aorta_media();
assert!(a.fiber_energy(1.2) > 0.0);
}
#[test]
fn test_artery_total_strain_energy() {
let a = ArterialWall::aorta_media();
let w = a.strain_energy(3.5, 1.1);
assert!(w > 0.0);
}
#[test]
fn test_artery_tissue_type() {
assert_eq!(
ArterialWall::aorta_media().tissue_type(),
TissueType::Artery
);
}
#[test]
fn test_muscle_max_force_at_optimal_length() {
let m = MuscleMaterial::biceps();
let f = m.active_force(1.0, m.optimal_fiber_length);
assert!(
approx_eq(f, m.max_isometric_force, 1.0),
"Active force at optimal length should equal F0"
);
}
#[test]
fn test_muscle_zero_force_at_zero_activation() {
let m = MuscleMaterial::biceps();
let f = m.active_force(0.0, m.optimal_fiber_length);
assert!(approx_eq(f, 0.0, EPS));
}
#[test]
fn test_muscle_force_length_bell() {
let m = MuscleMaterial::biceps();
let fl_opt = m.force_length_factor(m.optimal_fiber_length);
let fl_far = m.force_length_factor(m.optimal_fiber_length * 2.0);
assert!(approx_eq(fl_opt, 1.0, 1e-10));
assert!(fl_far < fl_opt);
}
#[test]
fn test_muscle_passive_force_zero_at_optimal() {
let m = MuscleMaterial::biceps();
assert!(approx_eq(m.passive_force(m.optimal_fiber_length), 0.0, EPS));
}
#[test]
fn test_muscle_passive_force_increases_with_stretch() {
let m = MuscleMaterial::biceps();
let f1 = m.passive_force(m.optimal_fiber_length + 0.01);
let f2 = m.passive_force(m.optimal_fiber_length + 0.02);
assert!(f2 > f1);
}
#[test]
fn test_muscle_total_force_sum() {
let m = MuscleMaterial::biceps();
let a = 0.7;
let l = m.optimal_fiber_length;
let total = m.total_force(a, l);
let expected = m.active_force(a, l) + m.passive_force(l);
assert!(approx_eq(total, expected, EPS));
}
#[test]
fn test_muscle_tissue_type() {
assert_eq!(MuscleMaterial::biceps().tissue_type(), TissueType::Muscle);
}
#[test]
fn test_reuss_modulus_two_phase() {
let e1 = 200.0e9; let e2 = 70.0e9; let vf1 = 0.5;
let er = reuss_modulus(e1, e2, vf1);
let ev = voigt_modulus(e1, e2, vf1);
assert!(er <= ev, "Reuss must be ≤ Voigt");
}
#[test]
fn test_voigt_modulus_endpoints() {
let e1 = 100.0e9;
let e2 = 50.0e9;
assert!(approx_eq(voigt_modulus(e1, e2, 0.0), e2, 1.0));
assert!(approx_eq(voigt_modulus(e1, e2, 1.0), e1, 1.0));
}
#[test]
fn test_reuss_modulus_equal_phases() {
let e = 100.0e9;
let er = reuss_modulus(e, e, 0.5);
assert!(approx_eq(er, e, 1.0));
}
#[test]
fn test_strain_energy_neo_hookean_zero_at_rest() {
assert!(approx_eq(
strain_energy_density_neo_hookean(1.0e4, 3.0),
0.0,
EPS
));
}
#[test]
fn test_strain_energy_neo_hookean_positive() {
let w = strain_energy_density_neo_hookean(1.0e4, 4.0);
assert!(w > 0.0);
}
#[test]
fn test_strain_energy_scales_linearly_with_c1() {
let i1 = 4.0;
let w1 = strain_energy_density_neo_hookean(1.0e4, i1);
let w2 = strain_energy_density_neo_hookean(2.0e4, i1);
assert!(approx_eq(w2, 2.0 * w1, EPS));
}
}