# HG changeset patch
# User Carl Witty
# Date 1243191638 25200
# Node ID df6569aa17054d6a2df0231c8ab9f4f171f539b3
# Parent c9e8cd4071ea2c6a9d798969aee539f1eb1ef78b
Fix a few typos and bugs in the new symbolics.
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/benchmark.py
--- a/sage/symbolic/benchmark.py Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/benchmark.py Sun May 24 12:00:38 2009 -0700
@@ -4,7 +4,7 @@
Tests that will take a long time if something is wrong, but be very
quick otherwise. See http://wiki.sagemath.org/symbench. The
parameters chosen below are such that with pynac most of these take
-well less than a second, but would not even feasible using Sage's
+well less than a second, but would not even be feasible using Sage's
Maxima-based symbolics.
Problem R1::
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/callable.py
--- a/sage/symbolic/callable.py Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/callable.py Sun May 24 12:00:38 2009 -0700
@@ -42,7 +42,7 @@
#########################################################################################
def is_CallableSymbolicExpressionRing(x):
"""
- Return True if x is a callable symbolic expression.
+ Return True if x is a callable symbolic expression ring.
INPUT:
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/constants.py
--- a/sage/symbolic/constants.py Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/constants.py Sun May 24 12:00:38 2009 -0700
@@ -471,8 +471,10 @@
def _singular_(self, singular):
"""
Returns the constant as a string in Singular. Since Singular
- does not have floating point numbers, we simply return the
- constant as a string.
+ does not always support floating point numbers, we simply
+ return the constant as a string. (Singular allows floating point
+ numbers if the current ring has floating point coefficients,
+ but not otherwise.)
EXAMPLES::
@@ -825,7 +827,7 @@
sage: loads(dumps(golden_ratio))
golden_ratio
"""
- conversions = dict(mathematica='N[(1+Sqrt[5])/2]', gp='(1+sqrt(5))/2',
+ conversions = dict(mathematica='(1+Sqrt[5])/2', gp='(1+sqrt(5))/2',
maple='(1+sqrt(5))/2', maxima='(1+sqrt(5))/2',
pari='(1+sqrt(5))/2', octave='(1+sqrt(5))/2',
kash='(1+Sqrt(5))/2')
@@ -937,7 +939,7 @@
sage: loads(dumps(log2))
log2
"""
- conversions = dict(mathematica='N[Log[2]]', kash='Log(2)',
+ conversions = dict(mathematica='Log[2]', kash='Log(2)',
maple='log(2)', maxima='log(2)', gp='log(2)',
pari='log(2)', octave='log(2)')
Constant.__init__(self, name, conversions=conversions,
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/expression_conversions.py
--- a/sage/symbolic/expression_conversions.py Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/expression_conversions.py Sun May 24 12:00:38 2009 -0700
@@ -3,7 +3,7 @@
This module provides routines for converting new symbolic expressions
to other types. Primarily, it provides a class :class:`Converter`
-which will walk to expression tree and make calls to methods
+which will walk the expression tree and make calls to methods
overridden by subclasses.
"""
###############################################################################
@@ -24,6 +24,17 @@
GaussianField = I.pyobject().parent()
class FakeExpression(object):
+ r"""
+ Pynac represents `x/y` as `xy^{-1}`. Often, tree-walkers would prefer
+ to see divisions instead of multiplications and negative exponents.
+ To allow for this (since Pynac internally doesn't have division at all),
+ there is a possibility to pass use_fake_div=True; this will rewrite
+ an Expression into a mixture of Expression and FakeExpression nodes,
+ where the FakeExpression nodes are used to represent divisions.
+ These nodes are intended to act sufficiently like Expression nodes
+ that tree-walkers won't care about the difference.
+ """
+
def __init__(self, operands, operator):
"""
EXAMPLES::
@@ -173,7 +184,8 @@
...
NotImplementedError: derivative
- We can set a default value for by setting the ``ex`` attribute::
+ We can set a default value for the argument by setting
+ the ``ex`` attribute::
sage: c.ex = SR(2)
sage: c()
@@ -1428,7 +1440,7 @@
class SubstituteFunction(Converter):
def __init__(self, ex, original, new):
"""
- A class that walks the tree and replaces on occurrence of a
+ A class that walks the tree and replaces occurrences of a
function with another.
EXAMPLES::
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/function.pyx
--- a/sage/symbolic/function.pyx Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/function.pyx Sun May 24 12:00:38 2009 -0700
@@ -814,11 +814,11 @@
def __call__(self, x, hold=False):
"""
- Evaluates this function at *x*. First, it checks to see if *x*
- is a float in which case it calls :meth:`_approx_`. Next,
- it checks to see if *x* has an attribute with the same name
- as. If both of those fail, then it returns the symbolic version
- provided by :class:`SFunction`.
+ Evaluates this function at *x*. First, it checks to see if
+ *x* is a float in which case it calls :meth:`_approx_`. Next,
+ it checks to see if *x* has an attribute with the same name as
+ this function. If both of those fail, then it returns the
+ symbolic version provided by :class:`SFunction`.
EXAMPLES::
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/pynac.pyx
--- a/sage/symbolic/pynac.pyx Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/pynac.pyx Sun May 24 12:00:38 2009 -0700
@@ -48,7 +48,7 @@
cdef public object ex_to_pyExpression(GEx juice):
"""
- Convert given GiNaC::ex object to a python Expression class.
+ Convert given GiNaC::ex object to a python Expression instance.
Used to pass parameters to custom power and series functions.
"""
@@ -89,7 +89,7 @@
Converts a std::multiset to a PyTuple.
Used to pass a list of parameter numbers with respect to which a function
- is differentiaed to the printing functions py_print_fderivative and
+ is differentiated to the printing functions py_print_fderivative and
py_latex_fderivative.
"""
cdef GParamSetIter itr = s.begin()
@@ -290,8 +290,8 @@
sage: from sage.symbolic.pynac import py_print_function_pystring
sage: from sage.symbolic.function import function, get_sfunction_from_serial, get_ginac_serial
- sage: var('x,y,z',ns=1)
- (x, y, z)
+ sage: var('x,y,z')
+ (x, y, z)
sage: foo = function('foo', 2)
sage: for i in range(get_ginac_serial(), get_ginac_serial()+50):
... if get_sfunction_from_serial(i) == foo: break
@@ -352,8 +352,8 @@
sage: from sage.symbolic.pynac import py_latex_function_pystring
sage: from sage.symbolic.function import function, get_sfunction_from_serial, get_ginac_serial
- sage: var('x,y,z',ns=1)
- (x, y, z)
+ sage: var('x,y,z')
+ (x, y, z)
sage: foo = function('foo', 2)
sage: for i in range(get_ginac_serial(), get_ginac_serial()+50):
... if get_sfunction_from_serial(i) == foo: break
@@ -1178,7 +1178,7 @@
return x.zeta()
except AttributeError:
pass
- # zeta() evaluates it's argument before calling this, so we should never
+ # zeta() evaluates its argument before calling this, so we should never
# end up here. Precision is not a problem here, since zeta() passes that
# on when it calls evalf() on the argument.
raise RuntimeError, "py_zeta() received non evaluated argument"
@@ -1213,7 +1213,7 @@
return x.exp()
except AttributeError:
pass
- # exp() evaluates it's argument before calling this, so we should never
+ # exp() evaluates its argument before calling this, so we should never
# end up here. Precision is not a problem here, since exp() passes that
# on when it calls evalf() on the argument.
raise RuntimeError, "py_exp() received non evaluated argument"
@@ -1248,7 +1248,7 @@
return x.log()
except AttributeError:
pass
- # log() evaluates it's argument before calling this, so we should never
+ # log() evaluates its argument before calling this, so we should never
# end up here. Precision is not a problem here, since log() passes that
# on when it calls evalf() on the argument.
raise RuntimeError, "py_log() received non evaluated argument"
diff -r c9e8cd4071ea -r df6569aa1705 sage/symbolic/random_tests.py
--- a/sage/symbolic/random_tests.py Thu May 21 01:35:50 2009 -0700
+++ b/sage/symbolic/random_tests.py Sun May 24 12:00:38 2009 -0700
@@ -6,10 +6,16 @@
import sage.symbolic.pynac
from sage.symbolic.constants import *
+# For creating simple expressions
+
fast_binary = [(0.4, operator.add), (0.1, operator.sub), (0.5, operator.mul)]
fast_unary = [(0.8, operator.neg), (0.2, operator.abs)]
fast_nodes = [(0.9, fast_binary, 2), (0.1, fast_unary, 1)]
+# For creating expressions with the full power of Pynac's simple expression
+# subset (with no quantifiers/operators; that is, no derivatives, integrals,
+# etc.)
+
full_binary = [(0.3, operator.add), (0.1, operator.sub), (0.3, operator.mul), (0.2, operator.div), (0.1, operator.pow)]
full_unary = [(0.8, operator.neg), (0.2, operator.inv)]
full_functions = [(1.0, f, f.number_of_arguments()) for f in sage.symbolic.pynac.symbol_table['functions'].values() if f.number_of_arguments() > 0 and 'elliptic' not in str(f) and 'dickman_rho' not in str(f)]
@@ -17,6 +23,36 @@
full_internal = [(0.6, full_binary, 2), (0.2, full_unary, 1), (0.2, full_functions)]
def normalize_prob_list(pl, extra=()):
+ r"""
+ INPUT:
+
+ - ``pl`` - A list of tuples, where the first element of each tuple is
+ a floating-point number (representing a relative probability). The
+ second element of each tuple may be a list or any other kind of object.
+
+ - ``extra`` - A tuple which is to be appended to every tuple in ``pl``.
+
+ This function takes such a list of tuples (a "probability list") and
+ normalizes the probabilities so that they sum to one. If any of the
+ values are lists, then those lists are first normalized; then
+ the probabilities in the list are multiplied by the main probability
+ and the sublist is merged with the main list.
+
+ For example, suppose we want to select between group A and group B with
+ 50% probability each. Then within group A, we select A1 or A2 with 50%
+ probability each (so the overall probability of selecting A1 is 25%);
+ and within group B, we select B1, B2, or B3 with probabilities in
+ a 1:2:2 ratio.
+
+ EXAMPLES::
+
+ sage: from sage.symbolic.random_tests import *
+ sage: A = [(0.5, 'A1'), (0.5, 'A2')]
+ sage: B = [(1, 'B1'), (2, 'B2'), (2, 'B3')]
+ sage: top = [(50, A, 'Group A'), (50, B, 'Group B')]
+ sage: normalize_prob_list(top)
+ [(0.250000000000000, 'A1', 'Group A'), (0.250000000000000, 'A2', 'Group A'), (0.10000000000000001, 'B1', 'Group B'), (0.20000000000000001, 'B2', 'Group B'), (0.20000000000000001, 'B3', 'Group B')]
+ """
if len(pl) == 0:
return pl
result = []
@@ -37,6 +73,33 @@
return result
def choose_from_prob_list(lst):
+ r"""
+ INPUT:
+
+ - ``lst`` - A list of tuples, where the first element of each tuple
+ is a nonnegative float (a probability), and the probabilities sum
+ to one.
+
+ OUTPUT:
+
+ A tuple randomly selected from the list according to the given
+ probabilities.
+
+ EXAMPLES::
+
+ sage: from sage.symbolic.random_tests import *
+ sage: v = [(0.1, False), (0.9, True)]
+ sage: choose_from_prob_list(v)
+ (0.900000000000000, True)
+ sage: true_count = 0
+ sage: for _ in range(10000):
+ ... if choose_from_prob_list(v)[1]:
+ ... true_count += 1
+ sage: true_count
+ 9033
+ sage: true_count - (10000 * 9/10)
+ 33
+ """
r = random()
for i in range(len(lst)-1):
if r < lst[i][0]:
@@ -46,9 +109,28 @@
def random_integer_vector(n, length):
r"""
+ Give a random list of length *length*, consisting of nonnegative
+ integers that sum to *n*.
+
This is an approximation to IntegerVectors(n, length).random_element().
That gives values uniformly at random, but might be slow; this
- routine is not uniform, but should always be fast.
+ routine is not uniform, but should always be fast.
+
+ (This routine is uniform if *length* is 1 or 2; for longer vectors,
+ we prefer approximately balanced vectors, where all the values
+ are around `n/{length}`.)
+
+ EXAMPLES::
+
+ sage: from sage.symbolic.random_tests import *
+ sage: random_integer_vector(100, 2)
+ [11, 89]
+ sage: random_integer_vector(100, 2)
+ [51, 49]
+ sage: random_integer_vector(100, 2)
+ [4, 96]
+ sage: random_integer_vector(10000, 20)
+ [332, 529, 185, 738, 82, 964, 596, 892, 732, 134, 834, 765, 398, 608, 358, 300, 652, 249, 586, 66]
"""
if length == 0:
return []
@@ -62,6 +144,24 @@
return [v] + random_integer_vector(n-v, length-1)
def random_expr_helper(n_nodes, internal, leaves, verbose):
+ r"""
+ Produce a random symbolic expression of size *n_nodes* (or slightly
+ larger). Internal nodes are selected from the *internal* probability
+ list; leaves are selected from *leaves*. If *verbose* is True,
+ then a message is printed before creating an internal node.
+
+ EXAMPLES::
+
+ sage: from sage.symbolic.random_tests import *
+ sage: random_expr_helper(9, [(0.5, operator.add, 2), (0.5, operator.neg, 1)], [(0.5, 1), (0.5, x)], True)
+ About to apply to [1, x]
+ About to apply to [x, x + 1]
+ About to apply to [1]
+ About to apply to [-1]
+ About to apply to [1]
+ About to apply to [2*x + 1, -1]
+ 2*x
+ """
if n_nodes == 1:
return choose_from_prob_list(leaves)[1]
else:
@@ -77,12 +177,43 @@
print "About to apply %r to %r" % (r[1], children)
return r[1](*children)
-def random_expr(size, nvars=1, nconsts=None, var_frac=0.5, internal=full_internal, nullary=full_nullary, nullary_frac=0.2, const_generator=QQ.random_element, verbose=False):
+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):
+ r"""
+ Produce a random symbolic expression of the given size. By
+ default, the expression involves (at most) one variable, an arbitrary
+ number of coefficients, and all of the symbolic functions and constants
+ (from the probability lists full_internal and full_nullary). It is
+ possible to adjust the ratio of leaves between symbolic constants,
+ variables, and coefficients (var_frac gives the fraction of variables,
+ and nullary_frac the fraction of symbolic constants; the remaining
+ leaves are coefficients).
+
+ The actual mix of symbolic constants and internal nodes can be modified
+ by specifying different probability lists.
+
+ To use a different type for coefficients, you can specify
+ coeff_generator, which should be a function that will return
+ a random coefficient every time it is called.
+
+ This function will often raise an error because it tries to create
+ an erroneous expression (such as a division by zero).
+
+ EXAMPLES::
+
+ sage: from sage.symbolic.random_tests import *
+ sage: random_expr(50, nvars=3, coeff_generator=CDF.random_element)
+ sinh(arcsech(-coth(v2)/csc(-(0.615863165633 + 0.879368031485*I)*v1^2*v3) - gamma(pi) + csch(-(0.708874026302 - 0.954135400334*I)*v3)))^coth(-cosh(-arcsinh((0.0574954975369 - 0.917809644424*I)*(v2^2 - 0.723896589334 - 0.799038508886*I)*v1))/(-(1.04308121136 - 1.20890075541*I)*v2 - (1.45598591076 + 0.129560576376*I)*e))
+ sage: random_expr(5, verbose=True)
+ About to apply exp to [pi]
+ About to apply to [e^pi, 1]
+ About to apply cosh to [e^pi]
+ cosh(e^pi)
+ """
vars = [(1.0, sage.calculus.calculus.var('v%d' % (n+1))) for n in range(nvars)]
- if nconsts is None:
- nconsts = size
- consts = [(1.0, const_generator()) for _ in range(nconsts)]
- leaves = [(var_frac, vars), (1.0 - var_frac - nullary_frac, consts), (nullary_frac, nullary)]
+ if ncoeffs is None:
+ ncoeffs = size
+ coeffs = [(1.0, coeff_generator()) for _ in range(ncoeffs)]
+ leaves = [(var_frac, vars), (1.0 - var_frac - nullary_frac, coeffs), (nullary_frac, nullary)]
leaves = normalize_prob_list(leaves)
internal = normalize_prob_list(internal)