123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
|
/*******************************************************************************
copyright: Copyright (c) 2008. Fawzi Mohamed
license: BSD style: $(LICENSE)
version: Initial release: July 2008
author: Fawzi Mohamed
*******************************************************************************/
module tango.math.random.Ziggurat;
import tango.math.Bracket:findRoot;
import tango.math.Math:abs;
import tango.math.ErrorFunction:erfc;
import tango.core.Traits;
/// ziggurat method for decreasing distributions.
/// Marsaglia, Tsang, Journal of Statistical Software, 2000
/// If has negative is true the distribution is assumed to be symmetric with respect to 0,
/// otherwise it is assumed to be from 0 to infinity.
/// Struct based to avoid extra indirection when wrapped in a class (and it should be wrapped
/// in a class and not used directly).
/// Call style initialization avoided on purpose (this is a big structure, you don't want to return it)
struct Ziggurat(RandG,T,alias probDensityF,alias tailGenerator,bool hasNegative=true){
static assert(isRealType!(T),T.stringof~" not acceptable, only floating point variables supported");
enum int nBlocks=256;
T[nBlocks+1] posBlock;
T[nBlocks+1] fVal;
RandG r;
alias Ziggurat SourceT;
/// initializes the ziggurat
static Ziggurat create(alias invProbDensityF, alias cumProbDensityFCompl)(RandG rGenerator,real xLast=-1.0L,bool check_error=true){
/// function to find xLast
real findXLast(real xLast){
real v=xLast*probDensityF(xLast)+cumProbDensityFCompl(xLast);
real fMax=probDensityF(0.0L);
real pAtt=xLast;
real fAtt=probDensityF(xLast);
for (int i=nBlocks-2;i!=0;--i){
fAtt+=v/pAtt;
if (fAtt>fMax) return fAtt+(i-1)*fMax;
pAtt=invProbDensityF(fAtt);
assert(pAtt>=0,"invProbDensityF is supposed to return positive values");
}
return fAtt+v/pAtt-fMax;
}
void findBracket(ref real xMin,ref real xMax){
real vMin=cumProbDensityFCompl(0.0L)/nBlocks;
real pAtt=0.0L;
for (int i=1;i<nBlocks;++i){
pAtt+=vMin/probDensityF(pAtt);
}
real df=findXLast(pAtt);
if (df>0) {
// (most likely)
xMin=pAtt;
real vMax=cumProbDensityFCompl(0.0L);
xMax=pAtt+vMax/probDensityF(pAtt);
} else {
xMax=pAtt;
xMin=vMin/probDensityF(0.0L);
}
}
if (xLast<=0){
real xMin,xMax;
findBracket(xMin,xMax);
xLast=findRoot(&findXLast,xMin,xMax);
// printf("xLast:%La => %La\n",xLast,findXLast(xLast));
}
Ziggurat res;
with (res){
r=rGenerator;
real v=probDensityF(xLast)*xLast+cumProbDensityFCompl(xLast);
real pAtt=xLast;
real fMax=probDensityF(0.0L);
posBlock[1]=cast(T)xLast;
real fAtt=probDensityF(xLast);
fVal[1]=cast(T)fAtt;
for (int i=2;i<nBlocks;++i){
fAtt+=v/pAtt;
assert(fAtt<=fMax,"Ziggurat contruction shoot out");
pAtt=invProbDensityF(fAtt);
assert(pAtt>=0,"invProbDensityF is supposed to return positive values");
posBlock[i]=cast(T)pAtt;
fVal[i]=cast(T)fAtt;
}
posBlock[nBlocks]=0.0L;
fVal[nBlocks]=cast(T)probDensityF(0.0L);
real error=fAtt+v/pAtt-probDensityF(0.0L);
assert((!check_error) || error<real.epsilon*10000.0,"Ziggurat error larger than expected");
posBlock[0]=cast(T)(xLast*(1.0L+cumProbDensityFCompl(xLast)/probDensityF(xLast)));
fVal[0]=0.0L;
for (int i=0;i<nBlocks;++i){
assert(posBlock[i]>=posBlock[i+1],"decresing posBlock");
assert(fVal[i]<=fVal[i+1],"increasing probabilty density function");
}
}
return res;
}
/// returns a single value with the probability distribution of the current Ziggurat
/// and slightly worse randomness (in the normal case uses only 32 random bits).
/// Cannot be 0.
T getFastRandom()
{
static if (hasNegative){
for (int iter=1000;iter!=0;--iter)
{
uint i0=r.uniform!(uint)();
uint i=i0 & 0xFFU;
enum T scaleF=(cast(T)1)/(cast(T)uint.max+1);
T u= (cast(T)i0+cast(T)0.5)*scaleF;
T x = posBlock[i]*u;
if (x<posBlock[i+1]) return ((i0 & 0x100u)?x:-x);
if (i == 0) return tailGenerator(r,posBlock[1],x<0);
if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*((cast(T)r.uniform!(uint)()+cast(T)0.5)*scaleF)) {
return ((i0 & 0x100u)?x:-x);
}
}
} else {
for (int iter=1000;iter!=0;--iter)
{
uint i0=r.uniform!(uint)();
uint i= i0 & 0xFFU;
enum T scaleF=(cast(T)1)/(cast(T)uint.max+1);
T u= (cast(T)i0+cast(T)0.5)*scaleF;
T x = posBlock[i]*u;
if (x<posBlock[i+1]) return x;
if (i == 0) return tailGenerator(r,posBlock[1]);
if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) {
return x;
}
}
}
throw new Exception("max nr of iterations in Ziggurat, this should have probability<1.0e-1000");
}
/// returns a single value with the probability distribution of the current Ziggurat
T getRandom()
{
static if (hasNegative){
for (int iter=1000;iter!=0;--iter)
{
uint i0 = r.uniform!(uint)();
uint i= i0 & 0xFF;
T u = r.uniform!(T)();
T x = posBlock[i]*u;
if (x<posBlock[i+1]) return ((i0 & 0x100u)?x:-x);
if (i == 0) return tailGenerator(r,posBlock[1],x<0);
if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) {
return ((i0 & 0x100u)?x:-x);
}
}
} else {
for (int iter=1000;iter!=0;--iter)
{
uint i=r.uniform!(ubyte)();
T u = r.uniform!(T)();
T x = posBlock[i]*u;
if (x<posBlock[i+1]) return x;
if (i == 0) return tailGenerator(r,posBlock[1]);
if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) {
return x;
}
}
}
throw new Exception("max nr of iterations in Ziggurat, this should have probability<1.0e-1000");
}
/// initializes the argument with the probability distribution given and returns it
/// for arrays this might potentially be faster than a naive loop
U randomize(U)(ref U a){
static if(is(U S:S[])){
size_t aL=a.length;
for (size_t i=0;i!=aL;++i){
a[i]=cast(BaseTypeOfArrays!(U))getRandom();
}
} else {
a=cast(U)getRandom();
}
return a;
}
/// initializes the variable with the result of mapping op on the random numbers (of type T)
// unfortunately this (more efficent version) cannot use local delegates
template randomizeOp2(alias op){
U randomizeOp2(U)(ref U a){
static if(is(U S:S[])){
alias BaseTypeOfArrays!(U) TT;
size_t aL=a.length;
for (size_t i=0;i!=aL;++i){
static if(isComplexType!(TT)) {
a[i]=cast(TT)(op(getRandom())+1i*op(getRandom()));
} else static if (isImaginaryType!(TT)){
a[i]=cast(TT)(1i*op(getRandom()));
} else {
a[i]=cast(TT)op(getRandom());
}
}
} else {
static if(isComplexType!(U)) {
a=cast(U)(op(getRandom())+1i*op(getRandom()));
} else static if (isImaginaryType!(U)){
el=cast(U)(1i*op(getRandom()));
} else {
a=cast(U)op(getRandom());
}
}
return a;
}
}
/// initializes the variable with the result of mapping op on the random numbers (of type T)
U randomizeOp(U,S)(scope S delegate(T) op,ref U a){
static if(is(U S:S[])){
alias BaseTypeOfArrays!(U) TT;
size_t aL=a.length;
for (size_t i=0;i!=aL;++i){
static if(isComplexType!(TT)) {
a[i]=cast(TT)(op(getRandom())+1i*op(getRandom()));
} else static if (isImaginaryType!(TT)){
a[i]=cast(TT)(1i*op(getRandom()));
} else {
a[i]=cast(TT)op(getRandom());
}
}
} else {
static if(isComplexType!(U)) {
a=cast(U)(op(getRandom())+1i*op(getRandom()));
} else static if (isImaginaryType!(U)){
el=cast(U)(1i*op(getRandom()));
} else {
a=cast(U)op(getRandom());
}
}
return a;
}
}
|