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 *> PROCEDUREGenerates a word, bit by bitGenerateBooleanFromBoolean (SELF: TBoolean; ): BOOLEAN = BEGIN RETURN SELF.engine(); END GenerateBooleanFromBoolean;
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.