Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/misc/cython.py
8814 views
1
"""
2
Cython -- C-Extensions for Python
3
4
AUTHORS:
5
6
- William Stein (2006-01-18): initial version
7
- William Stein (2007-07-28): update from sagex to cython
8
- Martin Albrecht & William Stein (2011-08): cfile & cargs
9
"""
10
#*****************************************************************************
11
# Copyright (C) 2006 William Stein <[email protected]>
12
#
13
# Distributed under the terms of the GNU General Public License (GPL)
14
#
15
# http://www.gnu.org/licenses/
16
#*****************************************************************************
17
18
from __future__ import print_function
19
20
import os, sys, platform
21
22
from sage.env import SAGE_LOCAL, SAGE_SRC, UNAME
23
from misc import SPYX_TMP
24
25
def cblas():
26
"""
27
Return the name of the cblas library on this system. If the environment
28
variable :envvar:`$SAGE_CBLAS` is set, just return its value. If not,
29
return ``'cblas'`` if :file:`/usr/lib/libcblas.so` or
30
:file:`/usr/lib/libcblas.dylib` exists, return ``'blas'`` if
31
:file:`/usr/lib/libblas.dll.a` exists, and return ``'gslcblas'`` otherwise.
32
33
EXAMPLES::
34
35
sage: sage.misc.cython.cblas() # random -- depends on OS, etc.
36
'cblas'
37
"""
38
if os.environ.has_key('SAGE_CBLAS'):
39
return os.environ['SAGE_CBLAS']
40
elif os.path.exists('/usr/lib/libcblas.dylib') or \
41
os.path.exists('/usr/lib/libcblas.so'):
42
return 'cblas'
43
elif os.path.exists('/usr/lib/libblas.dll.a'): # untested.
44
return 'blas'
45
else:
46
# This is very slow (?), but *guaranteed* to be available.
47
return 'gslcblas'
48
49
# In case of ATLAS we need to link against cblas as well as atlas
50
# In the other cases we just return the same library name as cblas()
51
# which is fine for the linker
52
#
53
# We should be using the Accelerate FrameWork on OS X, but that requires
54
# some magic due to distutils having ridden on the short bus :)
55
def atlas():
56
"""
57
Returns the name of the ATLAS library to use. On Darwin or Cygwin, this is
58
``'blas'``, and otherwise it is ``'atlas'``.
59
60
EXAMPLES::
61
62
sage: sage.misc.cython.atlas() # random -- depends on OS
63
'atlas'
64
"""
65
if UNAME == "Darwin" or "CYGWIN" in UNAME:
66
return 'blas'
67
else:
68
return 'atlas'
69
70
include_dirs = [os.path.join(SAGE_LOCAL,'include','csage'),
71
os.path.join(SAGE_LOCAL,'include'), \
72
os.path.join(SAGE_LOCAL,'include','python'+platform.python_version().rsplit('.', 1)[0]), \
73
os.path.join(SAGE_LOCAL,'lib','python','site-packages','numpy','core','include'), \
74
os.path.join(SAGE_SRC,'sage','ext'), \
75
os.path.join(SAGE_SRC), \
76
os.path.join(SAGE_SRC,'sage','gsl')]
77
78
79
standard_libs = ['mpfr', 'gmp', 'gmpxx', 'stdc++', 'pari', 'm', \
80
'ec', 'gsl', cblas(), atlas(), 'ntl', 'csage']
81
82
offset = 0
83
84
def parse_keywords(kwd, s):
85
r"""
86
Given a keyword ``kwd`` and a string ``s``, return a list of all arguments
87
on the same line as that keyword in ``s``, as well as a new copy of ``s``
88
in which each occurrence of ``kwd`` is in a comment. If a comment already
89
occurs on the line containing ``kwd``, no words after the ``#`` are added
90
to the list.
91
92
EXAMPLES::
93
94
sage: sage.misc.cython.parse_keywords('clib', " clib foo bar baz\n #cinclude bar\n")
95
(['foo', 'bar', 'baz'], ' #clib foo bar baz\n #cinclude bar\n')
96
97
sage: sage.misc.cython.parse_keywords('clib', "# qux clib foo bar baz\n #cinclude bar\n")
98
(['foo', 'bar', 'baz'], '# qux clib foo bar baz\n #cinclude bar\n')
99
sage: sage.misc.cython.parse_keywords('clib', "# clib foo bar # baz\n #cinclude bar\n")
100
(['foo', 'bar'], '# clib foo bar # baz\n #cinclude bar\n')
101
"""
102
j = 0
103
v = []
104
while True:
105
# see if kwd occurs
106
i = s[j:].find(kwd)
107
if i == -1: break
108
j = i + j
109
110
# add a hash, if necessary
111
last_hash = s[:j].rfind('#')
112
last_newline = s[:j].rfind('\n')
113
if last_hash > last_newline:
114
j += len(kwd)
115
else:
116
s = s[:j] + '#' + s[j:]
117
j += len(kwd) + 1
118
119
# find all other words on this line
120
k = s[j:].find('\n')
121
if k == -1:
122
k = len(s)
123
124
# add them to our list, until we find a comment
125
for X in s[j:j+k].split():
126
if X[0] == '#': # skip rest of line
127
break
128
v.append(X)
129
130
return v, s
131
132
def environ_parse(s):
133
r"""
134
Given a string s, find each substring of the form ``'\$ABC'``. If the
135
environment variable :envvar:`$ABC` is set, replace ``'\$ABC'`` with its
136
value and move on to the next such substring. If it is not set, stop
137
parsing there.
138
139
EXAMPLES::
140
141
sage: from sage.misc.cython import environ_parse
142
sage: environ_parse('$SAGE_LOCAL') == SAGE_LOCAL
143
True
144
sage: environ_parse('$THIS_IS_NOT_DEFINED_ANYWHERE')
145
'$THIS_IS_NOT_DEFINED_ANYWHERE'
146
sage: os.environ['DEFINE_THIS'] = 'hello'
147
sage: environ_parse('$DEFINE_THIS/$THIS_IS_NOT_DEFINED_ANYWHERE/$DEFINE_THIS')
148
'hello/$THIS_IS_NOT_DEFINED_ANYWHERE/$DEFINE_THIS'
149
"""
150
i = s.find('$')
151
if i == -1:
152
return s
153
j = s[i:].find('/')
154
if j == -1:
155
j = len(s)
156
else:
157
j = i + j
158
name = s[i+1:j]
159
if os.environ.has_key(name):
160
s = s[:i] + os.environ[name] + s[j:]
161
else:
162
return s
163
return environ_parse(s)
164
165
def pyx_preparse(s):
166
r"""
167
Preparse a pyx file:
168
169
* include ``cdefs.pxi``, ``interrupt.pxi``, ``stdsage.pxi``
170
* parse ``clang`` pragma (c or c++)
171
* parse ``clib`` pragma (additional libraries to link in)
172
* parse ``cinclude`` (additional include directories)
173
* parse ``cfile`` (additional files to be included)
174
* parse ``cargs`` (additional parameters passed to the compiler)
175
176
The pragmas:
177
178
- ``clang`` - may be either ``'c'`` or ``'c++'`` indicating whether a C or
179
C++ compiler should be used
180
181
- ``clib`` - additional libraries to be linked in, the space separated list
182
is split and passed to distutils.
183
184
- ``cinclude`` - additional directories to search for header files. The
185
space separated list is split and passed to distutils.
186
187
- ``cfile`` - additional C or C++ files to be compiled. Also,
188
:envvar:`$SAGE_SRC` and :envvar:`$SAGE_LOCAL` are expanded, but other
189
environment variables are not.
190
191
- ``cargs`` - additional parameters passed to the compiler
192
193
OUTPUT: preamble, libs, includes, language, files, args
194
195
EXAMPLES::
196
197
sage: from sage.misc.cython import pyx_preparse
198
sage: pyx_preparse("")
199
('\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi" # ctrl-c interrupt block support\n\ninclude "cdefs.pxi"\n',
200
['mpfr',
201
'gmp',
202
'gmpxx',
203
'stdc++',
204
'pari',
205
'm',
206
'ec',
207
'gsl',
208
'...blas',
209
...,
210
'ntl',
211
'csage'],
212
['.../include/csage',
213
'.../include',
214
'.../include/python2.7',
215
'.../lib/python/site-packages/numpy/core/include',
216
'.../sage/ext',
217
'...',
218
'.../sage/gsl'],
219
'c',
220
[], ['-w', '-O2'])
221
sage: s, libs, inc, lang, f, args = pyx_preparse("# clang c++\n #clib foo\n # cinclude bar\n")
222
sage: lang
223
'c++'
224
225
sage: libs
226
['foo', 'mpfr',
227
'gmp', 'gmpxx',
228
'stdc++',
229
'pari',
230
'm',
231
'ec',
232
'gsl', '...blas', ...,
233
'ntl',
234
'csage']
235
sage: libs[1:] == sage.misc.cython.standard_libs
236
True
237
238
sage: inc
239
['bar',
240
'.../include/csage',
241
'.../include',
242
'.../include/python2.7',
243
'.../lib/python/site-packages/numpy/core/include',
244
'.../sage/ext',
245
'...',
246
'.../sage/gsl']
247
248
sage: s, libs, inc, lang, f, args = pyx_preparse("# cargs -O3 -ggdb\n")
249
sage: args
250
['-w', '-O2', '-O3', '-ggdb']
251
252
TESTS::
253
254
sage: module = sage.misc.cython.import_test("trac11680") # long time (7s on sage.math, 2012)
255
sage: R.<x> = QQ[]
256
sage: module.evaluate_at_power_of_gen(x^3 + x - 7, 5) # long time
257
x^15 + x^5 - 7
258
"""
259
lang, s = parse_keywords('clang', s)
260
if lang:
261
lang = lang[0].lower() # this allows both C++ and c++
262
else:
263
lang = "c"
264
265
v, s = parse_keywords('clib', s)
266
libs = v + standard_libs
267
268
additional_source_files, s = parse_keywords('cfile', s)
269
270
v, s = parse_keywords('cinclude', s)
271
inc = [environ_parse(x.replace('"','').replace("'","")) for x in v] + include_dirs
272
s = """\ninclude "cdefs.pxi"\n""" + s
273
if lang != "c++": # has issues with init_csage()
274
s = """\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi" # ctrl-c interrupt block support\n""" + s
275
args, s = parse_keywords('cargs', s)
276
args = ['-w','-O2'] + args
277
278
return s, libs, inc, lang, additional_source_files, args
279
280
################################################################
281
# If the user attaches a .spyx file and changes it, we have
282
# to reload an .so.
283
#
284
# PROBLEM: Python does not allow one to reload an .so extension module.
285
# Solution, we create a different .so file and load that one,
286
# overwriting the definitions of everything in the original .so file.
287
#
288
# HOW: By using a sequence_number for each .spyx file; we keep
289
# these sequence numbers in a dict.
290
#
291
################################################################
292
293
sequence_number = {}
294
295
def cython(filename, verbose=False, compile_message=False,
296
use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True,
297
create_local_so_file=False):
298
"""
299
Compile a Cython file. This converts a Cython file to a C (or C++ file),
300
and then compiles that. The .c file and the .so file are
301
created in a temporary directory.
302
303
INPUTS:
304
305
- ``filename`` - the name of the file to be compiled. Should end with
306
'pyx'.
307
308
- ``verbose`` (bool, default False) - if True, print debugging
309
information.
310
311
- ``compile_message`` (bool, default False) - if True, print
312
``'Compiling <filename>...'`` to the standard error.
313
314
- ``use_cache`` (bool, default False) - if True, check the
315
temporary build directory to see if there is already a
316
corresponding .so file. If so, and if the .so file is newer than the
317
Cython file, don't recompile, just reuse the .so file.
318
319
- ``create_local_c_file`` (bool, default False) - if True, save a
320
copy of the .c file in the current directory.
321
322
- ``annotate`` (bool, default True) - if True, create an html file which
323
annotates the conversion from .pyx to .c. By default this is only created
324
in the temporary directory, but if ``create_local_c_file`` is also True,
325
then save a copy of the .html file in the current directory.
326
327
- ``sage_namespace`` (bool, default True) - if True, import
328
``sage.all``.
329
330
- ``create_local_so_file`` (bool, default False) - if True, save a
331
copy of the compiled .so file in the current directory.
332
333
TESTS:
334
335
Before :trac:`12975`, it would have beeen needed to write ``#clang c++``,
336
but upper case ``C++`` has resulted in an error::
337
338
sage: code = [
339
... "#clang C++",
340
... "#cinclude %s/include/singular %s/include/factory"%(SAGE_LOCAL, SAGE_LOCAL),
341
... "#clib m readline singular givaro ntl gmpxx gmp",
342
... "from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular",
343
... "from sage.libs.singular.polynomial cimport singular_polynomial_pow",
344
... "def test(MPolynomial_libsingular p):",
345
... " singular_polynomial_pow(&p._poly, p._poly, 2, p._parent_ring)"]
346
sage: cython(os.linesep.join(code))
347
348
The function ``test`` now manipulates internal C data of polynomials,
349
squaring them::
350
351
sage: P.<x,y>=QQ[]
352
sage: test(x)
353
sage: x
354
x^2
355
356
"""
357
if not filename.endswith('pyx'):
358
print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr)
359
360
# base is the name of the .so module that we create. If we are
361
# creating a local shared object file, we use a more natural
362
# naming convention. If we are not creating a local shared object
363
# file, the main constraint is that it is unique and determined by
364
# the file that we're running Cython on, so that in some cases we
365
# can cache the result (e.g., recompiling the same pyx file during
366
# the same session).
367
if create_local_so_file:
368
base, ext = os.path.splitext(os.path.basename(filename))
369
base = sanitize(base)
370
else:
371
base = sanitize(os.path.abspath(filename))
372
373
# This is the *temporary* directory where we build the pyx file.
374
# This is deleted when sage exits, which means pyx files must be
375
# rebuilt every time Sage is restarted at present.
376
build_dir = os.path.join(SPYX_TMP, base)
377
378
if os.path.exists(build_dir):
379
# There is already a module here. Maybe we do not have to rebuild?
380
# Find the name.
381
if use_cache:
382
prev_so = [F for F in os.listdir(build_dir) if F[-3:] == '.so']
383
if len(prev_so) > 0:
384
prev_so = prev_so[0] # should have length 1 because of deletes below
385
if os.path.getmtime(filename) <= os.path.getmtime('%s/%s'%(build_dir, prev_so)):
386
# We do not have to rebuild.
387
return prev_so[:-3], build_dir
388
else:
389
os.makedirs(build_dir)
390
for F in os.listdir(build_dir):
391
G = '%s/%s'%(build_dir,F)
392
try:
393
if not os.path.isdir(G):
394
os.unlink(G)
395
except OSError:
396
pass
397
398
# Get the absolute path to the directory that contains the pyx file.
399
# We will use this only to make some convenient symbolic links.
400
abs_base = os.path.split(os.path.abspath(filename))[0]
401
402
# bad things happen if the current directory is devel/sage-*
403
if not os.path.exists("%s/sage" % abs_base) and not os.path.exists("%s/c_lib" % abs_base):
404
cmd = 'cd "%s"; ln -sf "%s"/* .'%(build_dir, abs_base)
405
os.system(cmd)
406
if os.path.exists("%s/setup.py" % build_dir):
407
os.unlink("%s/setup.py" % build_dir)
408
409
if compile_message:
410
print("Compiling {}...".format(filename), file=sys.stderr)
411
412
F = open(filename).read()
413
414
F, libs, includes, language, additional_source_files, extra_args = pyx_preparse(F)
415
416
# add the working directory to the includes so custom headers etc. work
417
includes.append(os.path.split(os.path.splitext(filename)[0])[0])
418
419
if language == 'c++':
420
extension = "cpp"
421
else:
422
extension = "c"
423
424
if create_local_so_file:
425
name = base
426
else:
427
global sequence_number
428
if not sequence_number.has_key(base):
429
sequence_number[base] = 0
430
name = '%s_%s'%(base, sequence_number[base])
431
432
# increment the sequence number so will use a different one next time.
433
sequence_number[base] += 1
434
435
file_list = []
436
for fname in additional_source_files:
437
fname = fname.replace("$SAGE_SRC", SAGE_SRC)
438
fname = fname.replace("$SAGE_LOCAL", SAGE_LOCAL)
439
if fname.startswith(os.path.sep):
440
file_list.append("'"+fname+"'")
441
else:
442
file_list.append("'"+os.path.abspath(os.curdir)+"/"+fname+"'")
443
additional_source_files = ",".join(file_list)
444
445
pyx = '%s/%s.pyx'%(build_dir, name)
446
open(pyx,'w').write(F)
447
setup="""
448
# Build using 'python setup.py'
449
import distutils.sysconfig, os, sys
450
from distutils.core import setup, Extension
451
452
from sage.env import SAGE_LOCAL
453
454
extra_link_args = ['-L' + SAGE_LOCAL + '/lib']
455
extra_compile_args = %s
456
457
ext_modules = [Extension('%s', sources=['%s.%s', %s],
458
libraries=%s,
459
library_dirs=[SAGE_LOCAL + '/lib/'],
460
extra_compile_args = extra_compile_args,
461
extra_link_args = extra_link_args,
462
language = '%s' )]
463
464
setup(ext_modules = ext_modules,
465
include_dirs = %s)
466
"""%(extra_args, name, name, extension, additional_source_files, libs, language, includes)
467
open('%s/setup.py'%build_dir,'w').write(setup)
468
469
cython_include = ' '.join(["-I '%s'"%x for x in includes if len(x.strip()) > 0 ])
470
471
options = ['-p']
472
if annotate:
473
options.append('-a')
474
if sage_namespace:
475
options.append('--pre-import sage.all')
476
477
cmd = "cd '%s' && cython %s %s '%s.pyx' 1>log 2>err " % (build_dir, ' '.join(options), cython_include, name)
478
479
if create_local_c_file:
480
target_c = '%s/_%s.c'%(os.path.abspath(os.curdir), base)
481
if language == 'c++':
482
target_c = target_c + "pp"
483
cmd += " && cp '%s.c' '%s'"%(name, target_c)
484
if annotate:
485
target_html = '%s/_%s.html'%(os.path.abspath(os.curdir), base)
486
cmd += " && cp '%s.html' '%s'"%(name, target_html)
487
488
if verbose:
489
print(cmd)
490
if os.system(cmd):
491
log = open('%s/log'%build_dir).read()
492
err = subtract_from_line_numbers(open('%s/err'%build_dir).read(), offset)
493
raise RuntimeError, "Error converting %s to C:\n%s\n%s"%(filename, log, err)
494
495
if language=='c++':
496
os.system("cd '%s' && mv '%s.c' '%s.cpp'"%(build_dir,name,name))
497
498
## if make_c_file_nice and os.path.exists(target_c):
499
## R = open(target_c).read()
500
## R = "/* THIS IS A PARSED TO MAKE READABLE VERSION OF THE C FILE. */" + R
501
502
## # 1. Get rid of the annoying __pyx_'s before variable names.
503
## # R = R.replace('__pyx_v_','').replace('__pyx','')
504
## # 2. Replace the line number references by the actual code from the file,
505
## # since it is very painful to go back and forth, and the philosophy
506
## # of Sage is that everything that can be very easy *is*.
507
508
## pyx_file = os.path.abspath('%s/%s.pyx'%(build_dir,name))
509
## S = '/* "%s":'%pyx_file
510
## n = len(S)
511
## last_i = -1
512
## X = F.split('\n')
513
## stars = '*'*80
514
## while True:
515
## i = R.find(S)
516
## if i == -1 or i == last_i: break
517
## last_i = i
518
## j = R[i:].find('\n')
519
## if j == -1: break
520
## line_number = int(R[i+n: i+j])
521
## try:
522
## line = X[line_number-1]
523
## except IndexError:
524
## line = '(missing code)'
525
## R = R[:i+2] + '%s\n\n Line %s: %s\n\n%s'%(stars, line_number, line, stars) + R[i+j:]
526
527
## open(target_c,'w').write(R)
528
529
530
cmd = 'cd %s && python setup.py build 1>log 2>err'%build_dir
531
if verbose:
532
print(cmd)
533
if os.system(cmd):
534
log = open('%s/log'%build_dir).read()
535
err = open('%s/err'%build_dir).read()
536
raise RuntimeError, "Error compiling %s:\n%s\n%s"%(filename, log, err)
537
538
# Move from lib directory.
539
cmd = 'mv %s/build/lib.*/* %s'%(build_dir, build_dir)
540
if verbose:
541
print(cmd)
542
if os.system(cmd):
543
raise RuntimeError, "Error copying extension module for %s"%filename
544
545
if create_local_so_file:
546
# Copy from lib directory into local directory
547
libext = 'so'
548
UNAME = os.uname()[0].lower()
549
if UNAME[:6] == 'cygwin':
550
libext = 'dll'
551
cmd = 'cp %s/%s.%s %s'%(build_dir, name, libext, os.path.abspath(os.curdir))
552
if os.system(cmd):
553
raise RuntimeError, "Error making local copy of shared object library for %s"%filename
554
555
return name, build_dir
556
557
558
559
def subtract_from_line_numbers(s, n):
560
r"""
561
Given a string ``s`` and an integer ``n``, for any line of ``s`` which has
562
the form ``'text:NUM:text'`` subtract ``n`` from NUM and return
563
``'text:(NUM-n):text'``. Return other lines of ``s`` without change.
564
565
EXAMPLES::
566
567
sage: from sage.misc.cython import subtract_from_line_numbers
568
sage: subtract_from_line_numbers('hello:1234:hello', 3)
569
'hello:1231:hello\n'
570
sage: subtract_from_line_numbers('text:123\nhello:1234:', 3)
571
'text:123\nhello:1231:\n'
572
"""
573
ans = []
574
for X in s.split('\n'):
575
i = X.find(':')
576
j = i+1 + X[i+1:].find(':')
577
try:
578
ans.append('%s:%s:%s\n'%(X[:i], int(X[i+1:j]) - n, X[j+1:]))
579
except ValueError:
580
ans.append(X)
581
return '\n'.join(ans)
582
583
584
################################################################
585
# COMPILE
586
################################################################
587
def cython_lambda(vars, expr,
588
verbose=False,
589
compile_message=False,
590
use_cache=False):
591
"""
592
Create a compiled function which evaluates ``expr`` assuming machine values
593
for ``vars``.
594
595
INPUT:
596
597
- ``vars`` - list of pairs (variable name, c-data type), where the variable
598
names and data types are strings, OR a string such as ``'double x, int y,
599
int z'``
600
601
- ``expr`` - an expression involving the vars and constants; you can access
602
objects defined in the current module scope ``globals()`` using
603
``sage.object_name``.
604
605
.. warning::
606
607
Accessing ``globals()`` doesn't actually work, see :trac:`12446`.
608
609
EXAMPLES:
610
611
We create a Lambda function in pure Python (using the r to make sure the 3.2
612
is viewed as a Python float)::
613
614
sage: f = lambda x,y: x*x + y*y + x + y + 17r*x + 3.2r
615
616
We make the same Lambda function, but in a compiled form. ::
617
618
sage: g = cython_lambda('double x, double y', 'x*x + y*y + x + y + 17*x + 3.2')
619
sage: g(2,3)
620
55.2
621
sage: g(0,0)
622
3.2
623
624
The following should work but doesn't, see :trac:`12446`::
625
626
sage: a = 25
627
sage: f = cython_lambda('double x', 'sage.math.sin(x) + sage.a')
628
sage: f(10) # known bug
629
24.455978889110629
630
sage: a = 50
631
sage: f(10) # known bug
632
49.455978889110632
633
"""
634
if isinstance(vars, str):
635
v = vars
636
else:
637
v = ', '.join(['%s %s'%(typ,var) for typ, var in vars])
638
639
s = """
640
class _s:
641
def __getattr__(self, x):
642
return globals()[x]
643
644
sage = _s()
645
646
def f(%s):
647
return %s
648
"""%(v, expr)
649
if verbose:
650
print(s)
651
import sage.misc.misc
652
tmpfile = sage.misc.temporary_file.tmp_filename(ext=".spyx")
653
open(tmpfile,'w').write(s)
654
655
import sage.server.support
656
d = {}
657
sage.server.support.cython_import_all(tmpfile, d,
658
verbose=verbose, compile_message=compile_message,
659
use_cache=use_cache,
660
create_local_c_file=False)
661
return d['f']
662
663
def cython_create_local_so(filename):
664
r"""
665
Compile filename and make it available as a loadable shared object file.
666
667
INPUT:
668
669
- ``filename`` - string: a Cython (.spyx) file
670
671
OUTPUT: None
672
673
EFFECT: A compiled, python "importable" loadable shared object file is created.
674
675
.. note::
676
677
Shared object files are *not* reloadable. The intent is for
678
imports in other scripts. A possible development cycle might
679
go thus:
680
681
- Attach a .spyx file
682
- Interactively test and edit it to your satisfaction
683
- Use ``cython_create_local_so`` to create the shared object file
684
- Import the .so file in other scripts
685
686
EXAMPLES::
687
688
sage: curdir = os.path.abspath(os.curdir)
689
sage: dir = tmp_dir(); os.chdir(dir)
690
sage: f = file('hello.spyx', 'w')
691
sage: s = "def hello():\n print 'hello'\n"
692
sage: f.write(s)
693
sage: f.close()
694
sage: cython_create_local_so('hello.spyx')
695
Compiling hello.spyx...
696
sage: sys.path.append('.')
697
sage: import hello
698
sage: hello.hello()
699
hello
700
sage: os.chdir(curdir)
701
702
AUTHORS:
703
704
- David Fu (2008-04-09): initial version
705
"""
706
cython(filename, compile_message=True, use_cache=False, create_local_so_file=True)
707
708
def sanitize(f):
709
"""
710
Given a filename ``f``, replace it by a filename that is a valid Python
711
module name.
712
713
This means that the characters are all alphanumeric or ``_``'s and doesn't
714
begin with a numeral.
715
716
EXAMPLES::
717
718
sage: from sage.misc.cython import sanitize
719
sage: sanitize('abc')
720
'abc'
721
sage: sanitize('abc/def')
722
'abc_def'
723
sage: sanitize('123/def-hij/file.py')
724
'_123_def_hij_file_py'
725
"""
726
s = ''
727
if f[0].isdigit():
728
s += '_'
729
for a in f:
730
if a.isalnum():
731
s += a
732
else:
733
s += '_'
734
return s
735
736
737
def compile_and_load(code):
738
r"""
739
INPUT:
740
741
- ``code`` -- string containing code that could be in a .pyx file
742
that is attached or put in a %cython block in the notebook.
743
744
OUTPUT: a module, which results from compiling the given code and
745
importing it
746
747
EXAMPLES::
748
749
sage: module = sage.misc.cython.compile_and_load("def f(int n):\n return n*n")
750
sage: module.f(10)
751
100
752
"""
753
from sage.misc.temporary_file import tmp_filename
754
file = tmp_filename(ext=".pyx")
755
open(file,'w').write(code)
756
from sage.server.support import cython_import
757
return cython_import(file, create_local_c_file=False)
758
759
760
TESTS = {
761
'trac11680':"""
762
#cargs -std=c99 -O3 -ggdb
763
#cinclude $SAGE_SRC/sage/libs/flint $SAGE_LOCAL/include/FLINT
764
#clib flint
765
766
from sage.rings.rational cimport Rational
767
from sage.rings.polynomial.polynomial_rational_flint cimport Polynomial_rational_flint
768
from sage.libs.flint.fmpq_poly cimport (fmpq_poly_get_coeff_mpq, fmpq_poly_set_coeff_mpq,
769
fmpq_poly_length)
770
771
def evaluate_at_power_of_gen(Polynomial_rational_flint f, unsigned long n):
772
assert n >= 1
773
cdef Polynomial_rational_flint res = f._new()
774
cdef unsigned long k
775
cdef Rational z = Rational(0)
776
for k in range(fmpq_poly_length(f.__poly)):
777
fmpq_poly_get_coeff_mpq(z.value, f.__poly, k)
778
fmpq_poly_set_coeff_mpq(res.__poly, n*k, z.value)
779
return res
780
""",
781
782
'trac11680b':"""
783
def f(int a, int b, int c):
784
return a+b+c
785
"""
786
}
787
788
def import_test(name):
789
"""
790
This is used by the testing infrastructure to test building
791
Cython programs.
792
793
INPUT:
794
795
- ``name`` -- string; name of a key to the TESTS dictionary above
796
797
OUTPUT: a module, which results from compiling the given code and importing it
798
799
EXAMPLES::
800
801
sage: module = sage.misc.cython.import_test("trac11680b")
802
sage: module.f(2,3,4)
803
9
804
"""
805
return compile_and_load(TESTS[name])
806
807
808