random.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """
  2. When you need to use random numbers in SymPy library code, import from here
  3. so there is only one generator working for SymPy. Imports from here should
  4. behave the same as if they were being imported from Python's random module.
  5. But only the routines currently used in SymPy are included here. To use others
  6. import ``rng`` and access the method directly. For example, to capture the
  7. current state of the generator use ``rng.getstate()``.
  8. There is intentionally no Random to import from here. If you want
  9. to control the state of the generator, import ``seed`` and call it
  10. with or without an argument to set the state.
  11. Examples
  12. ========
  13. >>> from sympy.core.random import random, seed
  14. >>> assert random() < 1
  15. >>> seed(1); a = random()
  16. >>> b = random()
  17. >>> seed(1); c = random()
  18. >>> assert a == c
  19. >>> assert a != b # remote possibility this will fail
  20. """
  21. from sympy.utilities.iterables import is_sequence
  22. from sympy.utilities.misc import as_int
  23. import random as _random
  24. rng = _random.Random()
  25. choice = rng.choice
  26. random = rng.random
  27. randint = rng.randint
  28. randrange = rng.randrange
  29. sample = rng.sample
  30. # seed = rng.seed
  31. shuffle = rng.shuffle
  32. uniform = rng.uniform
  33. _assumptions_rng = _random.Random()
  34. _assumptions_shuffle = _assumptions_rng.shuffle
  35. def seed(a=None, version=2):
  36. rng.seed(a=a, version=version)
  37. _assumptions_rng.seed(a=a, version=version)
  38. def random_complex_number(a=2, b=-1, c=3, d=1, rational=False, tolerance=None):
  39. """
  40. Return a random complex number.
  41. To reduce chance of hitting branch cuts or anything, we guarantee
  42. b <= Im z <= d, a <= Re z <= c
  43. When rational is True, a rational approximation to a random number
  44. is obtained within specified tolerance, if any.
  45. """
  46. from sympy.core.numbers import I
  47. from sympy.simplify.simplify import nsimplify
  48. A, B = uniform(a, c), uniform(b, d)
  49. if not rational:
  50. return A + I*B
  51. return (nsimplify(A, rational=True, tolerance=tolerance) +
  52. I*nsimplify(B, rational=True, tolerance=tolerance))
  53. def verify_numerically(f, g, z=None, tol=1.0e-6, a=2, b=-1, c=3, d=1):
  54. """
  55. Test numerically that f and g agree when evaluated in the argument z.
  56. If z is None, all symbols will be tested. This routine does not test
  57. whether there are Floats present with precision higher than 15 digits
  58. so if there are, your results may not be what you expect due to round-
  59. off errors.
  60. Examples
  61. ========
  62. >>> from sympy import sin, cos
  63. >>> from sympy.abc import x
  64. >>> from sympy.core.random import verify_numerically as tn
  65. >>> tn(sin(x)**2 + cos(x)**2, 1, x)
  66. True
  67. """
  68. from sympy.core.symbol import Symbol
  69. from sympy.core.sympify import sympify
  70. from sympy.core.numbers import comp
  71. f, g = (sympify(i) for i in (f, g))
  72. if z is None:
  73. z = f.free_symbols | g.free_symbols
  74. elif isinstance(z, Symbol):
  75. z = [z]
  76. reps = list(zip(z, [random_complex_number(a, b, c, d) for _ in z]))
  77. z1 = f.subs(reps).n()
  78. z2 = g.subs(reps).n()
  79. return comp(z1, z2, tol)
  80. def test_derivative_numerically(f, z, tol=1.0e-6, a=2, b=-1, c=3, d=1):
  81. """
  82. Test numerically that the symbolically computed derivative of f
  83. with respect to z is correct.
  84. This routine does not test whether there are Floats present with
  85. precision higher than 15 digits so if there are, your results may
  86. not be what you expect due to round-off errors.
  87. Examples
  88. ========
  89. >>> from sympy import sin
  90. >>> from sympy.abc import x
  91. >>> from sympy.core.random import test_derivative_numerically as td
  92. >>> td(sin(x), x)
  93. True
  94. """
  95. from sympy.core.numbers import comp
  96. from sympy.core.function import Derivative
  97. z0 = random_complex_number(a, b, c, d)
  98. f1 = f.diff(z).subs(z, z0)
  99. f2 = Derivative(f, z).doit_numerically(z0)
  100. return comp(f1.n(), f2.n(), tol)
  101. def _randrange(seed=None):
  102. """Return a randrange generator.
  103. ``seed`` can be
  104. * None - return randomly seeded generator
  105. * int - return a generator seeded with the int
  106. * list - the values to be returned will be taken from the list
  107. in the order given; the provided list is not modified.
  108. Examples
  109. ========
  110. >>> from sympy.core.random import _randrange
  111. >>> rr = _randrange()
  112. >>> rr(1000) # doctest: +SKIP
  113. 999
  114. >>> rr = _randrange(3)
  115. >>> rr(1000) # doctest: +SKIP
  116. 238
  117. >>> rr = _randrange([0, 5, 1, 3, 4])
  118. >>> rr(3), rr(3)
  119. (0, 1)
  120. """
  121. if seed is None:
  122. return randrange
  123. elif isinstance(seed, int):
  124. rng.seed(seed)
  125. return randrange
  126. elif is_sequence(seed):
  127. seed = list(seed) # make a copy
  128. seed.reverse()
  129. def give(a, b=None, seq=seed):
  130. if b is None:
  131. a, b = 0, a
  132. a, b = as_int(a), as_int(b)
  133. w = b - a
  134. if w < 1:
  135. raise ValueError('_randrange got empty range')
  136. try:
  137. x = seq.pop()
  138. except IndexError:
  139. raise ValueError('_randrange sequence was too short')
  140. if a <= x < b:
  141. return x
  142. else:
  143. return give(a, b, seq)
  144. return give
  145. else:
  146. raise ValueError('_randrange got an unexpected seed')
  147. def _randint(seed=None):
  148. """Return a randint generator.
  149. ``seed`` can be
  150. * None - return randomly seeded generator
  151. * int - return a generator seeded with the int
  152. * list - the values to be returned will be taken from the list
  153. in the order given; the provided list is not modified.
  154. Examples
  155. ========
  156. >>> from sympy.core.random import _randint
  157. >>> ri = _randint()
  158. >>> ri(1, 1000) # doctest: +SKIP
  159. 999
  160. >>> ri = _randint(3)
  161. >>> ri(1, 1000) # doctest: +SKIP
  162. 238
  163. >>> ri = _randint([0, 5, 1, 2, 4])
  164. >>> ri(1, 3), ri(1, 3)
  165. (1, 2)
  166. """
  167. if seed is None:
  168. return randint
  169. elif isinstance(seed, int):
  170. rng.seed(seed)
  171. return randint
  172. elif is_sequence(seed):
  173. seed = list(seed) # make a copy
  174. seed.reverse()
  175. def give(a, b, seq=seed):
  176. a, b = as_int(a), as_int(b)
  177. w = b - a
  178. if w < 0:
  179. raise ValueError('_randint got empty range')
  180. try:
  181. x = seq.pop()
  182. except IndexError:
  183. raise ValueError('_randint sequence was too short')
  184. if a <= x <= b:
  185. return x
  186. else:
  187. return give(a, b, seq)
  188. return give
  189. else:
  190. raise ValueError('_randint got an unexpected seed')