Skip to content

Commit e3c434c

Browse files
Modularize microstructure generator (#207)
1 parent 22543c5 commit e3c434c

8 files changed

Lines changed: 75 additions & 76 deletions

File tree

docs/src/api-reference/models.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ evaluate_coefficient
2020
```@docs
2121
AnisotropicPlanarMicrostructureModel
2222
OrthotropicMicrostructureModel
23-
create_simple_microstructure_model
24-
Thunderbolt.streeter_type_fsn
23+
create_microstructure_model
24+
ODB25LTMicrostructureParameters
2525
```
2626

2727
## Boundary Conditions

docs/src/literate-tutorials/cm01_simple-active-stress.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ coordinate_system = compute_lv_coordinate_system(mesh);
4343

4444
# In this coordinate system we will now create a microstructure with linearly varying helix angle in transmural direction.
4545
# The compute microstructure field will be generated on the function space of piecewise continuous first order Lagrange polynomials.
46-
microstructure = create_simple_microstructure_model(
46+
microstructure = create_microstructure_model(
4747
coordinate_system,
48-
LagrangeCollection{1}()^3;
49-
endo_helix_angle = deg2rad(60.0),
50-
epi_helix_angle = deg2rad(-60.0),
48+
LagrangeCollection{1}()^3,
49+
ODB25LTMicrostructureParameters(),
5150
);
5251

5352
# Now we describe the model which we want to use.

docs/src/literate-tutorials/cm03_3d0d-coupling.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,11 @@ mesh = Thunderbolt.hexahedralize(mesh)
255255
# The 3D0D coupling does not yet support multiple subdomains.
256256

257257
coordinate_system = compute_lv_coordinate_system(mesh)
258-
microstructure = create_simple_microstructure_model(
258+
microstructure = create_microstructure_model(
259259
coordinate_system,
260260
LagrangeCollection{1}()^3,
261-
endo_helix_angle = deg2rad(60.0),
262-
epi_helix_angle = deg2rad(-60.0),
263-
)
261+
ODB25LTMicrostructureParameters(),
262+
);
264263
passive_material_model = Guccione1991PassiveModel()
265264
active_material_model = Guccione1993ActiveModel()
266265
function calcium_profile_function(x::LVCoordinate,t)

src/Thunderbolt.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ export
192192
OrthotropicMicrostructure,
193193
TransverselyIsotropicMicrostructureModel,
194194
TransverselyIsotropicMicrostructure,
195-
create_simple_microstructure_model,
195+
ODB25LTMicrostructureParameters,
196+
create_microstructure_model,
196197
# Coordinate system
197198
LVCoordinateSystem,
198199
LVCoordinate,

src/modeling/core/coordinate_systems.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ LV only part of the universal ventricular coordinate, containing
4646
* apicobasal
4747
* rotational
4848
"""
49-
struct LVCoordinate{T}
49+
Base.@kwdef struct LVCoordinate{T}
5050
transmural::T
5151
apicobasal::T
5252
rotational::T
@@ -327,7 +327,7 @@ Biventricular universal coordinate, containing
327327
* rotational
328328
* transventricular
329329
"""
330-
struct BiVCoordinate{T}
330+
Base.@kwdef struct BiVCoordinate{T}
331331
transmural::T
332332
apicobasal::T
333333
rotational::T

src/modeling/microstructure.jl

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -127,72 +127,55 @@ function evaluate_coefficient(fsn::OrthotropicMicrostructureCache, cell_cache, q
127127
return OrthotropicMicrostructure(orthogonalize_system(f, s, n)...)
128128
end
129129

130-
131130
"""
132-
streeter_type_fsn(transmural_direction::Vec{3}, circumferential_direction::Vec{3}, apicobasal_direction::Vec{3}, helix_angle, transversal_angle, sheetlet_pseudo_angle, make_orthogonal=true)
133-
134-
Compute fiber, sheetlet and normal direction from the transmural, circumferential, apicobasal directions
135-
in addition to given helix, transversal and sheetlet angles. The theory is based on the classical work by
136-
[StreSpoPatRosSon:1969:foc](@citet).
131+
Linear transmural distribution of the microstructure with three angles to describe all reachable physiological angles.
137132
"""
138-
function streeter_type_fsn(transmural_direction, circumferential_direction, apicobasal_direction, helix_angle, transversal_angle, sheetlet_pseudo_angle, make_orthogonal=true)
139-
# First we construct the helix rotation ...
140-
f₀ = rotate_around(circumferential_direction, transmural_direction, helix_angle)
141-
f₀ /= norm(f₀)
142-
# ... followed by the transversal_angle ...
143-
f₀ = rotate_around(f₀, apicobasal_direction, transversal_angle)
144-
f₀ /= norm(f₀)
145-
146-
# Then we construct the the orthogonal sheetlet vector ...
147-
s₀ = rotate_around(circumferential_direction, transmural_direction, helix_angle+π/2.0)
148-
s₀ /= norm(f₀)
149-
# FIXME this does not preserve the sheetlet angle
150-
s₀ = unproject(s₀, -transmural_direction, sheetlet_pseudo_angle)
151-
if make_orthogonal
152-
s₀ = orthogonalize(s₀/norm(s₀), f₀)
153-
end
154-
# TODO replace above with an implementation of the following pseudocode
155-
# 1. Compute plane via P = I - f₀ ⊗ f₀
156-
# 2. Eigen decomposition E of P
157-
# 3. Compute a generalized eigenvector s' from the non-zero eigenvalue (with ||s'||=1) from E such that <f₀,s'> minimal
158-
# 4. Compute s₀ by rotating s' around f₀ such that cos(sheetlet angle) = <s',f₀>
159-
s₀ /= norm(s₀)
160-
161-
# Compute normal :)
162-
n₀ = f₀ × s₀
163-
n₀ /= norm(n₀)
164-
165-
return OrthotropicMicrostructure(f₀, s₀, n₀)
133+
Base.@kwdef struct ODB25LTMicrostructureParameters{T}
134+
αendo::T = deg2rad(60.0)
135+
αepi::T = deg2rad(-60.0)
136+
βendo::T = 0.0
137+
βepi::T = 0.0
138+
γendo::T = 0.0
139+
γepi::T = 0.0
166140
end
167141

168-
169142
"""
170-
abc_fsn(transmural_direction::Vec{3}, circumferential_direction::Vec{3}, apicobasal_direction::Vec{3}, α, β, γ, make_orthogonal=true)
143+
compute_local_microstructure(params::..., x, axes)
171144
172145
Compute fiber, sheetlet and normal direction from the transmural, circumferential, apicobasal directions
173146
in addition to given helix, transversal and sheetlet angles. The theory is based on the classical work by
174147
[StreSpoPatRosSon:1969:foc](@citet).
175148
"""
176-
function abc_fsn(transmural_direction, circumferential_direction, apicobasal_direction, α, β, γ, make_orthogonal=true)
149+
function compute_local_microstructure(p::ODB25LTMicrostructureParameters, x::Union{LVCoordinate, BiVCoordinate}, axes::Union{LVCoordinate, BiVCoordinate})
150+
(; αendo, αepi, βendo, βepi, γendo, γepi) = p
151+
152+
# linear interpolation of rotation angle
153+
α = (1-x.transmural) * αendo + (x.transmural) * αepi
154+
β = (1-x.transmural) * βendo + (x.transmural) * βepi
155+
γ = (1-x.transmural) * γendo + (x.transmural) * γepi
156+
157+
circumferential_direction = axes.rotational
158+
transmural_direction = axes.transmural
159+
apicobasal_direction = axes.apicobasal
160+
177161
# First we abc_fsn the helix rotation ...
178162
f₀ = rotate_around(circumferential_direction, transmural_direction, α)
179-
f₀ /= norm(f₀)
163+
f₀ = normalize(f₀)
180164
# ... followed by the transversal_angle ...
181-
f₀ = rotate_around(f₀, apicobasal_direction, β)
182-
f₀ /= norm(f₀)
165+
f₀ = rotate_around(f₀, apicobasal_direction, -β)
166+
f₀ = normalize(f₀)
183167

184168
# Then we construct the the orthogonal sheetlet vector ...
185169
s₀ = rotate_around(circumferential_direction, transmural_direction, α+π/2.0)
186-
s₀ /= norm(f₀)
187-
if make_orthogonal
188-
s₀ = orthogonalize(s₀/norm(s₀), f₀)
189-
end
190-
s₀ = rotate_around(s₀, f₀, γ)
191-
s₀ /= norm(s₀)
170+
s₀ = normalize(s₀)
171+
s₀ = orthogonalize(s₀, f₀)
172+
s₀ = normalize(s₀)
173+
s₀ = rotate_around(s₀, f₀, -γ)
174+
s₀ = normalize(s₀)
192175

193176
# Compute normal :)
194177
n₀ = f₀ × s₀
195-
n₀ /= norm(n₀)
178+
n₀ = normalize(n₀)
196179

197180
return OrthotropicMicrostructure(f₀, s₀, n₀)
198181
end
@@ -202,7 +185,16 @@ end
202185
203186
Create a rotating fiber field by deducing the circumferential direction from apicobasal and transmural gradients.
204187
"""
205-
function create_simple_microstructure_model(coordinate_system, ip_collection::VectorizedInterpolationCollection{3}; endo_helix_angle = deg2rad(80.0), epi_helix_angle = deg2rad(-65.0), endo_transversal_angle = 0.0, epi_transversal_angle = 0.0, endo_rot_angle = 0.0, epi_rot_angle = 0.0, make_orthogonal=true)
188+
function create_simple_microstructure_model(coordinate_system, ip_collection::VectorizedInterpolationCollection{3}; endo_helix_angle = deg2rad(80.0), epi_helix_angle = deg2rad(-65.0), endo_transversal_angle = 0.0, epi_transversal_angle = 0.0, endo_rot_angle = 0.0, epi_rot_angle = 0.0)
189+
return create_microstructure_model(coordinate_system, ip_collection, ODB25LTMicrostructureParameters(endo_helix_angle, epi_helix_angle, endo_transversal_angle, epi_transversal_angle, endo_rot_angle, epi_rot_angle))
190+
end
191+
192+
"""
193+
create_microstructure_model(coordinate_system::CoordinateSystemCoefficient, ip::VectorInterpolationCollection, parameters)
194+
195+
Create a rotating fiber field by deducing the circumferential direction from apicobasal and transmural gradients.
196+
"""
197+
function create_microstructure_model(coordinate_system::CoordinateSystemCoefficient, ip_collection::VectorizedInterpolationCollection{3}, parameters)
206198
@unpack dh = coordinate_system
207199

208200
# TODO this storage is redundant, can we reduce the memory footprint?
@@ -234,15 +226,20 @@ function create_simple_microstructure_model(coordinate_system, ip_collection::Ve
234226
apicobasal_direction = apicobasal_direction - (apicobasal_direction transmural_direction) * transmural_direction # We do this fix to ensure local orthogonality
235227
circumferential_direction = transmural_direction × apicobasal_direction
236228
circumferential_direction /= norm(circumferential_direction)
229+
axes = LVCoordinate(;
230+
transmural = transmural_direction,
231+
rotational = circumferential_direction,
232+
apicobasal = apicobasal_direction,
233+
)
234+
# TODO grab these via some interface!
235+
x = LVCoordinate(;
236+
transmural = function_value(cv, qp, coordinate_system.u_transmural[dof_indices]),
237+
rotational = function_value(cv, qp, coordinate_system.u_rotational[dof_indices]),
238+
apicobasal = function_value(cv, qp, coordinate_system.u_apicobasal[dof_indices]),
239+
)
237240

238-
transmural = function_value(cv, qp, coordinate_system.u_transmural[dof_indices])
239-
240-
# linear interpolation of rotation angle
241-
helix_angle = (1-transmural) * endo_helix_angle + (transmural) * epi_helix_angle
242-
transversal_angle = (1-transmural) * endo_transversal_angle + (transmural) * epi_transversal_angle
243-
rot_angle = (1-transmural) * endo_rot_angle + (transmural) * epi_rot_angle
241+
coeff = compute_local_microstructure(parameters, x, axes)
244242

245-
coeff = abc_fsn(transmural_direction, circumferential_direction, apicobasal_direction, helix_angle, transversal_angle, rot_angle, make_orthogonal)
246243
f_buf[qp.i, cellindex] = Tv(coeff.f)
247244
s_buf[qp.i, cellindex] = Tv(coeff.s)
248245
n_buf[qp.i, cellindex] = Tv(coeff.n)

test/integration/test_solid_mechanics.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ end
221221
@test !any(isnan.(cs.u_apicobasal))
222222
@test !any(isnan.(cs.u_transmural))
223223
@test !any(isnan.(cs.u_rotational))
224-
microstructure_model = create_simple_microstructure_model(cs, LagrangeCollection{1}()^3)
224+
microstructure_parameters = ODB25LTMicrostructureParameters(αendo=deg2rad(80.0), αepi=deg2rad(-65.0))
225+
microstructure_model = create_microstructure_model(cs, LagrangeCollection{1}()^3, microstructure_parameters)
225226

226227
test_solve_contractile_ideal_lv(grid, ExtendedHillModel(
227228
HolzapfelOgden2009Model(),

test/test_microstructures.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
end
3030

3131
@testset "OrthotropicMicrostructureModel" begin
32-
ms = create_simple_microstructure_model(ring_cs, ip_collection,
33-
endo_helix_angle = deg2rad(0.0),
34-
epi_helix_angle = deg2rad(0.0),
35-
endo_transversal_angle = 0.0,
36-
epi_transversal_angle = 0.0,
37-
endo_rot_angle = deg2rad(0.0),
38-
epi_rot_angle = deg2rad(0.0),
32+
ms = create_microstructure_model(ring_cs, ip_collection,
33+
ODB25LTMicrostructureParameters(
34+
αendo = 0.0,
35+
αepi = 0.0,
36+
βendo = 0.0,
37+
βepi = 0.0,
38+
γendo = 0.0,
39+
γepi = 0.0,
40+
)
3941
)
4042
cache2 = Thunderbolt.setup_coefficient_cache(ms, qr, sdh)
4143
for cellcache in CellIterator(ring_grid)

0 commit comments

Comments
 (0)