Skip to content

sem: acoustic c² Laplacian without C-PML, one-copy node materials - #131

Merged
GeophyAI merged 1 commit into
devfrom
fix/sem-acoustic-bare-pde
Oct 27, 2026
Merged

GeophyAI merged 1 commit into
devfrom
fix/sem-acoustic-bare-pde

Conversation

@GeophyAI

Copy link
Copy Markdown
Collaborator

Two bugs in the constant-density acoustic SEM classes (AcousticSEM2D, AcousticSEM2DDeformed, AcousticSEM3D, AcousticSEM3DDeformed; the second also in AcousticPotentialSEM2D).

1. The bare (no C-PML) branch solved div(c² grad u), not c² Laplace u

With abcn=0 the stiffness integral carried c², so the scheme kept c² ∂u/∂n continuous at a velocity jump instead of ∂u/∂n, and reflections came out with the wrong sign. The C-PML branch was already right. Homogeneous tests cannot tell the two forms apart.

  • The bare branch now uses the c²-free stiffness and multiplies by c² per node, the same scheme as the C-PML branch. K stays symmetric (the mass matrix is effectively M / c²), so the scheme still conserves energy.

  • The 3-D bare CUDA kernels (acoustic3d_forward/backward, flat and deformed) have the same div(c² grad u) form in both forward and adjoint. They are no longer routed: impl='c' raises, and impl='auto' runs eager. The kernels are left untouched for a later csrc fix.

  • New test test_sem_acoustic_reflection_polarity.py sends a 1-D plane wave from 1000 into 3000 m/s (R = +0.5). The reflection/direct ratio is projected on the window shifted by the known traveltime; the 1-D response is an odd pulse, so a peak pick cannot see the polarity.

    before after
    four classes, bare -0.500 +0.497
    four classes, C-PML +0.497 +0.497

    The test also checks that the bare and C-PML paths agree to 1e-12 until the PML echo arrives.

2. The model gradient was multiplied by each node's share count

vp_squared_node[ibool] = c2 (and kappa_node.index_put_) backpropagates the node gradient into every element copy of the node. All copies interpolate the same grid point, so the grid gradient came out multiplied by 2–4 in 2-D and 2–8 in 3-D. Every C-vs-eager test agreed because every path scaled the same way; the DD _CutMultiplicity deliberately reproduced the factor.

  • mesh/nodes.py: node_field reads one fixed copy per node (the highest-numbered one) with a gather. On one CPU thread this is bitwise the old forward. At an element-wise material jump, the old scatter's choice of copy moved with the thread count and between CUDA runs; the gather does not.

  • The four classes and kappa_node use it. _CutMultiplicity and the share-count factor on the compact DD path are removed.

  • New test test_sem_acoustic_grid_gradient_fd.py compares PropSEM grid gradients against central differences (fp64, two step sizes, no absolute floor, outer ring untouched), with and without a C-PML. Relative error before this PR, with a C-PML:

    class before after
    AcousticSEM2D, AcousticSEM2DDeformed 0.98 < 1e-5
    AcousticSEM3D, AcousticSEM3DDeformed 0.40 < 1e-5
    AcousticPotentialSEM2D 33 < 1e-5
  • test_sem_dd_rim now crops NO + 1 pixels instead of 4. Rim mode's error stays in the first element outside the band, and the 5th pixel is the node that element shares with the next one. Beyond it the error is about 1e-6 before and after this PR. The old crop passed only while the overcount weighted that ring differently. The fluid-solid part of the same file already cropped band + one element.

  • test_sem_bs_rim::test_the_configuration_actually_discriminates, which used to fail (uncropped cos 0.992, over its 0.99 bound), now passes.

Tests

  • test/sem on a GPU: 856 passed, 1 skipped. The only failure is test_sem_dd_elastic::test_dd_elastic_per_family_store_dtype, which also fails on dev and is unrelated.
  • CPU CI replica (clean venv, CPU torch, 4 cores), this branch: 1512 passed, 0 failed, 508 s; dev: 1490 passed, 517 s.

🤖 Generated with Claude Code

The bare branch of the four acoustic classes solved div(c^2 grad u) and
flipped reflection polarity; it now keeps c^2 outside the stiffness as the
C-PML branch does. The 3-D bare CUDA kernels have the same bug and are
refused ('auto' runs eager). vp_squared_node / kappa_node read one element
copy, so the model gradient is no longer multiplied by the share count
(_CutMultiplicity removed). Tests: two-layer polarity, grid FD gradients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GeophyAI
GeophyAI merged commit d078689 into dev Oct 27, 2026
1 check passed
@GeophyAI
GeophyAI deleted the fix/sem-acoustic-bare-pde branch October 27, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant