Skip to content

Divide the solver's source filter back out of the recombination - #144

Draft
lispandfound wants to merge 1 commit into
ba18_bb_simfrom
nzvm/bb-matched-filter
Draft

Divide the solver's source filter back out of the recombination#144
lispandfound wants to merge 1 commit into
ba18_bb_simfrom
nzvm/bb-matched-filter

Conversation

@lispandfound

Copy link
Copy Markdown
Contributor

Both solvers low-pass their source time functions: SW4 through its
prefilter command, EMOD3D through bfilt with a corner derived from
min_vs and the grid spacing. The wave equation is linear, so filtering
the source is equivalent to filtering every trace -- which means the LF
leg arrives at bb-sim already low-passed, and the matched pair then
low-passes it a second time.

The result is a hole around the merge frequency: the matched high-pass
and low-pass are power-complementary by construction, but only if each
leg is filtered once. tests/test_bb_filters.py measures the hole and
then measures that each correction closes it.

Because the filter is applied to the source rather than the output,
bb-sim can divide it back out here instead of the solver having to be
re-run. --solver reads the filter the realisation actually specifies
(rather than assuming one), and refuses to guess: an SW4 prefilter that
is not a lowpass, or an EMOD3D configuration that also high-passes, is
an error rather than a correction for the wrong thing.

--filter chooses which leg absorbs it:

lf restores the low-frequency leg exactly. Needs the largest boost.
hf fills the missing power from the high-frequency side instead.
Cannot restore below the merge frequency, where the HF leg
carries nothing to scale up.
both one factor on both legs. Best conditioned -- current_power is
bounded below by the high-pass leg -- and restores the power sum
exactly, at the cost of touching both legs.

MAX_BOOST caps any correction at 10x. The two real configurations need
about 2.4x; anything near the cap means the source filter rolls off
faster than the target and the leg is being reconstructed from content
that is not there. warn_if_ill_conditioned says so up front.

Without --solver the recombination is bit-for-bit unchanged: the
correction is applied after the matched pair rather than in place of it,
and the gains are exactly 1. Verified against the previous commit's
output.

What was applied is written into the broadband file's attributes.
Without that the correction is invisible downstream and two files that
differ by it look identical.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com


Stack created with GitHub Stacks CLIGive Feedback 💬

Both solvers low-pass their *source* time functions: SW4 through its
`prefilter` command, EMOD3D through `bfilt` with a corner derived from
`min_vs` and the grid spacing. The wave equation is linear, so filtering
the source is equivalent to filtering every trace -- which means the LF
leg arrives at `bb-sim` already low-passed, and the matched pair then
low-passes it a second time.

The result is a hole around the merge frequency: the matched high-pass
and low-pass are power-complementary by construction, but only if each
leg is filtered once. `tests/test_bb_filters.py` measures the hole and
then measures that each correction closes it.

Because the filter is applied to the source rather than the output,
`bb-sim` can divide it back out here instead of the solver having to be
re-run. `--solver` reads the filter the realisation actually specifies
(rather than assuming one), and refuses to guess: an SW4 prefilter that
is not a lowpass, or an EMOD3D configuration that also high-passes, is
an error rather than a correction for the wrong thing.

`--filter` chooses which leg absorbs it:

  lf    restores the low-frequency leg exactly. Needs the largest boost.
  hf    fills the missing power from the high-frequency side instead.
        Cannot restore below the merge frequency, where the HF leg
        carries nothing to scale up.
  both  one factor on both legs. Best conditioned -- `current_power` is
        bounded below by the high-pass leg -- and restores the power sum
        exactly, at the cost of touching both legs.

`MAX_BOOST` caps any correction at 10x. The two real configurations need
about 2.4x; anything near the cap means the source filter rolls off
faster than the target and the leg is being reconstructed from content
that is not there. `warn_if_ill_conditioned` says so up front.

Without `--solver` the recombination is bit-for-bit unchanged: the
correction is applied after the matched pair rather than in place of it,
and the gains are exactly 1. Verified against the previous commit's
output.

What was applied is written into the broadband file's attributes.
Without that the correction is invisible downstream and two files that
differ by it look identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lispandfound
lispandfound force-pushed the nzvm/bb-matched-filter branch from dc8667f to 09437b1 Compare September 4, 2026 01:44
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