Skip to content

fix: symmetric N/* handling across strands in PWM energy routines (5.11.11) - #150

Merged
aviezerl merged 1 commit into
masterfrom
fix/pwm-n-strand-asymmetry
Jul 21, 2026
Merged

fix: symmetric N/* handling across strands in PWM energy routines (5.11.11)#150
aviezerl merged 1 commit into
masterfrom
fix/pwm-n-strand-asymmetry

Conversation

@aviezerl

Copy link
Copy Markdown
Collaborator

Context

Companion to a prego fix (tanaylab/prego#40). A user asked why prego::compute_pwm treats N differently on the two strands. It turned out the shared DnaPSSM scan code uses the column average (get_avg_log_prob()) for N/* on the forward strand but a flat log(0.25) on the reverse strand. In prego this is an observable ~1.6 nat strand-dependent error, because compute_pwm feeds ambiguous bases straight through the scan.

misha carries the same asymmetry in integrate_energy, integrate_energy_logspat, and integrate_energy_max_logspat (the PWM-energy vtrack path).

This change

Reverse-strand N/* now uses get_avg_log_prob(), matching the forward branch, the already-symmetric routines (max_like_match, integrate_like_seg), and prego's copy of the shared code.

Scope / caveat (please read)

This is a consistency fix with no observable behavior change for genomic scoring. In misha, a genomic N reaches the scorer as a masked 0-byte and the window returns -Inf at the if(!(*j)) guard before the N/* branch runs - verified empirically (flank windows score finitely, any N-containing window is -Inf, on both spatial and non-spatial paths). So:

  • No user-visible change for PWM vtracks over real genomes.
  • No positive regression test is possible (an N-window is always -Inf); correctness of the branch itself is covered by the prego test, which exercises the identical logic through an unmasked path.
  • The existing PWM suite passes unchanged (no regression) - that is what CI green confirms here.

The value is keeping the two near-identical DnaPSSM.cpp copies from drifting, and correctness for the * wildcard / any future path that scores ambiguous bases without masking.

Left as-is (different, out-of-scope asymmetries): integrate_like / update_like_vec (forward log(0.25), reverse avg); like_thresh_match is intentionally symmetric on log(0.25).

https://claude.ai/code/session_01PK3qefBGDoBwd9w5226FEq

…11.11)

integrate_energy(), integrate_energy_logspat() and
integrate_energy_max_logspat() scored N/* with the column average
(get_avg_log_prob) on the forward strand but a flat log(0.25) on the
reverse strand. Both strands now use the column average, matching the
forward branch, the symmetric likelihood routines (max_like_match,
integrate_like_seg), and the shared DnaPSSM code in the prego package.

No behavior change for genomic vtrack scoring: an N-containing window is
masked to -Inf before these branches run. This keeps misha's DnaPSSM in
sync with prego, where the same code path (compute_pwm) feeds ambiguous
bases through unmasked and the asymmetry was an observable ~1.6 nat
strand-dependent error.

Claude-Session: https://claude.ai/code/session_01PK3qefBGDoBwd9w5226FEq
@aviezerl
aviezerl force-pushed the fix/pwm-n-strand-asymmetry branch from 70d0801 to 6d8f11b Compare July 21, 2026 17:41
@aviezerl
aviezerl merged commit ec6a669 into master Jul 21, 2026
5 checks passed
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