Coverage for physioblocks / library / model_components / dynamics.py: 100%
271 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
1# SPDX-FileCopyrightText: Copyright INRIA
2#
3# SPDX-License-Identifier: LGPL-3.0-only
4#
5# Copyright INRIA
6#
7# This file is part of PhysioBlocks, a library mostly developed by the
8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9#
10# Authors:
11# - Colin Drieu
12# - Dominique Chapelle
13# - François Kimmig
14# - Philippe Moireau
15#
16# PhysioBlocks is free software: you can redistribute it and/or modify it under the
17# terms of the GNU Lesser General Public License as published by the Free Software
18# Foundation, version 3 of the License.
19#
20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23#
24# You should have received a copy of the GNU Lesser General Public License along with
25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
27"""
28Describes dynamics models.
29"""
31from dataclasses import dataclass
32from typing import Any
34import numpy as np
35from numpy.typing import NDArray
37import physioblocks.utils.math_utils as math_utils
38from physioblocks.computing import Expression, ModelComponent, Quantity, diff, mid_point
39from physioblocks.computing.assembling import EqSystem
40from physioblocks.registers import register_type
41from physioblocks.simulation import State, Time
42from physioblocks.simulation.solvers import NewtonSolver
44# Constant for the spherical dynamics type id
45SPHERICAL_DYNAMICS_TYPE_ID = "spherical_dynamics"
47SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID = "disp"
49_DISP_EPSILON = 1.0e-6
50_STATIC_PROBLEM_TOL = 1e-6
51_STATIC_PROBLEM_MAX_IT = 10
52_STATIC_PROBLEM_DISP_MAG = 0.010
53_STATIC_PROBLEM_MIN_PRESSURE_STEP = 1.0
56@dataclass
57class _SphericalDynamicsStaticModelComponent(ModelComponent):
58 """
59 ModelComponent containing the quantities and the expressions to solve the
60 static problem in the Spherical Dynamics Model in order to initialize the
61 displacement and fiber deformation.
62 """
64 disp: Quantity[np.float64]
65 """Displacement"""
67 pressure: Quantity[np.float64]
68 """pressure"""
70 thickness: Quantity[np.float64]
71 """thickness"""
73 hyperelastic_cst: Quantity[NDArray[np.float64]]
74 """hyperelastic constant"""
76 inv_radius: Quantity[np.float64]
77 """sphere radius"""
79 pressure_external: Quantity[np.float64]
80 """external pressure"""
82 def initialize(self) -> None:
83 """
84 Initialize block attributes from current quantity values
85 """
86 self.hyperelastic_cst_01 = (
87 self.hyperelastic_cst.current[0] * self.hyperelastic_cst.current[1]
88 )
89 self.hyperelastic_cst_23 = (
90 self.hyperelastic_cst.current[2] * self.hyperelastic_cst.current[3]
91 )
92 self.thickness_radius_ratio = self.thickness.current * self.inv_radius.current
93 self.half_thickness_radius_ratio = 0.5 * self.thickness_radius_ratio
95 def dynamics_static_residual(self) -> Any:
96 """
97 Compute the residual for the dynamics static problem.
99 :return: the static problem residual
100 :rtype: np.float64
101 """
102 pressure_external = mid_point(self.pressure_external)
104 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius.current
105 fluid_volume_diff = np.pow(
106 disp_new_ratio
107 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio,
108 2,
109 ) * (1.0 + np.pow(disp_new_ratio, -3) * self.thickness_radius_ratio)
111 j4_new = np.pow(disp_new_ratio, 2)
112 j1_new = 2.0 * j4_new + np.pow(disp_new_ratio, -4)
114 j4_diff_new = 2.0 * self.inv_radius.current * (disp_new_ratio)
115 j1_diff_new = 2.0 * (
116 j4_diff_new - 2.0 * self.inv_radius.current * np.pow(disp_new_ratio, -5)
117 )
119 hyperelastic_potential_diff = 2.0 * (
120 (
121 self.hyperelastic_cst_01
122 * j1_diff_new
123 * (j1_new - 3.0)
124 * np.exp(self.hyperelastic_cst.current[1] * np.pow(j1_new - 3.0, 2))
125 )
126 + (
127 self.hyperelastic_cst_23
128 * j4_diff_new
129 * (j4_new - 1.0)
130 * np.exp(self.hyperelastic_cst.current[3] * np.pow(j4_new - 1.0, 2))
131 )
132 )
134 passive_stress = self.thickness.current * hyperelastic_potential_diff
135 external_stress = (self.pressure.new - pressure_external) * fluid_volume_diff
137 return passive_stress - external_stress
139 def dynamics_static_residual_ddisp(self) -> Any:
140 """
141 Compute the partial derivative for disp for the the static problem residual.
143 :return: the residual partial derivative for disp
144 :rtype: np.float64
145 """
146 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius.current
147 pressure_external = mid_point(self.pressure_external)
149 j4_new = np.pow(disp_new_ratio, 2)
150 j1_new = 2.0 * j4_new + np.pow(disp_new_ratio, -4)
152 j4_diff_new = 2.0 * self.inv_radius.current * (disp_new_ratio)
153 j1_diff_new = 2.0 * (
154 j4_diff_new - 2.0 * self.inv_radius.current * np.pow(disp_new_ratio, -5)
155 )
157 j1_diff_diff_new = 4.0 * np.pow(self.inv_radius.current, 2) + 20.0 * np.pow(
158 self.inv_radius.current, 2
159 ) * np.pow(disp_new_ratio, -6)
160 j4_diff_diff_new = 2.0 * np.pow(self.inv_radius.current, 2)
162 hyperelastic_potential_diff_diff = 2.0 * (
163 self.hyperelastic_cst.current[0]
164 * self.hyperelastic_cst.current[1]
165 * (
166 j1_diff_diff_new * (j1_new - 3.0)
167 + np.pow(j1_diff_new, 2)
168 + (
169 2.0
170 * self.hyperelastic_cst.current[1]
171 * np.pow(j1_diff_new * (j1_new - 3.0), 2)
172 )
173 )
174 * np.exp(self.hyperelastic_cst.current[1] * np.pow(j1_new - 3.0, 2))
175 + self.hyperelastic_cst.current[2]
176 * self.hyperelastic_cst.current[3]
177 * (
178 j4_diff_diff_new * (j4_new - 1.0)
179 + np.pow(j4_diff_new, 2)
180 + (
181 2.0
182 * self.hyperelastic_cst.current[3]
183 * np.pow(j4_diff_new * (j4_new - 1.0), 2)
184 )
185 )
186 * np.exp(self.hyperelastic_cst.current[3] * np.pow(j4_new - 1.0, 2))
187 )
189 passive_stress_diff = self.thickness.current * hyperelastic_potential_diff_diff
191 fluid_volume_diff_diff = self.inv_radius.current * (
192 2.0
193 * (
194 1.0
195 + self.disp.new * self.inv_radius.current
196 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio
197 )
198 * np.pow(1.0 + np.pow(disp_new_ratio, -3) * self.thickness_radius_ratio, 2)
199 - 3.0
200 * self.thickness_radius_ratio
201 * np.pow(disp_new_ratio, -4)
202 * np.pow(
203 1.0
204 + self.disp.new * self.inv_radius.current
205 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio,
206 2,
207 )
208 )
210 ext_stress_diff = (
211 self.pressure.new - pressure_external
212 ) * fluid_volume_diff_diff
214 return passive_stress_diff - ext_stress_diff
217_spherical_dynamics_static_residual_expr = Expression(
218 1,
219 _SphericalDynamicsStaticModelComponent.dynamics_static_residual,
220 {
221 SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID: _SphericalDynamicsStaticModelComponent.dynamics_static_residual_ddisp # noqa: E501
222 },
223)
225_SphericalDynamicsStaticModelComponent.declares_internal_expression(
226 SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID, _spherical_dynamics_static_residual_expr
227)
230@dataclass
231@register_type(SPHERICAL_DYNAMICS_TYPE_ID)
232class SphericalDynamicsModelComponent(ModelComponent):
233 r"""
234 Implementation of the spherical dynamics model.
236 It provides a dynamics on :math:`y` the displacement of a sphere radius.
238 **Internal Equation:**
240 .. math::
242 \frac{P - P_{ext}}{|\Omega_0|} \frac{\partial V(y)}{\partial y}
243 - \rho_0 \ddot{y}
244 - \frac{\partial W_e(y)}{\partial y}
245 - \frac{1}{R_0} \Sigma_v(y,\dot{y})
246 - \frac{k_s}{R_0} \Bigl( \frac{y}{R_0}-e_c \Bigr) = 0
248 With:
250 * :math:`W_e(y)` represents an elastic energy per unit volume given by
252 .. math::
254 W_e(y) =
255 C_0 \exp \Bigl[ C_1 \bigl(2C(y)+C(y)^{-2}-3\bigr)^2 \Bigr]
256 + C_2 \exp \Bigl[ C_3 \bigl(C(y)-1\bigr)^2 \Bigr]
258 where :math:`(C_0,C_1,C_2,C_3)` denote material parameters of the passive
259 constitutive law, and :math:`C(y)= (1+y/R_0)^2` represents the component
260 of the right Cauchy-Green deformation tensor along the fiber direction
262 * :math:`\Sigma_v(y,\dot{y})` denotes a viscous stress defined by
264 .. math::
266 \Sigma_v(y,\dot{y}) =
267 2 \eta \, \bigl( C(y) + 2 C(y)^{-5} \bigr) \frac{\dot{y}}{R_0}
269 with :math:`\eta` a solid viscosity parameter
271 * :math:`k_s` denotes a passive elastic modulus
273 * :math:`|\Omega_0|` is the total volume of sphere tissue in the reference
274 configuration
276 .. math::
278 |\Omega_0| =
279 \frac{4}{3}\pi \Biggl[ \Bigl( R_0+\frac{d_0}{2} \Bigr)^3
280 - \Bigl( R_0-\frac{d_0}{2} \Bigr)^3 \Biggr]
282 with :math:`d_0` the wall thickness in the reference configuration, and
283 factorizing :math:`R_0` from the bracket a Taylor expansion readily gives
284 the following approximation that we use in the implementation up to the
285 third order in :math:`d_0/2R_0`:
287 .. math::
289 |\Omega_0| = 4\pi R_0^2 d_0
291 * :math:`V(y)` denotes the volume of the sphere, i.e.
293 .. math::
295 V(y) = \frac{4}{3}\pi \Bigl( R(y)-\frac{d(y)}{2} \Bigr)^3
296 = \frac{4}{3}\pi \Bigl( R_0+y-\frac{d_0}{2}C(y)^{-1} \Bigr)^3,
298 with :math:`d(y)` the wall thickness in the deformed configuration.
300 **Discretised:**
302 .. math::
304 \frac{P^{n + \frac{1}{2}}
305 - P_{ext}^{n + \frac{1}{2}}}{|\Omega_0|} DV^{{n + \frac{1}{2}}\sharp}
306 - \rho_0 \Bigl[ \frac{\dot{y}^{n+1}-\dot{y}^n}{\Delta t^n} \Bigr]
307 - DW^{{n + \frac{1}{2}}\sharp}
308 - \frac{1}{R_0} \Sigma_v(y^{n + \frac{1}{2}},\dot{y}^{n + \frac{1}{2}})
309 - \frac{k_s}{R_0} \Bigl(
310 \frac{y^{n + \frac{1}{2}}}{R_0}-e_c^{n + \frac{1}{2}} \Bigr) = 0
311 """
313 disp: Quantity[np.float64]
314 """:math:`y` the displacement along the radius."""
316 fib_deform: Quantity[np.float64]
317 """:math:`e_c` the sphere fiber deformation"""
319 pressure: Quantity[np.float64]
320 """:math:`P` the pressure in the sphere"""
322 pressure_external: Quantity[np.float64]
323 """:math:`P_{ext}` the pressure outside the sphere"""
325 vel: Quantity[np.float64]
326 """:math:`\\dot{y}` the displacement velocity"""
328 radius: Quantity[np.float64]
329 """:math:`R_0` the initial sphere radius"""
331 vol_mass: Quantity[np.float64]
332 """:math:`\\rho_0` the volumic mass of the sphere tissu"""
334 thickness: Quantity[np.float64]
335 """:math:`d_0` the initial thickness of the sphere"""
337 damping_coef: Quantity[np.float64]
338 """Damping coeff for the viscous coeff :math:`\\eta`"""
340 series_stiffness: Quantity[np.float64]
341 """:math:`k_s` the series stiffness"""
343 hyperelastic_cst: Quantity[NDArray[np.float64]]
344 """:math:`(C_0,C_1,C_2,C_3)` the hyperelastic constants"""
346 time: Time
347 """Simulation time"""
349 def initialize(self) -> None:
350 """
351 Compute initial block quantities (sphere radius, surface, volume, ...) and solve
352 the associated static problem to get initial displacement and fiber deformation.
353 """
354 self.inv_radius = 1.0 / self.radius.current
355 self.sphere_surface = 4.0 * np.pi * np.power(self.radius.current, 2)
356 self.sphere_volume = 4.0 / 3.0 * np.pi * np.power(self.radius.current, 3)
357 self.thickness_radius_ratio = self.thickness.current * self.inv_radius
358 self.half_thickness_radius_ratio = 0.5 * self.thickness_radius_ratio
359 self.viscous_coef = (
360 2.0
361 * self.damping_coef.current
362 * self.thickness.current
363 * np.pow(self.inv_radius, 2)
364 )
365 self.hyperelastic_cst_01 = (
366 self.hyperelastic_cst.current[0] * self.hyperelastic_cst.current[1]
367 )
368 self.hyperelastic_cst_23 = (
369 self.hyperelastic_cst.current[2] * self.hyperelastic_cst.current[3]
370 )
372 disp_init, fib_deform_init = self._solve_static_problem()
374 self.disp.initialize(disp_init)
375 self.fib_deform.initialize(fib_deform_init)
377 def dynamics_residual(self) -> Any:
378 """
379 Compute the residual giving a dynamics on `disp`
381 :return: the residual
382 :rtype: np.float64
383 """
385 disp_mid = mid_point(self.disp)
386 fib_deform_mid = mid_point(self.fib_deform)
387 p_mid_point = mid_point(self.pressure)
388 pressure_external = mid_point(self.pressure_external)
390 disp_diff = diff(self.disp)
392 disp_mid_ratio = 1.0 + disp_mid * self.inv_radius
393 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius
394 disp_cur_ratio = 1.0 + self.disp.current * self.inv_radius
396 j4_mid = np.pow(disp_mid_ratio, 2)
397 j4_diff_mid = 2.0 * self.inv_radius * disp_mid_ratio
399 j1_mid = 2.0 * j4_mid + np.pow(disp_mid_ratio, -4)
401 # inertia stress
402 inertia_stress = (
403 self.vol_mass.current
404 * self.thickness.current
405 * diff(self.vel)
406 * self.time.inv_dt
407 )
409 # viscous stress
410 viscous_diff_vel = (
411 self.viscous_coef * j4_mid * (1.0 + 2.0 * np.pow(disp_mid_ratio, -12))
412 )
413 viscous_stress = viscous_diff_vel * disp_diff * self.time.inv_dt
415 # active stress
416 active_stress = (
417 self.thickness_radius_ratio
418 * self.series_stiffness.current
419 * (disp_mid * self.inv_radius - fib_deform_mid)
420 )
421 if np.abs(disp_diff / self.disp.current) > _DISP_EPSILON:
422 disp_diff_ratio = disp_diff * self.inv_radius
424 # external stress
425 fluid_volume_cur = (
426 disp_cur_ratio
427 - np.pow(disp_cur_ratio, -2) * self.half_thickness_radius_ratio
428 )
429 fluid_volume_new = (
430 disp_new_ratio
431 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio
432 )
433 fluid_volume_diff3 = (
434 disp_diff_ratio
435 - math_utils.power_diff(
436 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -2
437 )
438 * self.half_thickness_radius_ratio
439 )
440 fluid_volume_diff = self.sphere_volume * math_utils.power_diff(
441 fluid_volume_new, fluid_volume_cur, fluid_volume_diff3, 3
442 )
444 external_stress = (
445 (p_mid_point - pressure_external)
446 * fluid_volume_diff
447 / (self.sphere_surface * disp_diff)
448 )
450 # passive stress
451 j4_new = np.pow(disp_new_ratio, 2)
452 j4_cur = np.pow(disp_cur_ratio, 2)
454 j4_diff = math_utils.power_diff(
455 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, 2
456 )
458 j1_new = 2.0 * j4_new + np.pow(disp_new_ratio, -4)
459 j1_cur = 2.0 * j4_cur + np.pow(disp_cur_ratio, -4)
460 j1_diff = 2.0 * j4_diff + math_utils.power_diff(
461 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -4
462 )
464 arg_exp_j1_new = self.hyperelastic_cst.current[1] * np.pow(j1_new - 3.0, 2)
465 arg_exp_j1_cur = self.hyperelastic_cst.current[1] * np.pow(j1_cur - 3.0, 2)
466 arg_exp_j1_diff = self.hyperelastic_cst.current[1] * math_utils.power_diff(
467 j1_new - 3.0, j1_cur - 3.0, j1_diff, 2
468 )
470 arg_exp_j4_new = self.hyperelastic_cst.current[3] * np.pow(j4_new - 1.0, 2)
471 arg_exp_j4_cur = self.hyperelastic_cst.current[3] * np.pow(j4_cur - 1.0, 2)
472 arg_exp_j4_diff = self.hyperelastic_cst.current[3] * math_utils.power_diff(
473 j4_new - 1.0, j4_cur - 1.0, j4_diff, 2
474 )
476 exp_j1_diff = math_utils.exp_diff(
477 arg_exp_j1_new, arg_exp_j1_cur, arg_exp_j1_diff
478 )
479 exp_j4_diff = math_utils.exp_diff(
480 arg_exp_j4_new, arg_exp_j4_cur, arg_exp_j4_diff
481 )
483 hyperelastic_potential_diff = (
484 self.hyperelastic_cst.current[0] * exp_j1_diff
485 + self.hyperelastic_cst.current[2] * exp_j4_diff
486 )
488 passive_stress = (
489 self.thickness.current * hyperelastic_potential_diff / disp_diff
490 )
492 else:
493 # external stress
494 disp_mid_ratio_adj = np.pow(
495 disp_mid_ratio
496 - np.pow(disp_mid_ratio, -2) * self.half_thickness_radius_ratio,
497 2,
498 )
499 external_stress = (
500 (p_mid_point - pressure_external)
501 * disp_mid_ratio_adj
502 * (1.0 + np.pow(disp_mid_ratio, -3) * self.thickness_radius_ratio)
503 )
505 # passive stress
506 exp_j1_mid = np.exp(
507 self.hyperelastic_cst.current[1] * np.pow(j1_mid - 3.0, 2)
508 )
509 exp_j4_mid = np.exp(
510 self.hyperelastic_cst.current[3] * np.pow(j4_mid - 1.0, 2)
511 )
513 j1_diff_mid = 2.0 * j4_diff_mid - 4.0 * self.inv_radius * np.pow(
514 disp_mid_ratio, -5
515 )
517 hyperelastic_potential_diff_mid = (
518 2.0
519 * self.hyperelastic_cst_01
520 * j1_diff_mid
521 * (j1_mid - 3.0)
522 * exp_j1_mid
523 ) + (
524 2.0
525 * self.hyperelastic_cst_23
526 * j4_diff_mid
527 * (j4_mid - 1.0)
528 * exp_j4_mid
529 )
531 passive_stress = self.thickness.current * hyperelastic_potential_diff_mid
533 return (
534 inertia_stress
535 + passive_stress
536 + viscous_stress
537 + active_stress
538 - external_stress
539 )
541 def dynamics_residual_ddisp(self) -> Any:
542 """
543 Compute the partial derivative of the residual for `disp`.
545 :return: the residual partial derivative for `disp`
546 :rtype: np.float64
547 """
549 disp_mid = mid_point(self.disp)
550 disp_diff = diff(self.disp)
551 disp_mid_ratio = 1.0 + disp_mid * self.inv_radius
552 disp_cur_ratio = 1.0 + self.disp.current * self.inv_radius
553 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius
554 pressure_external = mid_point(self.pressure_external)
556 p_mid_point = mid_point(self.pressure)
558 j4_mid = np.pow(disp_mid_ratio, 2)
559 j4_diff_mid = 2.0 * self.inv_radius * (disp_mid_ratio)
560 j1_mid = 2.0 * j4_mid + np.pow(disp_mid_ratio, -4)
562 # viscous
563 viscous_diff_vel = (
564 self.viscous_coef * j4_mid * (1.0 + 2.0 * np.pow(disp_mid_ratio, -12))
565 )
566 viscous_potential_diff_disp_mid = (
567 self.viscous_coef
568 * disp_diff
569 * self.time.inv_dt
570 * (
571 j4_diff_mid * (1.0 + 2.0 * np.pow(disp_mid_ratio, -12))
572 - 24.0 * self.inv_radius * j4_mid * np.pow(disp_mid_ratio, -13)
573 )
574 )
575 viscous_ddisp = (
576 0.5 * viscous_potential_diff_disp_mid + self.time.inv_dt * viscous_diff_vel
577 )
579 # active
580 active_ddisp = (
581 self.half_thickness_radius_ratio
582 * self.series_stiffness.current
583 * self.inv_radius
584 )
586 if np.fabs(disp_diff / self.disp.current) > _DISP_EPSILON:
587 # external
588 disp_diff_ratio = disp_diff * self.inv_radius
589 fluid_volume_term_cur = (
590 disp_cur_ratio
591 - np.pow(disp_cur_ratio, -2) * self.half_thickness_radius_ratio
592 )
593 fluid_volume_term_new = (
594 disp_new_ratio
595 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio
596 )
597 fluid_volume_term_diff3 = (
598 disp_diff_ratio
599 - math_utils.power_diff(
600 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -2
601 )
602 * self.half_thickness_radius_ratio
603 )
604 fluid_volume_diff = self.sphere_volume * math_utils.power_diff(
605 fluid_volume_term_new, fluid_volume_term_cur, fluid_volume_term_diff3, 3
606 )
608 fluid_volume_diff_new = (
609 self.sphere_surface
610 * np.pow(
611 disp_new_ratio
612 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio,
613 2,
614 )
615 * (1.0 + np.pow(disp_new_ratio, -3) * self.thickness_radius_ratio)
616 )
618 fluid_volume_diff_scheme_diff = (
619 (1.0 / self.sphere_surface)
620 * (fluid_volume_diff_new * disp_diff - fluid_volume_diff)
621 * np.pow(disp_diff, -2)
622 )
624 external_ddisp = (
625 p_mid_point - pressure_external
626 ) * fluid_volume_diff_scheme_diff
628 # passive
629 j4_cur = np.pow(disp_cur_ratio, 2)
630 j4_new = np.pow(disp_new_ratio, 2)
631 j4_diff_new = 2.0 * self.inv_radius * disp_new_ratio
632 j4_diff = math_utils.power_diff(
633 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, 2
634 )
636 j1_new = 2.0 * j4_new + np.pow(disp_new_ratio, -4)
637 j1_cur = 2.0 * j4_cur + np.pow(disp_cur_ratio, -4)
638 j1_diff_new = 2.0 * j4_diff_new - 4.0 * self.inv_radius * np.pow(
639 disp_new_ratio, -5
640 )
641 j1_diff = 2.0 * j4_diff + math_utils.power_diff(
642 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -4
643 )
645 arg_exp_j1_cur = self.hyperelastic_cst.current[1] * np.pow(j1_cur - 3.0, 2)
646 arg_exp_j1_new = self.hyperelastic_cst.current[1] * np.pow(j1_new - 3.0, 2)
647 arg_exp_j1_diff = self.hyperelastic_cst.current[1] * math_utils.power_diff(
648 j1_new - 3.0, j1_cur - 3.0, j1_diff, 2
649 )
650 exp_j1_new = np.exp(arg_exp_j1_new)
652 arg_exp_j4_cur = self.hyperelastic_cst.current[3] * np.pow(j4_cur - 1.0, 2)
653 arg_exp_j4_new = self.hyperelastic_cst.current[3] * np.pow(j4_new - 1.0, 2)
654 arg_exp_j4_diff = self.hyperelastic_cst.current[3] * math_utils.power_diff(
655 j4_new - 1.0, j4_cur - 1.0, j4_diff, 2
656 )
657 exp_j4_new = np.exp(arg_exp_j4_new)
659 exp_j4_diff = math_utils.exp_diff(
660 arg_exp_j4_new, arg_exp_j4_cur, arg_exp_j4_diff
661 )
662 exp_j1_diff = math_utils.exp_diff(
663 arg_exp_j1_new, arg_exp_j1_cur, arg_exp_j1_diff
664 )
666 hyperelastic_potential_diff = (
667 self.hyperelastic_cst.current[0] * exp_j1_diff
668 + self.hyperelastic_cst.current[2] * exp_j4_diff
669 )
670 hyperelastic_potential_diff_new = (
671 2.0
672 * self.hyperelastic_cst_01
673 * j1_diff_new
674 * (j1_new - 3.0)
675 * exp_j1_new
676 ) + (
677 2.0
678 * self.hyperelastic_cst_23
679 * j4_diff_new
680 * (j4_new - 1.0)
681 * exp_j4_new
682 )
683 passive_ddisp = (
684 self.thickness.current
685 * (
686 hyperelastic_potential_diff_new * disp_diff
687 - hyperelastic_potential_diff
688 )
689 / np.pow(disp_diff, 2)
690 )
692 else:
693 # external
694 disp_mid_ratio_adj = np.pow(
695 disp_mid_ratio
696 - np.pow(disp_mid_ratio, -2) * self.half_thickness_radius_ratio,
697 2,
698 )
699 fluid_volume_diff_diff_mid = (
700 4.0
701 * np.pi
702 * self.radius.current
703 * (
704 2.0
705 * (
706 disp_mid_ratio
707 - np.pow(disp_mid_ratio, -2) * self.half_thickness_radius_ratio
708 )
709 * np.pow(
710 1.0 + np.pow(disp_mid_ratio, -3) * self.thickness_radius_ratio,
711 2,
712 )
713 - (
714 3.0
715 * self.thickness_radius_ratio
716 * np.pow(disp_mid_ratio, -4)
717 * disp_mid_ratio_adj
718 )
719 )
720 )
721 fluid_volume_diff_scheme_diff = (
722 1.0 / self.sphere_surface * 0.5 * fluid_volume_diff_diff_mid
723 )
724 external_ddisp = (
725 p_mid_point - pressure_external
726 ) * fluid_volume_diff_scheme_diff
728 # passive
729 exp_j1_mid = np.exp(
730 self.hyperelastic_cst.current[1] * np.pow(j1_mid - 3.0, 2)
731 )
732 exp_j4_mid = np.exp(
733 self.hyperelastic_cst.current[3] * np.pow(j4_mid - 1.0, 2)
734 )
735 j1_diff_mid = 2.0 * j4_diff_mid - 4.0 * self.inv_radius * np.pow(
736 disp_mid_ratio, -5
737 )
738 j4_diff_diff = 2.0 * np.pow(self.inv_radius, 2)
739 j1_diff_diff_mid = 4.0 * np.pow(self.inv_radius, 2) + 20.0 * np.pow(
740 self.inv_radius, 2
741 ) * np.pow(disp_mid_ratio, -6)
742 hyperelastic_potential_diff_diff_mid = (
743 2.0
744 * self.hyperelastic_cst_01
745 * (
746 j1_diff_diff_mid * (j1_mid - 3.0)
747 + np.pow(j1_diff_mid, 2)
748 + 2.0
749 * self.hyperelastic_cst.current[1]
750 * np.pow(j1_diff_mid * (j1_mid - 3.0), 2)
751 )
752 * exp_j1_mid
753 + 2.0
754 * self.hyperelastic_cst_23
755 * (
756 j4_diff_diff * (j4_mid - 1.0)
757 + np.pow(j4_diff_mid, 2)
758 + 2.0
759 * self.hyperelastic_cst.current[3]
760 * np.pow(j4_diff_mid * (j4_mid - 1.0), 2)
761 )
762 * exp_j4_mid
763 )
764 passive_ddisp = (
765 self.thickness.current * 0.5 * hyperelastic_potential_diff_diff_mid
766 )
768 return viscous_ddisp + active_ddisp - external_ddisp + passive_ddisp
770 def dynamics_residual_dfib_deform(self) -> Any:
771 """
773 Compute the partial derivative of the residual for `fib_deform`.
775 :return: the residual partial derivative for `fib_deform`
776 :rtype: np.float64
777 """
778 # activ stress
779 return -self.half_thickness_radius_ratio * self.series_stiffness.current
781 def dynamics_residual_dvel(self) -> Any:
782 """
783 Compute the partial derivative of the residual for `vel`.
785 :return: the residual partial derivative for `vel`
786 :rtype: np.float64
787 """
788 # inertia
789 return self.vol_mass.current * self.thickness.current * self.time.inv_dt
791 def dynamics_residual_dpressure(self) -> Any:
792 """
793 Compute the partial derivative of the residual for the `pressure`.
795 :return: the residual partial derivative for `pressure`
796 :rtype: np.float64
797 """
799 # external
800 disp_diff = diff(self.disp)
801 disp_mid = mid_point(self.disp)
802 disp_mid_ratio = 1.0 + disp_mid * self.inv_radius
803 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius
804 disp_cur_ratio = 1.0 + self.disp.current * self.inv_radius
806 if np.fabs(disp_diff / self.disp.current) > _DISP_EPSILON:
807 disp_diff_ratio = disp_diff * self.inv_radius
808 fluid_volume_term_cur = (
809 disp_cur_ratio
810 - np.pow(disp_cur_ratio, -2) * self.half_thickness_radius_ratio
811 )
812 fluid_volume_term_new = (
813 disp_new_ratio
814 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio
815 )
817 fluid_volume_term_diff3 = (
818 disp_diff_ratio
819 - math_utils.power_diff(
820 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -2
821 )
822 * self.half_thickness_radius_ratio
823 )
825 fluid_volume_diff = self.sphere_volume * math_utils.power_diff(
826 fluid_volume_term_new, fluid_volume_term_cur, fluid_volume_term_diff3, 3
827 )
828 external_dpressure = -(
829 (0.5 * fluid_volume_diff) / (self.sphere_surface * disp_diff)
830 )
831 else:
832 disp_mid_ratio_adj = np.pow(
833 disp_mid_ratio
834 - np.pow(disp_mid_ratio, -2) * self.half_thickness_radius_ratio,
835 2,
836 )
837 external_dpressure = (
838 -0.5
839 * disp_mid_ratio_adj
840 * (1.0 + np.pow(disp_mid_ratio, -3) * self.thickness_radius_ratio)
841 )
843 return external_dpressure
845 def dynamics_residual_dpressure_external(self) -> Any:
846 """
847 Compute the partial derivative of the residual `pressure_external`.
849 :return: the residual partial derivative `pressure_external`.
850 :rtype: np.float64
851 """
853 disp_diff = diff(self.disp)
855 if np.abs(disp_diff / self.disp.current) > _DISP_EPSILON:
856 disp_diff_ratio = disp_diff * self.inv_radius
857 disp_new_ratio = 1.0 + self.disp.new * self.inv_radius
858 disp_cur_ratio = 1.0 + self.disp.current * self.inv_radius
860 fluid_volume_cur = (
861 disp_cur_ratio
862 - np.pow(disp_cur_ratio, -2) * self.half_thickness_radius_ratio
863 )
864 fluid_volume_new = (
865 disp_new_ratio
866 - np.pow(disp_new_ratio, -2) * self.half_thickness_radius_ratio
867 )
868 fluid_volume_diff3 = (
869 disp_diff_ratio
870 - math_utils.power_diff(
871 disp_new_ratio, disp_cur_ratio, disp_diff_ratio, -2
872 )
873 * self.half_thickness_radius_ratio
874 )
875 fluid_volume_diff = self.sphere_volume * math_utils.power_diff(
876 fluid_volume_new, fluid_volume_cur, fluid_volume_diff3, 3
877 )
879 dresidual_dpressure_external = (
880 0.5 * fluid_volume_diff / (self.sphere_surface * disp_diff)
881 )
883 else:
884 disp_mid = mid_point(self.disp)
885 disp_mid_ratio = 1.0 + disp_mid * self.inv_radius
887 disp_mid_ratio_adj = np.pow(
888 disp_mid_ratio
889 - np.pow(disp_mid_ratio, -2) * self.half_thickness_radius_ratio,
890 2,
891 )
893 dresidual_dpressure_external = (
894 0.5
895 * disp_mid_ratio_adj
896 * (1.0 + np.pow(disp_mid_ratio, -3) * self.thickness_radius_ratio)
897 )
899 return dresidual_dpressure_external
901 def _solve_static_problem(self) -> tuple[np.float64, np.float64]:
902 eq_system = EqSystem(1)
904 state = State()
905 state.add_variable(SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID, 1)
906 state[SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID].initialize(self.disp.current)
908 static_block = _SphericalDynamicsStaticModelComponent(
909 disp=state[SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID],
910 pressure=Quantity(self.pressure.current),
911 thickness=Quantity(self.thickness.current),
912 hyperelastic_cst=Quantity(self.hyperelastic_cst.current),
913 inv_radius=Quantity(self.inv_radius),
914 pressure_external=Quantity(self.pressure_external.current),
915 )
916 static_block.initialize()
917 static_expression = (
918 _SphericalDynamicsStaticModelComponent.get_internal_variable_expression(
919 SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID
920 )[0]
921 )
922 eq_system.add_system_part(
923 0,
924 static_expression.size,
925 static_expression.expr_func,
926 {
927 0: static_expression.expr_gradients[
928 SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID
929 ]
930 },
931 static_block,
932 )
934 solver = NewtonSolver(
935 _STATIC_PROBLEM_TOL,
936 _STATIC_PROBLEM_MAX_IT,
937 )
938 p_test = static_block.pressure.current
939 p_step = p_test
941 while (
942 p_test <= static_block.pressure.current
943 and p_test > _STATIC_PROBLEM_MIN_PRESSURE_STEP
944 ):
945 sol = solver.solve(
946 state,
947 eq_system,
948 {SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID: _STATIC_PROBLEM_DISP_MAG},
949 )
951 if sol.converged is True:
952 state[SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID].initialize(sol.x[0])
954 if np.abs(p_test - static_block.pressure.current) < _STATIC_PROBLEM_TOL:
955 break # disp and fib deform initialized
957 p_test = np.min([p_test + p_step, static_block.pressure.current])
958 static_block.pressure.update(p_test)
960 else:
961 # solver did not converged, update pressure
962 state.reset_state_vector()
963 p_step = p_step * 0.5
964 p_test = p_test - p_step
965 static_block.pressure.update(p_test)
967 disp_init = state[SPHERICAL_DYNAMICS_STATIC_DISP_LOCAL_ID].current
968 return disp_init, disp_init * self.inv_radius
971# Define the dynamics residual expression and its partial derivatives.
972_spherical_dynamics_residual_expr = Expression(
973 1,
974 SphericalDynamicsModelComponent.dynamics_residual,
975 {
976 "fib_deform": SphericalDynamicsModelComponent.dynamics_residual_dfib_deform,
977 "disp": SphericalDynamicsModelComponent.dynamics_residual_ddisp,
978 "vel": SphericalDynamicsModelComponent.dynamics_residual_dvel,
979 "pressure": SphericalDynamicsModelComponent.dynamics_residual_dpressure,
980 "pressure_external": SphericalDynamicsModelComponent.dynamics_residual_dpressure_external, # noqa: E501
981 },
982)
984SphericalDynamicsModelComponent.declares_internal_expression(
985 "disp", _spherical_dynamics_residual_expr
986)