2828/**
2929 * \ingroup python_interface_rng
3030 * \brief Internal data structure for storing references to the
31- * functions used from Python's random number generator.
31+ * functions and arguments used from Python's random number generator.
3232 */
3333typedef struct {
34+ PyObject * getrandbits_func ;
3435 PyObject * randint_func ;
3536 PyObject * random_func ;
3637 PyObject * gauss_func ;
38+
39+ PyObject * rng_bits_as_pyobject ;
40+ PyObject * zero_as_pyobject ;
41+ PyObject * one_as_pyobject ;
42+ PyObject * rng_max_as_pyobject ;
3743} igraph_i_rng_Python_state_t ;
3844
45+ /* igraph_rng_get_int31() is potentially faster if the max value of the RNG
46+ * is 0x7FFFFFFF; however, in case of Python, it is actually _slower_ because
47+ * Python long integers are not terribly efficient. We are better off with using
48+ * any other value here */
49+ #define RNG_MAX 0xFFFFFFFF
50+
51+ /* This must be consistent with the value of RNG_MAX above */
52+ #define RNG_BITS 32
53+
3954static igraph_i_rng_Python_state_t igraph_rng_Python_state = {0 , 0 , 0 };
4055static igraph_rng_t igraph_rng_Python = {0 , 0 , 0 };
4156static igraph_rng_t igraph_rng_default_saved = {0 , 0 , 0 };
@@ -66,25 +81,67 @@ PyObject* igraph_rng_Python_set_generator(PyObject* self, PyObject* object) {
6681 Py_RETURN_NONE ;
6782 }
6883
69- #define GET_FUNC (name ) {\
84+ #define GET_FUNC (name ) { \
7085 func = PyObject_GetAttrString(object, name); \
71- if (func == 0) \
72- return NULL ; \
73- if (!PyCallable_Check(func)) {\
74- PyErr_SetString(PyExc_TypeError, name "attribute must be callable"); \
75- return NULL ; \
86+ if (func == 0) { \
87+ return 0 ; \
88+ } else if (!PyCallable_Check(func)) { \
89+ PyErr_SetString(PyExc_TypeError, "'" name "' attribute must be callable"); \
90+ return 0 ; \
7691 } \
7792}
7893
94+ #define GET_OPTIONAL_FUNC (name ) { \
95+ if (PyObject_HasAttrString(object, name)) { \
96+ func = PyObject_GetAttrString(object, name); \
97+ if (func == 0) { \
98+ return 0; \
99+ } else if (!PyCallable_Check(func)) { \
100+ PyErr_SetString(PyExc_TypeError, "'" name "' attribute must be callable"); \
101+ return 0; \
102+ } \
103+ } else { \
104+ func = 0; \
105+ } \
106+ }
107+
108+ GET_OPTIONAL_FUNC ("getrandbits" ); new_state .getrandbits_func = func ;
79109 GET_FUNC ("randint" ); new_state .randint_func = func ;
80110 GET_FUNC ("random" ); new_state .random_func = func ;
81111 GET_FUNC ("gauss" ); new_state .gauss_func = func ;
82112
113+ /* construct the arguments of getrandbits(RNG_BITS) and randint(0, RNG_MAX)
114+ * in advance */
115+ new_state .rng_bits_as_pyobject = PyLong_FromLong (RNG_BITS );
116+ if (new_state .rng_bits_as_pyobject == 0 ) {
117+ return 0 ;
118+ }
119+ new_state .zero_as_pyobject = PyLong_FromLong (0 );
120+ if (new_state .zero_as_pyobject == 0 ) {
121+ return 0 ;
122+ }
123+ new_state .one_as_pyobject = PyLong_FromLong (1 );
124+ if (new_state .one_as_pyobject == 0 ) {
125+ return 0 ;
126+ }
127+ new_state .rng_max_as_pyobject = PyLong_FromUnsignedLong (RNG_MAX );
128+ if (new_state .rng_max_as_pyobject == 0 ) {
129+ return 0 ;
130+ }
131+
132+ #undef GET_FUNC
133+ #undef GET_OPTIONAL_FUNC
134+
83135 old_state = igraph_rng_Python_state ;
84136 igraph_rng_Python_state = new_state ;
137+ Py_XDECREF (old_state .getrandbits_func );
85138 Py_XDECREF (old_state .randint_func );
86139 Py_XDECREF (old_state .random_func );
87140 Py_XDECREF (old_state .gauss_func );
141+ Py_XDECREF (old_state .rng_bits_as_pyobject );
142+ Py_XDECREF (old_state .zero_as_pyobject );
143+ Py_XDECREF (old_state .one_as_pyobject );
144+ Py_XDECREF (old_state .rng_max_as_pyobject );
88145
89146 igraph_rng_set_default (& igraph_rng_Python );
90147
@@ -106,38 +163,75 @@ int igraph_rng_Python_seed(void *state, unsigned long int seed) {
106163 * \brief Generates an unsigned long integer using the Python random number generator.
107164 */
108165unsigned long int igraph_rng_Python_get (void * state ) {
109- PyObject * result = PyObject_CallFunction (igraph_rng_Python_state .randint_func , "kk" , 0 , LONG_MAX );
166+ PyObject * result ;
167+ PyObject * exc_type ;
110168 unsigned long int retval ;
111169
170+ if (igraph_rng_Python_state .getrandbits_func ) {
171+ /* This is the preferred code path if the random module given by the user
172+ * supports getrandbits(); it is faster than randint() but still slower
173+ * than simply calling random() */
174+ result = PyObject_CallFunctionObjArgs (
175+ igraph_rng_Python_state .getrandbits_func ,
176+ igraph_rng_Python_state .rng_bits_as_pyobject ,
177+ 0
178+ );
179+ } else {
180+ /* We want to avoid hitting this path at all costs because randint() is
181+ * very costly in the Python layer */
182+ result = PyObject_CallFunctionObjArgs (
183+ igraph_rng_Python_state .randint_func ,
184+ igraph_rng_Python_state .zero_as_pyobject ,
185+ igraph_rng_Python_state .rng_max_as_pyobject ,
186+ 0
187+ );
188+ }
189+
112190 if (result == 0 ) {
113- PyErr_WriteUnraisable (PyErr_Occurred ());
114- PyErr_Clear ();
191+ exc_type = PyErr_Occurred ();
192+ if (exc_type == PyExc_KeyboardInterrupt ) {
193+ /* KeyboardInterrupt is okay, we don't report it, just store it and let
194+ * the caller handler it at the earliest convenience */
195+ } else {
196+ /* All other exceptions are reported and cleared */
197+ PyErr_WriteUnraisable (exc_type );
198+ PyErr_Clear ();
199+ }
115200 /* Fallback to the C random generator */
116- return rand () * LONG_MAX ;
201+ return rand () * RNG_MAX ;
202+ } else {
203+ retval = PyLong_AsUnsignedLong (result );
204+ Py_DECREF (result );
205+ return retval ;
117206 }
118- retval = PyLong_AsLong (result );
119- Py_DECREF (result );
120- return retval ;
121207}
122208
123209/**
124210 * \ingroup python_interface_rng
125211 * \brief Generates a real number between 0 and 1 using the Python random number generator.
126212 */
127213igraph_real_t igraph_rng_Python_get_real (void * state ) {
128- PyObject * result = PyObject_CallObject ( igraph_rng_Python_state . random_func , 0 ) ;
214+ PyObject * exc_type ;
129215 double retval ;
216+ PyObject * result = PyObject_CallObject (igraph_rng_Python_state .random_func , 0 );
130217
131218 if (result == 0 ) {
132- PyErr_WriteUnraisable (PyErr_Occurred ());
133- PyErr_Clear ();
219+ exc_type = PyErr_Occurred ();
220+ if (exc_type == PyExc_KeyboardInterrupt ) {
221+ /* KeyboardInterrupt is okay, we don't report it, just store it and let
222+ * the caller handler it at the earliest convenience */
223+ } else {
224+ /* All other exceptions are reported and cleared */
225+ PyErr_WriteUnraisable (exc_type );
226+ PyErr_Clear ();
227+ }
134228 /* Fallback to the C random generator */
135229 return rand ();
230+ } else {
231+ retval = PyFloat_AsDouble (result );
232+ Py_DECREF (result );
233+ return retval ;
136234 }
137-
138- retval = PyFloat_AsDouble (result );
139- Py_DECREF (result );
140- return retval ;
141235}
142236
143237/**
@@ -146,19 +240,32 @@ igraph_real_t igraph_rng_Python_get_real(void *state) {
146240 * around zero with unit variance.
147241 */
148242igraph_real_t igraph_rng_Python_get_norm (void * state ) {
149- PyObject * result = PyObject_CallFunction ( igraph_rng_Python_state . gauss_func , "dd" , 0.0 , 1.0 ) ;
243+ PyObject * exc_type ;
150244 double retval ;
245+ PyObject * result = PyObject_CallFunctionObjArgs (
246+ igraph_rng_Python_state .gauss_func ,
247+ igraph_rng_Python_state .zero_as_pyobject ,
248+ igraph_rng_Python_state .one_as_pyobject ,
249+ 0
250+ );
151251
152252 if (result == 0 ) {
153- PyErr_WriteUnraisable (PyErr_Occurred ());
154- PyErr_Clear ();
253+ exc_type = PyErr_Occurred ();
254+ if (exc_type == PyExc_KeyboardInterrupt ) {
255+ /* KeyboardInterrupt is okay, we don't report it, just store it and let
256+ * the caller handler it at the earliest convenience */
257+ } else {
258+ /* All other exceptions are reported and cleared */
259+ PyErr_WriteUnraisable (exc_type );
260+ PyErr_Clear ();
261+ }
155262 /* Fallback to the C random generator */
156263 return 0 ;
264+ } else {
265+ retval = PyFloat_AsDouble (result );
266+ Py_DECREF (result );
267+ return retval ;
157268 }
158-
159- retval = PyFloat_AsDouble (result );
160- Py_DECREF (result );
161- return retval ;
162269}
163270
164271/**
@@ -169,7 +276,7 @@ igraph_real_t igraph_rng_Python_get_norm(void *state) {
169276igraph_rng_type_t igraph_rngtype_Python = {
170277 /* name= */ "Python random generator" ,
171278 /* min= */ 0 ,
172- /* max= */ LONG_MAX ,
279+ /* max= */ RNG_MAX ,
173280 /* init= */ igraph_rng_Python_init ,
174281 /* destroy= */ igraph_rng_Python_destroy ,
175282 /* seed= */ igraph_rng_Python_seed ,
0 commit comments