C++ 如何创建自定义随机分布函数?

C++ 如何创建自定义随机分布函数?,c++,c++11,math,random,C++,C++11,Math,Random,通常我使用生成值,但现在我需要创建表单的随机分布 f(x) = k*log(x) + m 可以定义自定义随机分布函数吗?对于我的实际模型,我有x=[1,1.4e7),k=-0.905787102751,m=14.913170454。理想情况下,我希望它能像当前内置分布那样工作: int main() { std::mt19937 generator; std::uniform_real_distribution<> dist(0.0, 1.0); my_

通常我使用生成值,但现在我需要创建表单的随机分布

f(x) = k*log(x) + m
可以定义自定义随机分布函数吗?对于我的实际模型,我有
x=[1,1.4e7),k=-0.905787102751,m=14.913170454
。理想情况下,我希望它能像当前内置分布那样工作:

int main() 
{
    std::mt19937 generator;

    std::uniform_real_distribution<> dist(0.0, 1.0);
    my_distribution my_dist(0.0, 10.0); // Distribution using f(x)

    double uni_val = dist(generator);
    double log_val = my_dist(generator);
}
intmain()
{
标准:mt19937发电机;
标准:均匀实分布区(0.0,1.0);
my_distribution my_dist(0.0,10.0);//使用f(x)的分布
双单位值=距离(发电机);
双对数=我的距离(发电机);
}

这是非常可能的,但它是一个C++问题的数学问题。创建伪随机数发生器的最一般方法是:基本上,任何PDF的CDF均匀分布在0和1之间(如果这不明显,只要记住CDF的值是一个概率,并在此考虑)。因此,您只需对0和1之间的随机均匀数进行采样,然后应用CDF的倒数

在你的例子中,用F(x)=k*log(x)+m $(你没有指定界限,但我假设它们在1和一些正数>1)CDF及其逆是相当混乱的问题-我留给你!C++中的实现看起来像

double inverseCDF(double p, double k, double m, double lowerBound, double upperBound) {
     // do math, which might include numerically finds roots of equations
}
然后生成代码将类似于

class my_distribution {
     // ... constructor, private variables, etc.
     template< class Generator >
     double operator()( Generator& g ) {
          std::uniform_real_distribution<> dist(0.0, 1.0);
          double cdf = dist(g);
          return inverseCDF(cdf,this->k,this->m,this->lowerBound,this->upperBound);
     }
}
class my\u分布{
//…构造函数、私有变量等。
模板<类生成器>
双运算符()(发电机和发电机){
标准:均匀实分布区(0.0,1.0);
双cdf=距离(g);
返回反向ECDF(cdf,this->k,this->m,this->lowerBound,this->upperBound);
}
}

我完全遵循了@jwimberley的想法,并认为我会在这里分享我的结果。我创建了一个类,该类可以执行以下操作:

  • 构造函数参数:
    • (正常化或非正常化),这是 函数的积分
    • 分布的上下限
    • (可选)分辨率,指示我们应获取多少CDF采样点
  • 从CDF->随机数x计算映射。这是我们的反向CDF函数
  • 通过以下方式生成随机点:
    • 使用
      std::random
      生成
      (0,1]
      之间的随机概率p
    • 在映射中对对应于p的CDF值进行二进制搜索。返回与CDF一起计算的x。提供附近“bucket”之间的可选线性积分,否则我们将得到n==分辨率离散步数
  • 守则:

    // sampled_distribution.hh
    #ifndef SAMPLED_DISTRIBUTION
    #define SAMPLED_DISTRIBUTION
    
    #include <algorithm>
    #include <vector>
    #include <random>
    #include <stdexcept>
    
    template <typename T = double, bool Interpolate = true>
    class Sampled_distribution
    {
    public:
        using CDFFunc = T (*)(T);
    
        Sampled_distribution(CDFFunc cdfFunc, T low, T high, unsigned resolution = 200) 
            : mLow(low), mHigh(high), mRes(resolution), mDist(0.0, 1.0)
        {
            if (mLow >= mHigh) throw InvalidBounds();
    
            mSampledCDF.resize(mRes + 1);
            const T cdfLow = cdfFunc(low);
            const T cdfHigh = cdfFunc(high);
            T last_p = 0;
            for (unsigned i = 0; i < mSampledCDF.size(); ++i) {
                const T x = i/mRes*(mHigh - mLow) + mLow;
                const T p = (cdfFunc(x) - cdfLow)/(cdfHigh - cdfLow); // normalising 
                if (! (p >= last_p)) throw CDFNotMonotonic();
                mSampledCDF[i] = Sample{p, x};
                last_p = p;
            }
        }
    
        template <typename Generator>
        T operator()(Generator& g) 
        {
            T cdf = mDist(g);
            auto s = std::upper_bound(mSampledCDF.begin(), mSampledCDF.end(), cdf);
            auto bs = s - 1;
            if (Interpolate && bs >= mSampledCDF.begin()) { 
                const T r = (cdf - bs->prob)/(s->prob - bs->prob);
                return r*bs->value + (1 - r)*s->value;
            }
            return s->value;
        }
    
    private:
        struct InvalidBounds : public std::runtime_error { InvalidBounds() : std::runtime_error("") {} };
        struct CDFNotMonotonic : public std::runtime_error { CDFNotMonotonic() : std::runtime_error("") {} };
    
        const T mLow, mHigh;
        const double mRes;
    
        struct Sample { 
            T prob, value; 
            friend bool operator<(T p, const Sample& s) { return p < s.prob; }
        };
    
        std::vector<Sample> mSampledCDF;
        std::uniform_real_distribution<> mDist;
    };
    
    #endif
    
    使用以下工具运行演示:

    clang++ -std=c++11 -stdlib=libc++ main.cc -o main; ./main; python dist_plot.py
    

    正如其他地方指出的,对任何PDF进行采样的标准方法是在从区间[0,1]中均匀随机选择的点处反转其CDF


    对于您的特定问题,CDF是一个简单的函数,但它的逆函数不是。在这种情况下,可以使用传统的数值工具(如牛顿-拉斐逊迭代)将其反转。不幸的是,您没有指定
    x
    的范围,也没有指定
    m
    k
    参数的允许选择为此,我们对代码进行了修改,代码> > k>代码>和范围()以满足C++,

    ,我喜欢这里提出的许多概念,导致一个非常纤细但非常强大的生成器。我刚刚做了一些清理嵌入C++ 17个特性,我要编辑Punl的答案,但是它却完全不同,所以我把它分开了。
    #pragma once
    
    #include <algorithm>
    #include <vector>
    #include <random>
    #include <stdexcept>
    
    template <typename T = double, bool Interpolate = true>
    class SampledDistribution {
      struct Sample { 
        T prob, value; 
        Sample(const T p, const T v): prob(p), value(v) {}
        friend bool operator<(T p, const Sample& s) { return p < s.prob; }
      };
    
      std::vector<Sample> SampledCDF;
    
    public:
      struct InvalidBounds:   std::runtime_error { using std::runtime_error::runtime_error; };
      struct CDFNotMonotonic: std::runtime_error { using std::runtime_error::runtime_error; };
    
      template <typename F>
      SampledDistribution(F&& cdfFunc, const T low, const T high, const unsigned resolution = 256) {
        if (low >= high) throw InvalidBounds("");
        SampledCDF.reserve( resolution );
        const T cdfLow = cdfFunc(low);
        const T cdfHigh = cdfFunc(high);
        for (unsigned i = 0; i < resolution; ++i) {
          const T x = (high - low)*i/(resolution-1) + low;
          const T p = (cdfFunc(x) - cdfLow)/(cdfHigh - cdfLow); // normalising 
          if (p < SampledCDF.back()) throw CDFNotMonotonic("");
          SampledCDF.emplace_back(p, x);
        }
      }
    
      template <typename Engine>
      T operator()(Engine& g) {
        const T cdf = std::uniform_real_distribution<T>{0.,1.}(g);
        auto s = std::upper_bound(SampledCDF.begin(), SampledCDF.end(), cdf);
        if (Interpolate && s != SampledCDF.begin()) { 
          auto bs = s - 1;
          const T r = (cdf - bs->prob)/(s->prob - bs->prob);
          return r*bs->value + (1 - r)*s->value;
        }
        return s->value;
      }
    };
    

    例如,C++的数学是这个问题。例如,看看什么是域?@ YVISDAUST,初始问题是在1 -1.4E7之间。我为我如何解决它添加了一个答案。请指定参数的期望范围:代码>代码> M>代码>和代码> K<代码>以及范围。>@Walter我在问题中添加了我的实际模型值作为编辑。谢谢。这是一个很好的建议,引导我走上了正确的道路。我添加了一个答案,概述了我是如何实现它的——这是你的想法吗?如果你觉得有什么问题,请提出改进建议。关于这段代码,有几点可以说,bu“这真的属于代码审查。@Walter这篇文章不要求审查。这是我如何创建自定义随机分布的答案,回答了我自己的问题。我真的对否决票感到惊讶。你的代码远远不是最优的。首先,你至少应该测试CDF的单调性。其次,你可以实现一种更好的方法来例如,使用样条曲线或多项式插值将其反转。第三,如果用户要求同时使用PDF和CDF,则可以使用牛顿-拉斐逊(Newton-Raphson)将后者反转,这可以收敛到机器精度。最后,这对于您的初始问题来说是过度的。@Walter我感谢您的反馈。您的所有评论都有价值——我的经验ce在这个问题上是有限的,我尽了最大的努力来实现它,以满足我的需要。关于它有点过分:我试图遵循jwimberley的想法,我能做些什么呢?@Walter应该没有必要测试单调性,一个有效的CDF总是单调的非递减的,因为任何有效的PDF都必须是非负的但必须修改离散分布的二进制搜索。
    clang++ -std=c++11 -stdlib=libc++ main.cc -o main; ./main; python dist_plot.py
    
    #pragma once
    
    #include <algorithm>
    #include <vector>
    #include <random>
    #include <stdexcept>
    
    template <typename T = double, bool Interpolate = true>
    class SampledDistribution {
      struct Sample { 
        T prob, value; 
        Sample(const T p, const T v): prob(p), value(v) {}
        friend bool operator<(T p, const Sample& s) { return p < s.prob; }
      };
    
      std::vector<Sample> SampledCDF;
    
    public:
      struct InvalidBounds:   std::runtime_error { using std::runtime_error::runtime_error; };
      struct CDFNotMonotonic: std::runtime_error { using std::runtime_error::runtime_error; };
    
      template <typename F>
      SampledDistribution(F&& cdfFunc, const T low, const T high, const unsigned resolution = 256) {
        if (low >= high) throw InvalidBounds("");
        SampledCDF.reserve( resolution );
        const T cdfLow = cdfFunc(low);
        const T cdfHigh = cdfFunc(high);
        for (unsigned i = 0; i < resolution; ++i) {
          const T x = (high - low)*i/(resolution-1) + low;
          const T p = (cdfFunc(x) - cdfLow)/(cdfHigh - cdfLow); // normalising 
          if (p < SampledCDF.back()) throw CDFNotMonotonic("");
          SampledCDF.emplace_back(p, x);
        }
      }
    
      template <typename Engine>
      T operator()(Engine& g) {
        const T cdf = std::uniform_real_distribution<T>{0.,1.}(g);
        auto s = std::upper_bound(SampledCDF.begin(), SampledCDF.end(), cdf);
        if (Interpolate && s != SampledCDF.begin()) { 
          auto bs = s - 1;
          const T r = (cdf - bs->prob)/(s->prob - bs->prob);
          return r*bs->value + (1 - r)*s->value;
        }
        return s->value;
      }
    };
    
    #include <iostream>
    #include "SampledDistribution.hpp"
    
    int main() {
      std::mt19937 gen;
      auto sinFunc = [](double x) { return x + std::cos(x); }; // PDF(x) = 1 - sin(x)
    
      unsigned resolution = 32;
      std::vector<int> v(resolution,0);
      SampledDistribution dist(sinFunc, 0.0, 6.28, resolution);
    
      for (int i = 0; i < 100000; i++) 
        ++v[ static_cast<size_t>(dist(gen)/(6.28) * resolution) ];
    
      for (auto i: v)
        std::cout << i << '\t' << std::string(i/100, '*') << std::endl;
    
      return 0;
    }
    
    $ g++ -std=c++17 main.cpp && ./a.out
    2882    ****************************
    2217    **********************
    1725    *****************
    1134    ***********
    690     ******
    410     ****
    182     *
    37  
    34  
    162     *
    411     ****
    753     *******
    1163    ***********
    1649    ****************
    2157    *********************
    2796    ***************************
    3426    **********************************
    4048    ****************************************
    4643    **********************************************
    5193    ***************************************************
    5390    *****************************************************
    5796    *********************************************************
    5979    ***********************************************************
    6268    **************************************************************
    6251    **************************************************************
    6086    ************************************************************
    5783    *********************************************************
    5580    *******************************************************
    5111    ***************************************************
    4646    **********************************************
    3964    ***************************************
    3434    **********************************