-
-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stan::math and std::complex #3006
Comments
@WardBrian, thanks for writing this down! I think relying on undefined behavior according to the C++ specification leads directly to this. Thank you for laying out the two options. They seem like the reasonable set of options; I can't think of another option to consider. Tradeoffs Between Option 1 and 2 In general, I think Option 1 is better than Option 2 only under these conditions:
I think Option 2 is better than Option 1 here, even if we have to write tests for almost all the behavior we rely on. |
I prefer option 2, since it eventually gets us to an island of stability. The downside is it kind of needs to be done monolithically/all at once, which is a lot of work Option 1 seems like we could eventually reach some kind of stable state where we're not using any ADL at all (we're testing against new clang/gcc versions to catch breaks early), but then the compilers could design to break something else too. |
Latest issue that arose from this is #3106 In the issue we opened against LLVM, maintainer said: llvm/llvm-project#109858 (comment)
It appears in this instance they're willing to entertain the idea of changing their signatures to un-break our use case, but this makes me even more convinced it is worth us defining our own |
With the upcoming major version bump, we could define a |
Other autodiff libraries seem to have hit this themselves: This makes the interesting note that |
I'd like us to look into |
Can we write a |
Description
Currently, Stan's complex number support is entirely built on
std::complex
, including autodiff, whichuses
std::complex<stan::math::var>
.This is, unfortunately, unspecified behavior in the C++ spec [26.4.2]:
For a reminder on what "unspecified behavior" means:
Essentially, unspecified behavior is the same as "implementation-defined behavior" but without the requirement that implementations document what they are doing. This is also often taken to mean there are no backwards compatibility guarantees on any specific unspecified behavior.
This creates both a maintenance burden (each new libstdc++/libc++ release can create arbitrary amounts of work for our developers) and a stability hazard (the idea that "Stan X.Y will continue to work a year from now, without needing to update to Stan X.Z" is false as things stand today)
Problems
Recent versions of
clang
/libstdc++
have made changes which they are fully within their rights to do by the spec, but have broken Stan builds.log(complex)
fromcomplex<T>(log(abs(x)), arg(x));
tocomplex<T>(std::log(std::abs(x)), std::arg(x));
. This broke argument dependent lookup for this function.A similar change broke
operator*
for our complex types.This lead to Unable to complete
make build
on Mac M1 cmdstan#1158, which was the reason we needed a 2.32.1 release.@andrjohns provided the fix in Add overloads for complex multiply to fix clang16 #2892
fabs
, which necessitated to Fix usages of fabs in check_symmetric with llvm17 #2991pow
was rewritten such that several overloads lead to a static assert failing if the type passed was not arithmetic: Compilation failures under LLVM 19 #3106What to do
This is less clear to me.
Option 1 - walk on egg shells
So far, all of the issues that have arisen from this have been due to argument dependent lookup breaking for these types. We can fix that by being much more explicit, as we did in #2892 and #2991. This requires auditing the existing usages, which probably requires a fair amount of C++ expertise to understand how the calls are being resolved.
Option 2 - our own type
We could rather trivially define our own
stan::math::complex<T>
type. We could make it assignable fromstd::complex<double>
, and I think be off to the races? I believe the complex linear algebra we use in Eigen all support a template argument for the complex type, rather than assumingstd::complex
.This would require a fair amount of boilerplate to actually do any math on it, and in the case of
double
we may lose out on some of the optimizations that having the type built in to the language grants, but we'd own it.The text was updated successfully, but these errors were encountered: