(* Ulm's Oberon Library Copyright (C) 1989-1994 by University of Ulm, SAI, D-89069 Ulm, Germany ---------------------------------------------------------------------------- Ulm's Oberon Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Ulm's Oberon Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ---------------------------------------------------------------------------- E-mail contact: oberon@mathematik.uni-ulm.de ---------------------------------------------------------------------------- $Id: RandomGener.om,v 1.9 2004/03/09 21:44:12 borchert Exp $ ---------------------------------------------------------------------------- $Log: RandomGener.om,v $ Revision 1.9 2004/03/09 21:44:12 borchert unpredictable added to the standard set of PRNGs Revision 1.8 2004/03/06 07:22:09 borchert Init asserts that the sequence has been registered at Services Revision 1.7 1998/02/14 22:04:09 martin Missing calls of Services.Init and Services.CreateType added. Revision 1.6 1997/10/11 21:22:03 martin assertion in ValS added, obsolete variable removed Revision 1.5 1997/10/10 16:26:49 martin RestartSequence added, range conversions improved, default implementation replaced. Revision 1.4 1997/04/01 16:33:41 borchert major revision of Random: - module renamed to RandomGenerators - abstraction instead of simple implementation (work by Frank Fischer) Revision 1.3 1994/09/01 18:15:41 borchert bug fix: avoid arithmetic overflow in ValS Revision 1.2 1994/08/30 09:48:00 borchert sequences added Revision 1.1 1994/02/23 07:25:30 borchert Initial revision ---------------------------------------------------------------------------- original implementation by AFB 2/90 conversion to abstraction by Frank B.J. Fischer 3/97 ---------------------------------------------------------------------------- *) MODULE ulmRandomGenerators; (* Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin. - John von Neumann (1951) *) IMPORT Clocks := ulmClocks, Disciplines := ulmDisciplines, Objects := ulmObjects, Operations := ulmOperations, Process := ulmProcess, Services := ulmServices, Times := ulmTimes, Types := ulmTypes, S := SYSTEM; TYPE Sequence* = POINTER TO SequenceRec; Int32ValSProc* = PROCEDURE (sequence: Sequence): Types.Int32; LongRealValSProc* = PROCEDURE (sequence: Sequence): LONGREAL; RewindSequenceProc* = PROCEDURE (sequence: Sequence); RestartSequenceProc* = PROCEDURE (sequence, seed: Sequence); SetValSProc* = PROCEDURE (sequence: Sequence; value: Operations.Operand); CONST int32ValS* = 0; longRealValS* = 1; rewindSequence* = 2; restartSequence* = 3; TYPE CapabilitySet* = SET; (* of [int32ValS..restartSequence] *) Interface* = POINTER TO InterfaceRec; InterfaceRec* = RECORD (Objects.ObjectRec) int32ValS* : Int32ValSProc; (* at least one of ... *) longRealValS* : LongRealValSProc; (* ... these required *) rewindSequence* : RewindSequenceProc; (* optional *) restartSequence*: RestartSequenceProc; (* optional *) END; SequenceRec* = RECORD (Services.ObjectRec) (* private components *) if : Interface; caps: CapabilitySet; END; VAR std* : Sequence; (* default sequence *) seed*: Sequence; (* sequence of seed values *) unpredictable*: Sequence; (* reasonably fast sequence of unpredictable values; is initially NIL *) (* ----- private definitions ----- *) CONST modulus1 = 2147483647; (* a Mersenne prime *) factor1 = 48271; (* passes spectral test *) quotient1 = modulus1 DIV factor1; (* 44488 *) remainder1 = modulus1 MOD factor1; (* 3399; must be < quotient1 *) modulus2 = 2147483399; (* a non-Mersenne prime *) factor2 = 40692; (* also passes spectral test *) quotient2 = modulus2 DIV factor2; (* 52774 *) remainder2 = modulus2 MOD factor2; (* 3791; must be < quotient2 *) TYPE DefaultSequence = POINTER TO DefaultSequenceRec; DefaultSequenceRec = RECORD (SequenceRec) seed1, seed2: LONGINT; value1, value2: LONGINT; END; ServiceDiscipline = POINTER TO ServiceDisciplineRec; ServiceDisciplineRec = RECORD (Disciplines.DisciplineRec) setValS: SetValSProc; END; VAR service : Services.Service; serviceDiscID: Disciplines.Identifier; sequenceType, defaultSequenceType: Services.Type; (* ----- bug workaround ----- *) PROCEDURE Entier(value: LONGREAL): LONGINT; VAR result: LONGINT; BEGIN result := ENTIER(value); IF result > value THEN DEC(result); END; RETURN result END Entier; (* ----- exported procedures ----- *) PROCEDURE Init*(sequence: Sequence; if: Interface; caps: CapabilitySet); (* initialize sequence *) VAR type: Services.Type; BEGIN ASSERT((if.int32ValS # NIL) OR (if.longRealValS # NIL)); ASSERT(~(int32ValS IN caps) OR (if.int32ValS # NIL)); ASSERT(~(longRealValS IN caps) OR (if.longRealValS # NIL)); ASSERT(~(rewindSequence IN caps) OR (if.rewindSequence # NIL)); Services.GetType(sequence, type); ASSERT(type # NIL); sequence.if := if; sequence.caps := caps; END Init; PROCEDURE Capabilities*(sequence: Sequence): CapabilitySet; (* tell which procedures are implemented *) BEGIN RETURN sequence.caps END Capabilities; PROCEDURE RewindSequence*(sequence: Sequence); (* re-examine sequence *) BEGIN ASSERT(rewindSequence IN sequence.caps); sequence.if.rewindSequence(sequence); END RewindSequence; PROCEDURE RestartSequence*(sequence, seed: Sequence); (* restart sequence with new seed values *) BEGIN ASSERT(restartSequence IN sequence.caps); sequence.if.restartSequence(sequence, seed); END RestartSequence; PROCEDURE ^ LongRealValS*(sequence: Sequence): LONGREAL; PROCEDURE Int32ValS*(sequence: Sequence): Types.Int32; (* get random 32-bit value from sequence *) VAR real: LONGREAL; BEGIN IF int32ValS IN sequence.caps THEN RETURN sequence.if.int32ValS(sequence) ELSE real := LongRealValS(sequence); RETURN SHORT(Entier( (1. - real - real) * MIN(Types.Int32) )) END; END Int32ValS; PROCEDURE Int32Val*(): Types.Int32; (* get random 32-bit value from std sequence *) BEGIN RETURN Int32ValS(std); END Int32Val; PROCEDURE LongRealValS*(sequence: Sequence): LONGREAL; (* get a uniformly distributed longreal value in [0..1) *) BEGIN IF longRealValS IN sequence.caps THEN RETURN sequence.if.longRealValS(sequence) ELSE RETURN 0.5 + Int32ValS(sequence) / (0. - MIN(Types.Int32) - MIN(Types.Int32)) END; END LongRealValS; PROCEDURE LongRealVal*(): LONGREAL; (* get a uniformly distributed longreal value in [0..1) *) BEGIN RETURN LongRealValS(std) END LongRealVal; PROCEDURE RealValS*(sequence: Sequence): REAL; (* get a uniformly distributed real value in [0..1) *) BEGIN RETURN SHORT(LongRealValS(sequence)) END RealValS; PROCEDURE RealVal*(): REAL; (* get a uniformly distributed real value in [0..1) *) BEGIN RETURN SHORT(LongRealValS(std)) END RealVal; PROCEDURE ValS*(sequence: Sequence; low, high: LONGINT): LONGINT; (* get a uniformly distributed integer in [low..high] *) BEGIN ASSERT(low <= high); RETURN Entier( low + LongRealValS(sequence) * (1. + high - low) ) END ValS; PROCEDURE Val*(low, high: LONGINT): LONGINT; (* get a uniformly distributed integer in [low..high] *) BEGIN RETURN ValS(std, low, high) END Val; PROCEDURE FlipS*(sequence: Sequence): BOOLEAN; (* return TRUE or FALSE *) BEGIN IF int32ValS IN sequence.caps THEN RETURN sequence.if.int32ValS(sequence) >= 0 ELSE RETURN sequence.if.longRealValS(sequence) >= 0.5 END; END FlipS; PROCEDURE Flip*(): BOOLEAN; (* return TRUE or FALSE *) BEGIN RETURN FlipS(std) END Flip; PROCEDURE Support*(type: Services.Type; setValS: SetValSProc); (* support service for type *) VAR serviceDisc: ServiceDiscipline; BEGIN NEW(serviceDisc); serviceDisc.id := serviceDiscID; serviceDisc.setValS := setValS; Disciplines.Add(type, serviceDisc); Services.Define(type, service, NIL); END Support; PROCEDURE SetValS*(sequence: Sequence; value: Operations.Operand); (* store random value from sequence into already initialized value *) VAR baseType : Services.Type; serviceDisc: ServiceDiscipline; ok : BOOLEAN; BEGIN Services.GetSupportedBaseType(value, service, baseType); ok := Disciplines.Seek(baseType, serviceDiscID, S.VAL(Disciplines.Discipline, serviceDisc)); ASSERT(ok); serviceDisc.setValS(sequence, value); END SetValS; PROCEDURE SetVal*(value: Operations.Operand); (* store random value from std sequence into already initialized value *) BEGIN SetValS(std, value); END SetVal; (* ----- DefaultSequence ----- *) PROCEDURE CongruentialStep(VAR value1, value2: LONGINT); BEGIN value1 := factor1 * (value1 MOD quotient1) - remainder1 * (value1 DIV quotient1); IF value1 < 0 THEN INC(value1, modulus1); END; value2 := factor2 * (value2 MOD quotient2) - remainder2 * (value2 DIV quotient2); IF value2 < 0 THEN INC(value2, modulus2); END; END CongruentialStep; PROCEDURE DefaultSequenceValue(sequence: Sequence): LONGREAL; VAR value: LONGINT; BEGIN WITH sequence: DefaultSequence DO CongruentialStep(sequence.value1, sequence.value2); value := sequence.value1 - sequence.value2; IF value <= 0 THEN INC(value, modulus1); END; RETURN (value - 1.) / (modulus1 - 1.) END; END DefaultSequenceValue; PROCEDURE DefaultSequenceRewind(sequence: Sequence); BEGIN WITH sequence: DefaultSequence DO sequence.value1 := sequence.seed1; sequence.value2 := sequence.seed2; END; END DefaultSequenceRewind; PROCEDURE DefaultSequenceRestart(sequence, seed: Sequence); BEGIN WITH sequence: DefaultSequence DO sequence.seed1 := ValS(seed, 1, modulus1-1); sequence.seed2 := ValS(seed, 1, modulus2-1); sequence.value1 := sequence.seed1; sequence.value2 := sequence.seed2; END; END DefaultSequenceRestart; PROCEDURE CreateDefaultSequences; VAR mySeed, myStd: DefaultSequence; if: Interface; daytime: Times.Time; timeval: Times.TimeValueRec; count: LONGINT; PROCEDURE Hash(str: ARRAY OF CHAR): LONGINT; VAR index, val: LONGINT; BEGIN val := 27567352; index := 0; WHILE str[index] # 0X DO val := (val MOD 16777216) * 128 + (val DIV 16777216 + ORD(str[index])) MOD 128; INC(index); END; (*WHILE*) RETURN val END Hash; BEGIN (* define interface for all default sequences *) NEW(if); if.longRealValS := DefaultSequenceValue; if.rewindSequence := DefaultSequenceRewind; if.restartSequence := DefaultSequenceRestart; (* fake initial randomness using some portably accessible sources *) NEW(mySeed); Services.Init(mySeed, defaultSequenceType); Init(mySeed, if, {longRealValS}); Clocks.GetTime(Clocks.system, daytime); Times.GetValue(daytime, timeval); (* extract those 31 bits from daytime that are most likely to vary *) mySeed.value1 := timeval.usec * 2048 + timeval.second MOD 65536 + 1; (* generate 31 more bits from the process name *) mySeed.value2 := Hash(Process.name) MOD (modulus2 - 1) + 1; (* scramble these values *) count := 0; WHILE count < 4 DO CongruentialStep(mySeed.value1, mySeed.value2); INC(count); END; (* mix them together *) DefaultSequenceRestart(mySeed, mySeed); seed := mySeed; (* now use our seed to initialize std sequence *) NEW(myStd); Services.Init(myStd, defaultSequenceType); Init(myStd, if, {longRealValS, rewindSequence, restartSequence}); DefaultSequenceRestart(myStd, mySeed); std := myStd; unpredictable := NIL; END CreateDefaultSequences; BEGIN serviceDiscID := Disciplines.Unique(); Services.Create(service, "RandomGenerators"); Services.CreateType(sequenceType, "RandomGenerators.Sequence", ""); Services.CreateType(defaultSequenceType, "RandomGenerators.DefaultSequence", "RandomGenerators.Sequence"); CreateDefaultSequences; END ulmRandomGenerators.