The confusion of the include order of headers in the Math library came up again in a pull request for Automated Style.
I’ll put some thoughts down when I get a chance. For now, here’s some older discussion we’ve had regarding this topic:
What’s the current recommendation for clients of the Math library?
We are currently encouraging clients of the Math library that need reverse-mode automatic differentiation and matrix operations to include one header file: stan/math.hpp
That should cover most of the use cases, including code generated by Stan. There are 12 different top-level include files depending on what the client needs.
The first thing that needs to be determined is whether the client needs primitives (no auto-diff,
prim), reverse mode auto-diff (
rev), forward mode auto-diff (
fwd), or mixed mode auto-diff (for higher order,
The second thing that needs to be determined is if the client needs scalars only (no
prim), arrays (
std::vector but not
arr), or matrix (
mat). Once both things are determined, the client code just needs to include one of these headers:
stan/math/rev/mat.hpp: this is what
Why do we bother with 12 includes?
The reason we split
mix is because of compiler restrictions. In a perfect world, we’d just include
mix and we’d be able to compile everywhere. Unfortunately, most Windows users would not be able to compile anything if we included
mix. They should be able to include
The main reason for splitting
mat is because we’ve had multiple users ask us to reduce dependencies. By that, they really mean Boost and Eigen. In
arr, we use Boost, but not Eigen. When we include
mat, we’re now using Eigen. (I think it’d be safe for us to squish
arr together if we wanted.) This is something we didn’t do early on. We were able to do this a few years ago (with a lot of work). We could always reevaluate the tradeoffs in keeping this separate, but there’s a secondary reason. We may be able to cut down on the time to compilation if we keep this separate. If we don’t, then we’re always gonna be compiling the
What’s the problem?
Our template metaprograms. And our desire to want to be efficient for compile time, runtime, and developer time.
Our code base is rad. For something like our distributions, we can write one function that be used for thousands of different instantiations (and that’s not an exaggeration). For example, we have the normal lpdf, which has three arguments,
sigma. That header only includes the metaprograms from
When it’s in
scal, all three of them can only be
double (well… that’s not even true; they could be instantiated as
float or some other type, but not from the Stan library). If we change
arr, then each of the three arguments could independently be
double. We’d need to change the template metaprograms included to go from the
scal to the
arr case. These need to be instantiated before including the normal lpdf file and once that’s done, this one function can now take on
std::vector arguments. And then again for
mat, but then we’re allowing for each of the arguments to be either
But wait… there’s more! Change
rev and now we can replace
stan::math::var. That requires a different set of the template metaprogram instantiations. And so on for
So it’s really our template metaprograms. We can’t just include the right metaprograms for
mat, although that’s the easiest for the devs, because if we did that, we’d alienate a lot of users who wouldn’t be able to even compile the math library.
And include what you use is actually pretty tough. We’d have to expect the devs to know which includes are necessary for the 12 different folders. It’s not impossible, but it does require thought. We could have 12 different meta includes and I’m pretty sure that would fix it. It’d be pretty easy to replace the overall header with the meta header.