math/statistics policy-interface feedback requested.

Following the successful review of the math-toolkit of special functions and statistical distributions the main feature request was for a better way of customising the library: both for choosing between precision vs speed tradeoffs, and for determining how errors are best handled. I've been experimenting with various policy-based interfaced based on Boost.Parameter, and I think I now have something useable, so I'd like to know what people think: Policy Defaults ~~~~~~~~~~~~~~~ The library will use a sensible set of defaults (throw on domain errors and internal evaluation errors, favour accuracy over speed etc), which can be changed via the usual macro mechanism, so adding: #define BOOST_MATH_DOMAIN_ERROR_POLICY errno_on_error #define BOOST_MATH_OVERFLOW_ERROR_POLICY ignor_error to a user-config header would do what they say to the default policies. Add Hock Changes: ~~~~~~~~~~~~~~~~~ We can create an ad-hock policy change at the call site by using the make_policy function, for example: using namespace boost::math::policy; using namespace boost::math; quantile( poisson(100), 0.05, make_policy( // 5 dicimal digits precision only digits10<10>(), // don't internally promote double->long double for extra precision: promote_double<false>(), // return integer result, immediately below the "real" value: discrete_quantile<integer_below>() )); Which returns the lower 95% quantile (ie critical value) of a poisson distribution with rate parameter of 100 event's per unit time. The result is calculated using only 10 decimal digits internal precision, truncated to the largest integer that gives a CDF less than 0.05. Predefined Policies ~~~~~~~~~~~~~~~~~~~ Although ad-hock policies are useful for testing, I imagine most sites would want a few carefully controlled (and tested) policies. To achieve that you define a typedef of the policy class: using namespace boost::math::policy; typedef policy< // Set error handling: domain_error<throw_on_error>, pole_error<throw_on_error>, overflow_error<throw_on_error>, evaluation_error<throw_on_error>, denorm_error<ignor_error>, underflow_error<ignor_error>, // calculate to 8 decimal digits internally digits10<8>, // don't promote double->long double for accuracy promote_double<false>, // Integer quantiles return the "outside edge": // below the real value for lower critical values, // above it for upper critical values, so that the // area inside contains *at least* the requested coverage: discrete_quantile<integer_outside_edge> > fast_quantile_policy; static fast_quantile_policy fast_quantile; Then we can just use: quantile( poisson(100), 0.05, fast_quantile); In our actual code. Currently this policy interface is vapourware: I have enough of a prototype implemented to know that it's possible to achieve this syntax (this is revision #3 already !), but there's a lot of hairy meta-programming to convert that into something that the library's internals can make use of... so I'd like to know what folks think before I invest too much time messing about with MPL :-) The main disadvantage I've noticed at present, is that the mangled names of the policy class - and therefore all the special functions etc - are *very* long. This has an impact on error messages: in particular we currently use BOOST_CURRENT_FUNCTION to get a nice formatted name of a function that's about to throw, but with function names a couple of pages long I don't think that will be possible with this interface any more :-( Thanks in advance, John Maddock.

