Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F63013994
bls.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, May 17, 03:51
Size
26 KB
Mime Type
text/x-python
Expires
Sun, May 19, 03:51 (2 d)
Engine
blob
Format
Raw Data
Handle
17720846
Attached To
R4670 PySONIC (old)
bls.py
View Options
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author: Theo Lemaire
# @Date: 2016-09-29 16:16:19
# @Email: theo.lemaire@epfl.ch
# @Last Modified by: Theo Lemaire
# @Last Modified time: 2018-08-21 16:10:35
import
inspect
import
logging
import
warnings
import
numpy
as
np
import
scipy.integrate
as
integrate
from
scipy.optimize
import
brentq
,
curve_fit
from
.utils
import
*
from
.constants
import
*
# Get package logger
logger
=
logging
.
getLogger
(
'PySONIC'
)
class
BilayerSonophore
:
""" This class contains the geometric and mechanical parameters of the
Bilayer Sonophore Model, as well as all the core functions needed to
compute the dynamics (kinetics and kinematics) of the bilayer membrane
cavitation, and run dynamic BLS simulations.
"""
# BIOMECHANICAL PARAMETERS
T
=
309.15
# Temperature (K)
Rg
=
8.314
# Universal gas constant (Pa.m^3.mol^-1.K^-1)
delta0
=
2.0e-9
# Thickness of the leaflet (m)
Delta_
=
1.4e-9
# Initial gap between the two leaflets on a non-charged membrane at equil. (m)
pDelta
=
1.0e5
# Attraction/repulsion pressure coefficient (Pa)
m
=
5.0
# Exponent in the repulsion term (dimensionless)
n
=
3.3
# Exponent in the attraction term (dimensionless)
rhoL
=
1075.0
# Density of the surrounding fluid (kg/m^3)
muL
=
7.0e-4
# Dynamic viscosity of the surrounding fluid (Pa.s)
muS
=
0.035
# Dynamic viscosity of the leaflet (Pa.s)
kA
=
0.24
# Area compression modulus of the leaflet (N/m)
alpha
=
7.56
# Tissue shear loss modulus frequency coefficient (Pa.s)
C0
=
0.62
# Initial gas molar concentration in the surrounding fluid (mol/m^3)
kH
=
1.613e5
# Henry's constant (Pa.m^3/mol)
P0
=
1.0e5
# Static pressure in the surrounding fluid (Pa)
Dgl
=
3.68e-9
# Diffusion coefficient of gas in the fluid (m^2/s)
xi
=
0.5e-9
# Boundary layer thickness for gas transport across leaflet (m)
c
=
1515.0
# Speed of sound in medium (m/s)
# BIOPHYSICAL PARAMETERS
epsilon0
=
8.854e-12
# Vacuum permittivity (F/m)
epsilonR
=
1.0
# Relative permittivity of intramembrane cavity (dimensionless)
def
__init__
(
self
,
diameter
,
Fdrive
,
Cm0
,
Qm0
,
embedding_depth
=
0.0
):
""" Constructor of the class.
:param diameter: in-plane diameter of the sonophore structure within the membrane (m)
:param Fdrive: frequency of acoustic perturbation (Hz)
:param Cm0: membrane resting capacitance (F/m2)
:param Qm0: membrane resting charge density (C/m2)
:param embedding_depth: depth of the embedding tissue around the membrane (m)
"""
logger
.
debug
(
'
%.1f
nm BLS initialization at
%.2f
kHz,
%.2f
nC/cm2'
,
diameter
*
1e9
,
Fdrive
*
1e-3
,
Qm0
*
1e5
)
# Extract resting constants and geometry
self
.
Cm0
=
Cm0
self
.
Qm0
=
Qm0
self
.
a
=
diameter
self
.
d
=
embedding_depth
self
.
S0
=
np
.
pi
*
self
.
a
**
2
# Derive frequency-dependent tissue elastic modulus
G_tissue
=
self
.
alpha
*
Fdrive
# G'' (Pa)
self
.
kA_tissue
=
2
*
G_tissue
*
self
.
d
# kA of the tissue layer (N/m)
# Check existence of lookups for derived parameters
lookups
=
get_BLS_lookups
(
self
.
a
)
Qkey
=
'{:.2f}'
.
format
(
Qm0
*
1e5
)
# If no lookup, compute parameters and store them in lookup
if
not
lookups
or
Qkey
not
in
lookups
:
# Find Delta that cancels out Pm + Pec at Z = 0 (m)
if
self
.
Qm0
==
0.0
:
D_eq
=
self
.
Delta_
else
:
(
D_eq
,
Pnet_eq
)
=
self
.
findDeltaEq
(
self
.
Qm0
)
assert
Pnet_eq
<
PNET_EQ_MAX
,
'High Pnet at Z = 0 with ∆ =
%.2f
nm'
%
(
D_eq
*
1e9
)
self
.
Delta
=
D_eq
# Find optimal Lennard-Jones parameters to approximate PMavg
(
LJ_approx
,
std_err
,
_
)
=
self
.
LJfitPMavg
()
assert
std_err
<
PMAVG_STD_ERR_MAX
,
'High error in PmAvg nonlinear fit:'
\
' std_err =
%.2f
Pa'
%
std_err
self
.
LJ_approx
=
LJ_approx
lookups
[
Qkey
]
=
{
'LJ_approx'
:
LJ_approx
,
'Delta_eq'
:
D_eq
}
logger
.
debug
(
'Saving BLS derived parameters to lookup file'
)
save_BLS_lookups
(
self
.
a
,
lookups
)
# If lookup exists, load parameters from it
else
:
logger
.
debug
(
'Loading BLS derived parameters from lookup file'
)
self
.
LJ_approx
=
lookups
[
Qkey
][
'LJ_approx'
]
self
.
Delta
=
lookups
[
Qkey
][
'Delta_eq'
]
# Compute initial volume and gas content
self
.
V0
=
np
.
pi
*
self
.
Delta
*
self
.
a
**
2
self
.
ng0
=
self
.
gasPa2mol
(
self
.
P0
,
self
.
V0
)
def
__str__
(
self
):
s
=
'-------- Bilayer Sonophore --------
\n
'
s
+=
'class attributes:
\n
'
class_attrs
=
inspect
.
getmembers
(
self
.
__class__
,
lambda
a
:
not
(
inspect
.
isroutine
(
a
)))
class_attrs
=
[
a
for
a
in
class_attrs
if
not
(
a
[
0
]
.
startswith
(
'__'
)
and
a
[
0
]
.
endswith
(
'__'
))]
for
ca
in
class_attrs
:
s
+=
'{} = {}
\n
'
.
format
(
ca
[
0
],
ca
[
1
])
s
+=
'instance attributes:
\n
'
inst_attrs
=
inspect
.
getmembers
(
self
,
lambda
a
:
not
(
inspect
.
isroutine
(
a
)))
inst_attrs
=
[
a
for
a
in
inst_attrs
if
not
(
a
[
0
]
.
startswith
(
'__'
)
and
a
[
0
]
.
endswith
(
'__'
))
and
a
not
in
class_attrs
]
for
ia
in
inst_attrs
:
s
+=
'{} = {}
\n
'
.
format
(
ia
[
0
],
ia
[
1
])
return
s
def
reinit
(
self
):
logger
.
debug
(
'Re-initializing BLS object'
)
# Find Delta that cancels out Pm + Pec at Z = 0 (m)
if
self
.
Qm0
==
0.0
:
D_eq
=
self
.
Delta_
else
:
(
D_eq
,
Pnet_eq
)
=
self
.
findDeltaEq
(
self
.
Qm0
)
assert
Pnet_eq
<
PNET_EQ_MAX
,
'High Pnet at Z = 0 with ∆ =
%.2f
nm'
%
(
D_eq
*
1e9
)
self
.
Delta
=
D_eq
# Compute initial volume and gas content
self
.
V0
=
np
.
pi
*
self
.
Delta
*
self
.
a
**
2
self
.
ng0
=
self
.
gasPa2mol
(
self
.
P0
,
self
.
V0
)
def
curvrad
(
self
,
Z
):
""" Return the (signed) instantaneous curvature radius of the leaflet.
:param Z: leaflet apex outward deflection value (m)
:return: leaflet curvature radius (m)
"""
if
Z
==
0.0
:
return
np
.
inf
else
:
return
(
self
.
a
**
2
+
Z
**
2
)
/
(
2
*
Z
)
def
v_curvrad
(
self
,
Z
):
''' Vectorized curvrad function '''
return
np
.
array
(
list
(
map
(
self
.
curvrad
,
Z
)))
def
surface
(
self
,
Z
):
""" Return the surface area of the stretched leaflet (spherical cap).
:param Z: leaflet apex outward deflection value (m)
:return: surface of the stretched leaflet (m^2)
"""
return
np
.
pi
*
(
self
.
a
**
2
+
Z
**
2
)
def
volume
(
self
,
Z
):
""" Return the total volume of the inter-leaflet space (cylinder +/-
spherical cap).
:param Z: leaflet apex outward deflection value (m)
:return: inner volume of the bilayer sonophore structure (m^3)
"""
return
np
.
pi
*
self
.
a
**
2
*
self
.
Delta
\
*
(
1
+
(
Z
/
(
3
*
self
.
Delta
)
*
(
3
+
Z
**
2
/
self
.
a
**
2
)))
def
arealstrain
(
self
,
Z
):
""" Compute the areal strain of the stretched leaflet.
epsilon = (S - S0)/S0 = (Z/a)^2
:param Z: leaflet apex outward deflection value (m)
:return: areal strain (dimensionless)
"""
return
(
Z
/
self
.
a
)
**
2
def
Capct
(
self
,
Z
):
""" Compute the membrane capacitance per unit area,
under the assumption of parallel-plate capacitor
with average inter-layer distance.
:param Z: leaflet apex outward deflection value (m)
:return: capacitance per unit area (F/m2)
"""
if
Z
==
0.0
:
return
self
.
Cm0
else
:
return
((
self
.
Cm0
*
self
.
Delta
/
self
.
a
**
2
)
*
(
Z
+
(
self
.
a
**
2
-
Z
**
2
-
Z
*
self
.
Delta
)
/
(
2
*
Z
)
*
np
.
log
((
2
*
Z
+
self
.
Delta
)
/
self
.
Delta
)))
def
v_Capct
(
self
,
Z
):
''' Vectorized Capct function '''
return
np
.
array
(
list
(
map
(
self
.
Capct
,
Z
)))
def
derCapct
(
self
,
Z
,
U
):
""" Compute the derivative of the membrane capacitance per unit area
with respect to time, under the assumption of parallel-plate capacitor.
:param Z: leaflet apex outward deflection value (m)
:param U: leaflet apex outward deflection velocity (m/s)
:return: derivative of capacitance per unit area (F/m2.s)
"""
dCmdZ
=
((
self
.
Cm0
*
self
.
Delta
/
self
.
a
**
2
)
*
((
Z
**
2
+
self
.
a
**
2
)
/
(
Z
*
(
2
*
Z
+
self
.
Delta
))
-
((
Z
**
2
+
self
.
a
**
2
)
*
np
.
log
((
2
*
Z
+
self
.
Delta
)
/
self
.
Delta
))
/
(
2
*
Z
**
2
)))
return
dCmdZ
*
U
def
localdef
(
self
,
r
,
Z
,
R
):
""" Compute the (signed) local transverse leaflet deviation at a distance
r from the center of the dome.
:param r: in-plane distance from center of the sonophore (m)
:param Z: leaflet apex outward deflection value (m)
:param R: leaflet curvature radius (m)
:return: local transverse leaflet deviation (m)
"""
if
np
.
abs
(
Z
)
==
0.0
:
return
0.0
else
:
return
np
.
sign
(
Z
)
*
(
np
.
sqrt
(
R
**
2
-
r
**
2
)
-
np
.
abs
(
R
)
+
np
.
abs
(
Z
))
def
Pacoustic
(
self
,
t
,
Adrive
,
Fdrive
,
phi
=
np
.
pi
):
""" Compute the acoustic pressure at a specific time, given
the amplitude, frequency and phase of the acoustic stimulus.
:param t: time of interest
:param Adrive: acoustic drive amplitude (Pa)
:param Fdrive: acoustic drive frequency (Hz)
:param phi: acoustic drive phase (rad)
"""
return
Adrive
*
np
.
sin
(
2
*
np
.
pi
*
Fdrive
*
t
-
phi
)
def
PMlocal
(
self
,
r
,
Z
,
R
):
""" Compute the local intermolecular pressure.
:param r: in-plane distance from center of the sonophore (m)
:param Z: leaflet apex outward deflection value (m)
:param R: leaflet curvature radius (m)
:return: local intermolecular pressure (Pa)
"""
z
=
self
.
localdef
(
r
,
Z
,
R
)
relgap
=
(
2
*
z
+
self
.
Delta
)
/
self
.
Delta_
return
self
.
pDelta
*
((
1
/
relgap
)
**
self
.
m
-
(
1
/
relgap
)
**
self
.
n
)
def
PMavg
(
self
,
Z
,
R
,
S
):
""" Compute the average intermolecular pressure felt across the leaflet
by quadratic integration.
:param Z: leaflet apex outward deflection value (m)
:param R: leaflet curvature radius (m)
:param S: surface of the stretched leaflet (m^2)
:return: averaged intermolecular resultant pressure across the leaflet (Pa)
.. warning:: quadratic integration is computationally expensive.
"""
# Intermolecular force over an infinitely thin ring of radius r
fMring
=
lambda
r
,
Z
,
R
:
2
*
np
.
pi
*
r
*
self
.
PMlocal
(
r
,
Z
,
R
)
# Integrate from 0 to a
fTotal
,
_
=
integrate
.
quad
(
fMring
,
0
,
self
.
a
,
args
=
(
Z
,
R
))
return
fTotal
/
S
def
v_PMavg
(
self
,
Z
,
R
,
S
):
''' Vectorized PMavg function '''
return
np
.
array
(
list
(
map
(
self
.
PMavg
,
Z
,
R
,
S
)))
def
LJfitPMavg
(
self
):
""" Determine optimal parameters of a Lennard-Jones expression
approximating the average intermolecular pressure.
These parameters are obtained by a nonlinear fit of the
Lennard-Jones function for a range of deflection values
between predetermined Zmin and Zmax.
:return: 3-tuple with optimized LJ parameters for PmAvg prediction (Map) and
the standard and max errors of the prediction in the fitting range (in Pascals)
"""
# Determine lower bound of deflection range: when Pm = Pmmax
PMmax
=
LJFIT_PM_MAX
# Pa
Zminlb
=
-
0.49
*
self
.
Delta
Zminub
=
0.0
f
=
lambda
Z
,
Pmmax
:
self
.
PMavg
(
Z
,
self
.
curvrad
(
Z
),
self
.
surface
(
Z
))
-
PMmax
Zmin
=
brentq
(
f
,
Zminlb
,
Zminub
,
args
=
(
PMmax
),
xtol
=
1e-16
)
# Create vectors for geometric variables
Zmax
=
2
*
self
.
a
Z
=
np
.
arange
(
Zmin
,
Zmax
,
1e-11
)
Pmavg
=
self
.
v_PMavg
(
Z
,
self
.
v_curvrad
(
Z
),
self
.
surface
(
Z
))
# Compute optimal nonlinear fit of custom LJ function with initial guess
x0_guess
=
self
.
delta0
C_guess
=
0.1
*
self
.
pDelta
nrep_guess
=
self
.
m
nattr_guess
=
self
.
n
pguess
=
(
x0_guess
,
C_guess
,
nrep_guess
,
nattr_guess
)
popt
,
_
=
curve_fit
(
lambda
x
,
x0
,
C
,
nrep
,
nattr
:
LennardJones
(
x
,
self
.
Delta
,
x0
,
C
,
nrep
,
nattr
),
Z
,
Pmavg
,
p0
=
pguess
,
maxfev
=
10000
)
(
x0_opt
,
C_opt
,
nrep_opt
,
nattr_opt
)
=
popt
Pmavg_fit
=
LennardJones
(
Z
,
self
.
Delta
,
x0_opt
,
C_opt
,
nrep_opt
,
nattr_opt
)
# Compute prediction error
residuals
=
Pmavg
-
Pmavg_fit
ss_res
=
np
.
sum
(
residuals
**
2
)
N
=
residuals
.
size
std_err
=
np
.
sqrt
(
ss_res
/
N
)
max_err
=
max
(
np
.
abs
(
residuals
))
logger
.
debug
(
'LJ approx: x0 =
%.2f
nm, C =
%.2f
kPa, m =
%.2f
, n =
%.2f
'
,
x0_opt
*
1e9
,
C_opt
*
1e-3
,
nrep_opt
,
nattr_opt
)
LJ_approx
=
{
"x0"
:
x0_opt
,
"C"
:
C_opt
,
"nrep"
:
nrep_opt
,
"nattr"
:
nattr_opt
}
return
(
LJ_approx
,
std_err
,
max_err
)
def
PMavgpred
(
self
,
Z
):
""" Return the predicted intermolecular pressure based on a specific Lennard-Jones
function fitted on the deflection physiological range.
:param Z: leaflet apex outward deflection value (m)
:return: predicted average intermolecular pressure (Pa)
"""
return
LennardJones
(
Z
,
self
.
Delta
,
self
.
LJ_approx
[
'x0'
],
self
.
LJ_approx
[
'C'
],
self
.
LJ_approx
[
'nrep'
],
self
.
LJ_approx
[
'nattr'
])
def
Pelec
(
self
,
Z
,
Qm
):
""" Compute the electric equivalent pressure term.
:param Z: leaflet apex outward deflection value (m)
:param Qm: membrane charge density (C/m2)
:return: electric equivalent pressure (Pa)
"""
relS
=
self
.
S0
/
self
.
surface
(
Z
)
abs_perm
=
self
.
epsilon0
*
self
.
epsilonR
# F/m
return
-
relS
*
Qm
**
2
/
(
2
*
abs_perm
)
# Pa
def
findDeltaEq
(
self
,
Qm
):
""" Compute the Delta that cancels out the (Pm + Pec) equation at Z = 0
for a given membrane charge density, using the Brent method to refine
the pressure root iteratively.
:param Qm: membrane charge density (C/m2)
:return: equilibrium value (m) and associated pressure (Pa)
"""
f
=
lambda
Delta
:
(
self
.
pDelta
*
((
self
.
Delta_
/
Delta
)
**
self
.
m
-
(
self
.
Delta_
/
Delta
)
**
self
.
n
)
+
self
.
Pelec
(
0.0
,
Qm
))
Delta_lb
=
0.1
*
self
.
Delta_
Delta_ub
=
2.0
*
self
.
Delta_
Delta_eq
=
brentq
(
f
,
Delta_lb
,
Delta_ub
,
xtol
=
1e-16
)
logger
.
debug
(
'∆eq =
%.2f
nm'
,
Delta_eq
*
1e9
)
return
(
Delta_eq
,
f
(
Delta_eq
))
def
gasflux
(
self
,
Z
,
P
):
""" Compute the gas molar flux through the BLS boundary layer for
an unsteady system.
:param Z: leaflet apex outward deflection value (m)
:param P: internal gas pressure in the inter-leaflet space (Pa)
:return: gas molar flux (mol/s)
"""
dC
=
self
.
C0
-
P
/
self
.
kH
return
2
*
self
.
surface
(
Z
)
*
self
.
Dgl
*
dC
/
self
.
xi
def
gasmol2Pa
(
self
,
ng
,
V
):
""" Compute the gas pressure in the inter-leaflet space for an
unsteady system, from the value of gas molar content.
:param ng: internal molar content (mol)
:param V: inner volume of the bilayer sonophore structure (m^3)
:return: internal gas pressure (Pa)
"""
return
ng
*
self
.
Rg
*
self
.
T
/
V
def
gasPa2mol
(
self
,
P
,
V
):
""" Compute the gas molar content in the inter-leaflet space for
an unsteady system, from the value of internal gas pressure.
:param P: internal gas pressure in the inter-leaflet space (Pa)
:param V: inner volume of the bilayer sonophore structure (m^3)
:return: internal gas molar content (mol)
"""
return
P
*
V
/
(
self
.
Rg
*
self
.
T
)
def
PtotQS
(
self
,
Z
,
ng
,
Qm
,
Pac
,
Pm_comp_method
):
""" Compute the balance pressure of the quasi-steady system, upon application
of an external perturbation on a charged membrane:
Ptot = Pm + Pg + Pec - P0 - Pac.
:param Z: leaflet apex outward deflection value (m)
:param ng: internal molar content (mol)
:param Qm: membrane charge density (C/m2)
:param Pac: external acoustic perturbation (Pa)
:param Pm_comp_method: type of method used to compute average intermolecular pressure
:return: total balance pressure (Pa)
"""
if
Pm_comp_method
is
PmCompMethod
.
direct
:
Pm
=
self
.
PMavg
(
Z
,
self
.
curvrad
(
Z
),
self
.
surface
(
Z
))
elif
Pm_comp_method
is
PmCompMethod
.
predict
:
Pm
=
self
.
PMavgpred
(
Z
)
return
Pm
+
self
.
gasmol2Pa
(
ng
,
self
.
volume
(
Z
))
-
self
.
P0
-
Pac
+
self
.
Pelec
(
Z
,
Qm
)
def
balancedefQS
(
self
,
ng
,
Qm
,
Pac
=
0.0
,
Pm_comp_method
=
PmCompMethod
.
predict
):
""" Compute the leaflet deflection upon application of an external
perturbation to a quasi-steady system with a charged membrane.
This function uses the Brent method (progressive approximation of
function root) to solve the following transcendental equation for Z:
Pm + Pg + Pec - P0 - Pac = 0.
:param ng: internal molar content (mol)
:param Qm: membrane charge density (C/m2)
:param Pac: external acoustic perturbation (Pa)
:param Pm_comp_method: type of method used to compute average intermolecular pressure
:return: leaflet deflection (Z) canceling out the balance equation
"""
lb
=
-
0.49
*
self
.
Delta
ub
=
self
.
a
Plb
=
self
.
PtotQS
(
lb
,
ng
,
Qm
,
Pac
,
Pm_comp_method
)
Pub
=
self
.
PtotQS
(
ub
,
ng
,
Qm
,
Pac
,
Pm_comp_method
)
assert
(
Plb
>
0
>
Pub
),
'[
%d
,
%d
] is not a sign changing interval for PtotQS'
%
(
lb
,
ub
)
return
brentq
(
self
.
PtotQS
,
lb
,
ub
,
args
=
(
ng
,
Qm
,
Pac
,
Pm_comp_method
),
xtol
=
1e-16
)
def
TEleaflet
(
self
,
Z
):
""" Compute the circumferential elastic tension felt across the
entire leaflet upon stretching.
:param Z: leaflet apex outward deflection value (m)
:return: circumferential elastic tension (N/m)
"""
return
self
.
kA
*
self
.
arealstrain
(
Z
)
def
TEtissue
(
self
,
Z
):
""" Compute the circumferential elastic tension felt across the
embedding viscoelastic tissue layer upon stretching.
:param Z: leaflet apex outward deflection value (m)
:return: circumferential elastic tension (N/m)
"""
return
self
.
kA_tissue
*
self
.
arealstrain
(
Z
)
def
TEtot
(
self
,
Z
):
""" Compute the total circumferential elastic tension (leaflet
and embedding tissue) felt upon stretching.
:param Z: leaflet apex outward deflection value (m)
:return: circumferential elastic tension (N/m)
"""
return
self
.
TEleaflet
(
Z
)
+
self
.
TEtissue
(
Z
)
def
PEtot
(
self
,
Z
,
R
):
""" Compute the total elastic tension pressure (leaflet + embedding
tissue) felt upon stretching.
:param Z: leaflet apex outward deflection value (m)
:param R: leaflet curvature radius (m)
:return: elastic tension pressure (Pa)
"""
return
-
self
.
TEtot
(
Z
)
/
R
def
PVleaflet
(
self
,
U
,
R
):
""" Compute the viscous stress felt across the entire leaflet
upon stretching.
:param U: leaflet apex outward deflection velocity (m/s)
:param R: leaflet curvature radius (m)
:return: leaflet viscous stress (Pa)
"""
return
-
12
*
U
*
self
.
delta0
*
self
.
muS
/
R
**
2
def
PVfluid
(
self
,
U
,
R
):
""" Compute the viscous stress felt across the entire fluid
upon stretching.
:param U: leaflet apex outward deflection velocity (m/s)
:param R: leaflet curvature radius (m)
:return: fluid viscous stress (Pa)
"""
return
-
4
*
U
*
self
.
muL
/
np
.
abs
(
R
)
def
accP
(
self
,
Pres
,
R
):
""" Compute the pressure-driven acceleration of the leaflet in the
unsteady system, upon application of an external perturbation.
:param Pres: net resultant pressure (Pa)
:param R: leaflet curvature radius (m)
:return: pressure-driven acceleration (m/s^2)
"""
return
Pres
/
(
self
.
rhoL
*
np
.
abs
(
R
))
def
accNL
(
self
,
U
,
R
):
""" Compute the non-linear term of the leaflet acceleration in the
unsteady system, upon application of an external perturbation.
:param U: leaflet apex outward deflection velocity (m/s)
:param R: leaflet curvature radius (m)
:return: nonlinear acceleration (m/s^2)
.. note:: A simplified version of nonlinear acceleration (neglecting
dR/dH) is used here.
"""
# return - (3/2 - 2*R/H) * U**2 / R
return
-
(
3
*
U
**
2
)
/
(
2
*
R
)
def
eqMech
(
self
,
y
,
t
,
Adrive
,
Fdrive
,
Qm
,
phi
,
Pm_comp_method
=
PmCompMethod
.
predict
):
""" Compute the derivatives of the 3-ODE mechanical system variables,
with an imposed constant charge density.
:param y: vector of HH system variables at time t
:param t: specific instant in time (s)
:param Adrive: acoustic drive amplitude (Pa)
:param Fdrive: acoustic drive frequency (Hz)
:param Qm: membrane charge density (F/m2)
:param phi: acoustic drive phase (rad)
:param Pm_comp_method: type of method used to compute average intermolecular pressure
:return: vector of mechanical system derivatives at time t
"""
# Split input vector explicitly
(
U
,
Z
,
ng
)
=
y
# Correct deflection value is below critical compression
if
Z
<
-
0.5
*
self
.
Delta
:
logger
.
warning
(
'Deflection out of range: Z =
%.2f
nm'
,
Z
*
1e9
)
Z
=
-
0.49
*
self
.
Delta
# Compute curvature radius
R
=
self
.
curvrad
(
Z
)
# Compute total pressure
Pg
=
self
.
gasmol2Pa
(
ng
,
self
.
volume
(
Z
))
if
Pm_comp_method
is
PmCompMethod
.
direct
:
Pm
=
self
.
PMavg
(
Z
,
self
.
curvrad
(
Z
),
self
.
surface
(
Z
))
elif
Pm_comp_method
is
PmCompMethod
.
predict
:
Pm
=
self
.
PMavgpred
(
Z
)
Ptot
=
(
Pm
+
Pg
-
self
.
P0
-
self
.
Pacoustic
(
t
,
Adrive
,
Fdrive
,
phi
)
+
self
.
PEtot
(
Z
,
R
)
+
self
.
PVleaflet
(
U
,
R
)
+
self
.
PVfluid
(
U
,
R
)
+
self
.
Pelec
(
Z
,
Qm
))
# Compute derivatives
dUdt
=
self
.
accP
(
Ptot
,
R
)
+
self
.
accNL
(
U
,
R
)
dZdt
=
U
dngdt
=
self
.
gasflux
(
Z
,
Pg
)
# Return derivatives vector
return
[
dUdt
,
dZdt
,
dngdt
]
def
run
(
self
,
Fdrive
,
Adrive
,
Qm
,
phi
=
np
.
pi
,
Pm_comp_method
=
PmCompMethod
.
predict
):
""" Compute short solutions of the mechanical system for specific
US stimulation parameters and with an imposed membrane charge density.
:param Fdrive: acoustic drive frequency (Hz)
:param Adrive: acoustic drive amplitude (Pa)
:param phi: acoustic drive phase (rad)
:param Qm: imposed membrane charge density (C/m2)
:param Pm_comp_method: type of method used to compute average intermolecular pressure
:return: 3-tuple with the time profile, the solution matrix and a state vector
"""
# Check validity of stimulation parameters
if
not
all
(
isinstance
(
param
,
float
)
for
param
in
[
Fdrive
,
Adrive
,
Qm
,
phi
]):
raise
InputError
(
'Invalid stimulation parameters (must be float typed)'
)
if
Fdrive
<=
0
:
raise
InputError
(
'Invalid US driving frequency: {} kHz (must be strictly positive)'
.
format
(
Fdrive
*
1e-3
))
if
Adrive
<
0
:
raise
InputError
(
'Invalid US pressure amplitude: {} kPa (must be positive or null)'
.
format
(
Adrive
*
1e-3
))
if
Qm
<
CHARGE_RANGE
[
0
]
or
Qm
>
CHARGE_RANGE
[
1
]:
raise
InputError
(
'Invalid applied charge: {} nC/cm2 (must be within [{}, {}] interval'
.
format
(
Qm
*
1e5
,
CHARGE_RANGE
[
0
]
*
1e5
,
CHARGE_RANGE
[
1
]
*
1e5
))
if
phi
<
0
or
phi
>=
2
*
np
.
pi
:
raise
InputError
(
'Invalid US pressure phase: {:.2f} rad (must be within [0, 2 PI[ rad'
.
format
(
phi
))
# Raise warnings as error
warnings
.
filterwarnings
(
'error'
)
# Determine mechanical system time step
Tdrive
=
1
/
Fdrive
dt_mech
=
Tdrive
/
NPC_FULL
t_mech_cycle
=
np
.
linspace
(
0
,
Tdrive
-
dt_mech
,
NPC_FULL
)
# Initialize system variables
t0
=
0.0
Z0
=
0.0
U0
=
0.0
ng0
=
self
.
ng0
# Solve quasi-steady equation to compute first deflection value
Pac1
=
self
.
Pacoustic
(
t0
+
dt_mech
,
Adrive
,
Fdrive
,
phi
)
Z1
=
self
.
balancedefQS
(
ng0
,
Qm
,
Pac1
,
Pm_comp_method
)
U1
=
(
Z1
-
Z0
)
/
dt_mech
# Construct arrays to hold system variables
states
=
np
.
array
([
1
,
1
])
t
=
np
.
array
([
t0
,
t0
+
dt_mech
])
y
=
np
.
array
([[
U0
,
U1
],
[
Z0
,
Z1
],
[
ng0
,
ng0
]])
# Integrate mechanical system for a few acoustic cycles until stabilization
j
=
0
ng_last
=
None
Z_last
=
None
periodic_conv
=
False
while
not
periodic_conv
and
j
<
NCYCLES_MAX
:
t_mech
=
t_mech_cycle
+
t
[
-
1
]
+
dt_mech
y_mech
=
integrate
.
odeint
(
self
.
eqMech
,
y
[:,
-
1
],
t_mech
,
args
=
(
Adrive
,
Fdrive
,
Qm
,
phi
,
Pm_comp_method
))
.
T
# Compare Z and ng signals over the last 2 acoustic periods
if
j
>
0
:
Z_rmse
=
rmse
(
Z_last
,
y_mech
[
1
,
:])
ng_rmse
=
rmse
(
ng_last
,
y_mech
[
2
,
:])
logger
.
debug
(
'step
%u
: Z_rmse =
%.2e
m, ng_rmse =
%.2e
mol'
,
j
,
Z_rmse
,
ng_rmse
)
if
Z_rmse
<
Z_ERR_MAX
and
ng_rmse
<
NG_ERR_MAX
:
periodic_conv
=
True
# Update last vectors for next comparison
Z_last
=
y_mech
[
1
,
:]
ng_last
=
y_mech
[
2
,
:]
# Concatenate time and solutions to global vectors
states
=
np
.
concatenate
([
states
,
np
.
ones
(
NPC_FULL
)],
axis
=
0
)
t
=
np
.
concatenate
([
t
,
t_mech
],
axis
=
0
)
y
=
np
.
concatenate
([
y
,
y_mech
],
axis
=
1
)
# Increment loop index
j
+=
1
if
j
==
NCYCLES_MAX
:
logger
.
warning
(
'No convergence: stopping after
%u
cycles'
,
j
)
else
:
logger
.
debug
(
'Periodic convergence after
%u
cycles'
,
j
)
states
[
-
1
]
=
0
# return output variables
return
(
t
,
y
[
1
:,
:],
states
)
Event Timeline
Log In to Comment