1#[allow(unused_imports)]
6use super::functions::*;
7use std::f64::consts::PI;
8
9#[derive(Debug, Clone)]
11pub struct MeniscusModel {
12 pub e_circumferential: f64,
14 pub e_radial: f64,
16 pub shear_modulus: f64,
18 pub permeability: f64,
20 pub fluid_fraction: f64,
22}
23impl MeniscusModel {
24 pub fn new(
26 e_circumferential: f64,
27 e_radial: f64,
28 shear_modulus: f64,
29 permeability: f64,
30 fluid_fraction: f64,
31 ) -> Self {
32 Self {
33 e_circumferential,
34 e_radial,
35 shear_modulus,
36 permeability,
37 fluid_fraction,
38 }
39 }
40 pub fn medial_meniscus() -> Self {
42 Self::new(150e6, 1.4e6, 0.12e6, 1.2e-15, 0.72)
43 }
44 pub fn hoop_stress(&self, joint_force: f64, area: f64) -> f64 {
46 joint_force / area
47 }
48}
49#[derive(Debug, Clone)]
51pub struct CorticalBone {
52 pub density: f64,
54 pub ash_fraction: f64,
56}
57impl CorticalBone {
58 pub fn new(density: f64, ash_fraction: f64) -> Self {
60 Self {
61 density,
62 ash_fraction,
63 }
64 }
65 pub fn femur_cortical() -> Self {
67 Self::new(1.85, 0.62)
68 }
69 pub fn elastic_modulus(&self) -> f64 {
73 2017.0 * self.density.powf(2.5)
74 }
75 pub fn strength_compressive(&self) -> f64 {
79 68.0 * self.density.powf(1.6)
80 }
81 pub fn strength_tensile(&self) -> f64 {
85 131.0 * self.density
86 }
87 pub fn mineral_stiffness_contribution(&self) -> f64 {
89 self.elastic_modulus() * (0.6 + 0.4 * self.ash_fraction)
90 }
91}
92#[derive(Debug, Clone)]
94pub struct MuscleModel {
95 pub f_max: f64,
97 pub l_opt: f64,
99 pub v_max: f64,
101 pub pennation_angle: f64,
103}
104impl MuscleModel {
105 pub fn new(f_max: f64, l_opt: f64, v_max: f64, pennation_angle: f64) -> Self {
107 Self {
108 f_max,
109 l_opt,
110 v_max,
111 pennation_angle,
112 }
113 }
114 pub fn tibialis_anterior() -> Self {
116 Self::new(600.0, 0.068, 0.51, 0.17)
117 }
118 pub fn quadriceps() -> Self {
120 Self::new(1169.0, 0.084, 0.63, 0.17)
121 }
122 pub fn active_force_length(&self, l_normalized: f64) -> f64 {
126 let omega = 0.45;
127 let x = (l_normalized - 1.0) / omega;
128 (-x * x).exp()
129 }
130 pub fn passive_force_length(&self, l_normalized: f64) -> f64 {
134 if l_normalized <= 1.0 {
135 return 0.0;
136 }
137 let k_p = 5.0;
138 let eps_0 = 0.6;
139 let numerator = (k_p * (l_normalized - 1.0) / eps_0).exp() - 1.0;
140 let denominator = k_p.exp() - 1.0;
141 (numerator / denominator).min(1.0)
142 }
143 pub fn force_velocity(&self, v_normalized: f64) -> f64 {
148 if v_normalized <= 0.0 {
149 let a_v = 0.25;
150 let v = v_normalized.max(-1.0 + 1e-9);
151 ((1.0 + v) / (1.0 - v / a_v)).max(0.0)
152 } else {
153 let v = v_normalized.min(1.0);
154 (1.8 - 0.8 * (1.0 + v) / (1.0 - 7.56 * v / 6.0)).max(0.0)
155 }
156 }
157 pub fn muscle_force(&self, l: f64, v: f64, activation: f64) -> f64 {
159 let l_norm = l / self.l_opt;
160 let v_norm = v / self.v_max;
161 let fl = self.active_force_length(l_norm);
162 let fp = self.passive_force_length(l_norm);
163 let fv = self.force_velocity(v_norm);
164 let a = activation.clamp(0.0, 1.0);
165 let cos_psi = self.pennation_angle.cos();
166 (a * fl * fv + fp) * self.f_max * cos_psi
167 }
168 pub fn max_power(&self) -> f64 {
170 self.f_max * self.v_max / 4.0
171 }
172}
173#[derive(Debug, Clone)]
175pub struct BloodVesselModel {
176 pub r0: f64,
178 pub h0: f64,
180 pub e_wall: f64,
182 pub nu_wall: f64,
184}
185impl BloodVesselModel {
186 pub fn new(r0: f64, h0: f64, e_wall: f64, nu_wall: f64) -> Self {
188 Self {
189 r0,
190 h0,
191 e_wall,
192 nu_wall,
193 }
194 }
195 pub fn aorta() -> Self {
197 Self::new(12.5e-3, 2.0e-3, 500e3, 0.45)
198 }
199 pub fn femoral_artery() -> Self {
201 Self::new(3.5e-3, 0.6e-3, 800e3, 0.45)
202 }
203 pub fn laplace_wall_stress(&self, p_transmural: f64) -> f64 {
207 p_transmural * self.r0 / self.h0
208 }
209 pub fn compliance(&self, _p: f64) -> f64 {
213 self.r0 * (1.0 - self.nu_wall * self.nu_wall) / (self.e_wall * self.h0 / self.r0)
214 }
215 pub fn pulse_wave_velocity(&self, rho: f64) -> f64 {
219 (self.e_wall * self.h0 / (2.0 * rho * self.r0)).sqrt()
220 }
221 pub fn radius_at_pressure(&self, p: f64) -> f64 {
223 let c = self.compliance(p);
224 self.r0 + c * p
225 }
226}
227#[derive(Debug, Clone)]
229pub struct CorneaModel {
230 pub e_small: f64,
232 pub e_large: f64,
234 pub transition_strain: f64,
236 pub cct: f64,
238 pub iop: f64,
240}
241impl CorneaModel {
242 pub fn new(e_small: f64, e_large: f64, transition_strain: f64, cct: f64, iop: f64) -> Self {
244 Self {
245 e_small,
246 e_large,
247 transition_strain,
248 cct,
249 iop,
250 }
251 }
252 pub fn healthy_cornea() -> Self {
254 Self::new(0.01e6, 0.5e6, 0.05, 550e-6, 2000.0)
255 }
256 pub fn hoop_stress_iop(&self) -> f64 {
260 let r = 7.8e-3;
261 self.iop * r / (2.0 * self.cct)
262 }
263 pub fn tangent_modulus(&self, strain: f64) -> f64 {
265 if strain < self.transition_strain {
266 self.e_small
267 } else {
268 self.e_large
269 }
270 }
271}
272#[allow(dead_code)]
274#[derive(Debug, Clone, PartialEq)]
275pub enum FailureMode {
276 NoFailure,
278 TensileFailure,
280 CompressiveFailure,
282 ShearFailure,
284 FatigueFailure,
286 FractureFailure,
288}
289#[derive(Debug, Clone)]
291pub struct HeartValveTissue {
292 pub e_circ: f64,
294 pub e_radial: f64,
296 pub flexural_rigidity: f64,
298 pub thickness: f64,
300}
301impl HeartValveTissue {
302 pub fn new(e_circ: f64, e_radial: f64, flexural_rigidity: f64, thickness: f64) -> Self {
304 Self {
305 e_circ,
306 e_radial,
307 flexural_rigidity,
308 thickness,
309 }
310 }
311 pub fn aortic_valve() -> Self {
313 Self::new(15e6, 2e6, 0.001, 0.5e-3)
314 }
315 pub fn bending_stiffness(&self, nu: f64) -> f64 {
317 self.e_circ * self.thickness.powi(3) / (12.0 * (1.0 - nu * nu))
318 }
319 pub fn anisotropy_ratio(&self) -> f64 {
321 self.e_circ / self.e_radial
322 }
323}
324#[derive(Debug, Clone)]
328pub struct SoftTissueMaterial {
329 pub c1: f64,
331 pub c2: f64,
333 pub alpha: f64,
335 pub beta: f64,
337}
338impl SoftTissueMaterial {
339 pub fn new(c1: f64, c2: f64, alpha: f64, beta: f64) -> Self {
341 Self {
342 c1,
343 c2,
344 alpha,
345 beta,
346 }
347 }
348 pub fn liver() -> Self {
350 Self::new(350.0, 0.57, 0.1, 1000.0)
351 }
352 pub fn brain() -> Self {
354 Self::new(264.0, 0.4, 0.05, 500.0)
355 }
356 pub fn q_exponent(&self, i1: f64, i2: f64, j: f64) -> f64 {
358 self.c2 * (i1 - 3.0) + self.alpha * (i2 - 3.0) + self.beta * (j - 1.0).powi(2)
359 }
360 pub fn strain_energy_density(&self, i1: f64, i2: f64, j: f64) -> f64 {
364 let q = self.q_exponent(i1, i2, j);
365 self.c1 * (q.exp() - 1.0)
366 }
367 pub fn cauchy_stress_green_lagrange(&self, strain: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
372 let id = identity3();
373 let mut f = [[0.0f64; 3]; 3];
374 for i in 0..3 {
375 for j in 0..3 {
376 f[i][j] = id[i][j] + strain[i][j];
377 }
378 }
379 let c = right_cauchy_green(&f);
380 let i1 = trace3(&c);
381 let i2 = second_invariant_c(&c);
382 let j = det3(&f).abs().max(1e-14);
383 let q = self.q_exponent(i1, i2, j);
384 let exp_q = q.exp();
385 let dw_di1 = self.c1 * self.c2 * exp_q;
386 let dw_di2 = self.c1 * self.alpha * exp_q;
387 let dw_dj = self.c1 * self.beta * 2.0 * (j - 1.0) * exp_q;
388 let ft = transpose3(&f);
389 let b = matmul3(&f, &ft);
390 let b2 = matmul3(&b, &b);
391 let i1_b = trace3(&b);
392 let term1 = scale3(2.0 / j * dw_di1, &b);
393 let term2_inner = add3(&scale3(i1_b, &b), &scale3(-1.0, &b2));
394 let term2 = scale3(2.0 / j * dw_di2, &term2_inner);
395 let term3 = scale3(dw_dj, &id);
396 let sigma1 = add3(&term1, &term2);
397 add3(&sigma1, &term3)
398 }
399}
400#[derive(Debug, Clone)]
404pub struct TendonLigamentModel {
405 pub toe_region_modulus: f64,
407 pub linear_modulus: f64,
409 pub transition_strain: f64,
411 pub c_relax: f64,
413 pub tau1: f64,
415 pub tau2: f64,
417}
418impl TendonLigamentModel {
419 pub fn new(
421 toe_region_modulus: f64,
422 linear_modulus: f64,
423 transition_strain: f64,
424 c_relax: f64,
425 tau1: f64,
426 tau2: f64,
427 ) -> Self {
428 Self {
429 toe_region_modulus,
430 linear_modulus,
431 transition_strain,
432 c_relax,
433 tau1,
434 tau2,
435 }
436 }
437 pub fn patellar_tendon() -> Self {
439 Self::new(200e6, 1500e6, 0.02, 0.4, 0.01, 10.0)
440 }
441 pub fn acl() -> Self {
443 Self::new(50e6, 300e6, 0.03, 0.35, 0.02, 15.0)
444 }
445 pub fn stress_strain_nonlinear(&self, strain: f64) -> f64 {
450 if strain < 0.0 {
451 return 0.0;
452 }
453 if strain < self.transition_strain {
454 self.toe_region_modulus * strain * strain / self.transition_strain
455 } else {
456 let sigma_transition = self.toe_region_modulus * self.transition_strain;
457 sigma_transition + self.linear_modulus * (strain - self.transition_strain)
458 }
459 }
460 pub fn relaxation_function(&self, t: f64) -> f64 {
465 if t <= 0.0 {
466 return 1.0;
467 }
468 let g1 = (-t / self.tau1).exp();
469 let g2 = (-t / self.tau2).exp();
470 let denom = 1.0 + self.c_relax * (self.tau2 / self.tau1).ln();
471 let amplitude = self.c_relax / denom;
472 let g_inf = 1.0 - amplitude;
473 g_inf + 0.5 * amplitude * g1 + 0.5 * amplitude * g2
474 }
475 pub fn viscoelastic_stress(&self, strain: f64, t: f64) -> f64 {
479 let sigma_elastic = self.stress_strain_nonlinear(strain);
480 let g_t = self.relaxation_function(t);
481 sigma_elastic * g_t
482 }
483 pub fn ultimate_stress(&self, failure_strain: f64) -> f64 {
485 self.stress_strain_nonlinear(failure_strain)
486 }
487}
488#[derive(Debug, Clone)]
490pub struct TrabecularBone {
491 pub bv_tv: f64,
493 pub ash_fraction: f64,
495}
496impl TrabecularBone {
497 pub fn new(bv_tv: f64, ash_fraction: f64) -> Self {
499 Self {
500 bv_tv,
501 ash_fraction,
502 }
503 }
504 pub fn vertebral() -> Self {
506 Self::new(0.15, 0.55)
507 }
508 pub fn elastic_modulus_trabecular(&self) -> f64 {
512 1904.0 * self.bv_tv.powf(1.64)
513 }
514 pub fn yield_stress_trabecular(&self) -> f64 {
518 24.8 * self.bv_tv.powf(1.6)
519 }
520 pub fn isotropy_ratio(&self) -> f64 {
522 (self.bv_tv / 0.35).min(1.0)
523 }
524}
525#[derive(Debug, Clone)]
527pub struct LigamentWrapModel {
528 pub rest_length: f64,
530 pub stiffness: f64,
532 pub slack_length: f64,
534}
535impl LigamentWrapModel {
536 pub fn new(rest_length: f64, stiffness: f64, slack_length: f64) -> Self {
538 Self {
539 rest_length,
540 stiffness,
541 slack_length,
542 }
543 }
544 pub fn restraint_force(&self, current_length: f64) -> f64 {
546 if current_length <= self.slack_length {
547 0.0
548 } else {
549 self.stiffness * (current_length - self.slack_length)
550 }
551 }
552 pub fn strain(&self, current_length: f64) -> f64 {
554 (current_length - self.rest_length) / self.rest_length
555 }
556}
557#[allow(dead_code)]
565#[derive(Debug, Clone)]
566pub struct TissueFailureCriteria {
567 pub uts: f64,
569 pub ucs: f64,
571 pub uss: f64,
573 pub fatigue_coeff: f64,
575 pub fatigue_exp: f64,
577 pub k_ic: f64,
579 pub g_c: f64,
581 pub tsai_wu_f12_star: f64,
583}
584impl TissueFailureCriteria {
585 pub fn cortical_bone() -> Self {
587 Self {
588 uts: 133e6,
589 ucs: 193e6,
590 uss: 68e6,
591 fatigue_coeff: 140e6,
592 fatigue_exp: -0.08,
593 k_ic: 2.2e6,
594 g_c: 600.0,
595 tsai_wu_f12_star: -0.5,
596 }
597 }
598 pub fn articular_cartilage() -> Self {
600 Self {
601 uts: 15e6,
602 ucs: 5e6,
603 uss: 4e6,
604 fatigue_coeff: 10e6,
605 fatigue_exp: -0.10,
606 k_ic: 0.2e6,
607 g_c: 100.0,
608 tsai_wu_f12_star: -0.5,
609 }
610 }
611 pub fn ligament() -> Self {
613 Self {
614 uts: 38e6,
615 ucs: 1e6,
616 uss: 10e6,
617 fatigue_coeff: 30e6,
618 fatigue_exp: -0.09,
619 k_ic: 0.5e6,
620 g_c: 200.0,
621 tsai_wu_f12_star: -0.5,
622 }
623 }
624 pub fn max_principal_stress(&self, s1: f64, s2: f64, s3: f64) -> FailureMode {
628 let max_tensile = s1.max(s2).max(s3);
629 let max_compressive = s1.min(s2).min(s3);
630 if max_tensile >= self.uts {
631 FailureMode::TensileFailure
632 } else if max_compressive <= -self.ucs {
633 FailureMode::CompressiveFailure
634 } else {
635 FailureMode::NoFailure
636 }
637 }
638 pub fn tensile_safety_factor(&self, sigma_max: f64) -> f64 {
640 if sigma_max <= 0.0 {
641 f64::INFINITY
642 } else {
643 self.uts / sigma_max
644 }
645 }
646 pub fn tsai_wu_index(&self, sigma1: f64, sigma2: f64, tau12: f64) -> f64 {
651 let f1 = 1.0 / self.uts - 1.0 / self.ucs;
652 let f2 = f1;
653 let f11 = 1.0 / (self.uts * self.ucs);
654 let f22 = f11;
655 let f66 = 1.0 / (self.uss * self.uss);
656 let f12 = self.tsai_wu_f12_star * (f11 * f22).sqrt();
657 f1 * sigma1
658 + f2 * sigma2
659 + f11 * sigma1 * sigma1
660 + f22 * sigma2 * sigma2
661 + 2.0 * f12 * sigma1 * sigma2
662 + f66 * tau12 * tau12
663 }
664 pub fn fatigue_life_cycles(&self, stress_amplitude: f64) -> f64 {
668 if stress_amplitude <= 0.0 {
669 return f64::INFINITY;
670 }
671 let ratio = stress_amplitude / self.fatigue_coeff;
672 if ratio <= 0.0 {
673 return f64::INFINITY;
674 }
675 let two_n = ratio.powf(1.0 / self.fatigue_exp);
676 two_n / 2.0
677 }
678 pub fn fracture_criterion(&self, k_i: f64) -> FailureMode {
682 if k_i >= self.k_ic {
683 FailureMode::FractureFailure
684 } else {
685 FailureMode::NoFailure
686 }
687 }
688 pub fn energy_release_rate(&self, k_i: f64, e_modulus: f64, poisson: f64) -> f64 {
692 k_i * k_i * (1.0 - poisson * poisson) / e_modulus
693 }
694 pub fn toughness_criterion(&self, k_i: f64, e_modulus: f64, poisson: f64) -> FailureMode {
696 let g = self.energy_release_rate(k_i, e_modulus, poisson);
697 if g >= self.g_c {
698 FailureMode::FractureFailure
699 } else {
700 FailureMode::NoFailure
701 }
702 }
703}
704#[derive(Debug, Clone)]
708pub struct CartilageModel {
709 pub solid_modulus_h_a: f64,
711 pub permeability: f64,
713 pub fluid_fraction: f64,
715 pub thickness: f64,
717}
718impl CartilageModel {
719 pub fn new(
721 solid_modulus_h_a: f64,
722 permeability: f64,
723 fluid_fraction: f64,
724 thickness: f64,
725 ) -> Self {
726 Self {
727 solid_modulus_h_a,
728 permeability,
729 fluid_fraction,
730 thickness,
731 }
732 }
733 pub fn articular_cartilage() -> Self {
735 Self::new(0.5e6, 1e-15, 0.75, 2e-3)
736 }
737 pub fn consolidation_coefficient(&self) -> f64 {
739 self.solid_modulus_h_a * self.permeability
740 }
741 pub fn creep_compliance(&self, t: f64) -> f64 {
745 let cv = self.consolidation_coefficient();
746 let h = self.thickness;
747 let mut sum = 0.0;
748 for n in 1..=20usize {
749 let m = (2 * n - 1) as f64;
750 let coeff = 8.0 / (PI * PI * m * m);
751 let exponent = -cv * m * m * PI * PI * t / (4.0 * h * h);
752 sum += coeff * exponent.exp();
753 }
754 (1.0 / self.solid_modulus_h_a) * (1.0 - sum)
755 }
756 pub fn stress_relaxation(&self, t: f64) -> f64 {
760 let cv = self.consolidation_coefficient();
761 let h = self.thickness;
762 let mut sum = 0.0;
763 for n in 1..=20usize {
764 let m = (2 * n - 1) as f64;
765 let coeff = 8.0 / (PI * PI * m * m);
766 let exponent = -cv * m * m * PI * PI * t / (4.0 * h * h);
767 sum += coeff * exponent.exp();
768 }
769 sum
770 }
771}
772#[derive(Debug, Clone)]
774pub struct SkinModel {
775 pub e_epidermis: f64,
777 pub e_dermis: f64,
779 pub nu_epidermis: f64,
781 pub nu_dermis: f64,
783 pub thickness_e: f64,
785 pub thickness_d: f64,
787}
788impl SkinModel {
789 pub fn new(
791 e_epidermis: f64,
792 e_dermis: f64,
793 nu_epidermis: f64,
794 nu_dermis: f64,
795 thickness_e: f64,
796 thickness_d: f64,
797 ) -> Self {
798 Self {
799 e_epidermis,
800 e_dermis,
801 nu_epidermis,
802 nu_dermis,
803 thickness_e,
804 thickness_d,
805 }
806 }
807 pub fn human_forearm() -> Self {
809 Self::new(1e6, 0.08e6, 0.48, 0.49, 0.1e-3, 1.2e-3)
810 }
811 pub fn effective_modulus(&self, indentation: f64) -> f64 {
815 let h_e = self.thickness_e;
816 let w = (indentation / h_e).min(1.0);
817
818 (1.0 - w) * self.e_epidermis + w * self.e_dermis
819 }
820 pub fn effective_poisson(&self, indentation: f64) -> f64 {
822 let w = (indentation / self.thickness_e).min(1.0);
823 (1.0 - w) * self.nu_epidermis + w * self.nu_dermis
824 }
825 pub fn indentation_force_hertz(&self, r_indenter: f64, e_eff: f64, delta: f64) -> f64 {
830 let nu = self.effective_poisson(delta);
831 let e_reduced = e_eff / (1.0 - nu * nu);
832 (4.0 / 3.0) * e_reduced * r_indenter.sqrt() * delta.abs().powf(1.5)
833 }
834 pub fn total_thickness(&self) -> f64 {
836 self.thickness_e + self.thickness_d
837 }
838}
839#[derive(Debug, Clone)]
841pub struct IntervertebralDiscModel {
842 pub nucleus_bulk_modulus: f64,
844 pub annulus_shear_modulus: f64,
846 pub height: f64,
848 pub outer_radius: f64,
850 pub inner_radius: f64,
852}
853impl IntervertebralDiscModel {
854 pub fn new(
856 nucleus_bulk_modulus: f64,
857 annulus_shear_modulus: f64,
858 height: f64,
859 outer_radius: f64,
860 inner_radius: f64,
861 ) -> Self {
862 Self {
863 nucleus_bulk_modulus,
864 annulus_shear_modulus,
865 height,
866 outer_radius,
867 inner_radius,
868 }
869 }
870 pub fn lumbar_l4_l5() -> Self {
872 Self::new(1.72e6, 0.17e6, 11e-3, 23e-3, 12e-3)
873 }
874 pub fn compressive_stiffness(&self) -> f64 {
876 let area_n = PI * self.inner_radius * self.inner_radius;
877 let k_nucleus = self.nucleus_bulk_modulus * area_n / self.height;
878 let area_a =
879 PI * (self.outer_radius * self.outer_radius - self.inner_radius * self.inner_radius);
880 let k_annulus = self.annulus_shear_modulus * area_a / self.height;
881 k_nucleus + k_annulus
882 }
883 pub fn intradiscal_pressure(&self, axial_force: f64) -> f64 {
885 let area_n = PI * self.inner_radius * self.inner_radius;
886 axial_force / area_n
887 }
888}
889#[derive(Debug, Clone)]
893pub struct FiberReinforcedTissue {
894 pub c_matrix: f64,
896 pub a0: [f64; 3],
898 pub kappa: f64,
900 pub k1: f64,
902 pub k2: f64,
904}
905impl FiberReinforcedTissue {
906 pub fn new(c_matrix: f64, a0: [f64; 3], kappa: f64, k1: f64, k2: f64) -> Self {
908 Self {
909 c_matrix,
910 a0,
911 kappa,
912 k1,
913 k2,
914 }
915 }
916 pub fn aortic_tissue() -> Self {
918 let a0 = [0.0, 1.0, 0.0];
919 Self::new(18.4e3, a0, 0.226, 996.6e3, 524.6)
920 }
921 pub fn fiber_strain_energy(&self, i4: f64) -> f64 {
925 if i4 < 1.0 {
926 return 0.0;
927 }
928 let e = i4 - 1.0;
929 (self.k1 / (2.0 * self.k2)) * ((self.k2 * e * e).exp() - 1.0)
930 }
931 pub fn total_strain_energy(&self, i1: f64, i4: f64) -> f64 {
935 let w_matrix = self.c_matrix * (i1 - 3.0);
936 let w_fiber = self.fiber_strain_energy(i4);
937 w_matrix + w_fiber
938 }
939 #[allow(clippy::needless_range_loop)]
941 pub fn compute_i4(&self, f: &[[f64; 3]; 3]) -> f64 {
942 let c = right_cauchy_green(f);
943 let mut ca = [0.0f64; 3];
944 for i in 0..3 {
945 for j in 0..3 {
946 ca[i] += c[i][j] * self.a0[j];
947 }
948 }
949 let mut i4 = 0.0;
950 for i in 0..3 {
951 i4 += self.a0[i] * ca[i];
952 }
953 i4
954 }
955}
956#[allow(dead_code)]
965#[derive(Debug, Clone)]
966pub struct CardiacMuscleModel {
967 pub c_passive: f64,
969 pub bf: f64,
971 pub bt: f64,
973 pub bfs: f64,
975 pub t_max: f64,
977 pub ca50: f64,
979 pub n_hill: f64,
981 pub l0: f64,
983}
984impl CardiacMuscleModel {
985 pub fn left_ventricle() -> Self {
987 Self {
988 c_passive: 880.0,
989 bf: 18.48,
990 bt: 3.58,
991 bfs: 1.627,
992 t_max: 135_480.0,
993 ca50: 0.805,
994 n_hill: 2.0,
995 l0: 1.58,
996 }
997 }
998 pub fn passive_strain_energy(&self, e_ff: f64, e_ss: f64, e_nn: f64, e_fs: f64) -> f64 {
1003 let q = self.bf * e_ff * e_ff
1004 + self.bt * (e_ss * e_ss + e_nn * e_nn)
1005 + self.bfs * (2.0 * e_fs * e_fs);
1006 self.c_passive / 2.0 * (q.exp() - 1.0)
1007 }
1008 pub fn active_fiber_stress(&self, l: f64, ca: f64) -> f64 {
1013 if l <= self.l0 {
1014 return 0.0;
1015 }
1016 let ca50_n = self.ca50.powf(self.n_hill);
1017 let ca_n = ca.powf(self.n_hill);
1018 let activation = ca_n / (ca_n + ca50_n);
1019 let l_factor = ((l - self.l0) / (2.3 - self.l0)).clamp(0.0, 1.0);
1020 self.t_max * activation * l_factor
1021 }
1022 pub fn total_fiber_stress(
1024 &self,
1025 e_ff: f64,
1026 e_ss: f64,
1027 e_nn: f64,
1028 e_fs: f64,
1029 l: f64,
1030 ca: f64,
1031 ) -> f64 {
1032 let w = self.passive_strain_energy(e_ff, e_ss, e_nn, e_fs);
1033 let dw = {
1034 let delta = 1e-6;
1035 let w2 = self.passive_strain_energy(e_ff + delta, e_ss, e_nn, e_fs);
1036 (w2 - w) / delta
1037 };
1038 dw + self.active_fiber_stress(l, ca)
1039 }
1040 pub fn elastance_at_peak(&self, volume: f64, v0: f64) -> f64 {
1044 self.t_max / (volume - v0).abs().max(1e-6)
1045 }
1046 pub fn stroke_work(&self, edv: f64, esv: f64, _map: f64) -> f64 {
1049 let sv = edv - esv;
1050 let ees = self.elastance_at_peak(edv, esv);
1051 ees * sv * sv / 2.0
1052 }
1053}
1054#[derive(Debug, Clone)]
1056pub struct BoneModel {
1057 pub cortical: CorticalBone,
1059 pub trabecular: TrabecularBone,
1061 pub cortical_thickness: f64,
1063 pub total_width: f64,
1065}
1066impl BoneModel {
1067 pub fn new(
1069 cortical: CorticalBone,
1070 trabecular: TrabecularBone,
1071 cortical_thickness: f64,
1072 total_width: f64,
1073 ) -> Self {
1074 Self {
1075 cortical,
1076 trabecular,
1077 cortical_thickness,
1078 total_width,
1079 }
1080 }
1081 pub fn femoral_neck() -> Self {
1083 Self::new(
1084 CorticalBone::femur_cortical(),
1085 TrabecularBone::vertebral(),
1086 2e-3,
1087 30e-3,
1088 )
1089 }
1090 pub fn effective_axial_modulus(&self) -> f64 {
1092 let r_total = self.total_width / 2.0;
1093 let r_inner = r_total - self.cortical_thickness;
1094 let a_cortical = PI * (r_total * r_total - r_inner * r_inner);
1095 let a_trabecular = PI * r_inner * r_inner;
1096 let a_total = PI * r_total * r_total;
1097 (self.cortical.elastic_modulus() * a_cortical
1098 + self.trabecular.elastic_modulus_trabecular() * a_trabecular)
1099 / a_total
1100 }
1101}
1102#[allow(dead_code)]
1112#[derive(Debug, Clone)]
1113pub struct HuxleyModel {
1114 pub crossbridge_stiffness: f64,
1116 pub h: f64,
1118 pub f1: f64,
1120 pub g1: f64,
1122 pub g2: f64,
1124 pub crossbridge_density: f64,
1126}
1127impl HuxleyModel {
1128 pub fn new(
1130 crossbridge_stiffness: f64,
1131 h: f64,
1132 f1: f64,
1133 g1: f64,
1134 g2: f64,
1135 crossbridge_density: f64,
1136 ) -> Self {
1137 Self {
1138 crossbridge_stiffness,
1139 h,
1140 f1,
1141 g1,
1142 g2,
1143 crossbridge_density,
1144 }
1145 }
1146 pub fn fast_twitch() -> Self {
1148 Self::new(2.0e-3, 11e-9, 3.68e9, 1.0e9, 3.68e9, 5.0e14)
1149 }
1150 pub fn steady_state_fraction(&self, x: f64) -> f64 {
1154 if x > 0.0 && x < self.h {
1155 self.f1 / (self.f1 + self.g1)
1156 } else {
1157 0.0
1158 }
1159 }
1160 pub fn isometric_force(&self) -> f64 {
1164 let n_ss = self.f1 / (self.f1 + self.g1);
1165 self.crossbridge_density * n_ss * self.crossbridge_stiffness * self.h * self.h / 2.0
1166 }
1167 pub fn force_velocity_ratio(&self, v: f64) -> f64 {
1172 if v.abs() < 1e-30 {
1173 return 1.0;
1174 }
1175 let f1 = self.f1;
1176 let g1 = self.g1;
1177 let g2 = self.g2;
1178 let h = self.h;
1179 if v > 0.0 {
1180 let phi = v / (h * (f1 + g1));
1181 let numerator = 1.0 - phi * (g2 / (f1 + g2));
1182 numerator.max(0.0)
1183 } else {
1184 let phi = (-v) / (h * (f1 + g1));
1185 (1.0 + phi * g2 / (f1 + g2)).min(1.8)
1186 }
1187 }
1188 pub fn power_output(&self, v: f64) -> f64 {
1190 let f0 = self.isometric_force();
1191 f0 * self.force_velocity_ratio(v) * v
1192 }
1193 pub fn optimal_velocity(&self, v_max: f64) -> f64 {
1197 let mut a = 0.0f64;
1198 let mut b = v_max;
1199 let phi = (5.0_f64.sqrt() - 1.0) / 2.0;
1200 let tol = 1e-9;
1201 let mut c = b - phi * (b - a);
1202 let mut d = a + phi * (b - a);
1203 while (b - a).abs() > tol {
1204 if self.power_output(c) < self.power_output(d) {
1205 a = c;
1206 } else {
1207 b = d;
1208 }
1209 c = b - phi * (b - a);
1210 d = a + phi * (b - a);
1211 }
1212 (a + b) / 2.0
1213 }
1214}