1 /******************************************************************************* 2 copyright: Copyright (c) 2008. Fawzi Mohamed 3 license: BSD style: $(LICENSE) 4 version: Initial release: July 2008 5 author: Fawzi Mohamed 6 *******************************************************************************/ 7 module tango.math.random.Ziggurat; 8 import tango.math.Bracket:findRoot; 9 import tango.math.Math:abs; 10 import tango.math.ErrorFunction:erfc; 11 import tango.core.Traits; 12 13 /// ziggurat method for decreasing distributions. 14 /// Marsaglia, Tsang, Journal of Statistical Software, 2000 15 /// If has negative is true the distribution is assumed to be symmetric with respect to 0, 16 /// otherwise it is assumed to be from 0 to infinity. 17 /// Struct based to avoid extra indirection when wrapped in a class (and it should be wrapped 18 /// in a class and not used directly). 19 /// Call style initialization avoided on purpose (this is a big structure, you don't want to return it) 20 struct Ziggurat(RandG,T,alias probDensityF,alias tailGenerator,bool hasNegative=true){ 21 static assert(isRealType!(T),T.stringof~" not acceptable, only floating point variables supported"); 22 enum int nBlocks=256; 23 T[nBlocks+1] posBlock; 24 T[nBlocks+1] fVal; 25 RandG r; 26 alias Ziggurat SourceT; 27 /// initializes the ziggurat 28 static Ziggurat create(alias invProbDensityF, alias cumProbDensityFCompl)(RandG rGenerator,real xLast=-1.0L,bool check_error=true){ 29 /// function to find xLast 30 real findXLast(real xLast){ 31 real v=xLast*probDensityF(xLast)+cumProbDensityFCompl(xLast); 32 real fMax=probDensityF(0.0L); 33 real pAtt=xLast; 34 real fAtt=probDensityF(xLast); 35 for (int i=nBlocks-2;i!=0;--i){ 36 fAtt+=v/pAtt; 37 if (fAtt>fMax) return fAtt+(i-1)*fMax; 38 pAtt=invProbDensityF(fAtt); 39 assert(pAtt>=0,"invProbDensityF is supposed to return positive values"); 40 } 41 return fAtt+v/pAtt-fMax; 42 } 43 void findBracket(ref real xMin,ref real xMax){ 44 real vMin=cumProbDensityFCompl(0.0L)/nBlocks; 45 real pAtt=0.0L; 46 for (int i=1;i<nBlocks;++i){ 47 pAtt+=vMin/probDensityF(pAtt); 48 } 49 real df=findXLast(pAtt); 50 if (df>0) { 51 // (most likely) 52 xMin=pAtt; 53 real vMax=cumProbDensityFCompl(0.0L); 54 xMax=pAtt+vMax/probDensityF(pAtt); 55 } else { 56 xMax=pAtt; 57 xMin=vMin/probDensityF(0.0L); 58 } 59 } 60 if (xLast<=0){ 61 real xMin,xMax; 62 findBracket(xMin,xMax); 63 xLast=findRoot(&findXLast,xMin,xMax); 64 // printf("xLast:%La => %La\n",xLast,findXLast(xLast)); 65 } 66 Ziggurat res; 67 with (res){ 68 r=rGenerator; 69 real v=probDensityF(xLast)*xLast+cumProbDensityFCompl(xLast); 70 real pAtt=xLast; 71 real fMax=probDensityF(0.0L); 72 posBlock[1]=cast(T)xLast; 73 real fAtt=probDensityF(xLast); 74 fVal[1]=cast(T)fAtt; 75 for (int i=2;i<nBlocks;++i){ 76 fAtt+=v/pAtt; 77 assert(fAtt<=fMax,"Ziggurat contruction shoot out"); 78 pAtt=invProbDensityF(fAtt); 79 assert(pAtt>=0,"invProbDensityF is supposed to return positive values"); 80 posBlock[i]=cast(T)pAtt; 81 fVal[i]=cast(T)fAtt; 82 } 83 posBlock[nBlocks]=0.0L; 84 fVal[nBlocks]=cast(T)probDensityF(0.0L); 85 real error=fAtt+v/pAtt-probDensityF(0.0L); 86 assert((!check_error) || error<real.epsilon*10000.0,"Ziggurat error larger than expected"); 87 posBlock[0]=cast(T)(xLast*(1.0L+cumProbDensityFCompl(xLast)/probDensityF(xLast))); 88 fVal[0]=0.0L; 89 for (int i=0;i<nBlocks;++i){ 90 assert(posBlock[i]>=posBlock[i+1],"decresing posBlock"); 91 assert(fVal[i]<=fVal[i+1],"increasing probabilty density function"); 92 } 93 } 94 return res; 95 } 96 /// returns a single value with the probability distribution of the current Ziggurat 97 /// and slightly worse randomness (in the normal case uses only 32 random bits). 98 /// Cannot be 0. 99 T getFastRandom() 100 { 101 static if (hasNegative){ 102 for (int iter=1000;iter!=0;--iter) 103 { 104 uint i0=r.uniform!(uint)(); 105 uint i=i0 & 0xFFU; 106 enum T scaleF=(cast(T)1)/(cast(T)uint.max+1); 107 T u= (cast(T)i0+cast(T)0.5)*scaleF; 108 T x = posBlock[i]*u; 109 if (x<posBlock[i+1]) return ((i0 & 0x100u)?x:-x); 110 if (i == 0) return tailGenerator(r,posBlock[1],x<0); 111 if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*((cast(T)r.uniform!(uint)()+cast(T)0.5)*scaleF)) { 112 return ((i0 & 0x100u)?x:-x); 113 } 114 } 115 } else { 116 for (int iter=1000;iter!=0;--iter) 117 { 118 uint i0=r.uniform!(uint)(); 119 uint i= i0 & 0xFFU; 120 enum T scaleF=(cast(T)1)/(cast(T)uint.max+1); 121 T u= (cast(T)i0+cast(T)0.5)*scaleF; 122 T x = posBlock[i]*u; 123 if (x<posBlock[i+1]) return x; 124 if (i == 0) return tailGenerator(r,posBlock[1]); 125 if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) { 126 return x; 127 } 128 } 129 } 130 throw new Exception("max nr of iterations in Ziggurat, this should have probability<1.0e-1000"); 131 } 132 /// returns a single value with the probability distribution of the current Ziggurat 133 T getRandom() 134 { 135 static if (hasNegative){ 136 for (int iter=1000;iter!=0;--iter) 137 { 138 uint i0 = r.uniform!(uint)(); 139 uint i= i0 & 0xFF; 140 T u = r.uniform!(T)(); 141 T x = posBlock[i]*u; 142 if (x<posBlock[i+1]) return ((i0 & 0x100u)?x:-x); 143 if (i == 0) return tailGenerator(r,posBlock[1],x<0); 144 if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) { 145 return ((i0 & 0x100u)?x:-x); 146 } 147 } 148 } else { 149 for (int iter=1000;iter!=0;--iter) 150 { 151 uint i=r.uniform!(ubyte)(); 152 T u = r.uniform!(T)(); 153 T x = posBlock[i]*u; 154 if (x<posBlock[i+1]) return x; 155 if (i == 0) return tailGenerator(r,posBlock[1]); 156 if ((cast(T)probDensityF(x))>fVal[i+1]+(fVal[i]-fVal[i+1])*r.uniform!(T)()) { 157 return x; 158 } 159 } 160 } 161 throw new Exception("max nr of iterations in Ziggurat, this should have probability<1.0e-1000"); 162 } 163 /// initializes the argument with the probability distribution given and returns it 164 /// for arrays this might potentially be faster than a naive loop 165 U randomize(U)(ref U a){ 166 static if(is(U S:S[])){ 167 size_t aL=a.length; 168 for (size_t i=0;i!=aL;++i){ 169 a[i]=cast(BaseTypeOfArrays!(U))getRandom(); 170 } 171 } else { 172 a=cast(U)getRandom(); 173 } 174 return a; 175 } 176 /// initializes the variable with the result of mapping op on the random numbers (of type T) 177 // unfortunately this (more efficent version) cannot use local delegates 178 template randomizeOp2(alias op){ 179 U randomizeOp2(U)(ref U a){ 180 static if(is(U S:S[])){ 181 alias BaseTypeOfArrays!(U) TT; 182 size_t aL=a.length; 183 for (size_t i=0;i!=aL;++i){ 184 static if(isComplexType!(TT)) { 185 a[i]=cast(TT)(op(getRandom())+1i*op(getRandom())); 186 } else static if (isImaginaryType!(TT)){ 187 a[i]=cast(TT)(1i*op(getRandom())); 188 } else { 189 a[i]=cast(TT)op(getRandom()); 190 } 191 } 192 } else { 193 static if(isComplexType!(U)) { 194 a=cast(U)(op(getRandom())+1i*op(getRandom())); 195 } else static if (isImaginaryType!(U)){ 196 el=cast(U)(1i*op(getRandom())); 197 } else { 198 a=cast(U)op(getRandom()); 199 } 200 } 201 return a; 202 } 203 } 204 /// initializes the variable with the result of mapping op on the random numbers (of type T) 205 U randomizeOp(U,S)(scope S delegate(T) op,ref U a){ 206 static if(is(U S:S[])){ 207 alias BaseTypeOfArrays!(U) TT; 208 size_t aL=a.length; 209 for (size_t i=0;i!=aL;++i){ 210 static if(isComplexType!(TT)) { 211 a[i]=cast(TT)(op(getRandom())+1i*op(getRandom())); 212 } else static if (isImaginaryType!(TT)){ 213 a[i]=cast(TT)(1i*op(getRandom())); 214 } else { 215 a[i]=cast(TT)op(getRandom()); 216 } 217 } 218 } else { 219 static if(isComplexType!(U)) { 220 a=cast(U)(op(getRandom())+1i*op(getRandom())); 221 } else static if (isImaginaryType!(U)){ 222 el=cast(U)(1i*op(getRandom())); 223 } else { 224 a=cast(U)op(getRandom()); 225 } 226 } 227 return a; 228 } 229 230 }