1 /* Copyright 2009-2016 Francesco Biscani (bluescarni@gmail.com)
2 
3 This file is part of the Piranha library.
4 
5 The Piranha library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8   * the GNU Lesser General Public License as published by the Free
9     Software Foundation; either version 3 of the License, or (at your
10     option) any later version.
11 
12 or
13 
14   * the GNU General Public License as published by the Free Software
15     Foundation; either version 3 of the License, or (at your option) any
16     later version.
17 
18 or both in parallel, as here.
19 
20 The Piranha library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the Piranha library.  If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #include "python_includes.hpp"
30 
31 #include <boost/numeric/conversion/cast.hpp>
32 #include <boost/python/class.hpp>
33 #include <boost/python/def.hpp>
34 #include <boost/python/docstring_options.hpp>
35 #include <boost/python/enum.hpp>
36 #include <boost/python/errors.hpp>
37 #include <boost/python/extract.hpp>
38 #include <boost/python/handle.hpp>
39 #include <boost/python/init.hpp>
40 #include <boost/python/module.hpp>
41 #include <boost/python/object.hpp>
42 #include <boost/python/scope.hpp>
43 #include <boost/python/stl_iterator.hpp>
44 #include <cstdint>
45 #include <mutex>
46 #include <stdexcept>
47 #include <string>
48 #include <type_traits>
49 
50 #include "../src/binomial.hpp"
51 #include "../src/config.hpp"
52 #include "../src/divisor.hpp"
53 #include "../src/divisor_series.hpp"
54 #include "../src/exceptions.hpp"
55 #include "../src/init.hpp"
56 #include "../src/invert.hpp"
57 #include "../src/kronecker_monomial.hpp"
58 #include "../src/math.hpp"
59 #include "../src/monomial.hpp"
60 #include "../src/mp_integer.hpp"
61 #include "../src/mp_rational.hpp"
62 #include "../src/poisson_series.hpp"
63 #include "../src/polynomial.hpp"
64 #include "../src/real.hpp"
65 #include "../src/s11n.hpp"
66 #include "../src/safe_cast.hpp"
67 #include "../src/thread_pool.hpp"
68 #include "../src/type_traits.hpp"
69 #include "exceptions.hpp"
70 #include "expose_divisor_series.hpp"
71 #include "expose_poisson_series.hpp"
72 #include "expose_polynomials.hpp"
73 #include "expose_utils.hpp"
74 #include "python_converters.hpp"
75 #include "type_system.hpp"
76 #include "utils.hpp"
77 
78 namespace bp = boost::python;
79 
80 static std::mutex global_mutex;
81 static bool inited = false;
82 
83 namespace pyranha
84 {
85 
86 PYRANHA_DECLARE_T_NAME(piranha::monomial)
87 PYRANHA_DECLARE_T_NAME(piranha::divisor)
88 }
89 
90 // A couple of utils to test exception translation.
91 template <typename Exc, piranha::enable_if_t<std::is_constructible<Exc, std::string>::value, int> = 0>
test_exception()92 static inline void test_exception()
93 {
94     piranha_throw(Exc, "hello world");
95 }
96 
97 template <typename Exc, piranha::enable_if_t<!std::is_constructible<Exc, std::string>::value, int> = 0>
test_exception()98 static inline void test_exception()
99 {
100     piranha_throw(Exc, );
101 }
102 
103 // Small helper to retrieve the argument error exception from python.
generate_argument_error(int)104 static inline void generate_argument_error(int)
105 {
106 }
107 
BOOST_PYTHON_MODULE(_core)108 BOOST_PYTHON_MODULE(_core)
109 {
110     // NOTE: this is a single big lock to avoid registering types/conversions multiple times and prevent contention
111     // if the module is loaded from multiple threads.
112     std::lock_guard<std::mutex> lock(global_mutex);
113     if (inited) {
114         return;
115     }
116     // Set the inited flag.
117     // NOTE: we do it here because if something goes wrong in the rest of the init, we do not have a way
118     // to roll back the initialisation, and if the user tries again the init probably a lot of things would
119     // go haywire. Like this, we will not re-run any init code in a successive attempt at loading the module.
120     inited = true;
121     // Piranha init.
122     piranha::init();
123     // Docstring options setup.
124     bp::docstring_options doc_options(false, false, false);
125     // Type generator class.
126     bp::class_<pyranha::type_generator> tg_class("_type_generator", bp::no_init);
127     tg_class.def("__call__", &pyranha::type_generator::operator());
128     tg_class.def("__repr__", &pyranha::type_generator::repr);
129     // Type generator template class.
130     bp::class_<pyranha::type_generator_template> tgt_class("_type_generator_template", bp::no_init);
131     tgt_class.def("__getitem__", &pyranha::type_generator_template::getitem_o);
132     tgt_class.def("__getitem__", &pyranha::type_generator_template::getitem_t);
133     tgt_class.def("__repr__", &pyranha::type_generator_template::repr);
134     // Create the types submodule.
135     std::string types_module_name = bp::extract<std::string>(bp::scope().attr("__name__") + ".types");
136     // NOTE: the nested namespace is created if not there, otherwise it will be returned.
137     ::PyObject *types_module_ptr = ::PyImport_AddModule(types_module_name.c_str());
138     if (!types_module_ptr) {
139         ::PyErr_SetString(PyExc_RuntimeError, "error while creating the 'types' submodule");
140         bp::throw_error_already_set();
141     }
142     // NOTE: I think at one point in the past there was a typo in the Python3 C API documentation
143     // which hinted at a difference in behaviour for PyImport_AddModule between 2 and 3 (new reference
144     // vs borrowed reference). Current documentation states that the reference is always
145     // borrowed, both in Python 2 and 3.
146     auto types_module = bp::object(bp::handle<>(bp::borrowed(types_module_ptr)));
147     bp::scope().attr("types") = types_module;
148     // Expose concrete instances of type generators.
149     pyranha::instantiate_type_generator<std::int_least16_t>("int16", types_module);
150     pyranha::instantiate_type_generator<double>("double", types_module);
151     pyranha::instantiate_type_generator<piranha::integer>("integer", types_module);
152     pyranha::instantiate_type_generator<piranha::rational>("rational", types_module);
153     pyranha::instantiate_type_generator<piranha::real>("real", types_module);
154     pyranha::instantiate_type_generator<piranha::k_monomial>("k_monomial", types_module);
155     // Register template instances of monomial, and instantiate the type generator template.
156     pyranha::instantiate_type_generator_template<piranha::monomial>("monomial", types_module);
157     pyranha::register_template_instance<piranha::monomial, piranha::rational>();
158     pyranha::register_template_instance<piranha::monomial, std::int_least16_t>();
159     // Same for divisor.
160     pyranha::instantiate_type_generator_template<piranha::divisor>("divisor", types_module);
161     pyranha::register_template_instance<piranha::divisor, std::int_least16_t>();
162     // Arithmetic converters.
163     pyranha::integer_converter i_c;
164     pyranha::rational_converter ra_c;
165     pyranha::real_converter re_c;
166     // Exceptions translation.
167     pyranha::generic_translate<&PyExc_ZeroDivisionError, piranha::zero_division_error>();
168     pyranha::generic_translate<&PyExc_NotImplementedError, piranha::not_implemented_error>();
169     pyranha::generic_translate<&PyExc_OverflowError, std::overflow_error>();
170     pyranha::generic_translate<&PyExc_OverflowError, boost::numeric::positive_overflow>();
171     pyranha::generic_translate<&PyExc_OverflowError, boost::numeric::negative_overflow>();
172     pyranha::generic_translate<&PyExc_OverflowError, boost::numeric::bad_numeric_cast>();
173     pyranha::generic_translate<&PyExc_ArithmeticError, piranha::math::inexact_division>();
174     pyranha::generic_translate<&PyExc_ValueError, piranha::safe_cast_failure>();
175 #if defined(PIRANHA_WITH_MSGPACK)
176     pyranha::generic_translate<&PyExc_TypeError, msgpack::type_error>();
177 #endif
178     // Exposed types list.
179     bp::def("_get_exposed_types_list", pyranha::get_exposed_types_list);
180     // The s11n enums.
181     bp::enum_<piranha::data_format>("data_format")
182         .value("boost_binary", piranha::data_format::boost_binary)
183         .value("boost_portable", piranha::data_format::boost_portable)
184         .value("msgpack_binary", piranha::data_format::msgpack_binary)
185         .value("msgpack_portable", piranha::data_format::msgpack_portable);
186     bp::enum_<piranha::compression>("compression")
187         .value("none", piranha::compression::none)
188         .value("zlib", piranha::compression::zlib)
189         .value("gzip", piranha::compression::gzip)
190         .value("bzip2", piranha::compression::bzip2);
191     // Expose polynomials.
192     pyranha::instantiate_type_generator_template<piranha::polynomial>("polynomial", types_module);
193     pyranha::expose_polynomials_0();
194     pyranha::expose_polynomials_1();
195     pyranha::expose_polynomials_2();
196     pyranha::expose_polynomials_3();
197     pyranha::expose_polynomials_4();
198     pyranha::expose_polynomials_5();
199     pyranha::expose_polynomials_6();
200     pyranha::expose_polynomials_7();
201     pyranha::expose_polynomials_8();
202     pyranha::expose_polynomials_9();
203     pyranha::expose_polynomials_10();
204     // Expose Poisson series.
205     pyranha::instantiate_type_generator_template<piranha::poisson_series>("poisson_series", types_module);
206     pyranha::expose_poisson_series_0();
207     pyranha::expose_poisson_series_1();
208     pyranha::expose_poisson_series_2();
209     pyranha::expose_poisson_series_3();
210     pyranha::expose_poisson_series_4();
211     pyranha::expose_poisson_series_5();
212     pyranha::expose_poisson_series_6();
213     pyranha::expose_poisson_series_7();
214     pyranha::expose_poisson_series_8();
215     pyranha::expose_poisson_series_9();
216     pyranha::expose_poisson_series_10();
217     pyranha::expose_poisson_series_11();
218     // Expose divisor series.
219     pyranha::instantiate_type_generator_template<piranha::divisor_series>("divisor_series", types_module);
220     pyranha::expose_divisor_series_0();
221     pyranha::expose_divisor_series_1();
222     pyranha::expose_divisor_series_2();
223     pyranha::expose_divisor_series_3();
224     pyranha::expose_divisor_series_4();
225     pyranha::expose_divisor_series_5();
226     // Expose the settings class.
227     bp::class_<piranha::settings> settings_class("_settings", bp::init<>());
228     settings_class.def("_get_max_term_output", piranha::settings::get_max_term_output)
229         .staticmethod("_get_max_term_output");
230     settings_class.def("_set_max_term_output", piranha::settings::set_max_term_output)
231         .staticmethod("_set_max_term_output");
232     settings_class.def("_reset_max_term_output", piranha::settings::reset_max_term_output)
233         .staticmethod("_reset_max_term_output");
234     settings_class.def("_set_n_threads", piranha::settings::set_n_threads).staticmethod("_set_n_threads");
235     settings_class.def("_get_n_threads", piranha::settings::get_n_threads).staticmethod("_get_n_threads");
236     settings_class.def("_reset_n_threads", piranha::settings::reset_n_threads).staticmethod("_reset_n_threads");
237     settings_class.def("_set_min_work_per_thread", piranha::settings::set_min_work_per_thread)
238         .staticmethod("_set_min_work_per_thread");
239     settings_class.def("_get_min_work_per_thread", piranha::settings::get_min_work_per_thread)
240         .staticmethod("_get_min_work_per_thread");
241     settings_class.def("_reset_min_work_per_thread", piranha::settings::reset_min_work_per_thread)
242         .staticmethod("_reset_min_work_per_thread");
243     settings_class.def("_set_thread_binding", piranha::settings::set_thread_binding)
244         .staticmethod("_set_thread_binding");
245     settings_class.def("_get_thread_binding", piranha::settings::get_thread_binding)
246         .staticmethod("_get_thread_binding");
247     // Factorial.
248     bp::def("_factorial", &piranha::math::factorial<0>);
249 // Binomial coefficient.
250 #define PYRANHA_EXPOSE_BINOMIAL(top, bot) bp::def("_binomial", &piranha::math::binomial<top, bot>)
251     PYRANHA_EXPOSE_BINOMIAL(double, double);
252     PYRANHA_EXPOSE_BINOMIAL(double, piranha::integer);
253     PYRANHA_EXPOSE_BINOMIAL(double, piranha::rational);
254     PYRANHA_EXPOSE_BINOMIAL(double, piranha::real);
255     PYRANHA_EXPOSE_BINOMIAL(piranha::integer, double);
256     PYRANHA_EXPOSE_BINOMIAL(piranha::integer, piranha::integer);
257     PYRANHA_EXPOSE_BINOMIAL(piranha::integer, piranha::rational);
258     PYRANHA_EXPOSE_BINOMIAL(piranha::integer, piranha::real);
259     PYRANHA_EXPOSE_BINOMIAL(piranha::rational, double);
260     PYRANHA_EXPOSE_BINOMIAL(piranha::rational, piranha::integer);
261     PYRANHA_EXPOSE_BINOMIAL(piranha::rational, piranha::rational);
262     PYRANHA_EXPOSE_BINOMIAL(piranha::rational, piranha::real);
263     PYRANHA_EXPOSE_BINOMIAL(piranha::real, double);
264     PYRANHA_EXPOSE_BINOMIAL(piranha::real, piranha::integer);
265     PYRANHA_EXPOSE_BINOMIAL(piranha::real, piranha::rational);
266     PYRANHA_EXPOSE_BINOMIAL(piranha::real, piranha::real);
267 #undef PYRANHA_EXPOSE_BINOMIAL
268 // Sine and cosine.
269 #define PYRANHA_EXPOSE_SIN_COS(arg)                                                                                    \
270     bp::def("_sin", &piranha::math::sin<arg>);                                                                         \
271     bp::def("_cos", &piranha::math::cos<arg>)
272     PYRANHA_EXPOSE_SIN_COS(double);
273     PYRANHA_EXPOSE_SIN_COS(piranha::integer);
274     PYRANHA_EXPOSE_SIN_COS(piranha::rational);
275     PYRANHA_EXPOSE_SIN_COS(piranha::real);
276 #undef PYRANHA_EXPOSE_SIN_COS
277 #define PYRANHA_EXPOSE_INVERT(arg) bp::def("_invert", &piranha::math::invert<arg>)
278     PYRANHA_EXPOSE_INVERT(double);
279     PYRANHA_EXPOSE_INVERT(piranha::integer);
280     PYRANHA_EXPOSE_INVERT(piranha::rational);
281     PYRANHA_EXPOSE_INVERT(piranha::real);
282 #undef PYRANHA_EXPOSE_INVERT
283     // GCD.
284     bp::def("_gcd", &piranha::math::gcd<piranha::integer, piranha::integer>);
285     // Tests for exception translation.
286     bp::def("_test_safe_cast_failure", &test_exception<piranha::safe_cast_failure>);
287     bp::def("_test_zero_division_error", &test_exception<piranha::zero_division_error>);
288     bp::def("_test_not_implemented_error", &test_exception<piranha::not_implemented_error>);
289     bp::def("_test_overflow_error", &test_exception<std::overflow_error>);
290     bp::def("_test_bn_poverflow_error", &test_exception<boost::numeric::positive_overflow>);
291     bp::def("_test_bn_noverflow_error", &test_exception<boost::numeric::negative_overflow>);
292     bp::def("_test_bn_bnc", &test_exception<boost::numeric::bad_numeric_cast>);
293     bp::def("_test_inexact_division", &test_exception<piranha::math::inexact_division>);
294     // Helper to generate an argument error.
295     bp::def("_generate_argument_error", &generate_argument_error);
296 
297     // Define a cleanup functor to be run when the module is unloaded.
298     struct cleanup_functor {
299         void operator()() const
300         {
301             // First we clean up the custom derivatives.
302             auto e_types = pyranha::get_exposed_types_list();
303             bp::stl_input_iterator<bp::object> end_it;
304             for (bp::stl_input_iterator<bp::object> it(e_types); it != end_it; ++it) {
305                 if (pyranha::hasattr(*it, "unregister_all_custom_derivatives")) {
306                     it->attr("unregister_all_custom_derivatives")();
307                 }
308             }
309             pyranha::builtin().attr("print")("Custom derivatives cleanup completed.");
310             // Next we clean up the pow caches.
311             for (bp::stl_input_iterator<bp::object> it(e_types); it != end_it; ++it) {
312                 if (pyranha::hasattr(*it, "clear_pow_cache")) {
313                     it->attr("clear_pow_cache")();
314                 }
315             }
316             pyranha::builtin().attr("print")("Pow caches cleanup completed.");
317             // Clean up the pyranha type system.
318             pyranha::et_map.clear();
319             pyranha::builtin().attr("print")("Pyranha's type system cleanup completed.");
320             // Finally, shut down the thread pool.
321             // NOTE: this is necessary in Windows/MinGW currently, otherwise the python
322             // interpreter hangs on exit (possibly due to either some implementation-defined
323             // static order destruction fiasco, or maybe a threading bug in MinGW).
324             std::cout << "Shutting down the thread pool.\n";
325             piranha::thread_pool_shutdown<void>();
326         }
327     };
328     // Expose it.
329     bp::class_<cleanup_functor> cl_c("_cleanup_functor", bp::init<>());
330     cl_c.def("__call__", &cleanup_functor::operator());
331     // Register it.
332     bp::object atexit_mod = bp::import("atexit");
333     atexit_mod.attr("register")(cleanup_functor{});
334 }
335