通常我使用the built in random functions生成值,但现在我需要创建表单的随机分布
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);
}
。理想情况下,我希望它能够用于当前内置发行版的工作方式:
{{1}}
答案 0 :(得分:1)
这是非常可能的,但它与C ++问题一样是一个数学问题。创建伪随机数生成器的最常用方法是Inverse transform sampling。从本质上讲,任何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);
}
}
答案 1 :(得分:1)
我非常关注@ jwimberley的想法,并认为我会在这里分享我的结果。我创建了一个执行以下操作的类:
(0, 1]
在std::random
之间生成随机概率 p 。代码:
// 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
以下是一些结果图。对于给定的PDF,我们需要首先通过积分分析计算CDF。
您可以使用以下演示自行尝试:
// main.cc
#include "sampled_distribution.hh"
#include <iostream>
#include <fstream>
int main()
{
auto logFunc = [](double x) {
const double k = -1.0;
const double m = 10;
return x*(k*std::log(x) + m - k); // PDF(x) = k*log(x) + m
};
auto sinFunc = [](double x) { return x + std::cos(x); }; // PDF(x) = 1 - sin(x)
std::mt19937 gen;
//Sampled_distribution<> dist(logFunc, 1.0, 1e4);
Sampled_distribution<> dist(sinFunc, 0.0, 6.28);
std::ofstream file("d.txt");
for (int i = 0; i < 100000; i++) file << dist(gen) << std::endl;
}
使用python绘制数据。
// dist_plot.py
import numpy as np
import matplotlib.pyplot as plt
d = np.loadtxt("d.txt")
fig, ax = plt.subplots()
bins = np.arange(d.min(), d.max(), (d.max() - d.min())/50)
ax.hist(d, edgecolor='white', bins=bins)
plt.show()
使用以下命令运行演示:
clang++ -std=c++11 -stdlib=libc++ main.cc -o main; ./main; python dist_plot.py
答案 2 :(得分:1)
我非常喜欢这里介绍的概念,这导致了非常纤巧但功能强大的生成器。我只是做了一些嵌入c ++ 17功能的清理工作,我打算编辑pingul的答案,但结果却大不相同,因此我将其分开发布。
#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 **********************************
答案 3 :(得分:0)
正如其他地方所指出的,采样任何PDF的标准方法是在区间[0,1]中从均匀随机选择的点反转其CDF。
如果您遇到特殊问题,CDF是一个简单的函数,但它的反函数却不是。在这种情况下,可以使用传统的数值工具将其反转,例如Newton-Raphson迭代。很遗憾,您未能指定x
的范围或参数m
和k
的允许选项。我已针对常规m
,k
和范围(and posted it on code review)实现了此功能,以满足C ++ RandomNumberDistribution concept。