Skip to content

Commit cbfa8df

Browse files
LinearEuler3D: heterogeneous sound speed + impedance-matched Riemann flux (#138)
Bring the 3-D linear Euler model up to parity with LinearEuler2D: - Carry the sound speed as solution variable 6 (nvar 5 -> 6), static in time (zero flux/source), so heterogeneous media are supported. The scalar attribute `c` remains as the reference value used by initial conditions (SphericalSoundWave fills variable 6 with it). - Replace the local Lax-Friedrichs Riemann solver with the impedance-matched (characteristic/Godunov) flux used by the 2-D model: interface states resolved with the one-sided impedances Z = rho0*c, so material interfaces in a heterogeneous c field get the physically correct transmission/reflection. - GPU backend: fluxmethod reads the per-node c (variable 6) and the boundaryflux kernel implements the impedance-matched flux; both drop the scalar `c` argument (signatures now mirror the 2-D kernels). The radiation BC kernel now takes the interior boundary state and copies c through to extBoundary so face Riemann solves see a consistent sound speed, with the acoustic perturbation still zeroed. - Register a CPU radiation BC in AdditionalInit_LinearEuler3D_t (previously the radiation BC existed only in the GPU backend); the GPU AdditionalInit calls the parent and overwrites it with the kernel wrapper, mirroring the 2-D module structure. - Fix the entropy function: s(3)*(3) typo -> s(3)*s(3), include the w contribution correctly, and use the per-node sound speed. - Update the model documentation accordingly. Motivation: 3-D ultrasound computed tomography forward modeling (SELF-UCT AcousticTomography3D) needs spatially varying sound speed with impedance-matched interface handling, exactly as the 2-D class has. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 45169bb commit cbfa8df

4 files changed

Lines changed: 186 additions & 132 deletions

File tree

docs/Models/linear-euler-3d-model.md

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ $$
2020
u \\
2121
v \\
2222
w \\
23-
p
23+
p \\
24+
c
2425
\end{pmatrix}
2526
$$
2627

27-
where $\rho$ is a density anomaly, referenced to the density $\rho_0$, $u$, $v$, and $w$ are the $x$, $y$, and $z$ components of the fluid velocity (respectively), and $p$ is the pressure. When we assume an ideal gas, and a motionless background state, the conservative fluxes are
28+
where $\rho$ is a density anomaly, referenced to the density $\rho_0$, $u$, $v$, and $w$ are the $x$, $y$, and $z$ components of the fluid velocity (respectively), and $p$ is the pressure. The sound speed $c$ is carried as a solution variable so that heterogeneous (spatially varying) media are supported; it has zero flux and zero source, so it is static in time and is set by the initial condition (mirroring the 2-D model). When we assume an ideal gas, and a motionless background state, the conservative fluxes are
2829

2930
$$
3031
\overleftrightarrow{f} =
@@ -33,11 +34,12 @@ $$
3334
p \hat{x} \\
3435
p \hat{y} \\
3536
p \hat{z} \\
36-
\rho_0c^2(u \hat{x} + v \hat{y} + w \hat{z})
37+
\rho_0c^2(u \hat{x} + v \hat{y} + w \hat{z}) \\
38+
0
3739
\end{pmatrix}
3840
$$
3941

40-
where $c$ is the (constant) speed of sound. The source term is set to zero.
42+
The source term is set to zero.
4143

4244
$$
4345
\vec{q} = \vec{0}
@@ -46,44 +48,35 @@ $$
4648
To track stability of the Euler equation, the total entropy function is
4749

4850
$$
49-
e = \frac{1}{2} \int_V u^2 + v^2 + \frac{p}{\rho_0 c^2} \hspace{1mm} dV
51+
e = \frac{1}{2} \int_V \rho_0 (u^2 + v^2 + w^2) + \frac{p^2}{\rho_0 c^2} \hspace{1mm} dV
5052
$$
5153

5254
## Implementation
53-
The Linear Euler 3D model is implemented as a type extension of the [`DGModel3D` class](../ford/type/dgmodel3d_t.html). The [`LinearEuler3D_t` class](../ford/type/lineareuler3d_t.html) adds parameters for the reference density and the speed speed of sound and overrides the `SetMetadata`, `entropy_func`, `flux3d`, and `riemannflux3d` type-bound procedures.
55+
The Linear Euler 3D model is implemented as a type extension of the [`DGModel3D` class](../ford/type/dgmodel3d_t.html). The [`LinearEuler3D_t` class](../ford/type/lineareuler3d_t.html) adds parameters for the reference density and the reference speed of sound and overrides the `SetMetadata`, `entropy_func`, `flux3d`, and `riemannflux3d` type-bound procedures.
5456

5557
### Riemann Solver
56-
The `LinearEuler3D` class is defined using the conservative form of the conservation law. The Riemman solver for the hyperbolic part of Euler equation is the local Lax Friedrichs upwind riemann solver
58+
The `LinearEuler3D` class is defined using the conservative form of the conservation law. The Riemann solver for the hyperbolic part of the Euler equation is the impedance-matched (characteristic/Godunov) upwind flux, identical in form to the 2-D model's. With the acoustic impedances $Z_L = \rho_0 c_L$ and $Z_R = \rho_0 c_R$ evaluated from the per-node sound speed on either side of the face, the interface normal velocity and pressure are
5759

5860
$$
59-
\overleftrightarrow{f}_h^* \cdot \hat{n} = \frac{1}{2}( \overleftrightarrow{f}_L \cdot \hat{n} + \overleftrightarrow{f}_R \cdot \hat{n} + c (\vec{s}_L - \vec{s}_R))
61+
u_n^* = \frac{Z_L u_{n,L} + Z_R u_{n,R} + (p_L - p_R)}{Z_L + Z_R}, \qquad
62+
p^* = \frac{Z_R p_L + Z_L p_R + Z_L Z_R (u_{n,L} - u_{n,R})}{Z_L + Z_R}
6063
$$
6164

62-
where
65+
and the normal flux is
6366

6467
$$
65-
\overleftrightarrow{f}_L \cdot \hat{n} =
68+
\overleftrightarrow{f}_h^* \cdot \hat{n} =
6669
\begin{pmatrix}
67-
\rho_0(u_L n_x + v_L n_y + w_L n_z) \\
68-
p_L n_x \\
69-
p_L n_y \\
70-
p_L n_z \\
71-
\rho_0c^2(u_L n_x + v_L n_y + w_L n_z)
72-
\end{pmatrix}
73-
$$
74-
75-
$$
76-
\overleftrightarrow{f}_R \cdot \hat{n} =
77-
\begin{pmatrix}
78-
\rho_0(u_R n_x + v_R n_y + w_R n_z) \\
79-
p_R n_x \\
80-
p_R n_y \\
81-
p_R n_z \\
82-
\rho_0c^2(u_R n_x + v_R n_y + w_R n_z)
83-
\end{pmatrix}
70+
\rho_0 u_n^* \\
71+
p^* n_x / \rho_0 \\
72+
p^* n_y / \rho_0 \\
73+
p^* n_z / \rho_0 \\
74+
\rho_0 \overline{c^2} u_n^* \\
75+
0
76+
\end{pmatrix}, \qquad \overline{c^2} = \frac{1}{2}(c_L^2 + c_R^2)
8477
$$
8578

86-
The details for this implementation can be found in [`self_lineareuler3d_t.f90`](../ford/sourcefile/self_lineareuler3d_t.f90.html)
79+
Because the interface states are resolved with the one-sided impedances, material interfaces in a heterogeneous sound-speed field produce the physically correct transmission and reflection. The details for this implementation can be found in [`self_lineareuler3d_t.f90`](../ford/sourcefile/self_lineareuler3d_t.f90.html)
8780

8881
### Boundary conditions
8982
When initializing the mesh for your Euler 3D equation solver, you can change the boundary conditions to

src/SELF_LinearEuler3D_t.f90

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,20 @@
2727
module self_LinearEuler3D_t
2828
!! This module defines a class that can be used to solve the Linear Euler
2929
!! equations in 3-D. The Linear Euler Equations, here, are the Euler equations
30-
!! linearized about a motionless background state.
30+
!! linearized about a motionless background state. The sound speed is carried
31+
!! as a solution variable (static in time, possibly spatially varying) so that
32+
!! heterogeneous media are supported, mirroring the 2-D model.
33+
!!
34+
!! The solution variables are
3135
!!
32-
!! The conserved variables are
33-
3436
!! \begin{equation}
3537
!! \vec{s} = \begin{pmatrix}
3638
!! \rho \\
3739
!! u \\
3840
!! v \\
3941
!! w \\
40-
!! p
42+
!! p \\
43+
!! c
4144
!! \end{pmatrix}
4245
!! \end{equation}
4346
!!
@@ -49,31 +52,36 @@ module self_LinearEuler3D_t
4952
!! \frac{p}{\rho_0} \hat{x} \\
5053
!! \frac{p}{\rho_0} \hat{y} \\
5154
!! \frac{p}{\rho_0} \hat{z} \\
52-
!! c^2 \rho_0 ( u \hat{x} + v \hat{y} + w \hat{z})
55+
!! c^2 \rho_0 ( u \hat{x} + v \hat{y} + w \hat{z}) \\
56+
!! 0
5357
!! \end{pmatrix}
5458
!! \end{equation}
5559
!!
56-
!! and the source terms are null.
60+
!! and the source terms are null. The sound speed variable has zero flux and
61+
!! zero source; it is set by the initial condition and preserved in time.
62+
!! The Riemann flux is the impedance-matched (characteristic) flux, identical
63+
!! in form to the 2-D model's; see riemannflux3d_LinearEuler3D_t.
5764
!!
5865

5966
use self_model
6067
use self_dgmodel3D
6168
use self_mesh
69+
use SELF_BoundaryConditions
6270

6371
implicit none
6472

6573
type,extends(dgmodel3D) :: LinearEuler3D_t
6674
! Add any additional attributes here that are specific to your model
6775
real(prec) :: rho0 = 1.0_prec ! Reference density
68-
real(prec) :: c = 1.0_prec ! Sound speed
76+
real(prec) :: c = 1.0_prec ! Reference sound speed (used to fill variable 6 in initial conditions)
6977
real(prec) :: g = 0.0_prec ! gravitational acceleration (y-direction only)
7078

7179
contains
7280
procedure :: SourceMethod => sourcemethod_LinearEuler3D_t
7381
procedure :: SetNumberOfVariables => SetNumberOfVariables_LinearEuler3D_t
7482
procedure :: SetMetadata => SetMetadata_LinearEuler3D_t
83+
procedure :: AdditionalInit => AdditionalInit_LinearEuler3D_t
7584
procedure :: entropy_func => entropy_func_LinearEuler3D_t
76-
!procedure :: hbc3D_NoNormalFlow => hbc3D_NoNormalFlow_LinearEuler3D_t
7785
procedure :: flux3D => flux3D_LinearEuler3D_t
7886
procedure :: riemannflux3D => riemannflux3D_LinearEuler3D_t
7987
procedure :: SphericalSoundWave => SphericalSoundWave_LinearEuler3D_t
@@ -86,7 +94,7 @@ subroutine SetNumberOfVariables_LinearEuler3D_t(this)
8694
implicit none
8795
class(LinearEuler3D_t),intent(inout) :: this
8896

89-
this%nvar = 5
97+
this%nvar = 6
9098

9199
endsubroutine SetNumberOfVariables_LinearEuler3D_t
92100

@@ -109,38 +117,65 @@ subroutine SetMetadata_LinearEuler3D_t(this)
109117
call this%solution%SetName(5,"P") ! Pressure
110118
call this%solution%SetUnits(5,"kg⋅m⁻¹⋅s⁻²")
111119

120+
call this%solution%SetName(6,"c") ! Sound speed (static; possibly heterogeneous)
121+
call this%solution%SetUnits(6,"m⋅s⁻¹")
122+
112123
endsubroutine SetMetadata_LinearEuler3D_t
113124

125+
subroutine AdditionalInit_LinearEuler3D_t(this)
126+
!! Register the (CPU) radiation boundary condition. GPU builds overwrite
127+
!! this registration with the device kernel in AdditionalInit_LinearEuler3D.
128+
implicit none
129+
class(LinearEuler3D_t),intent(inout) :: this
130+
! Local
131+
procedure(SELF_bcMethod),pointer :: bcfunc
132+
133+
bcfunc => hbc3d_Radiation_LinearEuler3D
134+
call this%hyperbolicBCs%RegisterBoundaryCondition( &
135+
SELF_BC_RADIATION,"radiation",bcfunc)
136+
137+
endsubroutine AdditionalInit_LinearEuler3D_t
138+
139+
subroutine hbc3d_Radiation_LinearEuler3D(bc,mymodel)
140+
!! Radiation BC: zero acoustic perturbation in the exterior state; the
141+
!! sound speed (variable 6) is copied from the interior side so the
142+
!! Riemann solver sees a consistent c.
143+
class(BoundaryCondition),intent(in) :: bc
144+
class(Model),intent(inout) :: mymodel
145+
! Local
146+
integer :: n,i,j,iEl,s
147+
148+
select type(m => mymodel)
149+
class is(LinearEuler3D_t)
150+
do n = 1,bc%nBoundaries
151+
iEl = bc%elements(n)
152+
s = bc%sides(n)
153+
do j = 1,m%solution%interp%N+1
154+
do i = 1,m%solution%interp%N+1
155+
m%solution%extBoundary(i,j,s,iEl,1:5) = 0.0_prec
156+
m%solution%extBoundary(i,j,s,iEl,6) = m%solution%boundary(i,j,s,iEl,6) ! c preserved
157+
enddo
158+
enddo
159+
enddo
160+
endselect
161+
162+
endsubroutine hbc3d_Radiation_LinearEuler3D
163+
114164
pure function entropy_func_LinearEuler3D_t(this,s) result(e)
115165
!! The entropy function is the sum of kinetic and internal energy
116166
!! For the linear model, this is
117167
!!
118168
!! \begin{equation}
119-
!! e = \frac{1}{2} \left( \rho_0*( u^2 + v^2 ) + \frac{P^2}{\rho_0 c^2} \right)
169+
!! e = \frac{1}{2} \left( \rho_0*( u^2 + v^2 + w^2 ) + \frac{P^2}{\rho_0 c^2} \right)
120170
class(LinearEuler3D_t),intent(in) :: this
121171
real(prec),intent(in) :: s(1:this%nvar)
122172
real(prec) :: e
123173

124-
e = 0.5_prec*this%rho0*(s(2)*s(2)+s(3)*(3)+s(4)*s(4))+ &
125-
0.5_prec*(s(5)*s(5)/(this%rho0*this%c*this%c))
174+
e = 0.5_prec*this%rho0*(s(2)*s(2)+s(3)*s(3)+s(4)*s(4))+ &
175+
0.5_prec*(s(5)*s(5)/(this%rho0*s(6)*s(6)))
126176

127177
endfunction entropy_func_LinearEuler3D_t
128178

129-
! pure function hbc3D_NoNormalFlow_LinearEuler3D_t(this,s,nhat) result(exts)
130-
! class(LinearEuler3D_t),intent(in) :: this
131-
! real(prec),intent(in) :: s(1:this%nvar)
132-
! real(prec),intent(in) :: nhat(1:2)
133-
! real(prec) :: exts(1:this%nvar)
134-
! ! Local
135-
! integer :: ivar
136-
137-
! exts(1) = s(1) ! density
138-
! exts(2) = (nhat(2)**2-nhat(1)**2)*s(2)-2.0_prec*nhat(1)*nhat(2)*s(3) ! u
139-
! exts(3) = (nhat(1)**2-nhat(2)**2)*s(3)-2.0_prec*nhat(1)*nhat(2)*s(2) ! v
140-
! exts(4) = (nhat(1)**2-nhat(2)**2)*s(3)-2.0_prec*nhat(1)*nhat(2)*s(2) ! w
141-
! exts(5) = s(4) ! p
142-
143-
! endfunction hbc3D_NoNormalFlow_LinearEuler3D_t
144179
subroutine sourcemethod_LinearEuler3D_t(this)
145180
implicit none
146181
class(LinearEuler3D_t),intent(inout) :: this
@@ -171,50 +206,58 @@ pure function flux3D_LinearEuler3D_t(this,s,dsdx) result(flux)
171206
flux(4,2) = 0.0_prec ! z-velocity, y flux; 0
172207
flux(4,3) = s(5)/this%rho0 ! z-velocity, z flux; p/rho0
173208

174-
flux(5,1) = this%c*this%c*this%rho0*s(2) ! pressure, x flux : rho0*c^2*u
175-
flux(5,2) = this%c*this%c*this%rho0*s(3) ! pressure, y flux : rho0*c^2*v
176-
flux(5,3) = this%c*this%c*this%rho0*s(4) ! pressure, y flux : rho0*c^2*w
209+
flux(5,1) = s(6)*s(6)*this%rho0*s(2) ! pressure, x flux : rho0*c^2*u
210+
flux(5,2) = s(6)*s(6)*this%rho0*s(3) ! pressure, y flux : rho0*c^2*v
211+
flux(5,3) = s(6)*s(6)*this%rho0*s(4) ! pressure, z flux : rho0*c^2*w
212+
213+
flux(6,1) = 0.0_prec ! sound speed, x flux; 0 (c held fixed in time)
214+
flux(6,2) = 0.0_prec ! sound speed, y flux; 0
215+
flux(6,3) = 0.0_prec ! sound speed, z flux; 0
177216
if(.false.) flux(1,1) = flux(1,1)+dsdx(1,1) ! suppress unused-dummy-argument warning
178217

179218
endfunction flux3D_LinearEuler3D_t
180219

181220
pure function riemannflux3D_LinearEuler3D_t(this,sL,sR,dsdx,nhat) result(flux)
182-
!! Uses a local lax-friedrich's upwind flux
183-
!! The max eigenvalue is taken as the sound speed
221+
!! Impedance-matched (characteristic/Godunov) Riemann flux, identical in
222+
!! form to the 2-D model's. The interface states are resolved with the
223+
!! acoustic impedances Z = rho0*c on either side, so material interfaces
224+
!! in a heterogeneous sound-speed field are handled with the physically
225+
!! correct transmission/reflection:
226+
!!
227+
!! un* = (ZL*unL + ZR*unR + (pL - pR)) / (ZL + ZR)
228+
!! p* = (ZR*pL + ZL*pR + ZL*ZR*(unL - unR)) / (ZL + ZR)
229+
!!
230+
!! The sound speed variable carries zero flux.
184231
class(LinearEuler3D_t),intent(in) :: this
185232
real(prec),intent(in) :: sL(1:this%nvar)
186233
real(prec),intent(in) :: sR(1:this%nvar)
187234
real(prec),intent(in) :: dsdx(1:this%nvar,1:3)
188235
real(prec),intent(in) :: nhat(1:3)
189236
real(prec) :: flux(1:this%nvar)
190237
! Local
191-
real(prec) :: fL(1:this%nvar)
192-
real(prec) :: fR(1:this%nvar)
193-
real(prec) :: u,v,w,p,c,rho0
194-
195-
u = sL(2)
196-
v = sL(3)
197-
w = sL(4)
198-
p = sL(5)
238+
real(prec) :: rho0,cL,cR,ZL,ZR,unL,unR,pL,pR,un_star,p_star,c2_avg
239+
199240
rho0 = this%rho0
200-
c = this%c
201-
fL(1) = rho0*(u*nhat(1)+v*nhat(2)+w*nhat(3)) ! density
202-
fL(2) = p*nhat(1)/rho0 ! u
203-
fL(3) = p*nhat(2)/rho0 ! v
204-
fL(4) = p*nhat(3)/rho0 ! w
205-
fL(5) = rho0*c*c*(u*nhat(1)+v*nhat(2)+w*nhat(3)) ! pressure
206-
207-
u = sR(2)
208-
v = sR(3)
209-
w = sR(4)
210-
p = sR(5)
211-
fR(1) = rho0*(u*nhat(1)+v*nhat(2)+w*nhat(3)) ! density
212-
fR(2) = p*nhat(1)/rho0 ! u
213-
fR(3) = p*nhat(2)/rho0 ! v'
214-
fR(4) = p*nhat(3)/rho0 ! w
215-
fR(5) = rho0*c*c*(u*nhat(1)+v*nhat(2)+w*nhat(3)) ! pressure
216-
217-
flux(1:5) = 0.5_prec*(fL(1:5)+fR(1:5))+c*(sL(1:5)-sR(1:5))
241+
cL = sL(6)
242+
cR = sR(6)
243+
ZL = rho0*cL
244+
ZR = rho0*cR
245+
246+
unL = sL(2)*nhat(1)+sL(3)*nhat(2)+sL(4)*nhat(3)
247+
unR = sR(2)*nhat(1)+sR(3)*nhat(2)+sR(4)*nhat(3)
248+
pL = sL(5)
249+
pR = sR(5)
250+
251+
un_star = (ZL*unL+ZR*unR+(pL-pR))/(ZL+ZR)
252+
p_star = (ZR*pL+ZL*pR+ZL*ZR*(unL-unR))/(ZL+ZR)
253+
c2_avg = 0.5_prec*(cL*cL+cR*cR)
254+
255+
flux(1) = rho0*un_star
256+
flux(2) = p_star*nhat(1)/rho0
257+
flux(3) = p_star*nhat(2)/rho0
258+
flux(4) = p_star*nhat(3)/rho0
259+
flux(5) = rho0*c2_avg*un_star
260+
flux(6) = 0.0_prec
218261
if(.false.) flux(1) = flux(1)+dsdx(1,1) ! suppress unused-dummy-argument warning
219262

220263
endfunction riemannflux3D_LinearEuler3D_t
@@ -260,6 +303,7 @@ subroutine SphericalSoundWave_LinearEuler3D_t(this,rhoprime,Lr,x0,y0,z0)
260303
this%solution%interior(i,j,k,iEl,3) = 0.0_prec
261304
this%solution%interior(i,j,k,iEl,4) = 0.0_prec
262305
this%solution%interior(i,j,k,iEl,5) = rho*this%c*this%c
306+
this%solution%interior(i,j,k,iEl,6) = this%c ! uniform background sound speed
263307

264308
enddo
265309

0 commit comments

Comments
 (0)