Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/ext/fast_eval.pyx
4057 views
1
r"""
2
Fast Numerical Evaluation.
3
4
For many applications such as numerical integration, differential
5
equation approximation, plotting a 3d surface, optimization problems,
6
monte-carlo simulations, etc., one wishes to pass around and evaluate
7
a single algebraic expression many, many times at various floating
8
point values. Doing this via recursive calls over a python
9
representation of the object (even if Maxima or other outside packages
10
are not involved) is extremely inefficient.
11
12
Up until now the solution has been to use lambda expressions, but this
13
is neither intuitive, Sage-like, nor efficient (compared to operating
14
on raw C doubles). This module provides a representation of algebraic
15
expression in Reverse Polish Notation, and provides an efficient
16
interpreter on C double values as a callable python object. It does
17
what it can in C, and will call out to Python if necessary.
18
19
Essential to the understanding of this class is the distinction
20
between symbolic expressions and callable symbolic expressions (where
21
the latter binds argument names to argument positions). The
22
\code{*vars} parameter passed around encapsulates this information.
23
24
See the function \code{fast_float(f, *vars)} to create a fast-callable
25
version of f.
26
27
NOTE: Sage temporarily has two implementations of this functionality;
28
one in this file, which will probably be deprecated soon, and one in
29
fast_callable.pyx. The following instructions are for the old
30
implementation; you probably want to be looking at fast_callable.pyx
31
instead.
32
33
To provide this interface for a class, implement
34
\code{_fast_float_(self, *vars)}. The basic building blocks are
35
provided by the functions \code{fast_float_constant} (returns a
36
constant function), \code{fast_float_arg} (selects the $n$-th value
37
when called with $\ge n$ arguments), and \code{fast_float_func} which
38
wraps a callable Python function. These may be combined with the
39
standard Python arithmetic operators, and support many of the basic
40
math functions such sqrt, exp, and trig functions.
41
42
EXAMPLES:
43
sage: from sage.ext.fast_eval import fast_float
44
sage: f = fast_float(sqrt(x^7+1), 'x', old=True)
45
sage: f(1)
46
1.4142135623730951
47
sage: f.op_list()
48
['load 0', 'push 7.0', 'pow', 'push 1.0', 'add', 'call sqrt(1)']
49
50
To interpret that last line, we load argument 0 ('x' in this case) onto
51
the stack, push the constant 2.0 onto the stack, call the pow function
52
(which takes 2 arguments from the stack), push the constant 1.0, add the
53
top two arguments of the stack, and then call sqrt.
54
55
Here we take sin of the first argument and add it to f:
56
sage: from sage.ext.fast_eval import fast_float_arg
57
sage: g = fast_float_arg(0).sin()
58
sage: (f+g).op_list()
59
['load 0', 'push 7.0', 'pow', 'push 1.0', 'add', 'call sqrt(1)', 'load 0', 'call sin(1)', 'add']
60
61
TESTS:
62
This used to segfault because of an assumption that assigning None to a
63
variable would raise a TypeError:
64
sage: from sage.ext.fast_eval import fast_float_arg, fast_float
65
sage: fast_float_arg(0)+None
66
Traceback (most recent call last):
67
...
68
TypeError
69
70
AUTHOR:
71
-- Robert Bradshaw (2008-10): Initial version
72
"""
73
74
75
#*****************************************************************************
76
# Copyright (C) 2008 Robert Bradshaw <[email protected]>
77
#
78
# Distributed under the terms of the GNU General Public License (GPL)
79
#
80
# This code is distributed in the hope that it will be useful,
81
# but WITHOUT ANY WARRANTY; without even the implied warranty of
82
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
83
# General Public License for more details.
84
#
85
# The full text of the GPL is available at:
86
#
87
# http://www.gnu.org/licenses/
88
#*****************************************************************************
89
90
from sage.ext.fast_callable import fast_callable, Wrapper
91
92
include "stdsage.pxi"
93
94
cdef extern from "Python.h":
95
int PyInt_AS_LONG(PyObject*)
96
PyObject* PyTuple_New(Py_ssize_t size)
97
PyObject* PyTuple_GET_ITEM(PyObject* t, Py_ssize_t index)
98
void PyTuple_SET_ITEM(PyObject* t, Py_ssize_t index, PyObject* item)
99
object PyObject_CallObject(PyObject* func, PyObject* args)
100
PyObject* PyFloat_FromDouble(double d)
101
void Py_DECREF(PyObject *)
102
103
cdef extern from "math.h":
104
double sqrt(double)
105
double pow(double, double)
106
107
double ceil(double)
108
double floor(double)
109
110
double sin(double)
111
double cos(double)
112
double tan(double)
113
114
double asin(double)
115
double acos(double)
116
double atan(double)
117
double atan2(double, double)
118
119
double sinh(double)
120
double cosh(double)
121
double tanh(double)
122
123
double asinh(double)
124
double acosh(double)
125
double atanh(double)
126
127
double exp(double)
128
double log(double)
129
double log10(double)
130
double log2_ "log2"(double)
131
132
133
# This is only needed on Cygwin since log2 is a macro.
134
# If we don't do this the cygwin GCC gets very confused.
135
cdef inline double log2(double x):
136
return log2_(x)
137
138
cdef extern from *:
139
void* memcpy(void* dst, void* src, size_t len)
140
141
cdef inline int max(int a, int b):
142
return a if a > b else b
143
144
cdef inline int min(int a, int b):
145
return a if a < b else b
146
147
cdef enum:
148
# stack
149
LOAD_ARG # push input argument n onto the stack
150
PUSH_CONST
151
POP
152
POP_N
153
DUP
154
155
# basic arithmetic
156
ADD
157
SUB
158
MUL
159
DIV
160
NEG
161
ABS
162
INVERT
163
POW
164
165
# basic comparison
166
LT
167
LE
168
EQ
169
NE
170
GT
171
GE
172
173
# functional
174
ONE_ARG_FUNC
175
TWO_ARG_FUNC
176
PY_FUNC
177
178
179
# These two dictionaries are for printable and machine independent representation.
180
181
op_names = {
182
LOAD_ARG: 'load',
183
PUSH_CONST: 'push',
184
POP: 'pop',
185
POP_N: 'popn',
186
DUP: 'dup',
187
188
ADD: 'add',
189
SUB: 'sub',
190
MUL: 'mul',
191
DIV: 'div',
192
NEG: 'neg',
193
ABS: 'abs',
194
INVERT: 'invert',
195
POW: 'pow',
196
197
198
LT: 'lt',
199
LE: 'le',
200
EQ: 'eq',
201
NE: 'ne',
202
GT: 'gt',
203
GE: 'ge',
204
205
206
ONE_ARG_FUNC: 'call',
207
TWO_ARG_FUNC: 'call',
208
PY_FUNC: 'py_call',
209
}
210
211
cfunc_names = {
212
<size_t>&sqrt: 'sqrt',
213
<size_t>&pow: 'pow',
214
215
<size_t>&ceil: 'ceil',
216
<size_t>&floor: 'floor',
217
218
<size_t>&sin: 'sin',
219
<size_t>&cos: 'cos',
220
<size_t>&tan: 'tan',
221
222
<size_t>&asin: 'asin',
223
<size_t>&atan: 'atan',
224
<size_t>&atan2: 'atan2',
225
226
<size_t>&sinh: 'sinh',
227
<size_t>&cosh: 'cosh',
228
<size_t>&tanh: 'tanh',
229
230
<size_t>&asinh: 'asinh',
231
<size_t>&acosh: 'acosh',
232
<size_t>&atanh: 'atanh',
233
234
<size_t>&exp: 'exp',
235
<size_t>&log: 'log',
236
<size_t>&log2: 'log2',
237
<size_t>&log10: 'log10',
238
239
}
240
241
cdef reverse_map(m):
242
r = {}
243
for key, value in m.iteritems():
244
r[value] = key
245
return r
246
247
# With all the functionality around the op struct, perhaps there should be
248
# a wrapper class, though we still wish to operate on pure structs for speed.
249
250
cdef op_to_string(fast_double_op op):
251
s = op_names[op.type]
252
if op.type in [LOAD_ARG, POP_N]:
253
s += " %s" % op.params.n
254
elif op.type == PUSH_CONST:
255
s += " %s" % op.params.c
256
elif op.type in [ONE_ARG_FUNC, TWO_ARG_FUNC]:
257
try:
258
cname = cfunc_names[<size_t>op.params.func]
259
except KeyError:
260
cname = "0x%x" % <size_t>op.params.func
261
s += " %s(%s)" % (cname, 1 if op.type == ONE_ARG_FUNC else 2)
262
elif op.type == PY_FUNC:
263
n, func = <object>(op.params.func)
264
s += " %s(%s)" % (func, n)
265
return s
266
267
cdef op_to_tuple(fast_double_op op):
268
s = op_names[op.type]
269
if op.type in [LOAD_ARG, POP_N]:
270
param = op.params.n
271
elif op.type == PUSH_CONST:
272
param = op.params.c
273
elif op.type in [ONE_ARG_FUNC, TWO_ARG_FUNC]:
274
param_count = 1 if op.type == ONE_ARG_FUNC else 2
275
try:
276
param = param_count, cfunc_names[<size_t>op.params.func]
277
except KeyError:
278
raise ValueError, "Unknown C function: 0x%x" % <size_t>op.params.func
279
elif op.type == PY_FUNC:
280
param = <object>(op.params.func)
281
else:
282
param = None
283
if param is None:
284
return (s,)
285
else:
286
return s, param
287
288
def _unpickle_FastDoubleFunc(nargs, max_height, op_list):
289
cdef FastDoubleFunc self = PY_NEW(FastDoubleFunc)
290
self.nops = len(op_list)
291
self.nargs = nargs
292
self.max_height = max_height
293
self.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op) * self.nops)
294
self.allocate_stack()
295
cfunc_addresses = reverse_map(cfunc_names)
296
op_enums = reverse_map(op_names)
297
cdef size_t address
298
cdef int i = 0, type
299
for op in op_list:
300
self.ops[i].type = type = op_enums[op[0]]
301
if type in [LOAD_ARG, POP_N]:
302
self.ops[i].params.n = op[1]
303
elif type == PUSH_CONST:
304
self.ops[i].params.c = op[1]
305
elif type in [ONE_ARG_FUNC, TWO_ARG_FUNC]:
306
param_count, cfunc = op[1]
307
address = cfunc_addresses[cfunc]
308
self.ops[i].params.func = <void *>address
309
self.ops[i].type = ['', ONE_ARG_FUNC, TWO_ARG_FUNC][param_count]
310
elif type == PY_FUNC:
311
if self.py_funcs is None:
312
self.py_funcs = op[1]
313
else:
314
self.py_funcs = self.py_funcs + (op[1],)
315
self.ops[i].params.func = <void *>op[1]
316
i += 1
317
return self
318
319
320
# This is where we wish we had case statements...
321
# It looks like gcc might be smart enough to figure it out.
322
cdef inline int process_op(fast_double_op op, double* stack, double* argv, int top) except -2:
323
324
# print [stack[i] for i from 0 <= i <= top], ':', op.type
325
326
cdef int i, n
327
cdef PyObject* py_args
328
329
# We have to do some trickery because Pyrex disallows function pointer casts
330
# This will be removed in a future version of Cython.
331
cdef double (*f)(double)
332
cdef void** fp = <void **>&f
333
cdef double (*ff)(double, double)
334
cdef void** ffp = <void **>&ff
335
336
if op.type == LOAD_ARG:
337
stack[top+1] = argv[op.params.n]
338
return top+1
339
340
elif op.type == PUSH_CONST:
341
stack[top+1] = op.params.c
342
return top+1
343
344
elif op.type == POP:
345
return top-1
346
347
elif op.type == POP_N:
348
return top-op.params.n
349
350
elif op.type == DUP:
351
stack[top+1] = stack[top]
352
return top+1
353
354
elif op.type == ADD:
355
stack[top-1] += stack[top]
356
return top-1
357
358
elif op.type == SUB:
359
stack[top-1] -= stack[top]
360
return top-1
361
362
elif op.type == MUL:
363
stack[top-1] *= stack[top]
364
return top-1
365
366
elif op.type == DIV:
367
stack[top-1] /= stack[top]
368
return top-1
369
370
elif op.type == NEG:
371
stack[top] = -stack[top]
372
return top
373
374
elif op.type == ABS:
375
if stack[top] < 0:
376
stack[top] = -stack[top]
377
return top
378
379
elif op.type == INVERT:
380
stack[top] = 1/stack[top]
381
return top
382
383
elif op.type == POW:
384
if stack[top-1] < 0 and stack[top] != floor(stack[top]):
385
raise ValueError, "negative number to a fractional power not real"
386
stack[top-1] = pow(stack[top-1], stack[top])
387
return top-1
388
389
elif op.type == LT:
390
stack[top-1] = 1.0 if stack[top-1] < stack[top] else 0.0
391
return top-1
392
393
elif op.type == LE:
394
stack[top-1] = 1.0 if stack[top-1] <= stack[top] else 0.0
395
return top-1
396
397
elif op.type == EQ:
398
stack[top-1] = 1.0 if stack[top-1] == stack[top] else 0.0
399
return top-1
400
401
elif op.type == NE:
402
stack[top-1] = 1.0 if stack[top-1] != stack[top] else 0.0
403
return top-1
404
405
elif op.type == GT:
406
stack[top-1] = 1.0 if stack[top-1] > stack[top] else 0.0
407
return top-1
408
409
elif op.type == GE:
410
stack[top-1] = 1.0 if stack[top-1] >= stack[top] else 0.0
411
return top-1
412
413
elif op.type == ONE_ARG_FUNC:
414
fp[0] = op.params.func
415
stack[top] = f(stack[top])
416
return top
417
418
elif op.type == TWO_ARG_FUNC:
419
ffp[0] = op.params.func
420
stack[top-1] = ff(stack[top-1], stack[top])
421
return top-1
422
423
elif op.type == PY_FUNC:
424
# Even though it's python, optimize this because it'll be used often...
425
# We also want to avoid cluttering the header and footer
426
# of this function Python variables bring.
427
n = PyInt_AS_LONG(PyTuple_GET_ITEM(op.params.func, 0))
428
top = top - n + 1
429
py_args = PyTuple_New(n)
430
for i from 0 <= i < n:
431
PyTuple_SET_ITEM(py_args, i, PyFloat_FromDouble(stack[top+i]))
432
stack[top] = PyObject_CallObject(PyTuple_GET_ITEM(op.params.func, 1), py_args)
433
Py_DECREF(py_args)
434
return top
435
436
raise RuntimeError, "Bad op code %s" % op.type
437
438
439
cdef class FastDoubleFunc:
440
"""
441
This class is for fast evaluation of algebraic expressions over
442
the real numbers (e.g. for plotting). It represents an expression
443
as a stack-based series of operations.
444
445
EXAMPLES:
446
sage: from sage.ext.fast_eval import FastDoubleFunc
447
sage: f = FastDoubleFunc('const', 1.5) # the constant function
448
sage: f()
449
1.5
450
sage: g = FastDoubleFunc('arg', 0) # the first argument
451
sage: g(5)
452
5.0
453
sage: h = f+g
454
sage: h(17)
455
18.5
456
sage: h = h.sin()
457
sage: h(pi/2-1.5)
458
1.0
459
sage: h.is_pure_c()
460
True
461
sage: list(h)
462
['push 1.5', 'load 0', 'add', 'call sin(1)']
463
464
We can wrap Python functions too:
465
sage: h = FastDoubleFunc('callable', lambda x,y: x*x*x - y, g, f)
466
sage: h(10)
467
998.5
468
sage: h.is_pure_c()
469
False
470
sage: list(h) # random address
471
['load 0', 'push 1.5', 'py_call <function <lambda> at 0x9fedf70>(2)']
472
473
Here's a more complicated expression:
474
sage: from sage.ext.fast_eval import fast_float_constant, fast_float_arg
475
sage: a = fast_float_constant(1.5)
476
sage: b = fast_float_constant(3.14)
477
sage: c = fast_float_constant(7)
478
sage: x = fast_float_arg(0)
479
sage: y = fast_float_arg(1)
480
sage: f = a*x^2 + b*x + c - y/sqrt(sin(y)^2+a)
481
sage: f(2,3)
482
16.846610528508116
483
sage: f.max_height
484
4
485
sage: f.is_pure_c()
486
True
487
sage: list(f)
488
['push 1.5', 'load 0', 'dup', 'mul', 'mul', 'push 3.14', 'load 0', 'mul', 'add', 'push 7.0', 'add', 'load 1', 'load 1', 'call sin(1)', 'dup', 'mul', 'push 1.5', 'add', 'call sqrt(1)', 'div', 'sub']
489
490
491
AUTHOR:
492
-- Robert Bradshaw
493
"""
494
def __init__(self, type, param, *args):
495
496
cdef FastDoubleFunc arg
497
cdef int i
498
499
if type == 'arg':
500
self.nargs = param+1
501
self.nops = 1
502
self.max_height = 1
503
self.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op))
504
self.ops[0].type = LOAD_ARG
505
self.ops[0].params.n = param
506
507
elif type == 'const':
508
self.nargs = 0
509
self.nops = 1
510
self.max_height = 1
511
self.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op))
512
self.ops[0].type = PUSH_CONST
513
self.ops[0].params.c = param
514
515
elif type == 'callable':
516
py_func = len(args), param
517
self.py_funcs = (py_func,) # just so it doesn't get garbage collected
518
self.nops = 1
519
self.nargs = 0
520
for i from 0 <= i < len(args):
521
a = args[i]
522
if not isinstance(a, FastDoubleFunc):
523
a = FastDoubleFunc('const', a)
524
args = args[:i] + (a,) + args[i+1:]
525
arg = a
526
self.nops += arg.nops
527
if arg.py_funcs is not None:
528
self.py_funcs += arg.py_funcs
529
self.nargs = max(self.nargs, arg.nargs)
530
self.max_height = max(self.max_height, arg.max_height+i)
531
self.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op) * self.nops)
532
if self.ops == NULL:
533
raise MemoryError
534
i = 0
535
for arg in args:
536
memcpy(self.ops + i, arg.ops, sizeof(fast_double_op) * arg.nops)
537
i += arg.nops
538
self.ops[self.nops-1].type = PY_FUNC
539
self.ops[self.nops-1].params.func = <void *>py_func
540
541
else:
542
raise ValueError, "Unknown operation: %s" % type
543
544
self.allocate_stack()
545
546
547
cdef int allocate_stack(FastDoubleFunc self) except -1:
548
self.argv = <double*>sage_malloc(sizeof(double) * self.nargs)
549
if self.argv == NULL:
550
raise MemoryError
551
self.stack = <double*>sage_malloc(sizeof(double) * self.max_height)
552
if self.stack == NULL:
553
raise MemoryError
554
555
def __dealloc__(self):
556
if self.ops:
557
sage_free(self.ops)
558
if self.stack:
559
sage_free(self.stack)
560
if self.argv:
561
sage_free(self.argv)
562
563
def __reduce__(self):
564
"""
565
TESTS:
566
sage: from sage.ext.fast_eval import fast_float_arg, fast_float_func
567
sage: f = fast_float_arg(0).sin() * 10 + fast_float_func(hash, fast_float_arg(1))
568
sage: loads(dumps(f)) == f
569
True
570
"""
571
L = [op_to_tuple(self.ops[i]) for i from 0 <= i < self.nops]
572
return _unpickle_FastDoubleFunc, (self.nargs, self.max_height, L)
573
574
def __cmp__(self, other):
575
"""
576
Two functions are considered equal if they represent the same
577
exact sequence of operations.
578
579
TESTS:
580
sage: from sage.ext.fast_eval import fast_float_arg
581
sage: fast_float_arg(0) == fast_float_arg(0)
582
True
583
sage: fast_float_arg(0) == fast_float_arg(1)
584
False
585
sage: fast_float_arg(0) == fast_float_arg(0).sin()
586
False
587
"""
588
cdef int c, i
589
cdef FastDoubleFunc left, right
590
try:
591
left, right = self, other
592
c = cmp((left.nargs, left.nops, left.max_height),
593
(right.nargs, right.nops, right.max_height))
594
if c != 0:
595
return c
596
for i from 0 <= i < self.nops:
597
if left.ops[i].type != right.ops[i].type:
598
return cmp(left.ops[i].type, right.ops[i].type)
599
for i from 0 <= i < self.nops:
600
c = cmp(op_to_tuple(left.ops[i]), op_to_tuple(right.ops[i]))
601
if c != 0:
602
return c
603
return c
604
except TypeError:
605
return cmp(type(self), type(other))
606
607
def __call__(FastDoubleFunc self, *args):
608
"""
609
EXAMPLES:
610
sage: from sage.ext.fast_eval import fast_float_arg
611
sage: f = fast_float_arg(2)
612
sage: f(0,1,2,3)
613
2.0
614
sage: f(10)
615
Traceback (most recent call last):
616
...
617
TypeError: Wrong number of arguments (need at least 3, got 1)
618
sage: f('blah', 1, 2, 3)
619
Traceback (most recent call last):
620
...
621
TypeError: a float is required
622
"""
623
if len(args) < self.nargs:
624
raise TypeError, "Wrong number of arguments (need at least %s, got %s)" % (self.nargs, len(args))
625
cdef int i = 0
626
for i from 0 <= i < self.nargs:
627
self.argv[i] = args[i]
628
res = self._call_c(self.argv)
629
return res
630
631
cdef double _call_c(FastDoubleFunc self, double* argv) except? -2:
632
# The caller must assure that argv has length at least self.nargs
633
# The bulk of this function is in the (inlined) function process_op.
634
cdef int i, top = -1
635
for i from 0 <= i < self.nops:
636
top = process_op(self.ops[i], self.stack, argv, top)
637
cdef double res = self.stack[0]
638
return res
639
640
def _fast_float_(self, *vars):
641
r"""
642
Returns \code{self} if there are enough arguments, otherwise raises a TypeError.
643
644
EXAMPLES:
645
sage: from sage.ext.fast_eval import fast_float_arg
646
sage: f = fast_float_arg(1)
647
sage: f._fast_float_('x','y') is f
648
True
649
sage: f._fast_float_('x') is f
650
Traceback (most recent call last):
651
...
652
TypeError: Needs at least 2 arguments (1 provided)
653
"""
654
if self.nargs > len(vars):
655
raise TypeError, "Needs at least %s arguments (%s provided)" % (self.nargs, len(vars))
656
return self
657
658
def op_list(self):
659
"""
660
Returns a list of string representations of the
661
operations that make up this expression.
662
663
Python and C function calls may be only available by function pointer addresses.
664
665
EXAMPLES:
666
sage: from sage.ext.fast_eval import fast_float_constant, fast_float_arg
667
sage: a = fast_float_constant(17)
668
sage: x = fast_float_arg(0)
669
sage: a.op_list()
670
['push 17.0']
671
sage: x.op_list()
672
['load 0']
673
sage: (a*x).op_list()
674
['push 17.0', 'load 0', 'mul']
675
sage: (a+a*x^2).sqrt().op_list()
676
['push 17.0', 'push 17.0', 'load 0', 'dup', 'mul', 'mul', 'add', 'call sqrt(1)']
677
"""
678
cdef int i
679
return [op_to_string(self.ops[i]) for i from 0 <= i < self.nops]
680
681
def __iter__(self):
682
"""
683
Returns the list of operations of self.
684
685
EXAMPLES:
686
sage: from sage.ext.fast_eval import fast_float_arg
687
sage: f = fast_float_arg(0)*2 + 3
688
sage: list(f)
689
['load 0', 'push 2.0', 'mul', 'push 3.0', 'add']
690
"""
691
return iter(self.op_list())
692
693
cpdef bint is_pure_c(self):
694
"""
695
Returns True if this function can be evaluated without
696
any python calls (at any level).
697
698
EXAMPLES:
699
sage: from sage.ext.fast_eval import fast_float_constant, fast_float_arg, fast_float_func
700
sage: fast_float_constant(2).is_pure_c()
701
True
702
sage: fast_float_arg(2).sqrt().sin().is_pure_c()
703
True
704
sage: fast_float_func(lambda _: 2).is_pure_c()
705
False
706
"""
707
cdef int i
708
for i from 0 <= i < self.nops:
709
if self.ops[i].type == PY_FUNC:
710
return 0
711
return 1
712
713
def python_calls(self):
714
"""
715
Returns a list of all python calls used by function.
716
717
EXAMPLES:
718
sage: from sage.ext.fast_eval import fast_float_func, fast_float_arg
719
sage: x = fast_float_arg(0)
720
sage: f = fast_float_func(hash, sqrt(x))
721
sage: f.op_list()
722
['load 0', 'call sqrt(1)', 'py_call <built-in function hash>(1)']
723
sage: f.python_calls()
724
[<built-in function hash>]
725
"""
726
L = []
727
cdef int i
728
for i from 0 <= i < self.nops:
729
if self.ops[i].type == PY_FUNC:
730
L.append((<object>self.ops[i].params.func)[1])
731
return L
732
733
###################################################################
734
# Basic Arithmetic
735
###################################################################
736
737
def __add__(left, right):
738
"""
739
EXAMPLES:
740
sage: from sage.ext.fast_eval import fast_float_arg
741
sage: f = fast_float_arg(0) + fast_float_arg(1)
742
sage: f(3,4)
743
7.0
744
"""
745
return binop(left, right, ADD)
746
747
def __sub__(left, right):
748
"""
749
EXAMPLES:
750
sage: from sage.ext.fast_eval import fast_float_arg
751
sage: f = fast_float_arg(0) - fast_float_arg(2)
752
sage: f(3,4,5)
753
-2.0
754
"""
755
return binop(left, right, SUB)
756
757
def __mul__(left, right):
758
"""
759
EXAMPLES:
760
sage: from sage.ext.fast_eval import fast_float_arg
761
sage: f = fast_float_arg(0) * 2
762
sage: f(17)
763
34.0
764
"""
765
return binop(left, right, MUL)
766
767
def __div__(left, right):
768
"""
769
EXAMPLES:
770
sage: from sage.ext.fast_eval import fast_float_arg
771
sage: f = fast_float_arg(0) / 7
772
sage: f(14)
773
2.0
774
"""
775
return binop(left, right, DIV)
776
777
def __pow__(FastDoubleFunc left, right, dummy):
778
"""
779
EXAMPLES:
780
sage: from sage.ext.fast_eval import FastDoubleFunc
781
sage: f = FastDoubleFunc('arg', 0)^2
782
sage: f(2)
783
4.0
784
sage: f = FastDoubleFunc('arg', 0)^4
785
sage: f(2)
786
16.0
787
sage: f = FastDoubleFunc('arg', 0)^-3
788
sage: f(2)
789
0.125
790
sage: f = FastDoubleFunc('arg', 0)^FastDoubleFunc('arg', 1)
791
sage: f(5,3)
792
125.0
793
794
TESTS:
795
sage: var('a,b')
796
(a, b)
797
sage: ff = (a^b)._fast_float_(a,b)
798
sage: ff(2, 9)
799
512.0
800
sage: ff(-2, 9)
801
-512.0
802
sage: ff(-2, 9.1)
803
Traceback (most recent call last):
804
...
805
ValueError: negative number to a fractional power not real
806
"""
807
if isinstance(right, FastDoubleFunc) and right.nargs == 0:
808
right = float(right)
809
if not isinstance(right, FastDoubleFunc):
810
if right == int(float(right)):
811
if right == 1:
812
return left
813
elif right == 2:
814
return left.unop(DUP).unop(MUL)
815
elif right == 3:
816
return left.unop(DUP).unop(DUP).unop(MUL).unop(MUL)
817
elif right == 4:
818
return left.unop(DUP).unop(MUL).unop(DUP).unop(MUL)
819
elif right < 0:
820
return (~left)**(-right)
821
right = FastDoubleFunc('const', right)
822
cdef FastDoubleFunc feval = binop(left, right, POW)
823
return feval
824
825
def __neg__(FastDoubleFunc self):
826
"""
827
EXAMPLE:
828
sage: from sage.ext.fast_eval import fast_float_arg
829
sage: f = -fast_float_arg(0)
830
sage: f(3.5)
831
-3.5
832
"""
833
return self.unop(NEG)
834
835
def __abs__(FastDoubleFunc self):
836
"""
837
EXAMPLE:
838
sage: from sage.ext.fast_eval import fast_float_arg
839
sage: f = abs(fast_float_arg(0))
840
sage: f(-3)
841
3.0
842
"""
843
return self.unop(ABS)
844
845
def __float__(self):
846
"""
847
EXAMPLES:
848
sage: from sage.ext.fast_eval import fast_float_constant, fast_float_arg
849
sage: ff = fast_float_constant(17)
850
sage: float(ff)
851
17.0
852
sage: ff = fast_float_constant(17) - fast_float_constant(2)^2
853
sage: float(ff)
854
13.0
855
sage: ff = fast_float_constant(17) - fast_float_constant(2)^2 + fast_float_arg(1)
856
sage: float(ff)
857
Traceback (most recent call last):
858
...
859
TypeError: Not a constant.
860
"""
861
if self.nargs == 0:
862
return self._call_c(NULL)
863
else:
864
raise TypeError, "Not a constant."
865
866
def abs(FastDoubleFunc self):
867
"""
868
EXAMPLE:
869
sage: from sage.ext.fast_eval import fast_float_arg
870
sage: f = fast_float_arg(0).abs()
871
sage: f(3)
872
3.0
873
"""
874
return self.unop(ABS)
875
876
def __invert__(FastDoubleFunc self):
877
"""
878
EXAMPLE:
879
sage: from sage.ext.fast_eval import fast_float_arg
880
sage: f = ~fast_float_arg(0)
881
sage: f(4)
882
0.25
883
"""
884
return self.unop(INVERT)
885
886
def sqrt(self):
887
"""
888
EXAMPLE:
889
sage: from sage.ext.fast_eval import fast_float_arg
890
sage: f = fast_float_arg(0).sqrt()
891
sage: f(4)
892
2.0
893
"""
894
return self.cfunc(&sqrt)
895
896
###################################################################
897
# Basic Comparison
898
###################################################################
899
900
def _richcmp_(left, right, op):
901
"""
902
Compare left and right.
903
EXAMPLES:
904
sage: from sage.ext.fast_eval import fast_float_arg
905
sage: import operator
906
sage: f = fast_float_arg(0)._richcmp_(2, operator.lt)
907
sage: [f(i) for i in (1..3)]
908
[1.0, 0.0, 0.0]
909
sage: f = fast_float_arg(0)._richcmp_(2, operator.le)
910
sage: [f(i) for i in (1..3)]
911
[1.0, 1.0, 0.0]
912
sage: f = fast_float_arg(0)._richcmp_(2, operator.eq)
913
sage: [f(i) for i in (1..3)]
914
[0.0, 1.0, 0.0]
915
sage: f = fast_float_arg(0)._richcmp_(2, operator.ne)
916
sage: [f(i) for i in (1..3)]
917
[1.0, 0.0, 1.0]
918
sage: f = fast_float_arg(0)._richcmp_(2, operator.ge)
919
sage: [f(i) for i in (1..3)]
920
[0.0, 1.0, 1.0]
921
sage: f = fast_float_arg(0)._richcmp_(2, operator.gt)
922
sage: [f(i) for i in (1..3)]
923
[0.0, 0.0, 1.0]
924
"""
925
import operator
926
if op == operator.lt: #<
927
return binop(left, right, LT)
928
elif op == operator.eq: #==
929
return binop(left, right, EQ)
930
elif op == operator.gt: #>
931
return binop(left, right, GT)
932
elif op == operator.le: #<=
933
return binop(left, right, LE)
934
elif op == operator.ne: #!=
935
return binop(left, right, NE)
936
elif op == operator.ge: #>=
937
return binop(left, right, GE)
938
939
940
###################################################################
941
# Exponential and log
942
###################################################################
943
944
def log(self, base=None):
945
"""
946
EXAMPLE:
947
sage: from sage.ext.fast_eval import fast_float_arg
948
sage: f = fast_float_arg(0).log()
949
sage: f(2)
950
0.693147180559945...
951
sage: f = fast_float_arg(0).log(2)
952
sage: f(2)
953
1.0
954
sage: f = fast_float_arg(0).log(3)
955
sage: f(9)
956
2.0...
957
"""
958
if base is None:
959
return self.cfunc(&log)
960
elif base == 2:
961
return self.cfunc(&log2)
962
elif base == 10:
963
return self.cfunc(&log10)
964
else:
965
try:
966
base = fast_float_constant(log(float(base)))
967
except TypeError, e:
968
base = fast_float(base.log())
969
return binop(self.cfunc(&log), base, DIV)
970
971
def exp(self):
972
"""
973
EXAMPLE:
974
sage: from sage.ext.fast_eval import fast_float_arg
975
sage: f = fast_float_arg(0).exp()
976
sage: f(1)
977
2.718281828459045...
978
sage: f(100)
979
2.6881171418161356e+43
980
"""
981
return self.cfunc(&exp)
982
983
###################################################################
984
# Rounding
985
###################################################################
986
987
def ceil(self):
988
"""
989
EXAMPLE:
990
sage: from sage.ext.fast_eval import fast_float_arg
991
sage: f = fast_float_arg(0).ceil()
992
sage: f(1.5)
993
2.0
994
sage: f(-1.5)
995
-1.0
996
"""
997
return self.cfunc(&ceil)
998
999
def floor(self):
1000
"""
1001
EXAMPLE:
1002
sage: from sage.ext.fast_eval import fast_float_arg
1003
sage: f = fast_float_arg(0).floor()
1004
sage: f(11.5)
1005
11.0
1006
sage: f(-11.5)
1007
-12.0
1008
"""
1009
return self.cfunc(&floor)
1010
1011
###################################################################
1012
# Trigonometric
1013
###################################################################
1014
1015
def sin(self):
1016
"""
1017
EXAMPLE:
1018
sage: from sage.ext.fast_eval import fast_float_arg
1019
sage: f = fast_float_arg(0).sin()
1020
sage: f(pi/2)
1021
1.0
1022
"""
1023
return self.cfunc(&sin)
1024
1025
def cos(self):
1026
"""
1027
EXAMPLE:
1028
sage: from sage.ext.fast_eval import fast_float_arg
1029
sage: f = fast_float_arg(0).cos()
1030
sage: f(0)
1031
1.0
1032
"""
1033
return self.cfunc(&cos)
1034
1035
def tan(self):
1036
"""
1037
EXAMPLE:
1038
sage: from sage.ext.fast_eval import fast_float_arg
1039
sage: f = fast_float_arg(0).tan()
1040
sage: f(pi/3)
1041
1.73205080756887...
1042
"""
1043
return self.cfunc(&tan)
1044
1045
def csc(self):
1046
"""
1047
EXAMPLE:
1048
sage: from sage.ext.fast_eval import fast_float_arg
1049
sage: f = fast_float_arg(0).csc()
1050
sage: f(pi/2)
1051
1.0
1052
"""
1053
return ~self.sin()
1054
1055
def sec(self):
1056
"""
1057
EXAMPLE:
1058
sage: from sage.ext.fast_eval import fast_float_arg
1059
sage: f = fast_float_arg(0).sec()
1060
sage: f(pi)
1061
-1.0
1062
"""
1063
return ~self.cos()
1064
1065
def cot(self):
1066
"""
1067
EXAMPLE:
1068
sage: from sage.ext.fast_eval import fast_float_arg
1069
sage: f = fast_float_arg(0).cot()
1070
sage: f(pi/4)
1071
1.0...
1072
"""
1073
return ~self.tan()
1074
1075
def arcsin(self):
1076
"""
1077
EXAMPLE:
1078
sage: from sage.ext.fast_eval import fast_float_arg
1079
sage: f = fast_float_arg(0).arcsin()
1080
sage: f(0.5)
1081
0.523598775598298...
1082
"""
1083
return self.cfunc(&asin)
1084
1085
def arccos(self):
1086
"""
1087
EXAMPLE:
1088
sage: from sage.ext.fast_eval import fast_float_arg
1089
sage: f = fast_float_arg(0).arccos()
1090
sage: f(sqrt(3)/2)
1091
0.5235987755982989...
1092
"""
1093
return self.cfunc(&acos)
1094
1095
def arctan(self):
1096
"""
1097
EXAMPLE:
1098
sage: from sage.ext.fast_eval import fast_float_arg
1099
sage: f = fast_float_arg(0).arctan()
1100
sage: f(1)
1101
0.785398163397448...
1102
"""
1103
return self.cfunc(&atan)
1104
1105
###################################################################
1106
# Hyperbolic
1107
###################################################################
1108
1109
def sinh(self):
1110
"""
1111
EXAMPLE:
1112
sage: from sage.ext.fast_eval import fast_float_arg
1113
sage: f = fast_float_arg(0).sinh()
1114
sage: f(log(2))
1115
0.75
1116
"""
1117
return self.cfunc(&sinh)
1118
1119
def cosh(self):
1120
"""
1121
EXAMPLE:
1122
sage: from sage.ext.fast_eval import fast_float_arg
1123
sage: f = fast_float_arg(0).cosh()
1124
sage: f(log(2))
1125
1.25
1126
"""
1127
return self.cfunc(&cosh)
1128
1129
def tanh(self):
1130
"""
1131
EXAMPLE:
1132
sage: from sage.ext.fast_eval import fast_float_arg
1133
sage: f = fast_float_arg(0).tanh()
1134
sage: f(0)
1135
0.0
1136
"""
1137
return self.cfunc(&tanh)
1138
1139
def arcsinh(self):
1140
"""
1141
EXAMPLE:
1142
sage: from sage.ext.fast_eval import fast_float_arg
1143
sage: f = fast_float_arg(0).arcsinh()
1144
sage: f(sinh(5))
1145
5.0
1146
"""
1147
return self.cfunc(&asinh)
1148
1149
def arccosh(self):
1150
"""
1151
EXAMPLE:
1152
sage: from sage.ext.fast_eval import fast_float_arg
1153
sage: f = fast_float_arg(0).arccosh()
1154
sage: f(cosh(5))
1155
5.0
1156
"""
1157
return self.cfunc(&acosh)
1158
1159
def arctanh(self):
1160
"""
1161
EXAMPLE:
1162
sage: from sage.ext.fast_eval import fast_float_arg
1163
sage: f = fast_float_arg(0).arctanh()
1164
sage: abs(f(tanh(0.5)) - 0.5) < 0.0000001
1165
True
1166
"""
1167
return self.cfunc(&atanh)
1168
1169
cdef FastDoubleFunc cfunc(FastDoubleFunc self, void* func):
1170
cdef FastDoubleFunc feval = self.unop(ONE_ARG_FUNC)
1171
feval.ops[feval.nops - 1].params.func = func
1172
feval.allocate_stack()
1173
return feval
1174
1175
###################################################################
1176
# Utility functions
1177
###################################################################
1178
1179
cdef FastDoubleFunc unop(FastDoubleFunc self, char type):
1180
cdef FastDoubleFunc feval = PY_NEW(FastDoubleFunc)
1181
feval.nargs = self.nargs
1182
feval.nops = self.nops + 1
1183
feval.max_height = self.max_height
1184
if type == DUP:
1185
feval.max_height += 1
1186
feval.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op) * feval.nops)
1187
memcpy(feval.ops, self.ops, sizeof(fast_double_op) * self.nops)
1188
feval.ops[feval.nops - 1].type = type
1189
feval.py_funcs = self.py_funcs
1190
feval.allocate_stack()
1191
return feval
1192
1193
cdef FastDoubleFunc binop(_left, _right, char type):
1194
r"""
1195
Returns a function that calculates left and right on the stack, leaving
1196
their results on the top, and then calls operation \code{type}.
1197
1198
EXAMPLES:
1199
sage: from sage.ext.fast_eval import fast_float_arg
1200
sage: f = fast_float_arg(1)
1201
sage: g = fast_float_arg(2) * 11
1202
sage: f.op_list()
1203
['load 1']
1204
sage: g.op_list()
1205
['load 2', 'push 11.0', 'mul']
1206
sage: (f+g).op_list()
1207
['load 1', 'load 2', 'push 11.0', 'mul', 'add']
1208
1209
Correctly calculates the maximum stack heights and number of arguments:
1210
sage: f.max_height
1211
1
1212
sage: g.max_height
1213
2
1214
sage: (f+g).max_height
1215
3
1216
sage: (g+f).max_height
1217
2
1218
1219
sage: f.nargs
1220
2
1221
sage: g.nargs
1222
3
1223
sage: (f+g).nargs
1224
3
1225
"""
1226
cdef FastDoubleFunc left, right
1227
try:
1228
left = _left
1229
except TypeError:
1230
left = fast_float(_left)
1231
try:
1232
right = _right
1233
except TypeError:
1234
right = fast_float(_right)
1235
1236
# In Cython assigning None does NOT raise a TypeError above.
1237
if left is None or right is None:
1238
raise TypeError
1239
1240
cdef FastDoubleFunc feval = PY_NEW(FastDoubleFunc)
1241
feval.nargs = max(left.nargs, right.nargs)
1242
feval.nops = left.nops + right.nops + 1
1243
feval.max_height = max(left.max_height, right.max_height+1)
1244
feval.ops = <fast_double_op *>sage_malloc(sizeof(fast_double_op) * feval.nops)
1245
memcpy(feval.ops, left.ops, sizeof(fast_double_op) * left.nops)
1246
memcpy(feval.ops + left.nops, right.ops, sizeof(fast_double_op) * right.nops)
1247
feval.ops[feval.nops - 1].type = type
1248
if left.py_funcs is None:
1249
feval.py_funcs = right.py_funcs
1250
elif right.py_funcs is None:
1251
feval.py_funcs = left.py_funcs
1252
else:
1253
feval.py_funcs = left.py_funcs + right.py_funcs
1254
feval.allocate_stack()
1255
return feval
1256
1257
1258
def fast_float_constant(x):
1259
"""
1260
Return a fast-to-evaluate constant function.
1261
1262
EXAMPLES:
1263
sage: from sage.ext.fast_eval import fast_float_constant
1264
sage: f = fast_float_constant(-2.75)
1265
sage: f()
1266
-2.75
1267
1268
This is all that goes on under the hood:
1269
sage: fast_float_constant(pi).op_list()
1270
['push 3.14159265359']
1271
"""
1272
return FastDoubleFunc('const', x)
1273
1274
def fast_float_arg(n):
1275
"""
1276
Return a fast-to-evaluate argument selector.
1277
1278
INPUT:
1279
n -- the (zero-indexed) argument to select
1280
1281
EXAMPLES:
1282
sage: from sage.ext.fast_eval import fast_float_arg
1283
sage: f = fast_float_arg(0)
1284
sage: f(1,2)
1285
1.0
1286
sage: f = fast_float_arg(1)
1287
sage: f(1,2)
1288
2.0
1289
1290
This is all that goes on under the hood:
1291
sage: fast_float_arg(10).op_list()
1292
['load 10']
1293
"""
1294
return FastDoubleFunc('arg', n)
1295
1296
def fast_float_func(f, *args):
1297
"""
1298
Returns a wrapper around a python function.
1299
1300
INPUT:
1301
f -- a callable python object
1302
args -- a list of FastDoubleFunc inputs
1303
1304
EXAMPLES:
1305
sage: from sage.ext.fast_eval import fast_float_func, fast_float_arg
1306
sage: f = fast_float_arg(0)
1307
sage: g = fast_float_arg(1)
1308
sage: h = fast_float_func(lambda x,y: x-y, f, g)
1309
sage: h(5, 10)
1310
-5.0
1311
1312
This is all that goes on under the hood:
1313
sage: h.op_list() # random memory address
1314
['load 0', 'load 1', 'py_call <function <lambda> at 0xb62b230>(2)']
1315
"""
1316
return FastDoubleFunc('callable', f, *args)
1317
1318
1319
new_fast_float=True
1320
1321
def fast_float(f, *vars, old=None, expect_one_var=False):
1322
"""
1323
Tries to create a function that evaluates f quickly using
1324
floating-point numbers, if possible. There are two implementations
1325
of fast_float in Sage; by default we use the newer, which is
1326
slightly faster on most tests.
1327
1328
On failure, returns the input unchanged.
1329
1330
INPUT:
1331
f -- an expression
1332
vars -- the names of the arguments
1333
old -- use the original algorithm for fast_float
1334
expect_one_var -- don't give deprecation warning if vars is
1335
omitted, as long as expression has only one var
1336
1337
EXAMPLES:
1338
sage: from sage.ext.fast_eval import fast_float
1339
sage: x,y = var('x,y')
1340
sage: f = fast_float(sqrt(x^2+y^2), 'x', 'y')
1341
sage: f(3,4)
1342
5.0
1343
1344
Specifying the argument names is essential, as fast_float objects
1345
only distinguish between arguments by order.
1346
sage: f = fast_float(x-y, 'x','y')
1347
sage: f(1,2)
1348
-1.0
1349
sage: f = fast_float(x-y, 'y','x')
1350
sage: f(1,2)
1351
1.0
1352
"""
1353
if old is None:
1354
old = not new_fast_float
1355
1356
if isinstance(f, (tuple, list)):
1357
return tuple([fast_float(x, *vars, expect_one_var=expect_one_var) for x in f])
1358
1359
cdef int i
1360
for i from 0 <= i < len(vars):
1361
if not PY_TYPE_CHECK(vars[i], str):
1362
v = str(vars[i])
1363
# inexact generators display as 1.00..0*x
1364
if '*' in v:
1365
v = v[v.index('*')+1:]
1366
vars = vars[:i] + (v,) + vars[i+1:]
1367
1368
try:
1369
if old:
1370
return f._fast_float_(*vars)
1371
else:
1372
return fast_callable(f, vars=vars, domain=float, _autocompute_vars_for_backward_compatibility_with_deprecated_fast_float_functionality=True, expect_one_var=expect_one_var)
1373
except AttributeError:
1374
pass
1375
1376
try:
1377
return FastDoubleFunc('const', float(f))
1378
except TypeError:
1379
pass
1380
1381
try:
1382
from sage.symbolic.ring import SR
1383
return fast_float(SR(f), *vars)
1384
except TypeError:
1385
pass
1386
1387
if f is None:
1388
raise TypeError, "no way to make fast_float from None"
1389
1390
return f
1391
1392
def is_fast_float(x):
1393
return PY_TYPE_CHECK(x, FastDoubleFunc) or PY_TYPE_CHECK(x, Wrapper)
1394
1395
1396