randomGenerator.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. # Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
  2. # McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """
  16. Module to offer 'really deterministic' (pseudo-)random number generation in a Distributed Time Warp implementation.
  17. For local simulation, using the random library from Python as usual is perfectly ok.
  18. """
  19. import random
  20. class RandomGenerator(object):
  21. """
  22. Base class, which implements a random number interface for the 'uniform' and 'random' Python standard library functions.
  23. .. note:: The generated random numbers are **not** equal to those generated by direct calls to the library functions, as we also use a random number to initialize the seed in the next iteration.
  24. """
  25. def __init__(self, seed):
  26. """
  27. Constructor
  28. :param seed: the seed to start with, this will simply be passed to the *random* library at every function call
  29. """
  30. #NOTE: This is implemented using only a seed (and actually, only a number), instead of using the 'getState()' en 'setState(state)'
  31. # functions provided by the library. This was done to allow far more simple comparison (for memoization), hashing (as we
  32. # have overwritten the comparison) and copying (for custom state saving).
  33. self.seed = seed
  34. def __eq__(self, other):
  35. """
  36. Compare two instances of random number generators.
  37. Needed for memoization.
  38. :param other: the instance to compare with
  39. :returns: bool -- do these random number generators return the same sequence?
  40. """
  41. return type(self) == type(other) and self.seed == other.seed
  42. def __hash__(self):
  43. """
  44. Hash this random number generator.
  45. Needed as the comparison method was changed!
  46. :returns: hash
  47. """
  48. return self.seed
  49. def copy(self):
  50. """
  51. A copy method to be used when defining custom state saving methods. It will return a complete copy of this random number
  52. generator, which will generate exactly the same sequence of numbers.
  53. """
  54. return RandomGenerator(self.seed)
  55. def __wrapFunction(self, func, args):
  56. """
  57. Internal wrapper for most functions, allows easy addition of new functions should the need arise. It updates the internal state and
  58. guarantees determinism even when revertions happen.
  59. :param func: the function to call on the *random* module (a string)
  60. :param args: the arguments to pass (a list)
  61. :returns: random -- the generated value
  62. """
  63. random.seed(self.seed)
  64. val = getattr(random, func)(*args)
  65. self.seed = random.random()
  66. return val
  67. def uniform(self, a, b):
  68. """
  69. Call the uniform function of the *random* library.
  70. :param a: lower bound of generated value
  71. :param b: upper bound of generated value
  72. :returns: float -- the generated value
  73. """
  74. return self.__wrapFunction("uniform", [a, b])
  75. def random(self):
  76. """
  77. Call the random function of the *random* library.
  78. :returns: float -- a random value between 0 and 1
  79. """
  80. return self.__wrapFunction("random", [])