Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/symbolic/random_tests.py
4057 views
1
from sage.misc.prandom import randint, random
2
import operator
3
from sage.rings.all import QQ
4
import sage.calculus.calculus
5
import sage.symbolic.pynac
6
from sage.symbolic.constants import *
7
8
def _mk_full_functions():
9
r"""
10
A simple function that returns a list of all Pynac functions of known
11
arity, sorted by name.
12
13
EXAMPLES::
14
15
sage: from sage.symbolic.random_tests import _mk_full_functions
16
sage: [f for (one,f,arity) in _mk_full_functions()] # random
17
[Ei, abs, arccos, arccosh, arccot, arccoth, arccsc, arccsch,
18
arcsec, arcsech, arcsin, arcsinh, arctan, arctan2, arctanh,
19
arg, beta, binomial, ceil, conjugate, cos, cosh, cot, coth,
20
csc, csch, dickman_rho, dilog, dirac_delta, elliptic_e,
21
elliptic_ec, elliptic_eu, elliptic_f, elliptic_kc,
22
elliptic_pi, erf, exp, factorial, floor, heaviside, imag_part,
23
integrate, kronecker_delta, log, polylog, real_part, sec,
24
sech, sgn, sin, sinh, tan, tanh, unit_step, zeta, zetaderiv]
25
26
Note that this doctest will fail whenever a Pynac function is added or
27
removed. In that case, it is very likely that the doctests for
28
random_expr will fail as well. That's OK; just fix the doctest
29
to match the new output.
30
"""
31
items = sage.symbolic.pynac.symbol_table['functions'].items()
32
items.sort()
33
return [(1.0, f, f.number_of_arguments())
34
for (name, f) in items
35
if hasattr(f, 'number_of_arguments') and
36
f.number_of_arguments() > 0]
37
38
# For creating simple expressions
39
40
fast_binary = [(0.4, operator.add), (0.1, operator.sub), (0.5, operator.mul)]
41
fast_unary = [(0.8, operator.neg), (0.2, operator.abs)]
42
fast_nodes = [(0.9, fast_binary, 2), (0.1, fast_unary, 1)]
43
44
# For creating expressions with the full power of Pynac's simple expression
45
# subset (with no quantifiers/operators; that is, no derivatives, integrals,
46
# etc.)
47
full_binary = [(0.3, operator.add), (0.1, operator.sub), (0.3, operator.mul), (0.2, operator.div), (0.1, operator.pow)]
48
full_unary = [(0.8, operator.neg), (0.2, operator.inv)]
49
full_functions = _mk_full_functions()
50
full_nullary = [(1.0, c) for c in [pi, e]] + [(0.05, c) for c in
51
[golden_ratio, log2, euler_gamma, catalan, khinchin, twinprime,
52
mertens, brun]]
53
full_internal = [(0.6, full_binary, 2), (0.2, full_unary, 1),
54
(0.2, full_functions)]
55
56
def normalize_prob_list(pl, extra=()):
57
r"""
58
INPUT:
59
60
- ``pl`` - A list of tuples, where the first element of each tuple is
61
a floating-point number (representing a relative probability). The
62
second element of each tuple may be a list or any other kind of object.
63
64
- ``extra`` - A tuple which is to be appended to every tuple in ``pl``.
65
66
This function takes such a list of tuples (a "probability list") and
67
normalizes the probabilities so that they sum to one. If any of the
68
values are lists, then those lists are first normalized; then
69
the probabilities in the list are multiplied by the main probability
70
and the sublist is merged with the main list.
71
72
For example, suppose we want to select between group A and group B with
73
50% probability each. Then within group A, we select A1 or A2 with 50%
74
probability each (so the overall probability of selecting A1 is 25%);
75
and within group B, we select B1, B2, or B3 with probabilities in
76
a 1:2:2 ratio.
77
78
EXAMPLES::
79
80
sage: from sage.symbolic.random_tests import *
81
sage: A = [(0.5, 'A1'), (0.5, 'A2')]
82
sage: B = [(1, 'B1'), (2, 'B2'), (2, 'B3')]
83
sage: top = [(50, A, 'Group A'), (50, B, 'Group B')]
84
sage: normalize_prob_list(top)
85
[(0.250000000000000, 'A1', 'Group A'), (0.250000000000000, 'A2', 'Group A'), (0.1, 'B1', 'Group B'), (0.2, 'B2', 'Group B'), (0.2, 'B3', 'Group B')]
86
"""
87
if len(pl) == 0:
88
return pl
89
result = []
90
total = sum([float(p[0]) for p in pl])
91
for p in pl:
92
prob = p[0]
93
val = p[1]
94
if len(p) > 2:
95
p_extra = p[2:]
96
else:
97
p_extra = extra
98
if isinstance(val, list):
99
norm_val = normalize_prob_list(val, extra=p_extra)
100
for np in norm_val:
101
result.append(((prob/total)*np[0], np[1]) + np[2:])
102
else:
103
result.append(((prob/total), val) + p_extra)
104
return result
105
106
def choose_from_prob_list(lst):
107
r"""
108
INPUT:
109
110
- ``lst`` - A list of tuples, where the first element of each tuple
111
is a nonnegative float (a probability), and the probabilities sum
112
to one.
113
114
OUTPUT:
115
116
A tuple randomly selected from the list according to the given
117
probabilities.
118
119
EXAMPLES::
120
121
sage: from sage.symbolic.random_tests import *
122
sage: v = [(0.1, False), (0.9, True)]
123
sage: choose_from_prob_list(v)
124
(0.900000000000000, True)
125
sage: true_count = 0
126
sage: for _ in range(10000):
127
... if choose_from_prob_list(v)[1]:
128
... true_count += 1
129
sage: true_count
130
9033
131
sage: true_count - (10000 * 9/10)
132
33
133
"""
134
r = random()
135
for i in range(len(lst)-1):
136
if r < lst[i][0]:
137
return lst[i]
138
r -= lst[i][0]
139
return lst[-1]
140
141
def random_integer_vector(n, length):
142
r"""
143
Give a random list of length *length*, consisting of nonnegative
144
integers that sum to *n*.
145
146
This is an approximation to IntegerVectors(n, length).random_element().
147
That gives values uniformly at random, but might be slow; this
148
routine is not uniform, but should always be fast.
149
150
(This routine is uniform if *length* is 1 or 2; for longer vectors,
151
we prefer approximately balanced vectors, where all the values
152
are around `n/{length}`.)
153
154
EXAMPLES::
155
156
sage: from sage.symbolic.random_tests import *
157
sage: random_integer_vector(100, 2)
158
[11, 89]
159
sage: random_integer_vector(100, 2)
160
[51, 49]
161
sage: random_integer_vector(100, 2)
162
[4, 96]
163
sage: random_integer_vector(10000, 20)
164
[332, 529, 185, 738, 82, 964, 596, 892, 732, 134, 834, 765, 398, 608, 358, 300, 652, 249, 586, 66]
165
"""
166
if length == 0:
167
return []
168
elif length == 1:
169
return [n]
170
elif length == 2:
171
v = randint(0, n)
172
return [v, n-v]
173
else:
174
v = randint(0, 2*n//length)
175
return [v] + random_integer_vector(n-v, length-1)
176
177
def random_expr_helper(n_nodes, internal, leaves, verbose):
178
r"""
179
Produce a random symbolic expression of size *n_nodes* (or slightly
180
larger). Internal nodes are selected from the *internal* probability
181
list; leaves are selected from *leaves*. If *verbose* is True,
182
then a message is printed before creating an internal node.
183
184
EXAMPLES::
185
186
sage: from sage.symbolic.random_tests import *
187
sage: random_expr_helper(9, [(0.5, operator.add, 2), (0.5, operator.neg, 1)], [(0.5, 1), (0.5, x)], True)
188
About to apply <built-in function add> to [1, x]
189
About to apply <built-in function add> to [x, x + 1]
190
About to apply <built-in function neg> to [1]
191
About to apply <built-in function neg> to [-1]
192
About to apply <built-in function neg> to [1]
193
About to apply <built-in function add> to [2*x + 1, -1]
194
2*x
195
"""
196
if n_nodes == 1:
197
return choose_from_prob_list(leaves)[1]
198
else:
199
r = choose_from_prob_list(internal)
200
n_nodes -= 1
201
n_children = r[2]
202
n_spare_nodes = n_nodes - n_children
203
if n_spare_nodes <= 0:
204
n_spare_nodes = 0
205
nodes_per_child = random_integer_vector(n_spare_nodes, n_children)
206
children = [random_expr_helper(n+1, internal, leaves, verbose) for n in nodes_per_child]
207
if verbose:
208
print "About to apply %r to %r" % (r[1], children)
209
return r[1](*children)
210
211
def random_expr(size, nvars=1, ncoeffs=None, var_frac=0.5, internal=full_internal, nullary=full_nullary, nullary_frac=0.2, coeff_generator=QQ.random_element, verbose=False):
212
r"""
213
Produce a random symbolic expression of the given size. By
214
default, the expression involves (at most) one variable, an arbitrary
215
number of coefficients, and all of the symbolic functions and constants
216
(from the probability lists full_internal and full_nullary). It is
217
possible to adjust the ratio of leaves between symbolic constants,
218
variables, and coefficients (var_frac gives the fraction of variables,
219
and nullary_frac the fraction of symbolic constants; the remaining
220
leaves are coefficients).
221
222
The actual mix of symbolic constants and internal nodes can be modified
223
by specifying different probability lists.
224
225
To use a different type for coefficients, you can specify
226
coeff_generator, which should be a function that will return
227
a random coefficient every time it is called.
228
229
This function will often raise an error because it tries to create
230
an erroneous expression (such as a division by zero).
231
232
EXAMPLES::
233
234
sage: from sage.symbolic.random_tests import *
235
sage: set_random_seed(2)
236
sage: random_expr(50, nvars=3, coeff_generator=CDF.random_element) # random
237
(euler_gamma - v3^(-e) + (v2 - e^(-e/v2))^(((2.85879036573 -
238
1.18163393202*I)*v2 + (2.85879036573 - 1.18163393202*I)*v3)*pi
239
- 0.247786879678 + 0.931826724898*I)*arccsc((0.891138386848 -
240
0.0936820840629*I)/v1) - (0.553423153995 - 0.5481180572*I)*v3
241
+ 0.149683576515 - 0.155746451854*I)*v1 + arccsch(pi +
242
e)*elliptic_eu(khinchin*v2, 1.4656989704 + 0.863754357069*I)
243
sage: random_expr(5, verbose=True) # random
244
About to apply dilog to [1]
245
About to apply arcsec to [0]
246
About to apply <built-in function add> to [1/6*pi^2, arcsec(0)]
247
1/6*pi^2 + arcsec(0)
248
"""
249
vars = [(1.0, sage.calculus.calculus.var('v%d' % (n+1))) for n in range(nvars)]
250
if ncoeffs is None:
251
ncoeffs = size
252
coeffs = [(1.0, coeff_generator()) for _ in range(ncoeffs)]
253
leaves = [(var_frac, vars), (1.0 - var_frac - nullary_frac, coeffs), (nullary_frac, nullary)]
254
leaves = normalize_prob_list(leaves)
255
256
internal = normalize_prob_list(internal)
257
258
return random_expr_helper(size, internal, leaves, verbose)
259
260