on Tue Jun 12 2007, "John Maddock" <john-AT-johnmaddock.co.uk> wrote:
define a typedef of the policy class:
using namespace boost::math::policy; typedef policy< // Set error handling: domain_error<throw_on_error>, pole_error<throw_on_error>, overflow_error<throw_on_error>, evaluation_error<throw_on_error>, denorm_error<ignor_error>, underflow_error<ignor_error>, // calculate to 8 decimal digits internally digits10<8>, // don't promote double->long double for accuracy promote_double<false>, // Integer quantiles return the "outside edge": // below the real value for lower critical values, // above it for upper critical values, so that the // area inside contains *at least* the requested coverage: discrete_quantile<integer_outside_edge> > fast_quantile_policy;
static fast_quantile_policy fast_quantile;
Then we can just use:
quantile( poisson(100), 0.05, fast_quantile);
In our actual code.
Currently this policy interface is vapourware: I have enough of a prototype implemented to know that it's possible to achieve this syntax (this is revision #3 already !), but there's a lot of hairy meta-programming to convert that into something that the library's internals can make use of... so I'd like to know what folks think before I invest too much time messing about with MPL :-)
The main disadvantage I've noticed at present, is that the mangled names of the policy class - and therefore all the special functions etc - are *very* long.
Why not make it allowable to use derivation: struct fast_quantile_policy : policy< // Set error handling: domain_error<throw_on_error>, pole_error<throw_on_error>, overflow_error<throw_on_error>, evaluation_error<throw_on_error>, denorm_error<ignor_error>, underflow_error<ignor_error>, // calculate to 8 decimal digits internally digits10<8>, // don't promote double->long double for accuracy promote_double<false>, // Integer quantiles return the "outside edge": // below the real value for lower critical values, // above it for upper critical values, so that the // area inside contains *at least* the requested coverage: discrete_quantile<integer_outside_edge> > {}; ? That will keep your type names under control.
This has an impact on error messages: in particular we currently use BOOST_CURRENT_FUNCTION to get a nice formatted name of a function that's about to throw, but with function names a couple of pages long I don't think that will be possible with this interface any more :-(
-- Dave Abrahams Boost Consulting http://www.boost-consulting.com The Astoria Seminar ==> http://www.astoriaseminar.com

David Abrahams wrote:
The main disadvantage I've noticed at present, is that the mangled names of the policy class - and therefore all the special functions etc - are *very* long.
Why not make it allowable to use derivation:
struct fast_quantile_policy : policy< // Set error handling: domain_error<throw_on_error>, pole_error<throw_on_error>, overflow_error<throw_on_error>, evaluation_error<throw_on_error>, denorm_error<ignor_error>, underflow_error<ignor_error>, // calculate to 8 decimal digits internally digits10<8>, // don't promote double->long double for accuracy promote_double<false>, // Integer quantiles return the "outside edge": // below the real value for lower critical values, // above it for upper critical values, so that the // area inside contains *at least* the requested coverage: discrete_quantile<integer_outside_edge> > {};
? That will keep your type names under control.
Interesting idea. Actually as far as the code internals are concerned the only thing that matters are the member typedefs of the policy class used, so this should "just work".... but one of things I was planning on doing is "normalising" the policy class passed to the function: basically decompose it and then recompose in a particular order: that way if the same function is called with different policy types that actually name the same policies, then internally only one template gets instantiated and we reduce code bloat. The same mechanism can stip out policies that have no relevence to the function being called, again potentially reducing code bloat. At present I don't see a way to combine that with your inheritance mechanism. I also need to be able to inject certain computed policies into the policy object when it get's normalised (basically a combination of the existing policy and it's combination with the actual real-number type being used for the computation). I do have a Boost.Format based mechanism to create nicely formatted function names though - and yes only invoked when needed - so I guess the question is whether these long mangled names have any other consequences: compile and or link time for example? Thanks, John.

Hi John,
The main disadvantage I've noticed at present, is that the mangled names of the policy class - and therefore all the special functions etc - are *very* long. This has an impact on error messages: in particular we currently use BOOST_CURRENT_FUNCTION to get a nice formatted name of a function that's about to throw, but with function names a couple of pages long I don't think that will be possible with this interface any more :-(
What about moving the policy stuff into a traits class instead, combined with a policy id? namespace boost { namespace math { template <size_t ID> struct policy_traits { // default policy static const bool check_domain = true; static const size_t internal_precision = 16; // ... }; // predefined policies const size_t default_policy_id = 0; const size_t fast_policy_id = 1; template <> struct policy_traits<fast_policy_id> : public policy_traits<default_policy_id> { static const bool check_domain = false; // overwrite check_domain }; // a simple size_t wrapper template <size_t ID> struct policy { static const size_t value = ID; }; typedef policy<fast_policy_id> fast_policy; } } Users then could define their own policies by providing a new specialization of the policy_traits class for their (unique) ID and would call the special functions similar to quantile( poisson(100), 0.05, fast_policy ); This would avoid the complicated meta-programming you suggested without compromising on the level of control. Regards, Stephan

Stephan Tolksdorf wrote:
Users then could define their own policies by providing a new specialization of the policy_traits class for their (unique) ID and would call the special functions similar to
quantile( poisson(100), 0.05, fast_policy );
This would avoid the complicated meta-programming you suggested without compromising on the level of control.
Thanks, that's an interesting idea, however, I've written the meta-programming code now and got it as fast to compile as policy-free code for the default case, so I think it'll work out OK - fingers and toes crossed! I think I'll test this out with the gamma functions and see how it shapes up. Regards, John.
participants (3)
-
David Abrahams
-
John Maddock
-
Stephan Tolksdorf