Skip to content

fix: avoid atan branch cut in complex trigonometric test (musl CI)#1336

Merged
serge-sans-paille merged 1 commit intoxtensor-stack:masterfrom
DiamonDinoia:fix/musl-complex-trig
Apr 29, 2026
Merged

fix: avoid atan branch cut in complex trigonometric test (musl CI)#1336
serge-sans-paille merged 1 commit intoxtensor-stack:masterfrom
DiamonDinoia:fix/musl-complex-trig

Conversation

@DiamonDinoia
Copy link
Copy Markdown
Contributor

@DiamonDinoia DiamonDinoia commented Apr 29, 2026

Summary (I used claude to reproduce locally, as it is a hassle to handle docker for this)

test_complex_trigonometric.cpp fails on musl-based CI (e.g. void-linux x86_64-musl, see void-packages CI run) with two mismatches in [complex trigonometric] for complex<float> and complex<double>.

Root cause

atan_input[i] = (-10 + i*20/N, -9 + i*21/N) lands on z = (0, 1.5) at i = N/2, which is on the branch cut of complex atan (Re=0, |Im|>=1). Per C99 §7.3.4.1, the sign of Re(catan(±0 + iy)) for |y|>1 is implementation-defined:

  • glibc: atan(0 + 1.5i) = (+π/2, 0.8047…)
  • musl: atan(0 + 1.5i) = (-π/2, 0.8047…)

xsimd's atan implementation matches glibc, so CHECK_BATCH_EQ(ref, out) against the musl reference fails. The previous test code used get_nb_diff which was lossy; commit ae3822f replaced it with per-element CHECK_BATCH_EQ, exposing this latent issue.

Fix

Shift the real start from -10 to -9.5 so Re=0 occurs at i = 19000 where |Im| ≈ 0.975 < 1 — analytic territory where atan is uniquely defined and all libcs agree. Symmetric negative/positive real coverage is preserved; only the single sample on a region of unspecified behavior is dropped.

Verification

  • Reproduced the libc divergence with a standalone snippet calling std::atan(std::complex<T>) on glibc vs alpine/musl: confirmed Re sign flip at (0, 1.5).
  • Pre-fix on alpine/musl: [complex trigonometric] 2/2 cases fail.
  • Post-fix on glibc and alpine/musl: 2/2 cases pass.

The atan_input generator sampled (Re=0, Im=1.5) at i=N/2, which lies on
the branch cut of complex atan at Re=0, |Im|>=1. C99 7.3.4.1 leaves the
sign of Re(catan(+/-0 + i*y)) for |y|>1 implementation-defined: glibc
returns +pi/2, musl returns -pi/2. xsimd matches glibc, so the test
fails on musl-based CI (e.g. void-linux x86_64-musl) with two
mismatches (one per complex<float>/complex<double> batch type).

Shift the real start from -10 to -9.5 so Re=0 occurs at i=19000, where
|Im|~0.975<1 - analytic territory where atan is uniquely defined and
all libcs agree. Negative/positive real coverage is preserved; only
the singular point on a region of unspecified behavior is dropped.
@DiamonDinoia
Copy link
Copy Markdown
Contributor Author

@jason1987d could you verify that this fixes the issue?

@jason1987d
Copy link
Copy Markdown

@serge-sans-paille
Copy link
Copy Markdown
Contributor

Thank you so much for handling this 🙇

@serge-sans-paille serge-sans-paille merged commit 93a450e into xtensor-stack:master Apr 29, 2026
79 checks passed
@serge-sans-paille
Copy link
Copy Markdown
Contributor

@DiamonDinoia would it make sense to have a musl validation bot?

@DiamonDinoia
Copy link
Copy Markdown
Contributor Author

DiamonDinoia commented Apr 29, 2026

I makes sense as it uses a different glibc. However, we should be mindful of the length of pipelines. What if push release candidates so the repo maintainers can do these validations and report issues before we push a final release? We could give a week of grace.

@serge-sans-paille
Copy link
Copy Markdown
Contributor

serge-sans-paille commented Apr 29, 2026 via email

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.

3 participants