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 }