A modular extension for a computer algebra system
Migran N. Gevorkyan, Anna V. Korolkova, Dmitry S. Kulyabov, Leonid A. Sevastianov
aa r X i v : . [ c s . M S ] M a y A modular extension for a computer algebra system
Migran N. Gevorkyan, ∗ Anna V. Korolkova, † Dmitry S. Kulyabov,
1, 2, ‡ and Leonid A. Sevastianov
1, 3, § Department of Applied Probability and Informatics,Peoples’ Friendship University of Russia (RUDN University),6 Miklukho-Maklaya St, Moscow, 117198, Russian Federation Laboratory of Information TechnologiesJoint Institute for Nuclear Research6 Joliot-Curie, Dubna, Moscow region, 141980, Russia Bogoliubov Laboratory of Theoretical PhysicsJoint Institute for Nuclear Research6 Joliot-Curie, Dubna, Moscow region, 141980, Russia
Computer algebra systems are complex software systems that cover a wide range of scientific andpractical problems. However, the absolute coverage cannot be achieved. Often, it is required tocreate a user extension for an existing computer algebra system. In this case, the extensibility ofthe system should be taken into account. In this paper, we consider a technology for extendingthe SymPy computer algebra system with a low-level module that implements a random numbergenerator.
I. INTRODUCTION
The SymPy system [1] is essentially a module writtenin Python [2, 3]. Hence, to extend the functionality ofSymPy, it is sufficient to write a function or module inPython.Even though Python is a universal programming lan-guage that can be used to implement any algorithm, ithas a significant drawback: low performance. Perfor-mance drop is especially noticeable when the algorithmcontains loops. This problem is due to the dynamic na-ture of the language: even elementary data types, e.g.,int and float, are implemented in the standard interpretercpython as composite data structures.Python is well suited for prototyping applications. Inaddition, Python is a kind of glue: it can be used for link-ing different libraries [4]. However, an attempt to write alarge, fast program in this language is likely to fail. Thereason why Python is so successful in solving scientificand engineering problems is that it uses a low-level in-terface to the libraries written in more computationallyefficient programming languages. This gives the impres-sion that Python is as fast as the code written, e.g., inC++. As a result, for practical programming in Python,one should also learn lower-level languages. If standard li-braries are sufficient to solve a problem, then Python maybe the only tool. However, if we need to add new function-ality, then we have to employ lower-level programminglanguages. This paper discusses the use of the Pythonmodule ctypes for the integration of C functions into aPython program. As an example, we consider a librarythat implements a random number generator. This ex- ∗ [email protected] † [email protected] ‡ [email protected] § [email protected] ample is interesting because it is quite resource-intensiveand impractical to implement in pure Python.It should be noted that the idea of using a compiledstatically-typed language to improve the performance ofindividual components of a program written in an inter-preted dynamically-typed language is not new [5, 6]. Onthe contrary, there are many technological approachesthat enable this extension. Moreover, the variety of theseapproaches makes it difficult for novice programmers toenter this area. In this paper, we present a brief overviewof approaches to improving the performance of Pythonprograms.The main focus is placed on using the built-in mod-ule ctypes for the direct call of C functions from Pythonprograms. The choice of this module is substantiated.The discussion is practice focused and addresses the de-velopment of a library in C, as well as its compilation forfurther use with ctypes on various platforms. That partof the paper can be used as an introduction to ctypesfor beginners. The last part of the paper describes ourmodule, which uses ctypes to call functions from the Clibrary that implements a number of pseudorandom gen-erators. We also present some test results to comparethe proposed pseudorandom generator module with thegenerators from the random module and NumPy library. II. IMPROVING THE PERFORMANCE OFSYMPY/PYTHON PROGRAMS
Below we list the basic approaches generally employedto boost Python programs.• The use of third-party libraries, e.g., NumPy [7, 8]and SciPy [9], in which resource-intensive algo-rithms are implemented in C/C++ and Fortran,while Python is used as a glue language to providea convenient programming interface.• The optimizing static compiler Cython [10, 11],which allows Python code to be translated intoC/C++ code. In this case, the program itself iswritten in a special dialect of Python, which en-ables static typing in performance-critical sectionsof the code.• The Numba project [12], which is a JIT compilerfor Python. Numba allows one to write programsin pure Python by using decorators for functionsand loops the performance of which needs to beimproved.• The module ctypes [13] from the standard Cpythoninterpreter library, which allows C functions to bedirectly called from static or shared libraries justas ordinary Python functions.None of these approaches is universal. The specializedlibraries NumPy and SciPy are focused mostly on scien-tific and engineering computational problems, which iswhy their sets of algorithms, although being extensive,are restricted by their specialization. Numba (JIT com-piler) provides a significant increase in speed; however,this project is still under development and its functional-ity is limited by standard application scenarios.Cython (static compiler) is by far one of the most pop-ular tools for program speedup. It is employed in manylibraries, including NumPy and SciPy, to improve theperformance and integration of libraries in C/C++.In this paper, we use the ctypes module as it has sev-eral advantages over Cython that are important for ourresearch:• it is a standard Cpython module, while Cythonmust be installed separately;• it does not mix several dialects of Python;• it is especially useful when the required functional-ity is already implemented in C; in this case, theimplementation of a function call is extremely easyand takes just a few lines of code. It is also use-ful when implemented C functions are simple butactively use loops and primitive data types.Thus, with ctypes, programming is divided into twosteps. At the first step, the programmer implementsfunctions in C and compiles them to assemble a staticor shared (dynamic) library. The second step involvesseparate implementation of some wrapper functions inPython. These wrapper functions provide an interfacefor the convenient call of the implemented C functionsfrom Python programs. Note that the C function callfunctionality is implemented using the ctypes module in-cluded in the standard CPython interpreter library.
III. CTYPES MODULE
This section describes the full cycle of developing aC library and its use in combination with ctypes. The official documentation on ctypes contains some examplesof using functions from this module; however, it does notcover the development of libraries in C.The use of the ctypes module begins with loading alibrary file. That is why, before proceeding to the basicfunctionality of ctypes, we consider an example of assem-bling static and dynamic (shared) libraries by using theC compiler from the gcc compiler set. For this purpose,we used gcc 8.3.0 under GNU Linux (Ubuntu 19.04).
A. Compiling C functions and assembling thelibrary
The code of a typical C library consists of a set ofsource code files ( crc_01.c , crc_02.c , etc.) and a num-ber of header files ( header_01.h , etc.). Common prac-tice [14] is to place all source files in the subdirectory src of the project and header files in the include subdirec-tory.To compile the source files, the following compiler keysare used.• -c : allows one to create an object file without as-sembling the whole program or library.• -Wall : in addition to messages about syntax errors,the compiler prints warnings that can poten- tiallylead to an incorrect operation of the program.• -Werror : the compiler interprets all warnings aserrors.• -fPIC : tells the compiler to translate the pro- graminto position-independent code (PIC), where alljumps are made only at relative addresses. Thisflag is important because the library can be loadedanywhere in the program.• -I./include : tells the compiler to search forheader files in the local include directory of ourproject.• -L./lib : tells the compiler to search for libraryfiles in the local lib directory of our project. It isimportant to follow the sequence and set the -L flagonly after the -I flag.Upon debugging the program, one can also add the op-timization flag: -O2 or -O3 . It should be noted, however,that aggressive optimization in some cases can cause in-correct operation of functions.All flags listed above are stored in the environmentvariable CFLAGS . To compile a source file into an objectfile, the following command is executed for each file: gcc -c $CFLAGS src/src_01.c -o lib/obj_01.o
Once all object files are created, they can be packedinto a static library by using the ar utility; for this pur-pose, the following command is executed: ar crs libmy.a lib/obj_01.o lib/obj_02.o
The crs option means that it is required to create anarchive with the replacement of the files, if it containsany, and also create an index. The successful executionof the command yields a static library file libmy.a , whichcan be used for connection by means of ctypes .To create a static library in Windows, the -Wl option isused, which allows one to pass additional options to thelinker and specify (using the linker option --out-implib )the path to the file of the static library that needs to becreated. gcc -shared lib/obj_01.o lib/obj_02.olibmy.so -o bin/libmy.dll-Wl,--out-implib,lib/win/libmy.a ֒ → ֒ → If necessary, a shared library (rather than a static one)can be created: gcc -shared lib/obj_01.o lib/obj_02.o libmy.so
This command yields a shared library file libmy.so . Thesame command allows one to create a dynamic library inWindows. It is only required to specify the file extension .dll instead of .so . B. Loading the library into the python program
Assuming that we have successfully compiled and as-sembled the library libmy.so , we can proceed to its im-port into the Python program. Suppose that the libraryfile is in the same directory as our Python program. Letus consider the following code fragment. import ctypesimport sysimport ospath = os.path.dirname(os.path.realpath(__file__ )) ֒ → if sys.platform.startswith('win'):clib = ctypes.CDLL(os.path.join(path,'libmy.dll')) ֒ → else:clib = ctypes.CDLL(os.path.join(path,'libmy.so')) ֒ → First, the ctypes module and some additional modulesneed to be loaded. Next, we obtain the absolute path tothe directory of the program. Then, we determine thetype of the operating system and, depending on it, loadthe .dll or .so file.It should be noted that the library file must be loadedby the absolute path if we organize our Python programas a module and want to store the library file in themodule’s directory. C. Calling functions form the library
Once imported, all library functions are available forcalling as attributes of the clib object. Suppose that thelibmy library contains the following function: uint64_t uint64_var(uint64_t var) {uint64_t i = 9223372036854775807llu;printf("Function uint64_var, arg uint64var = %llu\n", i); ֒ → return i;} To call it from the Python program, the following codeis used: clib.uint64_var.argtypes = [ctypes.c_uint64]clib.uint64_var.restype = ctypes.c_uint64res = clib.uint64_var(0)
Before calling the function, the type of its argument isspecified using a single-element list because it has onlyone argument. Next, the type of the return value is speci-fied; once this is done, the function can be called. Ctypesdefines all standard types of C, and these three lines ofcode cover all instances of calling any simple functionthat receives and returns arguments of basic types.Let us consider a more complex example where theargument is passed to the function by a pointer. Sup-pose that we have the following C function: void change_var(double* var) {*var = 2.0;}
The following code illustrates a way to call this func-tion by using ctypes : x = ctypes.c_double(1.0)print(f"x = {x.value}")clib.change_var(ctypes.byref(x))print(f"x = {x.value}") Here, we first assign the value of the variable x byusing the constructor c_double and then pass it as anargument to the function change_var while additionallyspecifying (by using byref ) that the argument is passedby a reference. Since the function does not return anyvalues, it is not necessary to specify restype , and, havingpassed the variable of the known type as an argument, wedo not have to specify the type of the argument.Finally, let us consider the call of a function that takesan array as an argument: double avg_value(long long int array[],size_t len) { ֒ → double avg = 0.0;for (size_t i = 0; i < len; ++i) {avg += (double) array[i] / (double)len; ֒ → array[i] = 0.0; }return avg;} The function avg_value takes an array as the firstargument and an integer (the size of the array) as thesecond argument. To facilitate the call of this functionfrom the Python code, we can write the following wrap-per function. def avg_value(l: list) -> float: """avg_value wrapper""" clib.avg_value.restype =ctypes.c_longdouble ֒ → A = (ctypes.c_longlong * len(l))(*l)n = ctypes.c_size_t(len(l))return clib.avg_value(A, n)
First, the return type ( long double ) is specified; next,memory is allocated for the array, which is immediatelyinitialized with values from the list l . Then, the variable n of the type size_t is created, and the C function iscalled.Using wrapper functions is justified in most cases asit allows one to hide the routine operations on argumentinitialization and data type specification, thus providinga user-friendly interface. IV. GENERATION OF PSEUDORANDOMNUMBERS
The generation of truly random numbers is quite a dif-ficult problem. For this purpose, various physical pro-cesses are generally used. The main problem of truerandom number generators is their low rate of genera-tion [15]. Thus, in practice, pseudorandom generatorsare employed [16, 17].The SymPy package does not implement individualpseudorandom generators because all necessary function-ality is provided by the standard module random andsubmodule numpy.random of the NumPy library.The functions of both the modules are based on theMersenne Twister (MT) algorithm [18], which generatesuniformly distributed pseudorandom sequences of un-signed integers. This algorithm yields high-quality se-quences of pseudorandom numbers; however, it has rela-tively low performance due to its awkwardness. Presently,a number of alternative algorithms appeared [19–22],which also yield high-quality sequences of pseudorandomnumbers while outperforming the MT in speed.Modern algorithms for pseudorandom number gener-ation use bitwise logical operations and shift operations,which is why system programming languages that provideminimum abstractions in favor of maximum performanceare a natural choice for implementing these algorithms.Most developers also implement them in C or C++.These implementations are compact functions with thefollowing signature: uint32_t generator(uint32_t seed[]); /* or */ uint64_t generator(uint64_t seed[]); where the array seed contains the initial values for theinitialization of the generator. To generate a sequence ofpseudorandom numbers, it is sufficient to call this func-tion in a loop N times. void rand_n(uint64_t N, uint64_t seed[],uint64_t res[]) { ֒ → for (uint64_t i=0; i < N; ++i) {res[i] = generator(seed);}} The internal state of the generator is determined by aset of numbers seed and is preserved from call to callbecause the seed array is passed by a reference.To obtain numbers from the half-interval [0 , , it issufficient to normalize the numbers generated: double normed_gen(uint64_t seed[]) {return (double) generator(seed) /(double) UINT64_MAX; ֒ → } A. Library structure
We implemented a compact library in C [23] thatincludes some modern pseudorandom generators [19–22, 24]. The library has the following structure.• The directory src contains the source files of vari-ous pseudorandom generator algorithms (the nameof the file corresponds to the name of the algo-rithm).• The directory include contains a single header filein which all functions implemented in the libraryare declared.• The directory tools stores a C program that im-plements the command-line utility random , which isused to run any generator and print the resultingsequence.• To assemble the library and utility under Unix-likeoperating systems, a makefile was written; for Win-dows, a .bat file is used.• After compilation and assembly, we obtain files ofthe shared and static libraries that are located inthe directories lib/shared and lib/static , re-spectively. In addition, the command utility ran-dom is assembled in the directory bin . B. Wrapping the library by using ctypes
The library described above is integrated into thePython/SymPy environment by using the standard mod-ule ctypes and is designed as a Python module called crandom . The functionality of the module is implementedas a class
Random stored in the file crandom.py .For correct operation, the standard modules random , typing , ctypes , sys , and os are required. In addition,to generate pseudorandom arrays, the NumPy library isemployed.Let us consider the basic capabilities of crandom onsome examples. To run the examples, we used Python3.6.8 Miniconda and Jupyter 4.4.0.The work with the module begins with the selectionand initialization of the generator. Consider the followingexample. import crandomgen = crandom.Random('xorshift+')gen.set_seed([233, 43]) Here, the object gen is created for the algorithm xor-shift+ . In this example, the generator is also initializedby passing it two integers with the use of the methodfunction set_seed . If the generator is not initialized byexplicitly calling set_seed , then the function randint from the standard module random is employed. In addi-tion, when selecting initial values, we should adhere tocertain recommendations [25], and numbers 233 and 43were selected so as not to overload the example.The state of the generator is stored in the seed at-tribute of the gen object. Depending on the type of thegenerator, seed can be either a single unsigned integer ora sequence of unsigned integers. When called without ar-guments, the method function set_seed determines thenumber of integers required for initialization.Once the gen object is created and initialized, it canbe used to derive a pseudorandom sequence of a specifiedsize. This can be done as follows: r = gen.generate(size=10)r = gen.generate(size=10, type=float)r = gen(size=10)
The first call yields a sequence of ten unsigned integers.During the second call, the optional argument type re-ceives float , which results in a sequence of floating-pointnumbers generated on the half-interval [0 , . Finally, thethird line demonstrates that it is not necessary to use thefunction method generate because the class defines themethod __call__ and the object itself can be called asa function.The array of pseudorandom numbers is generated bythe function written in C (each generator has its ownfunction). Then, this array is converted to a numpy array.For this purpose, the function np.array with the option copy=False is called, which allows the substitution tobe carried out in place (instead of copying the array inmemory). The state of the generator is preserved by means of thePython program. Once the sequence is generated, its lastelements are written into the seed attribute to be usedas new initial values.If memory saving is a priority, then the generator canrun in iterator mode as the class implements the specialmethod functions __next__ and __iter__ . The follow-ing example illustrates this. gen.set_iterator(10, int)for i in gen:print(i) The iterator is initialized by the function set_iterator . As arguments, the quantity of pseudo-random numbers to be generated and the type of thenumbers ( int or float ) are specified. Then, the gen object can be used in a loop just as the standard Pythoniterator. In this case, the loop is implemented in Python;as a result, the performance is lower than that whenusing the function generate . The state of the generatoris preserved as in the case of generate . C. Performance testing
The use of C functions allows us to achieve high perfor-mance. For example, let us compare our generator witha generator from the NumPy randint library. Perfor-mance is measured by the command %timeit built intothe interactive shells iPython and
Jupyter . As an argu-ment, it receives the code fragment the performance ofwhich needs to be evaluated. As a result, we obtain theaverage runtime of the code, its standard deviation, andthe number of code executions. %timeit np.random.randint(low=0,high=(2**64-1), size=10000,dtype=np.uint64) ֒ → ֒ → %timeit gen.generate(size=10000) To generate a sequence of 64-bit unsigned integers,when calling the function randint , we must set the value np.uint64 of the argument dtype , as well as specify the low and high boundaries.For both functions, the command %timeit made sevenruns with 10000 repetitions in each. The average runtimeof the function randint was 85.4 microseconds with thestandard deviation of 1.67 microseconds. For the func-tion generate , it was 39.4 microseconds with the stan-dard deviation of 1.18 microseconds.With the generators in the NumPy library also beingimplemented in C, this difference can be explained bya higher efficiency of the xorshift algorithm. It shouldalso be noted that the library was compiled with the -O3 optimization key.Note that the standard module random does not con-tain any functions for generating sequences of integers.Instead, we can use multiple calls of the randint func-tion and list assembly, which is `a priori slower than theNumPy version (the time measurement yields 11.6 mil-liseconds).To check the correctness of the generator implemen-tations, a number of visual tests were conducted. Thefollowing diagrams were constructed:• scatter plot;• lag plot;• autocorrelation function (ACF) plot.These visual tests allowed us to estimate the indepen-dency of distribution for the derived sequences of pseu-dorandom numbers. For more rigorous testing, we usedthe following test sets: DieHarder [26], TestU01 [27, 28],PractRand [29], and gjrand [30]. The results of theDieHarder tests are available in the repository [23].
V. CONCLUSIONS
Our library and module for its integration into theSymPy/Python environment can be easily extended by adding new C functions with the corresponding wrappingfunctions in Python.It should also be noted that, in the case of pseudo-random generators, the choice of the ctypes module isreasonable because the algorithms implemented use bit-wise operations and primitive data types. Hence, theirfull implementation in a system programming languageprovides a significant increase in performance and de-crease in memory consumption.
ACKNOWLEDGMENTS
The publication has been prepared with the support ofthe “RUDN University Program 5-100”. [1] R. Lamy, Instant SymPy Starter, Packt Publishing, 2013.[2] B. Slatkin, Effective Python 90 Specific Ways to WriteBetter Python, Effective Software Development, 2 ed.,Addison-Wesley Professional, 2019.[3] B. Lubanovic, Introducing Python: Modern Computingin Simple Packages, 2 ed., O’Reilly Media, 2019.[4] D. S. Kulyabov, A. V. Korol’kova, L. A. Sev-ast’yanov, New Features in the Second Version ofthe Cadabra Computer Algebra System, Program-ming and Computer Software 45 (2019) 58–64. doi:doi:10.1134/S0361768819020063. arXiv:1906.02599 .[5] V. Aladjev, M. Bogdevicius, Maple: Programming, Phys-ical and Engineering Problems, 2006.[6] R. M. Corless, Essential Maple 7: An Introduction forScientific Programmers, Springer Science and BusinessMedia, 2007.[7] I. Idris, NumPy Cookbook, Packt Publishing, 2012.[8] T. E. Oliphant, Guide to NumPy, 2 ed., CreateSpaceIndependent Publishing Platform, 2015.[9] T. E. Oliphant, Python for Scientific Computing, Com-puting in Science and Engineering 9 (2007) 10–20. doi:doi:10.1109/MCSE.2007.58.[10] S. Behnel, R. Bradshaw, C. Citro, L. Dalcin, D. S. Sel-jebotn, K. Smith, Cython: The Best of Both Worlds,Computing in Science and Engineering 13 (2011) 31–39.doi:doi:10.1109/MCSE.2010.118.[11] K. Smith, Cython. A Guide for Python Programmers,O’Reilly Media, 2015.[12] S. K. Lam, A. Pitrou, S. Seibert, Numba: a LLVM-basedPython JIT compiler, in: Proceedings of the SecondWorkshop on the LLVM Compiler Infrastructure in HPC,LLVM ’15, ACM Press, Austin, Texas, 2015, pp. 7.1–6.doi:doi:10.1145/2833157.2833162.[13] M. Spreitzenbarth, J. Uhrmann, Mastering Python Forensics, Packt Publishing, 2015.[14] B. Klemens, 21st Century C, 2 ed., O’Reilly Media, 2014.[15] F. GALTON, Dice for Statistical Experiments, Nature42 (1890) 13–14. doi:doi:10.1038/042013a0.[16] D. E. Knuth, The Art of Computer Programming, vol-ume 2, 3rd ed., Addison-Wesley Longman Publishing Co.,Inc., Boston, MA, USA, 1997.[17] M. N. Gevorkyan, A. V. Demidova, A. V. Korolkova,D. S. Kulyabov, L. A. Sevastianov, I. M. Gostev, Pseudo-random number generator based on neural network, in:V. Korenkov, A. Nechaevskiy, T. Zaikina, E. Mazhitova(Eds.), Selected Papers of the 8th International Confer-ence "Distributed Computing and Grid-technologies inScience and Education", volume 2267 of
CEUR Work-shop Proceedings , Dubna, 2018, pp. 568–572.[18] M. Matsumoto, T. Nishimura, Mersenne Twister: A 623-dimensionally Equidistributed Uniform Pseudo-randomNumber Generator, ACM Trans. Model. Comput. Simul.8 (1998) 3–30. doi:doi:10.1145/272991.272995.[19] G. Marsaglia, Xorshift RNGs, Journal of Statistical Soft-ware 8 (2003) 1–6. doi:doi:10.18637/jss.v008.i14.[20] F. Panneton, P. L’Ecuyer, On the Xorshift Random Num-ber Generators, ACM Trans. Model. Comput. Simul. 15(2005) 346–361.[21] P. Boldi, S. Vigna, On the Lattice of Antichains of FiniteIntervals, Order 35 (2018) 57–81. doi:doi:10.1007/s11083-016-9418-8.[22] M. E. O’Neill, PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Num-ber Generation, Technical Report HMC-CS-2014-0905,Harvey Mudd College, Claremont, CA, 2014.[23] M. N. Gevorkyan, D. S. Kulyabov, A. V. Ko-rolkova, L. A. Sevastianov, Random Number Gen-erators for Computer Algebra Systems, 2019. URL: https://bitbucket.org/yamadharma/articles-2019-rng-generator-code .[24] G. G. Rose, KISS: A bit too simple, Cryptogra-phy and Communications 10 (2018) 123–137. doi:doi:10.1007/s12095-017-0225-x.[25] D. Jones, Good practice in (pseudo) random number gen-eration for bioinformatics applications, 2010.[26] R. G. Brown, D. Eddelbuettel, D. Bauer, Dieharder:A Random Number Test Suite, 2017. URL: https://webhome.phy.duke.edu/~rgb/General/dieharder.php .[27] P. L’Ecuyer, R. Simard, TestU01: A C library for empiri-cal testing of random number generators, ACM Transac-tions on Mathematical Software (TOMS) 33 (2007) 22.[28] P. L’Ecuyer, R. Simard, TestU01 — Empirical Test-ing of Random Number Generators, 2009. URL: http://simul.iro.umontreal.ca/testu01/tu01.html .[29] C. Doty-Humphrey, PractRand official site, 2018. URL: http://pracrand.sourceforge.net/ .[30] Gjrand random numbers official site, 2014. URL: http://gjrand.sourceforge.net/ . r X i v : . [ c s . M S ] M a y Пример модульного расширения системы компьютерной алгебры
М. Н. Геворкян, ∗ А. В. Королькова, † Д. С. Кулябов,
1, 2, ‡ и Л. А. Севастьянов § Кафедра прикладной информатики и теории вероятностей,Российский университет дружбы народов,117198, Москва, ул. Миклухо-Маклая, д. 6 Лаборатория информационных технологий,Объединённый институт ядерных исследований,ул. Жолио-Кюри 6, Дубна, Московская область, Россия, 141980 Лаборатория теоретической физики,Объединённый институт ядерных исследований,ул. Жолио-Кюри 6, Дубна, Московская область, Россия, 141980
Системы компьютерной алгебры представляют из себя сложные программные комплексы,охватывающие широкий спектр научных и практических проблем. Однако абсолютная полно-та недостижима. И зачастую возникает задача создания пользовательского расширения суще-ствующей системы компьютерной алгебры. При этом следует учитывать расширяемость самойсистемы. В статье рассматривается технология расширения системы компьютерной алгебрыSymPy низкоуровневым модулем, реализующим генератор случайных чисел.
I. ВВЕДЕНИЕ
Система компьютерной алгебры SymPy [1] явля-ется по своей сути модулем, написанным на языкеPython [2, 3], поэтому для расширения функциональ-ности SymPy достаточно написать функцию или мо-дуль на самом Python.Хотя язык Python является универсальным язы-ком программирования и на нем можно реализоватьлюбые алгоритмы, однако ему присущ существен-ный недостаток — малая производительность. Паде-ние быстродействия особенно заметно в случае еслив алгоритме присутствуют циклы. Данная проблемаобусловлена динамической природой языка из-за кото-рой даже элементарные типы данных, такие как int и float реализованы в стандартном интерпретаторе cpython в виде составных структур данных.Язык Python хорошо подходит для прототипирова-ния приложения. Также язык Python является в неко-тором роде клеем — он хорошо подходит для связыва-ния разных библиотек вместе [4]. Но попытка создатьна основе этого языка большую быструю программускорее всего обречена на провал. Причина, по которойон так успешно справляется в научными и инженер-ными задачами состоит в том, что Python использу-ет низкоуровневый интерфейс к библиотекам, напи-санным на более вычислительно эффективных язы-ках программирования. Таким образом создаётся впе-чатление, что Python работает так же быстро, как икод, написанный, например, на C++. Побочным эф-фектом этого является то, что для практического про-граммирования на Python необходимо также програм-мировать и на языках более низкого уровня. Если для ∗ [email protected] † [email protected] ‡ [email protected] § [email protected] задачи достаточно использования стандартных биб-лиотек, то ничего кроме Python может и не понадобит-ся. Однако, если необходимо добавить новую функци-ональность, то следует использовать более низкоуров-невые языки программирования.В данной статье рассмотрено использование моду-ля языка Python ctypes для интеграции C-функций вPython-программу. В качестве примера рассматрива-ется библиотека, реализующая генератор случайныхчисел. Этот пример интересен тем, что он является до-статочно ресурсоёмким и его нецелесообразно делатьна чистом Python.Следует заметить, что идея использования компи-лируемого языка со статической типизацией для повы-шения производительности отдельных элементов про-граммы, написанной на интерпретируемом языке с ди-намической типизацией, не является новой [5, 6]. На-против, исторически сложилось множество техноло-гических подходов, позволяющих осуществить такоерасширение. Такое многообразие подходов затрудняетдоступ начинающих в эту область. В нашей статье да-ётся краткий обзор подходов, позволяющих повыситьбыстродействие Python-программ и краткая характе-ристика каждого из них.Основной упор делается на использование встроен-ного модуля ctypes для непосредственного вызова C-функций из Python-программ. Обосновывается выборименно данного модуля. Изложение носит практиче-ский характер и затрагивает вопросы создания биб-лиотеки на языке Си, ее компиляции для дальнейше-го использования с ctypes под различные платформы.Данная часть статьи может использоваться как вве-дение в возможности ctypes для начинающих. В по-следней части статьи описывается созданный нами мо-дуль, использующий ctypes для вызова функций избиблиотеки на языке C, которая реализует ряд генера-торов псевдослучайных чисел. Приведены тесты длясравнения предложенного модуля генератора псевдо-случайных чисел с имеющимися генераторами из мо-дуля random и библиотеки NumPy . II. СПОСОБЫ ПОВЫШЕНИЯБЫСТРОДЕЙСТВИЯ SYMPY/PYTHONПРОГРАММ
Перечислим основные средства, которые использу-ются в настоящее время для повышения быстродей-ствия Python-программ.• Использование сторонних библиотек, таких как
NumPy [7, 8] и
SciPy [9], в которых ресурсоём-кие алгоритмы реализованы на языках С/С++и Fortran, а сам Python используется как язык-связка для предоставления удобного программ-ного интерфейса.• Оптимизирующий статический компилятор
Cython [10, 11] который позволяет транслиро-вать Python-код в код на C/C++. При этомсама программа пишется на специальном диа-лекте Python, который позволяет применятьстатическую типизацию в критичных дляпроизводительности участках кода.• Проект
Numba [12], представляющий собой JIT-компилятор Python кода. Numba позволяет пи-сать программу на чистом Python, используядекораторы для функций и циклов, производи-тельность которых необходимо повысить.• Модуль ctypes [13] из стандартной библиоте-ки интерпретатора Cpython, который позволяетнепосредственно вызвать C-функции, из стати-ческих или разделяемых (shared) библиотек, какобычные Python-функции.Заметим, что ни одно из перечисленных средств неявляется универсальным. Специализированные биб-лиотеки
NumPy и SciPy нацелены в основном на на-учные и инженерные вычислительные задачи, поэто-му реализуемый ими набор алгоритмов хоть и обши-рен, но ограничен рамками этой специализации. JIT-компилятор
Numba даёт существенный прирост ско-рости, однако проект пока находится на стадии разра-ботки и его функциональные возможности ограниче-ны стандартными сценариями применения.Статический компилятор
Cython на сегодняшнийдень является одним из самых часто используемыхсредств для повышения производительности. Его ис-пользуют многие библиотеки, в том числе
NumPy и SciPy для повышения производительности и интегра-ции библиотек на C/C++.В данной работе мы используем модуль ctypes , по-скольку для наших задач он имеет ряд преимуществнад
Cython :• это стандартный модуль Cpython, тогда как
Cython необходимо устанавливать отдельно; • не смешиваются несколько диалектов языкаPython;• ctypes особенно полезен, если необходимаяфункциональность уже реализован на С. В этомслучае подготовка вызова функций крайне про-ста и занимает буквально несколько строк ко-да. Также его разумно применять в случае, еслиреализуемые C-функции просты, но активно ис-пользуют циклы и работу с примитивными ти-пами данных.Таким образом, при использовании ctypes работанад программой делится на два этапа. На первом эта-пе программист реализует функции на языке C, ком-пилирует и собирает из них статическую или разде-ляемую (динамическую) библиотеку. Второй шаг —отдельная реализация ряда функций-обёрток уже наязыке Python. Данные функции-обёртки по сути пред-ставляют собой интерфейс для удобного вызова ужереализованных C-функций из Python-программ. От-метим, что функциональность вызова C-функций реа-лизуется с помощью модуля ctypes , входящего в стан-дартную библиотеку интерпретатора CPython.
III. МОДУЛЬ CTYPES
В данном разделе описан полный цикл созданиябиблиотеки на языке Си и ее использование совместнос сtypes. Официальная документация ctypes приводитпримеры использования функций из данного модуля,однако в ней не затрагивается процесс создания биб-лиотеки на языке C.Использование модуля сtypes начинается с загрузкифайла библиотеки, поэтому прежде чем переходитьк описанию базовой функциональности ctypes, приве-дём пример сборки статической и динамической (раз-деляемой) библиотек на примере компилятора языкаC из набора компиляторов gcc . Авторы использовалиgcc версии 8.3.0 под операционной системой Gnu Linux,дистрибутив Ubuntu 19.04.
A. Компиляция C-функций и сборка библиотеки
Код типичной библиотеки на языке C состоит из на-бора файлов с исходным кодом ( src_01.c , src_02.c и т.д.) и ряда заголовочных файлов ( header_01.h ит.д.). Общепринятой практикой [14] является размеще-ние всех файлов с исходным кодом в поддиректории src проекта, а заголовочных файлов в поддиректории include .Для компиляции исходных файлов используютсяследующие ключи компилятора.• -c — позволяет создать объектный файл, безсборки всей программы или библиотеки.• -Wall — компилятор будет распечатывать сооб-щения не только о синтаксических ошибках, нои предупреждения, которые потенциально могутпривести к некорректной работе программы.• -Werror — все предупреждения будут интерпре-тироваться компилятором как ошибки.• -fPIC — указывает компилятору о необ-ходимости транслировать программу впозиционно-независимый машинный код(position-independent code — PIC), где всепереходы осуществляются только по отно-сительным адресам. Этот флаг важен, таккак библиотека потенциально может бытьзагружена в любом месте программы.• -I./include — указывает компилятору, что фай-лы заголовков следует искать в локальной дирек-тории include нашего проекта.• -L./lib — указывает компилятору, что файлы библиотек следует искать в локальной директо-рии lib нашего проекта. Важно соблюдать по-следовательность и указывать флаг -L толькопосле флага -I .После отладки программы можно также добавитьфлаг оптимизации -O2 или -O3 , помня, однако, чтоагрессивная оптимизация в некоторых случаях можетпривести к некорректной работе функций.Все перечисленные флаги сохраняем в переменнуюокружения CFLAGS и для компиляции файла с исход-ным кодом в объектный файл для каждого файла вы-полняется следующая команда: gcc -c $CFLAGS src/src_01.c -o lib/obj_01.o
После того, как будут созданы все объектные фай-лы, их можно запаковать в статическую библиотеку спомощью утилиты ar , выполнив следующую команду: ar crs libmy.a lib/obj_01.o lib/obj_02.o Опции сrs говорят о том, что нужно создать архивс заменой файлов, если таковые уже в нем есть и до-полнительно создать индекс. После успешного выпол-нения команды получим файл статической библиоте-ки libmy.a , который можно будет использовать дляподключения средствами ctypes .Для создания статической библиотеки в средеWindows следует использовать опцию -Wl , котораяпозволит передать дополнительные опции компонов-щику (linker) и указать с помощью опции компонов-щика --out-implib путь к файлу статической библио-теки, которую необходимо создать. gcc -shared lib/obj_01.o lib/obj_02.olibmy.so -o bin/libmy.dll-Wl,--out-implib,lib/win/libmy.a ֒ → ֒ → При необходимости, можно создать не статическую,а разделяемую библиотеку: gcc -shared lib/obj_01.o lib/obj_02.o libmy.so
В результате получим файл разделяемой библиотеки libmy.so . Та же команда позволяет получить дина-мическую библиотеку и в системе Windows. Следуетлишь указать расширение файла как dll вместо so . B. Загрузка библиотеки в Python программу
Предполагая, что мы успешно скомпилировали и со-брали библиотеку libmy.so , опишем процедуру её им-порта в Python-программу. Предполагаем, что файлбиблиотеки будет находится в той же директории,что и наша Python-программа. Разберём следующийфрагмент кода. import ctypesimport sysimport ospath = os.path.dirname(os.path.realpath(__file__ )) ֒ → if sys.platform.startswith('win'):clib = ctypes.CDLL(os.path.join(path,'libmy.dll')) ֒ → else:clib = ctypes.CDLL(os.path.join(path,'libmy.so')) ֒ → Вначале необходимо загрузить модуль ctypes и ряддополнительных модулей. Далее получаем абсолют-ный путь до директории с программой. Затем опре-деляем тип операционной системы и в зависимости отэтого загружаем файл .dll или .so .Стоит отметить, что загрузка файла библиотеки поабсолютному пути обязательна в том случае, если мыорганизуем нашу Python-программу в виде модуля ихотим хранить файл библиотеки внутри директориимодуля.
C. Вызов функций из библиотеки
После импорта все функции библиотеки будут до-ступны для вызова в виде атрибутов объекта clib .Пусть, например, в библиотеке libmy присутствуетследующая функция: uint64_t uint64_var(uint64_t var) {uint64_t i = 9223372036854775807llu;printf("Function uint64_var, arg uint64var = %llu\n", i); ֒ → return i;} Для её вызова из Python-программы можно использо-вать следующий код: clib.uint64_var.argtypes = [ctypes.c_uint64]clib.uint64_var.restype = ctypes.c_uint64res = clib.uint64_var(0)
Перед вызовом функции мы указали тип аргументаиспользуя список из одного элемента, так как аргу-мент единственный. Далее указывается тип возвраща-емого значения, после чего можно вызвать требуемуюфункцию. В ctypes определены все стандартные типыязыка C и вызов любой простой функции, принимаю-щей и возвращающей аргументы базовых типов, укла-дывается в вышеприведённые три строки кода.Рассмотрим чуть более сложный пример, когда ар-гумент передаётся в функцию по указателю. Пустьимеется следующая C-функция: void change_var(double* var) {*var = 2.0;}
Следующий код показывает способ вызвать этуфункцию с помощью ctypes : x = ctypes.c_double(1.0)print(f"x = {x.value}")clib.change_var(ctypes.byref(x))print(f"x = {x.value}") Здесь мы вначале с помощью конструктора c_double присвоили значение переменной x , а затемпередали её в виде аргумента функции change_var ,указав дополнительно с помощью byref , что аргументпередаётся по ссылке. Так как функция не возвраща-ет никаких значений, то не нужно указывать restype ,а так как мы передали в качестве аргумента перемен-ную уже известного типа, то указывать тип аргументатоже не пришлось.Наконец рассмотрим вызов функции, принимаю-щей в качестве аргумента массив: double avg_value(long long int array[],size_t len) { ֒ → double avg = 0.0;for (size_t i = 0; i < len; ++i) {avg += (double) array[i] / (double)len; ֒ → array[i] = 0.0;}return avg;} Функция avg_value принимает в качестве первогоаргумента массив, а в качестве второго целое чис-ло — размер массива. Для удобного вызова этойфункции из Python-кода можно написать следующуюфункцию-обёртку. def avg_value(l: list) -> float: """avg_value wrapper""" clib.avg_value.restype =ctypes.c_longdouble ֒ → A = (ctypes.c_longlong * len(l))(*l)n = ctypes.c_size_t(len(l))return clib.avg_value(A, n)
Вначале определяется возвращаемый тип( long double ), затем выделяется память для массива,который сразу же инициализируется значениями изсписка l . После чего создаётся переменная n типа size_t и происходит вызов C-функции.Использование обёрточных функций оправданно вбольшинстве случаев, так как позволяет спрятатьрутинные действия по инициализации аргументов иуказанию типов данных, предоставляя пользователюудобный интерфейс. IV. ГЕНЕРАЦИЯ ПСЕВДОСЛУЧАЙНЫХЧИСЕЛ
Получение истинно случайных чисел представляетдостаточно трудную задачу. Обычно для этого исполь-зуют разнообразные физические процессы. Основнойпроблемой генераторов истинно случайных чисел яв-ляется низкая интенсивность генерации случайныхчисел [15]. Поэтому для практических целей исполь-зуют генераторы псевдослучайных чисел [16–20].Пакет SymPy не реализует отдельных генерато-ров псевдослучайных чисел, так как вся необходимаяфункциональность присутствует в стандартном моду-ле random и в подмодуле numpy.random библиотекиNumPy.Функции обоих модулей основаны на алгоритме подназванием вихрь Мерсенна [21], который генерируетпсевдослучайные равномерно распределённые после-довательности беззнаковых целых чисел. Данный ал-горитм позволяет получить качественную последова-тельность псевдослучайных чисел, однако отличаетсясравнительно низкой производительностью ввиду гро-моздкости самого алгоритма. В настоящее время по-явился ряд альтернативных алгоритмов [22–25], кото-рые также генерируют качественные последователь-ности псевдослучайных чисел, но при этом выигрыва-ют в быстродействии.Современные алгоритмы генераторов псевдослучай-ных чисел используют побитовые логические опера-ции и операции сдвига, поэтому естественным выбо-ром для реализации таких алгоритмов являются си-стемные языки программирования, обеспечивающийминимум абстракций в пользу максимального уровнябыстродействия. Большинство разработчиков данныхалгоритмов предоставляют также примеры реализа-ций на языках C или С++.Такие реализации представляют собой компактныефункции, сигнатура которых имеет следующий вид: uint32_t generator(uint32_t seed[]); /* or */ uint64_t generator(uint64_t seed[]); где массив seed представляет собой начальные зна-чения для инициализации генератора. Для генерациипоследовательности псевдослучайных чисел даннуюфункцию достаточно вызвать в цикле N раз. void rand_n(uint64_t N, uint64_t seed[],uint64_t res[]) { ֒ → for (uint64_t i=0; i < N; ++i) {res[i] = generator(seed);}} Внутреннее состояние генератора определяется набо-ром чисел seed и сохраняется от вызова к вызову, таккак массив seed передаётся по ссылке.Для получения чисел из полуинтервала [0 , доста-точно нормировать генерируемые числа. Нижеследу-ющий код показывает как это сделать. double normed_gen(uint64_t seed[]) {return (double) generator(seed) /(double) UINT64_MAX; ֒ → } A. Структура библиотеки
Авторами была реализована компактная библиоте-ка на языке C [26], в которой был собран ряд современ-ных генераторов псевдослучайных чисел [22–25, 27].Библиотека имеет следующую структуру.• В директории src располагаются файлы с исход-ным кодом, реализующие различные алгоритмыгенераторов псевдослучайных чисел. Файлы на-званы именем алгоритма, реализация которогосодержится внутри.• В директории include содержится единствен-ный файл заголовка, в котором объявлены всефункции, реализованные в библиотеке.• В директории tools находятся C-программа, ре-ализующая утилиту командной строки random ,с помощью которой можно запустить любой ге-нератор для вывода сгенерированной последова-тельности на печать.• Для сборки библиотеки и утилиты под опера-ционной системой типа Unix написан makefile,а для сборки под ОС Windows командный bat-файл.• Результатом компиляции и сборки будут файлыразделяемой и статической библиотек, располо-женные в директориях lib/shared и lib/static соответственно. Также в директории bin будетсобрана командная утилита random B. Обёртка библиотеки с помощью ctypes
Вышеописанная библиотека была интегрирована всреду Python/SymPy с помощью стандартного модуля ctypes и оформлена в виде Python-модуля под назва-нием crandom . Все функциональные возможности мо-дуля реализованы в файле crandom.py в виде класса
Random .Для корректного функционирования требуютсястандартные модули random , typing , ctypes , sys и os .Также для генерации массивов псевдослучайных чи-сел требуется библиотека NumPy.Рассмотрим основные возможности crandom на при-мерах. Для запуска примеров использовался дистри-бутив языка Python 3.6.8 Miniconda и интерактивнаяоболочка Jupyter 4.4.0.Работа с модулем начинается с выбора и инициали-зации генератора. Рассмотрим пример: import crandomgen = crandom.Random('xorshift+')gen.set_seed([233, 43]) Здесь мы создали объект gen который будет ис-пользовать алгоритм xorshift+ своей работе. Такжемы инициализировали генератор передав ему два це-лых числа с помощью функции-метода set_seed . Ес-ли генератор не инициализировать явным вызовом set_seed , то будет использована функция randint изстандартного модуля random . Также следует отметить,что при выборе начальных значений следует придер-живаться ряда рекомендаций [28] и числа 233, 43 бы-ли выбраны только для того, чтобы не загромождатьпример.Состояние генератора сохраняется в атрибуте seed объекта gen . В зависимости от типа генератора seed может быть как единственным беззнаковым целымчислом, так и последовательностью беззнаковых це-лых чисел. Вызванная без аргументов, функция-метод set_seed самостоятельно определяет сколькоцелых чисел необходимо для инициализации.После того, как объект gen создан и инициализиро-ван, его можно использовать для получения последо-вательности псевдослучайных чисел заданного разме-ра. Сделать это можно следующими способами. r = gen.generate(size=10)r = gen.generate(size=10, type=float)r = gen(size=10)
При первом вызове будет сгенерированна последо-вательность из 10 беззнаковых целых чисел. При вто-ром вызове необязательному аргументу type передано float , что приводит к генерации последовательностичисел с плавающей запятой из полуинтервала [0 , .Наконец третья строка показывает, что необязатель-но использовать функцию-метод generate так как вклассе определён метод __call__ , и сам объект можновызывать как функцию.Генерацию массива псевдослучайных чисел полно-стью осуществляет функция на языке C (для каждо-го генератора своя). Затем сгенерированный массивконвертируется в numpy-массив. Для конвертации вы-зывается функция np.array с опцией copy=False , чтопозволяет не копировать массив в памяти, а заместитьпо месту (in place).Состояние генератора сохраняется средствамиPython-программы. После того, как последователь-ность сгенерирована, в атрибут seed записываютсяпоследние элементы этой последовательности. Онибудут использованы в качестве новых начальныхзначений.Если приоритетом является экономия памяти, тогенератор можно использовать в режиме итератора,так как в классе реализованы специальные функции-методы __next__ и __iter__ Следующий пример ил-люстрирует как это сделать. gen.set_iterator(10, int)for i in gen:print(i)
Инициализация итератора осуществляется функци-ей set_iterator . В качестве аргументов указываетсяколичество чисел, которое должен произвести генера-тор, и тип чисел ( int или float ). Затем объект gen можно использовать в цикле, как стандартный python-итератор. При этом работает цикл, реализованный наPython, в результате чего производительность нижечем при использовании функции generate . Состояниегенератора сохраняется также, как и при использова-нии generate . C. Тестирование производительности
Использование C-функций позволяет достичь вы-сокой производительности. Сравним, например, рабо-ту нашего генератора с генератором из библиотекиNumPy randint . Быстродействие измеряется коман-дой %timeit , встроенной в интерактивные оболочки iPython и Jupyter . В качестве аргумента ей передаётсяфрагмент кода, быстродействие которого следует за-мерить. В качестве результата распечатывается значе-ние среднего времени работы кода, среднеквадратич-ное отклонение и количество выполнений кода. %timeit np.random.randint(low=0,high=(2**64-1), size=10000,dtype=np.uint64) ֒ → ֒ → %timeit gen.generate(size=10000) Для получения последовательности 64-битных без-знаковых целых чисел, при вызове функции randint необходимо указать значение np.uint64 аргумента dtype , а также указать нижнюю ( low ) и верхнюю( high ) границы.Для обеих функций команда %timeit произвела 7запусков по 10000 повторений в каждом. Среднее вре- мя работы функции randint составило , со стан-дартным отклонением в , микросекунд. Для функ-ции generate — , микросекунд, со стандартнымотклонением , микросекунд.Так как в библиотеке NumPy генераторы также ре-ализованы на языке C, то полученную разницу мож-но объяснить большей эффективностью алгоритма xorshift . Следует также отметить, что компиляциябиблиотеки выполнялась с ключом оптимизации -O3 .Отметим, что в стандартном модуле random нет функции, позволяющей сгенерировать после-довательность целых чисел. Взамен этого можновоспользоваться многократным вызовом функцииmintinlinepython|randint| и списковой сборкой, что бу-дет заведомо медленней NumPy-версии (замер време-ни дает значение , миллисекунд).Для проверки корректности реализации генерато-ров был проведён ряд визуальных тестов. Были по-строены следующие диаграммы:• диаграмма рассеяния (scatter plot);• диаграмма лага (Lag-plot);• диаграмма автокорреляции в зависимости от ла-га (auto-correlation function plot, ACF-plot).Данные визуальные тесты позволяют оценить на-сколько полученная последовательность псевдослу-чайных чисел является независимо распределённойи выявить лишь грубые ошибки. В качестве болеестрогих тестов были использованы наборы тестовDieHarder [29], TestU01 [30, 31], PractRand [32] иgjrand [33]. Отчёты по тестам DieHarder доступны врепозитории [26]. V. ЗАКЛЮЧЕНИЕ
Созданная нами библиотека и модуль для её ин-теграции в среду SymPy/Python могут быть легкорасширены добавлением новых функций на языке Cи соответствующих обёрточные функций на языкеPython.Отметим также, что в случае генераторов псевдо-случайных чисел выбор модуля ctypes был обоснован,так как реализуемые алгоритмы используют побито-вые операции и примитивные типы данных, поэтомуих реализация полностью на системном языке про-граммирования даёт существенное увеличение произ-водительности и уменьшение расхода памяти.
БЛАГОДАРНОСТИ
Публикация подготовлена при поддержке Програм-мы РУДН «5-100». [1] Lamy Ronan. Instant SymPy Starter. — Packt Publish-ing, 2013. — 52 p.[2] Слаткин Бретт. Секреты Python. — М. : Вильямс,2017. — 272 с.[3] Любанович Билл. Простой Python. Современныйстиль программирования. Бестселлеры O’Reilly. — М. :Питер, 2019. — 480 с.[4] Кулябов Дмитрий Сергеевич, Королькова Анна Вла-диславовна, Севастьянов Леонид Антонович. Новыевозможности второй версии пакета компьютерной ал-гебры Cadabra // Программирование. — 2019. — № 2. —С. 41–48.[5]
Aladjev Victor, Bogdevicius Marijonas. Maple: Pro-gramming, Physical and Engineering Problems. —2006. — 403 p.[6] Corless Robert M. Essential Maple 7: An Introductionfor Scientific Programmers. — Springer Science and Busi-ness Media, 2007. — 282 p.[7] Idris Ivan. NumPy Cookbook. — Packt Publishing,2012. — 226 p.[8] Oliphant Travis E. Guide to NumPy. — 2 edition. —CreateSpace Independent Publishing Platform, 2015. —364 p.[9] Oliphant Travis E. Python for Scientific Computing //Computing in Science and Engineering. — 2007. —Vol. 9, no. 3. — P. 10–20.[10] Behnel Stefan, Bradshaw Robert, Citro Craiget al. Cython: The Best of Both Worlds //Computing in Science and Engineering. — 2011. —mar. — Vol. 13, no. 2. — P. 31–39.[11] Smith Kurt. Cython. A Guide for Python Program-mers. — O’Reilly Media, 2015. — 238 p.[12] Lam Siu Kwan, Pitrou Antoine, Seibert Stanley.Numba: a LLVM-based Python JIT compiler // Pro-ceedings of the Second Workshop on the LLVM CompilerInfrastructure in HPC. — LLVM ’15. — Austin, Texas :ACM Press, 2015. — nov. — P. 7.1–6.[13] Spreitzenbarth Michael, Uhrmann Johann. MasteringPython Forensics. — Packt Publishing, 2015. — 192 p.[14]
Клеменс Бен. Язык C в XXI веке. — М. : ДМК Пресс,2017. — 376 с.[15]
GALTON FRANCIS. Dice for Statistical Experiments //Nature. — 1890. — may. — Vol. 42, no. 1070. — P. 13–14.[16]
Кнут Д. Э. Искусство программирования. — 3 изд. —М. : Вильямс, 2001. — Т. 2. — 832 с.[17] Дроздова И. И., Жилин В. В. Генераторы случайныхи псевдослучайных чисел // Технические науки в Рос-сии и за рубежом: материалы VII Междунар. науч.конф. — М. : Буки-Веди, 2017. — nov. — С. 13–15.[18] Колчин В. Ф., Севастьянов Б. А., Чистяков Влади-мир Павлович. Случайные размещения. — М. : Наука,1976. — 224 с.[19] Тюрин Ю. Н., Макаров А. А. Статистический анализданных на компьютере / Под ред. В. Э. Фигурнова. —М. : ИНФРА, 1998. — 528 с. [20]
Gevorkyan Migran Nelsonovich, Demidova Anas-tasiya Vyacheslavovna, Korolkova Anna Vladislavovnaet al. Pseudo-random number generator based on neuralnetwork // Selected Papers of the 8th International Con-ference "Distributed Computing and Grid-technologiesin Science and Education" / Ed. by Vladimir Korenkov,Andrey Nechaevskiy, Tatiana Zaikina, Elena Mazhi-tova. — Vol. 2267 of CEUR Workshop Proceedings. —Dubna, 2018. — sep. — P. 568–572.[21] Matsumoto Makoto, Nishimura Takuji. MersenneTwister: A 623-dimensionally EquidistributedUniform Pseudo-random Number Generator //ACM Trans. Model. Comput. Simul. — 1998. — Vol. 8,no. 1. — P. 3–30.[22] Marsaglia George. Xorshift RNGs //Journal of Statistical Software. — 2003. — Vol. 8,no. 1. — P. 1–6.[23] Panneton F rançois, L’Ecuyer Pierre. On the XorshiftRandom Number Generators // ACM Trans. Model.Comput. Simul. — 2005. — Vol. 15, no. 4. — P. 346–361.[24] Boldi Paolo, Vigna Sebastiano. On the Lattice of An-tichains of Finite Intervals // Order. — 2018. — mar. —Vol. 35, no. 1. — P. 57–81.[25] PCG: A Family of Simple Fast Space-Efficient Statisti-cally Good Algorithms for Random Number Generation :Rep. : HMC-CS-2014-0905 / Harvey Mudd College ; Ex-ecutor: Melissa E O’Neill. — Claremont, CA : 2014.[26] Gevorkyan Migran Nelsonovich,Kulyabov Dmitry Sergeevich, Ko-rolkova Anna Vladislavovna, Sevas-tianov Leonid Antonovich. Random Number Gen-erators for Computer Algebra Systems. — 2019. — URL: https://bitbucket.org/yamadharma/articles-2019-rng-generator-code .[27] Rose Gregory G. KISS: A bit too simple //Cryptography and Communications. — 2018. — Vol. 10,no. 1. — P. 123–137.[28] Jones David. Good practice in (pseudo) random numbergeneration for bioinformatics applications. — 2010.[29] Brown Robert G, Eddelbuettel Dirk,Bauer David. Dieharder: A RandomNumber Test Suite. — 2017. — URL: https://webhome.phy.duke.edu/~rgb/General/dieharder.php .[30] L’Ecuyer Pierre, Simard Richard. TestU01: A C li-brary for empirical testing of random number genera-tors // ACM Transactions on Mathematical Software(TOMS). — 2007. — Vol. 33, no. 4. — P. 22.[31] L’Ecuyer Pierre, Simard Richard. TestU01 — EmpiricalTesting of Random Number Generators. — 2009. — URL: http://simul.iro.umontreal.ca/testu01/tu01.html .[32] Doty-Humphrey Chris. PractRand official site. —2018. — URL: http://pracrand.sourceforge.net/ .[33] Gjrand random numbers official site. — 2014. — URL: http://gjrand.sourceforge.net/http://gjrand.sourceforge.net/