Boost:任意分布的向量

Boost: Vector of Distributions with any

本文关键字:向量 分布 任意 Boost      更新时间:2023-10-16

亲爱的栈交换专家,

我试图建立一个类(多元分布函数),将boost分布存储在std::向量(边际分布函数)中。虽然这可以使用boost::variant(参见我的问题:boost:在Vector中存储指向分布的指针),但我也尝试了boost::any。原因是,在设置变体时,我必须硬编码潜在的类型(边际分布),我想避免这种情况。

虽然不同实现的发行版类没有共享一个共同的父类,但有一些函数,如boost::math::cdf或boost::math::pdf,可以应用于所有发行版,我想在std::vector上应用迭代。

我生成了下面的代码(运行良好),但是现在我有一个问题,函数any_cdf需要检查类型。

虽然我在设置向量时避免了硬编码类型(对于变体),但我现在需要硬编码any_cdf函数中的类型(而变体的解决方案可以通过模板化的访问者函数处理cdf函数的应用程序,因此没有任何类型规范),这意味着要管理大量代码,大量if语句…

然而,逻辑根本没有改变(我转换类型,然后在所有if语句中应用cdf函数),如果列表中存储的不是boost分布,我也不会真正关心函数的行为。

那么是否有机会拥有我的蛋糕并吃掉它,这意味着不被迫硬编码any_cdf中的分布的强制类型(很像变体的模板化访问者函数)?

非常感谢你的帮助,H.

注。如果这是不可行的,那么在这种情况下,我通常使用boost::any或boost::变体会更好吗?

#include <boost/math/distributions.hpp>
#include <boost/any.hpp>
#include <vector>
#include <iostream>
#include <limits>
//template function to apply cdf
template<class T> T any_cdf(boost::any a, T &x){
    //declare return value
    T y;
    //cast any with hardcoded types
    if (a.type() == typeid(boost::math::normal_distribution<T>)){
    y = boost::math::cdf(boost::any_cast< boost::math::normal_distribution<T> >(a),x);
    } else if (a.type() == typeid(boost::math::students_t_distribution<T>)){
    y = boost::math::cdf(boost::any_cast< boost::math::students_t_distribution<T> >(a), x);
    } else {
    //return NaN in case of failure or do something else (throw exception...)
    y =  std::numeric_limits<T>::quiet_NaN();
    }
    return(y);
}

int main (int, char*[])
{
    //get distribution objects
    boost::math::normal_distribution<double> s;
    boost::math::students_t_distribution<double> t(1);
    //use any to put just any kind of objects in one vector 
    std::vector<boost::any> vec_any;
    vec_any.push_back(s);
    vec_any.push_back(t);
    //evaluation point and return value 
    double y;
    double x = 1.96;
    for (std::vector<boost::any>::const_iterator iter = vec_any.begin(); iter != vec_any.end(); ++iter){
        y = any_cdf<double>(*iter,x);
        std::cout << y << std::endl;
    }
    return 0;
}
编辑:关于评论,any似乎不是手头任务最简单/最好的选择。然而,出于完整性的考虑,boost::any的类似访问者的实现在:

注释参见我的旧答案关于向量和boost::anyboost::variant的解的讨论。

如果你实际上不需要分布的动态向量,而只是想应用一个静态已知的分布列表,你可以"摆脱"它们的tuple<>

现在,用一点(嗯,很多)来自凤凰和融合的魔法,你可以"仅仅"适应cdf函数作为一个懒惰演员:

BOOST_PHOENIX_ADAPT_FUNCTION(double, cdf_, boost::math::cdf, 2)

在这种情况下,等效的扩展代码示例缩小为:查看它Live On Coliru

int main()
{
    typedef boost::tuple<bm::normal, bm::students_t> Dists;
    Dists dists(bm::normal(), bm::students_t(1));
    double x = 1.96;
    boost::fusion::for_each(dists, std::cout << cdf_(arg1, x) << "n");
    std::cout << "nComposite (multiplication):t" << boost::fusion::accumulate(dists, 1.0, arg1 * cdf_(arg2, x));
    std::cout << "nComposite (mean):tt"         << boost::fusion::accumulate(dists, 0.0, arg1 + cdf_(arg2, x)) / boost::tuples::length<Dists>::value;
}

哇。这是……不到6行代码:)最好的部分是它已经完全兼容c++03了

