# HG changeset patch
# User Burcin Erocal <burcin@erocal.org>
# Date 1306263152 -7200
# Node ID ceef058f2d756de71375e35a8464975eba83bb7d
# Parent 3de52b3e5a84b45ac98d6cdf48eb3d3ff9859e3e
trac 9240: fix evaluation of factorial
The evaluation of factorial() is defined by GiNaC, to get a numeric result for
any numeric input. This definition makes sense since GiNaC limits the numeric
evaluation function of factorial to accept positive integer arguments only.
Pynac extends this function to other values using the gamma function, without
changing the evaluation behavior. This led to several inconsistencies.
We now override the evaluation for factorial() in Sage to return exact values
when possible. For example factorial(1/2) -> 1/2*sqrt(pi).
This patch also adds doctests to py_tgamma() and py_factorial() and makes
py_tgamma() return numeric results in some corner cases.
diff --git a/sage/functions/other.py b/sage/functions/other.py
|
a
|
b
|
|
| 4 | 4 | from sage.symbolic.function import GinacFunction, BuiltinFunction |
| 5 | 5 | from sage.symbolic.expression import Expression |
| 6 | 6 | from sage.symbolic.pynac import register_symbol, symbol_table |
| | 7 | from sage.symbolic.pynac import py_factorial_py |
| 7 | 8 | from sage.libs.pari.gen import pari |
| 8 | 9 | from sage.symbolic.all import SR |
| 9 | 10 | from sage.rings.all import Integer, Rational, RealField, CC, RR, \ |
| … |
… |
|
| 733 | 734 | 120 |
| 734 | 735 | sage: gamma(1/2) |
| 735 | 736 | sqrt(pi) |
| | 737 | sage: gamma(-4/3) |
| | 738 | gamma(-4/3) |
| 736 | 739 | sage: gamma(-1) |
| 737 | 740 | Infinity |
| | 741 | sage: gamma(0) |
| | 742 | Infinity |
| 738 | 743 | |
| 739 | 744 | :: |
| 740 | 745 | |
| … |
… |
|
| 1045 | 1050 | """ |
| 1046 | 1051 | GinacFunction.__init__(self, "factorial", latex_name='{\\rm factorial}', |
| 1047 | 1052 | conversions=dict(maxima='factorial', mathematica='Factorial')) |
| 1048 | | |
| | 1053 | |
| | 1054 | def _eval_(self, x): |
| | 1055 | """ |
| | 1056 | Evaluate the factorial function. |
| | 1057 | |
| | 1058 | Note that this method overrides the eval method defined in GiNaC |
| | 1059 | which calls numeric evaluation on all numeric input. We preserve |
| | 1060 | exact results if the input is a rational number. |
| | 1061 | |
| | 1062 | EXAMPLES:: |
| | 1063 | |
| | 1064 | sage: k = var('k') |
| | 1065 | sage: k.factorial() |
| | 1066 | factorial(k) |
| | 1067 | sage: SR(1/2).factorial() |
| | 1068 | 1/2*sqrt(pi) |
| | 1069 | sage: SR(3/4).factorial() |
| | 1070 | gamma(7/4) |
| | 1071 | sage: SR(5).factorial() |
| | 1072 | 120 |
| | 1073 | sage: SR(3245908723049857203948572398475r).factorial() |
| | 1074 | factorial(3245908723049857203948572398475L) |
| | 1075 | sage: SR(3245908723049857203948572398475).factorial() |
| | 1076 | factorial(3245908723049857203948572398475) |
| | 1077 | """ |
| | 1078 | if isinstance(x, Rational): |
| | 1079 | return gamma(x+1) |
| | 1080 | elif isinstance(x, (Integer, int)) or \ |
| | 1081 | (not isinstance(x, Expression) and is_inexact(x)): |
| | 1082 | return py_factorial_py(x) |
| | 1083 | |
| | 1084 | return None |
| | 1085 | |
| 1049 | 1086 | factorial = Function_factorial() |
| 1050 | 1087 | |
| 1051 | 1088 | class Function_binomial(GinacFunction): |
diff --git a/sage/rings/integer.pyx b/sage/rings/integer.pyx
|
a
|
b
|
|
| 3696 | 3696 | def factorial(self): |
| 3697 | 3697 | r""" |
| 3698 | 3698 | Return the factorial `n! = 1 \cdot 2 \cdot 3 \cdots n`. |
| 3699 | | Self must fit in an ``unsigned long int``. |
| | 3699 | |
| | 3700 | If the input does not fit in an ``unsigned long int`` a symbolic |
| | 3701 | expression is returned. |
| 3700 | 3702 | |
| 3701 | 3703 | EXAMPLES:: |
| 3702 | 3704 | |
| … |
… |
|
| 3709 | 3711 | 4 24 |
| 3710 | 3712 | 5 120 |
| 3711 | 3713 | 6 720 |
| | 3714 | sage: 234234209384023842034.factorial() |
| | 3715 | factorial(234234209384023842034) |
| 3712 | 3716 | """ |
| 3713 | 3717 | if mpz_sgn(self.value) < 0: |
| 3714 | 3718 | raise ValueError, "factorial -- self = (%s) must be nonnegative"%self |
| 3715 | 3719 | |
| 3716 | 3720 | if not mpz_fits_uint_p(self.value): |
| 3717 | | raise ValueError, "factorial not implemented for n >= 2^32.\nThis is probably OK, since the answer would have billions of digits." |
| | 3721 | from sage.functions.all import factorial |
| | 3722 | return factorial(self, hold=True) |
| 3718 | 3723 | |
| 3719 | 3724 | cdef Integer z = PY_NEW(Integer) |
| 3720 | 3725 | |
diff --git a/sage/symbolic/expression.pyx b/sage/symbolic/expression.pyx
|
a
|
b
|
|
| 6706 | 6706 | Check that the problem with applying full_simplify() to gamma functions (Trac 9240) |
| 6707 | 6707 | has been fixed:: |
| 6708 | 6708 | |
| 6709 | | sage: gamma(37) |
| 6710 | | 371993326789901217467999448150835200000000 |
| 6711 | | sage: gamma(0) |
| 6712 | | Infinity |
| 6713 | | sage: gamma(-4) |
| 6714 | | Infinity |
| 6715 | | sage: gamma(-4/3) |
| 6716 | | gamma(-4/3) |
| | 6709 | sage: gamma(1/3) |
| | 6710 | gamma(1/3) |
| 6717 | 6711 | sage: gamma(1/3).full_simplify() |
| 6718 | 6712 | gamma(1/3) |
| | 6713 | sage: gamma(4/3) |
| | 6714 | gamma(4/3) |
| 6719 | 6715 | sage: gamma(4/3).full_simplify() |
| 6720 | 6716 | 1/3*gamma(1/3) |
| 6721 | 6717 | |
diff --git a/sage/symbolic/function.pyx b/sage/symbolic/function.pyx
|
a
|
b
|
|
| 127 | 127 | cdef _register_function(self): |
| 128 | 128 | cdef GFunctionOpt opt |
| 129 | 129 | opt = g_function_options_args(self._name, self._nargs) |
| 130 | | opt.set_python_func() |
| 131 | 130 | |
| 132 | 131 | if hasattr(self, '_eval_'): |
| 133 | 132 | opt.eval_func(self) |
diff --git a/sage/symbolic/pynac.pyx b/sage/symbolic/pynac.pyx
|
a
|
b
|
|
| 1030 | 1030 | # SPECIAL FUNCTIONS |
| 1031 | 1031 | ################################################################# |
| 1032 | 1032 | cdef public object py_tgamma(object x) except +: |
| | 1033 | """ |
| | 1034 | The gamma function exported to pynac. |
| | 1035 | |
| | 1036 | TESTS:: |
| | 1037 | |
| | 1038 | sage: from sage.symbolic.pynac import py_tgamma_for_doctests as py_tgamma |
| | 1039 | sage: py_tgamma(4) |
| | 1040 | 6 |
| | 1041 | sage: py_tgamma(1/2) |
| | 1042 | 1.77245385090552 |
| | 1043 | """ |
| | 1044 | if PY_TYPE_CHECK_EXACT(x, int) or PY_TYPE_CHECK_EXACT(x, long): |
| | 1045 | x = float(x) |
| 1033 | 1046 | if PY_TYPE_CHECK_EXACT(x, float): |
| 1034 | 1047 | return sage_tgammal(x) |
| 1035 | | #FIXME: complex |
| | 1048 | |
| | 1049 | # try / except blocks are faster than |
| | 1050 | # if hasattr(x, 'gamma') |
| 1036 | 1051 | try: |
| 1037 | | return x.gamma() |
| | 1052 | res = x.gamma() |
| 1038 | 1053 | except AttributeError: |
| 1039 | 1054 | return CC(x).gamma() |
| 1040 | 1055 | |
| | 1056 | # the result should be numeric, however the gamma method of rationals may |
| | 1057 | # return symbolic expressions. for example (1/2).gamma() -> sqrt(pi). |
| | 1058 | if isinstance(res, Expression): |
| | 1059 | try: |
| | 1060 | return RR(res) |
| | 1061 | except ValueError: |
| | 1062 | return CC(res) |
| | 1063 | return res |
| | 1064 | |
| | 1065 | def py_tgamma_for_doctests(x): |
| | 1066 | """ |
| | 1067 | This function is for testing py_tgamma(). |
| | 1068 | |
| | 1069 | TESTS:: |
| | 1070 | |
| | 1071 | sage: from sage.symbolic.pynac import py_tgamma_for_doctests |
| | 1072 | sage: py_tgamma_for_doctests(3) |
| | 1073 | 2 |
| | 1074 | """ |
| | 1075 | return py_tgamma(x) |
| | 1076 | |
| 1041 | 1077 | from sage.rings.arith import factorial |
| 1042 | 1078 | cdef public object py_factorial(object x) except +: |
| | 1079 | """ |
| | 1080 | The factorial function exported to pynac. |
| | 1081 | |
| | 1082 | TESTS:: |
| | 1083 | |
| | 1084 | sage: from sage.symbolic.pynac import py_factorial_py as py_factorial |
| | 1085 | sage: py_factorial(4) |
| | 1086 | 24 |
| | 1087 | sage: py_factorial(-2/3) |
| | 1088 | 2.67893853470775 |
| | 1089 | """ |
| 1043 | 1090 | # factorial(x) is only defined for non-negative integers x |
| 1044 | 1091 | # so we first test if x can be coerced into ZZ and is non-negative. |
| 1045 | 1092 | # If this is not the case then we return the symbolic expression gamma(x+1) |
| … |
… |
|
| 1055 | 1102 | else: |
| 1056 | 1103 | return py_tgamma(x+1) |
| 1057 | 1104 | |
| | 1105 | def py_factorial_py(x): |
| | 1106 | """ |
| | 1107 | This function is a python wrapper around py_factorial(). This wrapper |
| | 1108 | is needed when we override the eval() method for GiNaC's factorial |
| | 1109 | function in sage.functions.other.Function_factorial. |
| | 1110 | |
| | 1111 | TESTS:: |
| | 1112 | |
| | 1113 | sage: from sage.symbolic.pynac import py_factorial_py |
| | 1114 | sage: py_factorial_py(3) |
| | 1115 | 6 |
| | 1116 | """ |
| | 1117 | return py_factorial(x) |
| | 1118 | |
| 1058 | 1119 | cdef public object py_doublefactorial(object x) except +: |
| 1059 | 1120 | n = Integer(x) |
| 1060 | 1121 | if n < -1: |
| … |
… |
|
| 1467 | 1528 | gsl_sf_lngamma_complex_e(PyComplex_RealAsDouble(x),PyComplex_ImagAsDouble(x), &lnr, &arg) |
| 1468 | 1529 | res = gsl_complex_polar(lnr.val, arg.val) |
| 1469 | 1530 | return PyComplex_FromDoubles(res.dat[0], res.dat[1]) |
| 1470 | | elif hasattr(x, 'log_gamma'): |
| 1471 | | return x.log_gamma() |
| 1472 | 1531 | elif isinstance(x, Integer): |
| 1473 | 1532 | return x.gamma().log().n() |
| 1474 | | elif hasattr(x, 'gamma'): |
| | 1533 | |
| | 1534 | # try / except blocks are faster than |
| | 1535 | # if hasattr(x, 'log_gamma') |
| | 1536 | try: |
| | 1537 | return x.log_gamma() |
| | 1538 | except AttributeError: |
| | 1539 | pass |
| | 1540 | |
| | 1541 | try: |
| 1475 | 1542 | return x.gamma().log() |
| | 1543 | except AttributeError: |
| | 1544 | pass |
| | 1545 | |
| 1476 | 1546 | return CC(x).gamma().log() |
| 1477 | 1547 | |
| 1478 | 1548 | def py_lgamma_for_doctests(x): |