MODULERandomBasic EXPORTSRandomBasic ,RandomRep ;
Arithmetic for Modula-3, see doc for details
Abstract: Random number generators
3/16/96 Harry George Initial version (basic structure) 3/17/96 Warren Smith Gamma, Gaussian, Dirichlet deviates
IMPORT LongFloat;
IMPORT LongRealBasic AS R,
LongRealTrans AS RT,
LongReal AS RSpec,
LongRealIntegerPower AS RP,
Word AS W,
Arithmetic AS Arith;
CONST Module = "RandomBasic.";
REVEAL
TBoolean = TBooleanPublic BRANDED OBJECT
OVERRIDES
generateBoolean := GenerateBooleanFromBoolean;
generateWord := GenerateWordFromBoolean;
generateReal := GenerateRealFromBoolean;
END;
<* INLINE *>
PROCEDURE GenerateBooleanFromBoolean (SELF: TBoolean; ): BOOLEAN =
BEGIN
RETURN SELF.engine();
END GenerateBooleanFromBoolean;
Generates a word, bit by bit
PROCEDUREGenerates a longreal, bit by bitGenerateWordFromBoolean (SELF: TBoolean; ): W.T = VAR x: W.T := 0; BEGIN FOR i := 0 TO W.Size DO x := W.Plus(W.LeftShift(x, 1), ORD(SELF.engine())); END; RETURN x; END GenerateWordFromBoolean;
PROCEDURE********************* PROCEDURE Gaussian1 (SELF: T; ): R.T = (* gaussian, mean=0, var=1 based on NR92GenerateRealFromBoolean (SELF: TBoolean; ): R.T = VAR x: R.T := R.Zero; BEGIN FOR i := 0 TO RSpec.Precision - 1 DO x := RT.Half * (x + FLOAT(ORD(SELF.engine()), R.T)); END; <* ASSERT R.Zero <= x *> <* ASSERT x < R.One *> RETURN x; END GenerateRealFromBoolean; REVEAL TWord = TWordPublic BRANDED OBJECT OVERRIDES generateWord := GenerateWordFromWord; END; <* INLINE *> PROCEDUREGenerateWordFromWord (SELF: TWord; ): W.T = BEGIN RETURN SELF.engine(); END GenerateWordFromWord; REVEAL TReal = TRealPublic BRANDED OBJECT OVERRIDES generateReal := GenerateRealFromReal; END; <* INLINE *> PROCEDUREGenerateRealFromReal (SELF: TReal; ): R.T = BEGIN RETURN SELF.engine(); END GenerateRealFromReal; REVEAL T = TPrivate BRANDED OBJECT OVERRIDES uniform := Uniform; exponential := Exponential; gaussian := NormalDev; gamma := GammaDev; dirichlet := Dirichlet; (* poisson:=Poisson; *) binomial := Binomial; END; PROCEDUREUniform (SELF: T; min : R.T := R.Zero; (* from min *) max : R.T := R.One; (* up to but not including max *) ): R.T (* return uniform deviate *) RAISES {Arith.Error} = VAR t: R.T; BEGIN t := SELF.generateReal(); IF min = Min AND max = Max THEN RETURN t; END; IF min >= max THEN RAISE Arith.Error(NEW(Arith.ErrorOutOfRange).init()); END; RETURN min + t * (max - min); END Uniform; PROCEDUREExponential (SELF: T; ): R.T = (* exponential, mean=1 *) BEGIN RETURN -RT.Ln(SELF.generateReal()); END Exponential;
VAR v1, v2, Rsq, tmp, result: R.T;
BEGIN
IF NOT SELF.start THEN SELF.start := TRUE; RETURN SELF.gauss_y; END;
REPEAT
v1 := R.Two * SELF.generateReal() - R.One;
v2 := R.Two * SELF.generateReal() - R.One;
Rsq := v1 * v1 + v2 * v2;
UNTIL (Rsq > R.Zero) AND (Rsq < R.One);
tmp := R.sqrt(-R.Two * R.log(Rsq)) / Rsq;
result := v1 * tmp;
SELF.gauss_y := v2 * tmp;
SELF.start := FALSE;
RETURN result;
END Gaussian1;
*********************************)
---Warren Smith's Normal---
*Generates a normal (Gaussian) deviate with mean 0 and variance 1.
* The series method [Devroye page 170] is buggy, so I am
* using Marsaglia-Bray method on page 390 Devroye, see
* G.Marsaglia & T.A. Bray: A convenient method for
* generating normal random variables, SIAM Review 6 (1964) 260-264.*
PROCEDURE************************************** PROCEDURE Gamma1(SELF:T; event:[1..LAST(INTEGER)]; ): R.T= (* gamma, waiting time for event in Poisson process, mean=1 based on NR92NormalDev (SELF: T; ): R.T = <* FATAL Arith.Error *> (* we preserve, that it is always min<=max *) VAR v, u, w, x, sum: R.T; BEGIN u := SELF.generateReal(); IF u <= 0.8638D0 THEN v := SELF.uniform(-R.One, R.One); w := SELF.uniform(-R.One, R.One); x := 2.3153508D0 * u - R.One + v + w; RETURN x; ELSIF u <= 0.9745D0 THEN v := SELF.generateReal(); x := 1.5D0 * (v - R.One + 9.0334237D0 * (u - 0.8638D0)); RETURN x; (* we only get here with probability 0.0255: *) ELSIF u > 0.9973002D0 THEN REPEAT v := SELF.generateReal(); w := SELF.generateReal(); x := 4.5D0 - RT.Ln(w); UNTIL x * v * v <= 4.5D0; x := LongFloat.CopySign(RT.SqRt(x + x), u - 0.9986501D0); RETURN x; ELSE REPEAT x := SELF.uniform(-3.0D0, 3.0D0); u := SELF.uniform(); v := ABS(x); w := 3.0D0 - v; w := 6.6313339D0 * w * w; sum := R.Zero; IF v < 1.5D0 THEN sum := 6.0432809D0 * (1.5D0 - v); END; IF v < R.One THEN sum := sum + 13.2626678D0 * (3.0D0 - v * v) - w; END; UNTIL u <= 49.0024445D0 * RT.Exp(-v * v * 0.5D0) - sum - w; RETURN x; END; END NormalDev;
CONST
cutoff=7;
VAR
x,v1,v2,tanU,a0,x0,ratio:R.T;
BEGIN
IF event < cutoff THEN
x:=R.One;
FOR i:=1 TO event DO
x:=x*SELF.generateReal();
END;
x:=-R.log(x);
ELSE
x0:=FLOAT(event-1,R.T);
a0:=R.sqrt(R.Two*x0+R.One);
REPEAT
REPEAT
REPEAT
v1:=R.Two*SELF.generateReal()-R.One;
v2:=SELF.generateReal();
UNTIL (v1*v1+v2*v2) <= R.One; (* within unit half-circle *)
tanU:=v2/v1;
x:=a0*tanU+x0;
UNTIL x > R.Zero; (* within positive probabilities *)
ratio:=(R.One+tanU*tanU)*R.exp(x0*R.log(x/x0) - a0*tanU);
UNTIL SELF.generateReal() > ratio;
END;
RETURN x;
END Gamma1;
***********************************)
* Returns a Gamma deviate with parameter a>=0.
* Density(x) = x^(a-1) * exp(-x) / GAMMA(a) for x>=0.
* mean = a. variance = a.
*
* Cheng's algorithm
* [Devroye page 413] if a>=1 and Berman's algorithm [Devroye page 419]
* if a<=1 would have done the job, but they both have bugs. Other possible
* algorithms in Devroye include Wilson-Haferty page 141 for a>=0.5,
* Vaduva algorithm page 415 for a<1, and algorithms GS and RGS for a<1
* pages 425, 426.
*
* Present code is based on code by Steve Omohundro based on
* Brian D. Ripley: Stochastic Simulation, John Wiley and Sons, NY 1987,
* p88-90. It appears to work now
* according to mean and variance tests at a=.3,.5,.6,.9,1,2,3.
**************************************
PROCEDURE* Will generate a sample from a Dirichlet distribution * with parameters p[]. * Follows L.Devroye: Non-uniform random variate generation, * Springer 1986. p[] is overwritten by the Dirichlet deviate.GammaDev (SELF: T; a: R.T; ): R.T = BEGIN <* ASSERT a > R.Zero *> IF a < R.One THEN VAR u0, u1, x: R.T; BEGIN LOOP u0 := SELF.generateReal(); u1 := SELF.generateReal(); IF (a + RT.E) * u0 > RT.E THEN x := -RT.Ln((a + RT.E) * (R.One - u0) / (a * RT.E)); IF u1 <= RT.Pow(x, a - R.One) THEN <* ASSERT x >= R.Zero *> RETURN x; END; ELSE x := RT.Pow((a + RT.E) * u0 / RT.E, R.One / a); IF u1 <= RT.Exp(-x) THEN <* ASSERT x >= R.Zero *> RETURN x; END; END; END; (*LOOP*) END; ELSIF a > R.One THEN (* Cheng+Feast algorithm [CACM 23,7 (1980) 389-394?] for a>1: *) VAR c1 := a - R.One; c2 := (a - R.One / (6.0D0 * a)) / c1; c3 := 2.0D0 / c1; c4 := c3 + 2.0D0; c5 := R.One / RT.SqRt(a); u1, u2, w: R.T; BEGIN LOOP REPEAT u1 := SELF.generateReal(); u2 := SELF.generateReal(); IF a > 2.5D0 THEN u1 := u2 + c5 * (R.One - 1.86D0 * u1); END; UNTIL R.Zero < u1 AND u1 < R.One; w := c2 * u2 / u1; IF c3 * u1 + w + R.One / w <= c4 OR c3 * RT.Ln(u1) - RT.Ln(w) + w < R.One THEN w := w * c1; <* ASSERT w >= R.Zero *> RETURN w; END; END; (*LOOP*) END; ELSE (* a=1, just use exponential: *) RETURN -RT.Ln(SELF.generateReal()); END; END GammaDev;
PROCEDUREDirichlet (SELF: T; p: R.Array; ) = VAR t, sum: R.T; n1 := FIRST(p^); nn := LAST(p^); BEGIN sum := R.Zero; FOR n := nn TO n1 BY -1 DO t := GammaDev(SELF, p[n]); p[n] := t; sum := sum + t; END; t := R.One / sum; FOR n := nn TO n1 BY -1 DO p[n] := p[n] * t; END; END Dirichlet;
PROCEDURE Poisson(SELF:T; m:R.T (* mean
):R.T=
Poisson, integer returned as real
<*UNUSED*> CONST ftn = Module & "Poisson"; BEGIN END Poisson; *)an alternative for Binomial which is probably slower than Binomial but has the potential to be sped up if one can sum up the first n binomial coefficients fast partition the interval [0,1] into pieces with sizes according to the probabilities
<* UNUSED *> PROCEDUREBinomialIntervalPartition (SELF: T; p: R.T; n: CARDINAL; ): CARDINAL = PROCEDURE Calc (p, q: R.T; ): CARDINAL = <* FATAL Arith.Error *> (* bad_size can't be raised by RP.Power *) VAR pq := p / q; prob := RP.Power(q, n); rnd := SELF.generateReal(); den := R.Zero; num := FLOAT(n, R.T); k : CARDINAL := 0; BEGIN WHILE prob < rnd DO rnd := rnd - prob; den := den + R.One; prob := prob * pq * num / den; num := num - R.One; INC(k); END; RETURN k; END Calc; <* UNUSED *> CONST ftn = Module & "Binomial"; BEGIN IF n = 0 THEN RETURN 0; ELSIF p < RT.Half THEN RETURN n - Calc(R.One - p, p); ELSE RETURN Calc(p, R.One - p); END; END BinomialIntervalPartition; PROCEDUREBinomial (SELF: T; p: R.T; n: CARDINAL; ): CARDINAL = <* UNUSED *> CONST ftn = Module & "Binomial"; VAR cnt: CARDINAL := 0; BEGIN FOR i := 0 TO n - 1 DO IF SELF.generateReal() <= p THEN INC(cnt); END; END; RETURN cnt; END Binomial; BEGIN END RandomBasic.