Update这是假设一个向量和boost::any vs. boost::variant的答案。如果您可以使用tuple<> ,请参阅我的其他答案

你最终会以这样或那样的方式硬编码潜在的类型。

使用variant,您可以通过使用visitor:

对复杂性进行分组和隐藏。
struct invoke_member_foo : boost::static_visitor<double>
{
     template <typename Obj, typename... Args> 
        double operator()(Obj o, Args const&... a) const {
            return o.foo(a...);
     }
};

这可以应用到你的变体,如

boost::apply_visitor(invoke_member_foo(), my_variant);

使用boost any,您将以无聊且手动的方式进行类型转换:

if (auto dist1 = boost::any_cast<distribution1_t>(&my_any))
    dist1->foo();
else if (auto dist2 = boost::any_cast<distribution2_t>(&my_any))
    dist2->foo();
else if (auto dist3 = boost::any_cast<distribution3_t>(&my_any))
    dist3->foo();

在我看来,这显然是较差的可维护性,例如

  • 你不能很容易地用一个足够相似的元素类型来扩展类型列表,以满足相同的概念并获得它的支持——你需要手动向类型切换添加大小写(如果你不这样做——你就不走运了,没有错误,你会有(无声的)bug。使用variant,当你的访问者不处理你的类型时,你会得到一个编译错误。

  • 这个工作^(类型切换)对于你想要实现的每个操作都是重复的。当然,您可以实现类型切换一次,并提供作为函子的实际实现,但此时您将实现与static_visitor完全相同的,就像我在变体中展示的那样,除了实现效率要低得多。

  • boost::any只能包含CopyConstructibleBoost variant甚至可以包含引用(例如boost::variant<dist1_t&, dist2_t&>),并且具有(一些)移动语义支持

简而言之,boost::any节省了预先考虑的时间,但它所做的只是将工作转移到调用站点。


从积极的方面来说,让我和你分享一个我喜欢的习语,它使访问者作为普通的免费函数来访问。让我们为以下变量重写any_cdf函数:

namespace detail
{
    template <typename T> struct var_cdf_visitor : boost::static_visitor<T> {
        template <typename Dist>
            T operator()(Dist& dist, T& x) const { return boost::math::cdf(dist, x); }
    };
}
template<class T> T var_cdf(VarDist<T> a, T &x) 
{
    static detail::var_cdf_visitor<T> vis;
    return boost::apply_visitor(
            boost::bind(vis, ::_1, boost::ref(x)),
            a);
}

一个完整的运行程序可以找到Live On Coliru

演示清单

#include <boost/bind.hpp>
#include <boost/math/distributions.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <limits>
#include <vector>
namespace detail
{
    template <typename T> struct var_cdf_visitor : boost::static_visitor<T> {
        template <typename Dist>
            T operator()(Dist const& dist, T const& x) const { return boost::math::cdf(dist, x); }
    };
}
template<class T, typename... Dist> T var_cdf(boost::variant<Dist...> const& a, T const& x) {
    return boost::apply_visitor(boost::bind(detail::var_cdf_visitor<T>(), ::_1, x), a);
}
int main()
{
    namespace bm = boost::math;
    typedef std::vector<boost::variant<bm::normal, bm::students_t> > Vec;
    Vec vec { bm::normal(), bm::students_t(1) };
    //evaluation point and return value 
    double x = 1.96;
    for (auto& dist : vec)
        std::cout << var_cdf(dist,x) << std::endl;
}

实际上,虽然我使用了一点c++11,但使用一些c++1y特性(如果你的编译器有的话)可以使它更漂亮。

最后,你也可以为c++03工作;只是需要的时间比我现在能投入的时间要多。

关于:

int main (int, char*[])
{
    boost::math::normal_distribution<double> s;
    boost::math::students_t_distribution<double> t(1);
    typedef std::vector<boost::function<double (double)> > vec_t; 
    vec_t vec_func;
    vec_func.push_back(boost::bind(boost::math::cdf<double>, boost::ref(s), _1));
    vec_func.push_back(boost::bind(boost::math::cdf<double>, boost::ref(t), _1));
    //evaluation point and return value 
    double y;
    double x = 1.96;
    for (vec_t::const_iterator iter = vec_func.begin(); iter != vec_func.end(); ++iter){
        y = (*iter)(x);
        std::cout << y << std::endl;
    }
    return 0;
}

将参数绑定到函数模板可能很棘手。