Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/symbolic/random_tests.py
8817 views
1
"""
2
Randomized tests of GiNaC / PyNaC.
3
"""
4
5
###############################################################################
6
# Sage: Open Source Mathematical Software
7
# Copyright (C) 2008 William Stein <[email protected]>
8
# Copyright (C) 2008 Burcin Erocal <[email protected]>
9
# Distributed under the terms of the GNU General Public License (GPL),
10
# version 2 or any later version. The full text of the GPL is available at:
11
# http://www.gnu.org/licenses/
12
###############################################################################
13
14
15
from sage.misc.prandom import randint, random
16
import operator
17
from sage.rings.all import QQ
18
import sage.calculus.calculus
19
import sage.symbolic.pynac
20
from sage.symbolic.constants import *
21
22
23
###################################################################
24
### Generate random expressions for doctests ######################
25
###################################################################
26
27
def _mk_full_functions():
28
r"""
29
A simple function that returns a list of all Pynac functions of known
30
arity, sorted by name.
31
32
EXAMPLES::
33
34
sage: from sage.symbolic.random_tests import _mk_full_functions
35
sage: [f for (one,f,arity) in _mk_full_functions()] # random
36
[Ei, abs, arccos, arccosh, arccot, arccoth, arccsc, arccsch,
37
arcsec, arcsech, arcsin, arcsinh, arctan, arctan2, arctanh,
38
arg, beta, binomial, ceil, conjugate, cos, cosh, cot, coth,
39
csc, csch, dickman_rho, dilog, dirac_delta, elliptic_e,
40
elliptic_ec, elliptic_eu, elliptic_f, elliptic_kc,
41
elliptic_pi, erf, exp, factorial, floor, heaviside, imag_part,
42
integrate, kronecker_delta, log, polylog, real_part, sec,
43
sech, sgn, sin, sinh, tan, tanh, unit_step, zeta, zetaderiv]
44
45
Note that this doctest will fail whenever a Pynac function is added or
46
removed. In that case, it is very likely that the doctests for
47
random_expr will fail as well. That's OK; just fix the doctest
48
to match the new output.
49
"""
50
items = sage.symbolic.pynac.symbol_table['functions'].items()
51
items.sort()
52
return [(1.0, f, f.number_of_arguments())
53
for (name, f) in items
54
if hasattr(f, 'number_of_arguments') and
55
f.number_of_arguments() > 0]
56
57
# For creating simple expressions
58
59
fast_binary = [(0.4, operator.add), (0.1, operator.sub), (0.5, operator.mul)]
60
fast_unary = [(0.8, operator.neg), (0.2, operator.abs)]
61
fast_nodes = [(0.9, fast_binary, 2), (0.1, fast_unary, 1)]
62
63
# For creating expressions with the full power of Pynac's simple expression
64
# subset (with no quantifiers/operators; that is, no derivatives, integrals,
65
# etc.)
66
full_binary = [(0.3, operator.add), (0.1, operator.sub), (0.3, operator.mul), (0.2, operator.div), (0.1, operator.pow)]
67
full_unary = [(0.8, operator.neg), (0.2, operator.inv)]
68
full_functions = _mk_full_functions()
69
full_nullary = [(1.0, c) for c in [pi, e]] + [(0.05, c) for c in
70
[golden_ratio, log2, euler_gamma, catalan, khinchin, twinprime,
71
mertens, brun]]
72
full_internal = [(0.6, full_binary, 2), (0.2, full_unary, 1),
73
(0.2, full_functions)]
74
75
def normalize_prob_list(pl, extra=()):
76
r"""
77
INPUT:
78
79
- ``pl`` - A list of tuples, where the first element of each tuple is
80
a floating-point number (representing a relative probability). The
81
second element of each tuple may be a list or any other kind of object.
82
83
- ``extra`` - A tuple which is to be appended to every tuple in ``pl``.
84
85
This function takes such a list of tuples (a "probability list") and
86
normalizes the probabilities so that they sum to one. If any of the
87
values are lists, then those lists are first normalized; then
88
the probabilities in the list are multiplied by the main probability
89
and the sublist is merged with the main list.
90
91
For example, suppose we want to select between group A and group B with
92
50% probability each. Then within group A, we select A1 or A2 with 50%
93
probability each (so the overall probability of selecting A1 is 25%);
94
and within group B, we select B1, B2, or B3 with probabilities in
95
a 1:2:2 ratio.
96
97
EXAMPLES::
98
99
sage: from sage.symbolic.random_tests import *
100
sage: A = [(0.5, 'A1'), (0.5, 'A2')]
101
sage: B = [(1, 'B1'), (2, 'B2'), (2, 'B3')]
102
sage: top = [(50, A, 'Group A'), (50, B, 'Group B')]
103
sage: normalize_prob_list(top)
104
[(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')]
105
"""
106
if len(pl) == 0:
107
return pl
108
result = []
109
total = sum([float(p[0]) for p in pl])
110
for p in pl:
111
prob = p[0]
112
val = p[1]
113
if len(p) > 2:
114
p_extra = p[2:]
115
else:
116
p_extra = extra
117
if isinstance(val, list):
118
norm_val = normalize_prob_list(val, extra=p_extra)
119
for np in norm_val:
120
result.append(((prob/total)*np[0], np[1]) + np[2:])
121
else:
122
result.append(((prob/total), val) + p_extra)
123
return result
124
125
def choose_from_prob_list(lst):
126
r"""
127
INPUT:
128
129
- ``lst`` - A list of tuples, where the first element of each tuple
130
is a nonnegative float (a probability), and the probabilities sum
131
to one.
132
133
OUTPUT:
134
135
A tuple randomly selected from the list according to the given
136
probabilities.
137
138
EXAMPLES::
139
140
sage: from sage.symbolic.random_tests import *
141
sage: v = [(0.1, False), (0.9, True)]
142
sage: choose_from_prob_list(v)
143
(0.900000000000000, True)
144
sage: true_count = 0
145
sage: for _ in range(10000):
146
... if choose_from_prob_list(v)[1]:
147
... true_count += 1
148
sage: true_count
149
9033
150
sage: true_count - (10000 * 9/10)
151
33
152
"""
153
r = random()
154
for i in range(len(lst)-1):
155
if r < lst[i][0]:
156
return lst[i]
157
r -= lst[i][0]
158
return lst[-1]
159
160
def random_integer_vector(n, length):
161
r"""
162
Give a random list of length *length*, consisting of nonnegative
163
integers that sum to *n*.
164
165
This is an approximation to IntegerVectors(n, length).random_element().
166
That gives values uniformly at random, but might be slow; this
167
routine is not uniform, but should always be fast.
168
169
(This routine is uniform if *length* is 1 or 2; for longer vectors,
170
we prefer approximately balanced vectors, where all the values
171
are around `n/{length}`.)
172
173
EXAMPLES::
174
175
sage: from sage.symbolic.random_tests import *
176
sage: random_integer_vector(100, 2)
177
[11, 89]
178
sage: random_integer_vector(100, 2)
179
[51, 49]
180
sage: random_integer_vector(100, 2)
181
[4, 96]
182
sage: random_integer_vector(10000, 20)
183
[332, 529, 185, 738, 82, 964, 596, 892, 732, 134,
184
834, 765, 398, 608, 358, 300, 652, 249, 586, 66]
185
"""
186
if length == 0:
187
return []
188
elif length == 1:
189
return [n]
190
elif length == 2:
191
v = randint(0, n)
192
return [v, n-v]
193
else:
194
v = randint(0, 2*n//length)
195
return [v] + random_integer_vector(n-v, length-1)
196
197
def random_expr_helper(n_nodes, internal, leaves, verbose):
198
r"""
199
Produce a random symbolic expression of size *n_nodes* (or slightly
200
larger). Internal nodes are selected from the *internal* probability
201
list; leaves are selected from *leaves*. If *verbose* is True,
202
then a message is printed before creating an internal node.
203
204
EXAMPLES::
205
206
sage: from sage.symbolic.random_tests import *
207
sage: random_expr_helper(9, [(0.5, operator.add, 2),
208
... (0.5, operator.neg, 1)], [(0.5, 1), (0.5, x)], True)
209
About to apply <built-in function add> to [1, x]
210
About to apply <built-in function add> to [x, x + 1]
211
About to apply <built-in function neg> to [1]
212
About to apply <built-in function neg> to [-1]
213
About to apply <built-in function neg> to [1]
214
About to apply <built-in function add> to [2*x + 1, -1]
215
2*x
216
"""
217
if n_nodes == 1:
218
return choose_from_prob_list(leaves)[1]
219
else:
220
r = choose_from_prob_list(internal)
221
n_nodes -= 1
222
n_children = r[2]
223
n_spare_nodes = n_nodes - n_children
224
if n_spare_nodes <= 0:
225
n_spare_nodes = 0
226
nodes_per_child = random_integer_vector(n_spare_nodes, n_children)
227
children = [random_expr_helper(n+1, internal, leaves, verbose) for n in nodes_per_child]
228
if verbose:
229
print "About to apply %r to %r" % (r[1], children)
230
return r[1](*children)
231
232
def random_expr(size, nvars=1, ncoeffs=None, var_frac=0.5,
233
internal=full_internal,
234
nullary=full_nullary, nullary_frac=0.2,
235
coeff_generator=QQ.random_element, verbose=False):
236
r"""
237
Produce a random symbolic expression of the given size. By
238
default, the expression involves (at most) one variable, an arbitrary
239
number of coefficients, and all of the symbolic functions and constants
240
(from the probability lists full_internal and full_nullary). It is
241
possible to adjust the ratio of leaves between symbolic constants,
242
variables, and coefficients (var_frac gives the fraction of variables,
243
and nullary_frac the fraction of symbolic constants; the remaining
244
leaves are coefficients).
245
246
The actual mix of symbolic constants and internal nodes can be modified
247
by specifying different probability lists.
248
249
To use a different type for coefficients, you can specify
250
coeff_generator, which should be a function that will return
251
a random coefficient every time it is called.
252
253
This function will often raise an error because it tries to create
254
an erroneous expression (such as a division by zero).
255
256
EXAMPLES::
257
258
sage: from sage.symbolic.random_tests import *
259
sage: set_random_seed(53)
260
sage: random_expr(50, nvars=3, coeff_generator=CDF.random_element) # random
261
(v1^(0.97134084277 + 0.195868299334*I)/csc(-pi + v1^2 + v3) + sgn(1/
262
((-v3 - 0.760455994772 - 0.554367254855*I)*erf(v3 + 0.982759757946 -
263
0.0352136502348*I)) + binomial(arccoth(v1^pi), 0.760455994772 +
264
0.554367254855*I) + arccosh(2*v2 - (v2 + 0.841911550437 -
265
0.303757179824*I)/sinh_integral(pi) + arccoth(v3 + 0.530133230474 +
266
0.532140303485*I))))/v2
267
sage: random_expr(5, verbose=True) # random
268
About to apply <built-in function inv> to [31]
269
About to apply sgn to [v1]
270
About to apply <built-in function add> to [1/31, sgn(v1)]
271
sgn(v1) + 1/31
272
273
"""
274
vars = [(1.0, sage.calculus.calculus.var('v%d' % (n+1))) for n in range(nvars)]
275
if ncoeffs is None:
276
ncoeffs = size
277
coeffs = [(1.0, coeff_generator()) for _ in range(ncoeffs)]
278
leaves = [(var_frac, vars), (1.0 - var_frac - nullary_frac, coeffs), (nullary_frac, nullary)]
279
leaves = normalize_prob_list(leaves)
280
281
internal = normalize_prob_list(internal)
282
283
return random_expr_helper(size, internal, leaves, verbose)
284
285
286
###################################################################
287
### Test the ordering of operands #################################
288
###################################################################
289
290
def assert_strict_weak_order(a,b,c, cmp_func):
291
r"""
292
Checks that ``cmp_func`` is a strict weak order.
293
294
A strict weak order is a binary relation ``<`` such that
295
296
* For all `x`, it is not the case that `x < x` (irreflexivity).
297
298
* For all `x\not=y`, if `x < y` then it is not the case that `y <
299
x` (asymmetric).
300
301
* For all `x`, `y`, and `z`, if `x < y` and `y < z` then `x < z`
302
(transitivity).
303
304
* For all `x`, `y`, and `z`, if x is incomparable with `y`, and
305
`y` is incomparable with `z`, then `x` is incomparable with `z`
306
(transitivity of equivalence).
307
308
INPUT:
309
310
- ``a``, ``b``, ``c`` -- anything that can be compared by ``cmp_func``.
311
312
- ``cmp_func`` -- function of two arguments that returns their
313
comparison (i.e. either ``True`` or ``False``).
314
315
OUTPUT:
316
317
Does not return anything. Raises a ``ValueError`` if ``cmp_func``
318
is not a strict weak order on the three given elements.
319
320
REFERENCES:
321
322
http://en.wikipedia.org/wiki/Strict_weak_ordering
323
324
EXAMPLES:
325
326
The usual ordering of integers is a strict weak order::
327
328
sage: from sage.symbolic.random_tests import assert_strict_weak_order
329
sage: a, b, c = [ randint(-10,10) for i in range(0,3) ]
330
sage: assert_strict_weak_order(a,b,c, lambda x,y: x<y)
331
332
sage: x = [SR(unsigned_infinity), SR(oo), -SR(oo)]
333
sage: cmp = matrix(3,3)
334
sage: indices = list(CartesianProduct(range(0,3),range(0,3)))
335
sage: for i,j in CartesianProduct(range(0,3),range(0,3)):
336
... cmp[i,j] = x[i].__cmp__(x[j])
337
sage: cmp
338
[ 0 -1 -1]
339
[ 1 0 -1]
340
[ 1 1 0]
341
"""
342
from sage.matrix.constructor import matrix
343
from sage.combinat.cartesian_product import CartesianProduct
344
from sage.combinat.permutation import Permutations
345
x = (a,b,c)
346
cmp = matrix(3,3)
347
indices = list(CartesianProduct(range(0,3),range(0,3)))
348
for i,j in indices:
349
cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter
350
msg = 'The binary relation failed to be a strict weak order on the elements\n'
351
msg += ' a = '+str(a)+'\n'
352
msg += ' b = '+str(b)+'\n'
353
msg += ' c = '+str(c)+'\n'
354
msg += str(cmp)
355
356
for i in range(0,3): # irreflexivity
357
if cmp[i,i]: raise ValueError, msg
358
359
for i,j in indices: # asymmetric
360
if i==j: continue
361
#if x[i] == x[j]: continue
362
if cmp[i,j] and cmp[j,i]: raise ValueError, msg
363
364
for i,j,k in Permutations([0,1,2]): # transitivity
365
if cmp[i,j] and cmp[j,k] and not cmp[i,k]: raise ValueError, msg
366
367
def incomparable(i,j):
368
return (not cmp[i,j]) and (not cmp[j,i])
369
for i,j,k in Permutations([0,1,2]): # transitivity of equivalence
370
if incomparable(i,j) and incomparable(j,k) and not incomparable(i,k): raise ValueError, msg
371
372
def test_symbolic_expression_order(repetitions=100):
373
r"""
374
Tests whether the comparison of random symbolic expressions
375
satisfies the strict weak order axioms.
376
377
This is important because the C++ extension class uses
378
``std::sort()`` which requires a strict weak order. See also
379
:trac:`9880`.
380
381
EXAMPLES::
382
383
sage: from sage.symbolic.random_tests import test_symbolic_expression_order
384
sage: test_symbolic_expression_order(200)
385
sage: test_symbolic_expression_order(10000) # long time
386
"""
387
rnd_length = 50
388
nvars = 10
389
ncoeffs = 10
390
var_frac = 0.5
391
nullary_frac = 0.05
392
393
def coeff_generator():
394
return randint(-100,100)/randint(1,100)
395
396
def make_random_expr():
397
while True:
398
try:
399
return sage.symbolic.random_tests.random_expr(
400
rnd_length, nvars=nvars, ncoeffs=ncoeffs, var_frac=var_frac,
401
nullary_frac=nullary_frac, coeff_generator=coeff_generator,
402
internal=sage.symbolic.random_tests.fast_nodes)
403
except (ZeroDivisionError, ValueError):
404
pass
405
406
for rep in range(0, repetitions):
407
a = make_random_expr()
408
b = make_random_expr()
409
c = make_random_expr()
410
assert_strict_weak_order(a, b, c, lambda x,y: x._cmp_(y))
411
assert_strict_weak_order(a, b, c, lambda x,y: x._cmp_add(y))
412
assert_strict_weak_order(a, b, c, lambda x,y: x._cmp_mul(y))
413
414