Skip to content

Commit 02ff656

Browse files
LinearEuler 2D/3D: variable background density via static nodal rho0 (#139)
* LinearEuler 2D/3D: variable background density via static nodal rho0 Promote the background density rho0 from a scalar model attribute to a static per-node solution variable, mirroring the heterogeneous sound-speed pattern from #138. This lets rho0 vary in space (e.g. across material interfaces) alongside c, so heterogeneous media with both density and sound-speed contrast are supported. - 2D: nvar 5->6 (rho0 = variable 6), nstepped stays 4. - 3D: nvar 6->7 (rho0 = variable 7); set nstepped=5 (previously unset, relying only on zero flux/source to hold the auxiliary fields static). - Interior flux, entropy_func, and the impedance-matched Riemann flux now read per-node rho0. The Riemann solver uses per-side impedance Z = rho0*c, so material interfaces (density and/or sound-speed jumps) get the physically correct reflection/transmission; the reconstructed density/momentum/pressure fluxes use the face-averaged rho0 and c^2 (the same pragmatic treatment already used for c^2). The scalar rho0 attribute is retained as the reference value that fills the field in the built-in initial conditions. - GPU: fluxmethod/boundaryflux kernels read per-node rho0 and drop the scalar rho0 argument; BC kernels copy rho0 through to the exterior state (signatures now mirror the c handling in #138). - Entropy stability: for piecewise-constant material regions aligned with element boundaries, element interiors have grad(rho0)=grad(c)=0 (so the flux-divergence form is exact) and the impedance-matched Riemann flux carries the entropy-stable interface dissipation. Also in this change: - Add a CPU radiation BC to the 2D model (previously only the GPU backend registered one); it zeros the acoustic perturbation and copies c and rho0, mirroring the 3D model. Register it in AdditionalInit_LinearEuler2D_t. - Add a PML entropy_func override so the PML variant keeps using its scalar rho0 (its variable 6 is the auxiliary phi_rho, not rho0). - Fix the 2D entropy_func s(3)*(3) typo (already fixed for 3D in #138). - boneandmarrow example: stamp a per-material rho0 (muscle/bone/marrow) alongside c, exercising a genuinely heterogeneous impedance field. - planewave examples: set the new rho0 variable in the initial condition and the prescribed boundary state. - Update the 2D/3D model docs. CPU (gfortran) build is clean and all 7 LinearEuler ctests pass; boneandmarrow entropy is finite and monotonically decreasing. GPU kernels to be validated by CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fix LinearEuler doc staleness after variable-density change - 2D model: the base LinearEuler2D_t now registers a CPU radiation handler (not GPU-only), so update the BC section accordingly. - 3D model: remove two dead example links (spherical_soundwave_closeddomain and planewave_propagation examples do not exist); keep the radiation example, which is the only 3D example present. - PML model: document the new entropy_func override and clarify that the PML variant keeps rho0 as a scalar (its slot 6 is phi_rho, whereas the parent model now carries rho0 as a per-node solution variable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cbfa8df commit 02ff656

13 files changed

Lines changed: 325 additions & 170 deletions

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ $$
1818
u \\
1919
v \\
2020
p \\
21-
c
21+
c \\
22+
\rho_0
2223
\end{pmatrix}
2324
$$
2425

25-
where $\rho$ is a density anomaly, referenced to the density $\rho_0$, $u$ and $v$ are the $x$ and $y$ components of the fluid velocity (respectively), $p$ is the pressure, and $c$ is the speed of sound. The sound speed is carried as a per-node solution variable so that it can vary in space (e.g. across material interfaces); its flux and source are identically zero, so $c$ is held fixed in time at each spatial location.
26+
where $\rho$ is a density anomaly, referenced to the background density $\rho_0$, $u$ and $v$ are the $x$ and $y$ components of the fluid velocity (respectively), $p$ is the pressure, $c$ is the speed of sound, and $\rho_0$ is the background density. Both the sound speed $c$ and the background density $\rho_0$ are carried as per-node solution variables so that they can vary in space (e.g. across material interfaces); their flux and source are identically zero, so $c$ and $\rho_0$ are held fixed in time at each spatial location. This is entropy-stable for piecewise-constant material regions aligned with element boundaries: element interiors have $\nabla \rho_0 = \nabla c = 0$ (so the flux-divergence form is exact) and the impedance-matched Riemann flux handles the jumps at faces.
2627

2728
When we assume an ideal gas, and a motionless background state, the conservative fluxes are
2829

@@ -33,6 +34,7 @@ $$
3334
\frac{p}{\rho_0} \hat{x} \\
3435
\frac{p}{\rho_0} \hat{y} \\
3536
\rho_0 c^2 (u \hat{x} + v \hat{y}) \\
37+
\vec{0} \\
3638
\vec{0}
3739
\end{pmatrix}
3840
$$
@@ -50,7 +52,7 @@ $$
5052
$$
5153

5254
## Implementation
53-
The Linear Euler 2D model is implemented as a type extension of the [`DGModel2D` class](../ford/type/dgmodel2d_t.html). The [`LinearEuler2D_t` class](../ford/type/lineareuler2d_t.html) adds a parameter for the reference density $\rho_0$ and overrides `SetNumberOfVariables` (to declare `nvar = 5`), `SetMetadata`, `AdditionalInit`, `entropy_func`, `flux2d`, and `riemannflux2d`. The sound speed is no longer a scalar attribute of the model — it lives in `solution(:,:,:,5)` and can be set independently per node when initializing the simulation.
55+
The Linear Euler 2D model is implemented as a type extension of the [`DGModel2D` class](../ford/type/dgmodel2d_t.html). The [`LinearEuler2D_t` class](../ford/type/lineareuler2d_t.html) keeps a scalar `rho0` attribute (used as the reference value that fills variable 6 in the built-in initial conditions) and overrides `SetNumberOfVariables` (to declare `nvar = 6` with `nstepped = 4`, so the last two variables are static), `SetMetadata`, `AdditionalInit`, `entropy_func`, `flux2d`, and `riemannflux2d`. The sound speed lives in `solution(:,:,:,5)` and the background density in `solution(:,:,:,6)`; both can be set independently per node when initializing the simulation.
5456

5557
### Riemann Solver
5658
The `LinearEuler2D` class is defined using the conservative form of the conservation law. The interface flux is the exact upwind (Godunov) solver for the linearized acoustic system obtained by characteristic decomposition. The normal-flux Jacobian has eigenstructure
@@ -67,29 +69,31 @@ $$
6769
p^* = \frac{Z_R p_L + Z_L p_R + Z_L Z_R (u_{n,L} - u_{n,R})}{Z_L + Z_R},
6870
$$
6971

70-
with acoustic impedance $Z = \rho_0 c$. This reduces correctly to Fresnel reflection / transmission across an impedance jump (e.g. $c_L \neq c_R$ at a material interface). The interface flux is then
72+
with the per-side acoustic impedance $Z = \rho_0 c$, where each side uses its own background density $\rho_0$ and sound speed $c$. This reduces correctly to Fresnel reflection / transmission across an impedance jump (e.g. $Z_L \neq Z_R$ at a material interface, whether from a density jump, a sound-speed jump, or both). The interface flux is then
7173

7274
$$
7375
\overleftrightarrow{f}^* \cdot \hat{n} =
7476
\begin{pmatrix}
75-
\rho_0\,u_n^* \\
76-
p^*\, n_x / \rho_0 \\
77-
p^*\, n_y / \rho_0 \\
78-
\rho_0\,\overline{c^2}\,u_n^* \\
77+
\overline{\rho_0}\,u_n^* \\
78+
p^*\, n_x / \overline{\rho_0} \\
79+
p^*\, n_y / \overline{\rho_0} \\
80+
\overline{\rho_0}\,\overline{c^2}\,u_n^* \\
81+
0 \\
7982
0
8083
\end{pmatrix}, \qquad
84+
\overline{\rho_0} = \tfrac{1}{2}(\rho_{0,L} + \rho_{0,R}), \quad
8185
\overline{c^2} = \tfrac{1}{2}(c_L^2 + c_R^2).
8286
$$
8387

84-
The pressure-flux uses an averaged $c^2$ as a pragmatic treatment of the non-conservative product $\rho_0 c^2 \nabla \cdot \vec{v}$ at a face where $c$ jumps. The previously used local Lax-Friedrichs solver was found to over-dissipate the tangential and entropy modes and to fail to stably handle impedance mismatch at high polynomial order (aliasing instability at material interfaces), and has been replaced by the characteristic flux above. Details are in [`self_lineareuler2d_t.f90`](../ford/sourcefile/self_lineareuler2d_t.f90.html).
88+
The reconstructed density/momentum/pressure fluxes use the face-averaged $\overline{\rho_0}$ and $\overline{c^2}$ as a pragmatic treatment of the non-conservative products $p/\rho_0$ and $\rho_0 c^2 \nabla \cdot \vec{v}$ at a face where the coefficients jump; the physically important reflection/transmission is carried exactly by the per-side impedances above. The previously used local Lax-Friedrichs solver was found to over-dissipate the tangential and entropy modes and to fail to stably handle impedance mismatch at high polynomial order (aliasing instability at material interfaces), and has been replaced by the characteristic flux above. Details are in [`self_lineareuler2d_t.f90`](../ford/sourcefile/self_lineareuler2d_t.f90.html).
8589

8690
### Boundary conditions
87-
Boundary conditions are managed through the [extensible boundary-condition system](../Learning/BoundaryConditions.md). Each model registers its hyperbolic boundary conditions inside `AdditionalInit` by calling `hyperbolicBCs%RegisterBoundaryCondition(id, name, fn)`. `LinearEuler2D_t` registers a no-normal-flow handler out of the box; the GPU build additionally registers a radiation handler that runs on the device. Prescribed boundary conditions are registered by user code (or by an example subclass) so that the external state can be set as a function of position and time.
91+
Boundary conditions are managed through the [extensible boundary-condition system](../Learning/BoundaryConditions.md). Each model registers its hyperbolic boundary conditions inside `AdditionalInit` by calling `hyperbolicBCs%RegisterBoundaryCondition(id, name, fn)`. `LinearEuler2D_t` registers both a no-normal-flow and a radiation handler out of the box (CPU); the GPU build overwrites both registrations with equivalent device kernels. Prescribed boundary conditions are registered by user code (or by an example subclass) so that the external state can be set as a function of position and time.
8892

8993
The built-in boundary identifiers used with the mesh generators are
9094

9195
* `SELF_BC_RADIATION` — set the external state on the boundary to zero in the Riemann solver (open/non-reflecting).
92-
* `SELF_BC_NONORMALFLOW` — reflect the velocity vector about the boundary normal and prolong $\rho$, $p$, and $c$. This produces a reflecting (free-slip) wall and works for arbitrarily oriented normals.
96+
* `SELF_BC_NONORMALFLOW` — reflect the velocity vector about the boundary normal and prolong $\rho$, $p$, $c$, and $\rho_0$. This produces a reflecting (free-slip) wall and works for arbitrarily oriented normals.
9397
* `SELF_BC_PRESCRIBED` — use a user-registered handler to fill the external state.
9498

9599
As an example, when using the built-in structured mesh generator,
@@ -115,17 +119,18 @@ integer :: bcids(1:4)
115119
See the [Structured Mesh documentation](../MeshGeneration/StructuredMesh.md) for details on using the `structuredmesh` procedure, and the [Boundary Condition System](../Learning/BoundaryConditions.md) for how to register new BC handlers.
116120

117121
!!! note
118-
To set a prescribed state as a function of position and time, create a type-extension of `LinearEuler2D` and register a custom BC method against `SELF_BC_PRESCRIBED` from `AdditionalInit`. Remember that your handler must also fill `solution%extBoundary(:,:,:,5)` with the appropriate sound-speed value at the boundary — the planewave examples show this pattern.
122+
To set a prescribed state as a function of position and time, create a type-extension of `LinearEuler2D` and register a custom BC method against `SELF_BC_PRESCRIBED` from `AdditionalInit`. Remember that your handler must also fill `solution%extBoundary(:,:,:,5)` (sound speed) and `solution%extBoundary(:,:,:,6)` (background density) with the appropriate values at the boundary — the planewave examples show this pattern.
119123

120-
### Setting the sound speed
124+
### Setting the sound speed and background density
121125

122-
Because $c$ is a solution variable, you initialize it the same way you initialize $\rho$, $u$, $v$, and $p$:
126+
Because $c$ and $\rho_0$ are solution variables, you initialize them the same way you initialize $\rho$, $u$, $v$, and $p$:
123127

124128
```fortran
125129
this%solution%interior(i,j,iel,5) = c_value_at_this_node
130+
this%solution%interior(i,j,iel,6) = rho0_value_at_this_node
126131
```
127132

128-
For a uniform background, set every node to the same constant. For a piecewise-constant medium (e.g. bone embedded in marrow), assign the local material's $c$. The `SphericalSoundWave` initializer takes the (uniform) sound speed as an explicit argument:
133+
For a uniform background, set every node to the same constants. For a piecewise-constant medium (e.g. bone embedded in marrow), assign the local material's $c$ and $\rho_0$. The `SphericalSoundWave` initializer takes the (uniform) sound speed as an explicit argument and fills the background density from the scalar `this%rho0`:
129134

130135
```fortran
131136
call model%SphericalSoundWave(rhoprime=1.0e-2_prec, Lr=0.1_prec, &
@@ -157,4 +162,4 @@ For examples, see any of the following
157162
* [`examples/lineareuler2d_spherical_soundwave_closeddomain.f90`](https://github.com/FluidNumerics/SELF/blob/main/examples/linear_euler2d_spherical_soundwave_closeddomain.f90) - Simulation with a gaussian pressure and density anomaly as an initial condition in a domain with no-normal-flow boundary conditions on all sides. Demonstrates uniform sound speed via the `SphericalSoundWave` initializer.
158163
* [`examples/linear_euler2d_planewave_propagation.f90`](https://github.com/FluidNumerics/SELF/blob/main/examples/linear_euler2d_planewave_propagation.f90) - Gaussian plane wave that propagates at a $45^\circ$ angle through a square domain. The initial and boundary conditions are an exact plane-wave solution to the linear Euler equations. The example subclass carries its own `c` attribute and writes it into `solution(...,5)` for both the initial condition and the prescribed boundary state.
159164
* [`examples/linear_euler2d_planewave_reflection.f90`](https://github.com/FluidNumerics/SELF/blob/main/examples/linear_euler2d_planewave_reflection.f90) - Gaussian plane wave reflected off a wall at $x=1$ via the method of images. Combines prescribed boundary conditions with no-normal-flow on the reflecting side.
160-
* [`examples/linear_euler2d_boneandmarrow.f90`](https://github.com/FluidNumerics/SELF/blob/main/examples/linear_euler2d_boneandmarrow.f90) - Heterogeneous-medium test on a HOHQMesh ISM-MM mesh tagged with three materials (muscle/bone/marrow). Each material is mapped to a representative sound speed written into `solution(...,5)`, and a Gaussian acoustic pulse refracts and reflects at the material interfaces. Exercises the impedance-matched Riemann solver across $c$ discontinuities.
165+
* [`examples/linear_euler2d_boneandmarrow.f90`](https://github.com/FluidNumerics/SELF/blob/main/examples/linear_euler2d_boneandmarrow.f90) - Heterogeneous-medium test on a HOHQMesh ISM-MM mesh tagged with three materials (muscle/bone/marrow). Each material is mapped to a representative sound speed and background density, written into `solution(...,5)` and `solution(...,6)`, and a Gaussian acoustic pulse refracts and reflects at the material interfaces. Exercises the impedance-matched Riemann solver across $\rho_0 c$ (impedance) discontinuities.

docs/Models/linear-euler-2d-pml-model.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ Both paths are covered step-by-step in the [PML tutorial](../Tutorials/LinearEul
7676
* `SetMetadata` — registers names and units for the four PML auxiliaries (plus `sigma_x`, `sigma_y`).
7777
* `AdditionalInit` — allocates `sigma_x` and `sigma_y` and registers PML-aware no-normal-flow and radiation boundary handlers.
7878
* `AdditionalFree` — releases the damping fields.
79-
* `flux2d`, `riemannflux2d` — reuse the parent linear-Euler flux for variables 1–5 and return zero for variables 6–9 (auxiliaries carry no flux).
79+
* `flux2d`, `riemannflux2d` — use the parent linear-Euler acoustic flux form for variables 1–5 and return zero for variables 6–9 (auxiliaries carry no flux). Note the PML variant uses the **scalar** background density `rho0`: unlike the parent model (which carries `rho0` as a per-node solution variable in slot 6), the PML model repurposes slot 6 for the auxiliary `phi_rho`.
80+
* `entropy_func` — acoustic energy using the scalar `rho0` and the per-node sound speed `s(5)`. This override is required because the parent's `entropy_func` reads `rho0` from `s(6)`, which the PML model uses for `phi_rho`.
8081
* `sourcemethod` — implements the ADE source term node-by-node. We override `sourcemethod` rather than `source2d` because the pure `source2d(s, dsdx)` signature has no access to the per-node $\sigma_x(i,j,iel)$, $\sigma_y(i,j,iel)$ lookups.
8182

8283
A new procedure `SetPMLProfile(x_interior_min, x_interior_max, y_interior_min, y_interior_max, pml_width, sigma_max, ramp_exponent)` populates `sigma_x` and `sigma_y` from the per-element material tags and the geometric ramp.

0 commit comments

Comments
 (0)