codekingpro/portable-devtools
115k
1#define FORTRANOBJECT_C
2#include "fortranobject.h"
3
4#ifdef __cplusplus
5extern "C" {
6#endif
7
8#include <stdarg.h>
9#include <stdlib.h>
10#include <string.h>
11
12/*
13 This file implements: FortranObject, array_from_pyobj, copy_ND_array
14
15 Author: Pearu Peterson <pearu@cens.ioc.ee>
16 $Revision: 1.52 $
17 $Date: 2005/07/11 07:44:20 $
18*/
19
20int
21F2PyDict_SetItemString(PyObject *dict, char *name, PyObject *obj)
22{
23 if (obj == NULL) {
24 fprintf(stderr, "Error loading %s\n", name);
25 if (PyErr_Occurred()) {
26 PyErr_Print();
27 PyErr_Clear();
28 }
29 return -1;
30 }
31 return PyDict_SetItemString(dict, name, obj);
32}
33
34/*
35 * Python-only fallback for thread-local callback pointers
36 */
37void *
38F2PySwapThreadLocalCallbackPtr(char *key, void *ptr)
39{
40 PyObject *local_dict, *value;
41 void *prev;
42
43 local_dict = PyThreadState_GetDict();
44 if (local_dict == NULL) {
45 Py_FatalError(
46 "F2PySwapThreadLocalCallbackPtr: PyThreadState_GetDict "
47 "failed");
48 }
49
50 value = PyDict_GetItemString(local_dict, key); // noqa: borrowed-ref OK
51 if (value != NULL) {
52 prev = PyLong_AsVoidPtr(value);
53 if (PyErr_Occurred()) {
54 Py_FatalError(
55 "F2PySwapThreadLocalCallbackPtr: PyLong_AsVoidPtr failed");
56 }
57 }
58 else {
59 prev = NULL;
60 }
61
62 value = PyLong_FromVoidPtr((void *)ptr);
63 if (value == NULL) {
64 Py_FatalError(
65 "F2PySwapThreadLocalCallbackPtr: PyLong_FromVoidPtr failed");
66 }
67
68 if (PyDict_SetItemString(local_dict, key, value) != 0) {
69 Py_FatalError(
70 "F2PySwapThreadLocalCallbackPtr: PyDict_SetItemString failed");
71 }
72
73 Py_DECREF(value);
74
75 return prev;
76}
77
78void *
79F2PyGetThreadLocalCallbackPtr(char *key)
80{
81 PyObject *local_dict, *value;
82 void *prev;
83
84 local_dict = PyThreadState_GetDict();
85 if (local_dict == NULL) {
86 Py_FatalError(
87 "F2PyGetThreadLocalCallbackPtr: PyThreadState_GetDict failed");
88 }
89
90 value = PyDict_GetItemString(local_dict, key); // noqa: borrowed-ref OK
91 if (value != NULL) {
92 prev = PyLong_AsVoidPtr(value);
93 if (PyErr_Occurred()) {
94 Py_FatalError(
95 "F2PyGetThreadLocalCallbackPtr: PyLong_AsVoidPtr failed");
96 }
97 }
98 else {
99 prev = NULL;
100 }
101
102 return prev;
103}
104
105static PyArray_Descr *
106get_descr_from_type_and_elsize(const int type_num, const int elsize) {
107 PyArray_Descr * descr = PyArray_DescrFromType(type_num);
108 if (type_num == NPY_STRING) {
109 // PyArray_DescrFromType returns descr with elsize = 0.
110 PyArray_DESCR_REPLACE(descr);
111 if (descr == NULL) {
112 return NULL;
113 }
114 PyDataType_SET_ELSIZE(descr, elsize);
115 }
116 return descr;
117}
118
119/************************* FortranObject *******************************/
120
121typedef PyObject *(*fortranfunc)(PyObject *, PyObject *, PyObject *, void *);
122
123PyObject *
124PyFortranObject_New(FortranDataDef *defs, f2py_void_func init)
125{
126 int i;
127 PyFortranObject *fp = NULL;
128 PyObject *v = NULL;
129 if (init != NULL) { /* Initialize F90 module objects */
130 (*(init))();
131 }
132 fp = PyObject_New(PyFortranObject, &PyFortran_Type);
133 if (fp == NULL) {
134 return NULL;
135 }
136 if ((fp->dict = PyDict_New()) == NULL) {
137 Py_DECREF(fp);
138 return NULL;
139 }
140 fp->len = 0;
141 while (defs[fp->len].name != NULL) {
142 fp->len++;
143 }
144 if (fp->len == 0) {
145 goto fail;
146 }
147 fp->defs = defs;
148 for (i = 0; i < fp->len; i++) {
149 if (fp->defs[i].rank == -1) { /* Is Fortran routine */
150 v = PyFortranObject_NewAsAttr(&(fp->defs[i]));
151 if (v == NULL) {
152 goto fail;
153 }
154 PyDict_SetItemString(fp->dict, fp->defs[i].name, v);
155 Py_XDECREF(v);
156 }
157 else if ((fp->defs[i].data) !=
158 NULL) { /* Is Fortran variable or array (not allocatable) */
159 PyArray_Descr *
160 descr = get_descr_from_type_and_elsize(fp->defs[i].type,
161 fp->defs[i].elsize);
162 if (descr == NULL) {
163 goto fail;
164 }
165 v = PyArray_NewFromDescr(&PyArray_Type, descr, fp->defs[i].rank,
166 fp->defs[i].dims.d, NULL, fp->defs[i].data,
167 NPY_ARRAY_FARRAY, NULL);
168 if (v == NULL) {
169 Py_DECREF(descr);
170 goto fail;
171 }
172 PyDict_SetItemString(fp->dict, fp->defs[i].name, v);
173 Py_XDECREF(v);
174 }
175 }
176 return (PyObject *)fp;
177fail:
178 Py_XDECREF(fp);
179 return NULL;
180}
181
182PyObject *
183PyFortranObject_NewAsAttr(FortranDataDef *defs)
184{ /* used for calling F90 module routines */
185 PyFortranObject *fp = NULL;
186 fp = PyObject_New(PyFortranObject, &PyFortran_Type);
187 if (fp == NULL)
188 return NULL;
189 if ((fp->dict = PyDict_New()) == NULL) {
190 PyObject_Del(fp);
191 return NULL;
192 }
193 fp->len = 1;
194 fp->defs = defs;
195 if (defs->rank == -1) {
196 PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("function %s", defs->name));
197 } else if (defs->rank == 0) {
198 PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("scalar %s", defs->name));
199 } else {
200 PyDict_SetItemString(fp->dict, "__name__", PyUnicode_FromFormat("array %s", defs->name));
201 }
202 return (PyObject *)fp;
203}
204
205/* Fortran methods */
206
207static void
208fortran_dealloc(PyFortranObject *fp)
209{
210 Py_XDECREF(fp->dict);
211 PyObject_Del(fp);
212}
213
214/* Returns number of bytes consumed from buf, or -1 on error. */
215static Py_ssize_t
216format_def(char *buf, Py_ssize_t size, FortranDataDef def)
217{
218 char *p = buf;
219 int i;
220 npy_intp n;
221
222 n = PyOS_snprintf(p, size, "array(%" NPY_INTP_FMT, def.dims.d[0]);
223 if (n < 0 || n >= size) {
224 return -1;
225 }
226 p += n;
227 size -= n;
228
229 for (i = 1; i < def.rank; i++) {
230 n = PyOS_snprintf(p, size, ",%" NPY_INTP_FMT, def.dims.d[i]);
231 if (n < 0 || n >= size) {
232 return -1;
233 }
234 p += n;
235 size -= n;
236 }
237
238 if (size <= 0) {
239 return -1;
240 }
241
242 *p++ = ')';
243 size--;
244
245 if (def.data == NULL) {
246 static const char notalloc[] = ", not allocated";
247 if ((size_t)size < sizeof(notalloc)) {
248 return -1;
249 }
250 memcpy(p, notalloc, sizeof(notalloc));
251 p += sizeof(notalloc);
252 size -= sizeof(notalloc);
253 }
254
255 return p - buf;
256}
257
258static PyObject *
259fortran_doc(FortranDataDef def)
260{
261 char *buf, *p;
262 PyObject *s = NULL;
263 Py_ssize_t n, origsize, size = 100;
264
265 if (def.doc != NULL) {
266 size += strlen(def.doc);
267 }
268 origsize = size;
269 buf = p = (char *)PyMem_Malloc(size);
270 if (buf == NULL) {
271 return PyErr_NoMemory();
272 }
273
274 if (def.rank == -1) {
275 if (def.doc) {
276 n = strlen(def.doc);
277 if (n > size) {
278 goto fail;
279 }
280 memcpy(p, def.doc, n);
281 p += n;
282 size -= n;
283 }
284 else {
285 n = PyOS_snprintf(p, size, "%s - no docs available", def.name);
286 if (n < 0 || n >= size) {
287 goto fail;
288 }
289 p += n;
290 size -= n;
291 }
292 }
293 else {
294 PyArray_Descr *d = PyArray_DescrFromType(def.type);
295 n = PyOS_snprintf(p, size, "%s : '%c'-", def.name, d->type);
296 Py_DECREF(d);
297 if (n < 0 || n >= size) {
298 goto fail;
299 }
300 p += n;
301 size -= n;
302
303 if (def.data == NULL) {
304 n = format_def(p, size, def);
305 if (n < 0) {
306 goto fail;
307 }
308 p += n;
309 size -= n;
310 }
311 else if (def.rank > 0) {
312 n = format_def(p, size, def);
313 if (n < 0) {
314 goto fail;
315 }
316 p += n;
317 size -= n;
318 }
319 else {
320 n = strlen("scalar");
321 if (size < n) {
322 goto fail;
323 }
324 memcpy(p, "scalar", n);
325 p += n;
326 size -= n;
327 }
328 }
329 if (size <= 1) {
330 goto fail;
331 }
332 *p++ = '\n';
333 size--;
334
335 /* p now points one beyond the last character of the string in buf */
336 s = PyUnicode_FromStringAndSize(buf, p - buf);
337
338 PyMem_Free(buf);
339 return s;
340
341fail:
342 fprintf(stderr,
343 "fortranobject.c: fortran_doc: len(p)=%zd>%zd=size:"
344 " too long docstring required, increase size\n",
345 p - buf, origsize);
346 PyMem_Free(buf);
347 return NULL;
348}
349
350static FortranDataDef *save_def; /* save pointer of an allocatable array */
351static void
352set_data(char *d, npy_intp *f)
353{ /* callback from Fortran */
354 if (*f) /* In fortran f=allocated(d) */
355 save_def->data = d;
356 else
357 save_def->data = NULL;
358 /* printf("set_data: d=%p,f=%d\n",d,*f); */
359}
360
361static PyObject *
362fortran_getattr(PyFortranObject *fp, char *name)
363{
364 int i, j, k, flag;
365 if (fp->dict != NULL) {
366 // python 3.13 added PyDict_GetItemRef
367#if PY_VERSION_HEX < 0x030D0000
368 PyObject *v = _PyDict_GetItemStringWithError(fp->dict, name); // noqa: borrowed-ref OK
369 if (v == NULL && PyErr_Occurred()) {
370 return NULL;
371 }
372 else if (v != NULL) {
373 Py_INCREF(v);
374 return v;
375 }
376#else
377 PyObject *v;
378 int result = PyDict_GetItemStringRef(fp->dict, name, &v);
379 if (result == -1) {
380 return NULL;
381 }
382 else if (result == 1) {
383 return v;
384 }
385#endif
386
387 }
388 for (i = 0, j = 1; i < fp->len && (j = strcmp(name, fp->defs[i].name));
389 i++)
390 ;
391 if (j == 0)
392 if (fp->defs[i].rank != -1) { /* F90 allocatable array */
393 if (fp->defs[i].func == NULL)
394 return NULL;
395 for (k = 0; k < fp->defs[i].rank; ++k) fp->defs[i].dims.d[k] = -1;
396 save_def = &fp->defs[i];
397 (*(fp->defs[i].func))(&fp->defs[i].rank, fp->defs[i].dims.d,
398 set_data, &flag);
399 if (flag == 2)
400 k = fp->defs[i].rank + 1;
401 else
402 k = fp->defs[i].rank;
403 if (fp->defs[i].data != NULL) { /* array is allocated */
404 PyObject *v = PyArray_New(
405 &PyArray_Type, k, fp->defs[i].dims.d, fp->defs[i].type,
406 NULL, fp->defs[i].data, 0, NPY_ARRAY_FARRAY, NULL);
407 if (v == NULL)
408 return NULL;
409 /* Py_INCREF(v); */
410 return v;
411 }
412 else { /* array is not allocated */
413 Py_RETURN_NONE;
414 }
415 }
416 if (strcmp(name, "__dict__") == 0) {
417 Py_INCREF(fp->dict);
418 return fp->dict;
419 }
420 if (strcmp(name, "__doc__") == 0) {
421 PyObject *s = PyUnicode_FromString(""), *s2, *s3;
422 for (i = 0; i < fp->len; i++) {
423 s2 = fortran_doc(fp->defs[i]);
424 s3 = PyUnicode_Concat(s, s2);
425 Py_DECREF(s2);
426 Py_DECREF(s);
427 s = s3;
428 }
429 if (PyDict_SetItemString(fp->dict, name, s))
430 return NULL;
431 return s;
432 }
433 if ((strcmp(name, "_cpointer") == 0) && (fp->len == 1)) {
434 PyObject *cobj =
435 F2PyCapsule_FromVoidPtr((void *)(fp->defs[0].data), NULL);
436 if (PyDict_SetItemString(fp->dict, name, cobj))
437 return NULL;
438 return cobj;
439 }
440 PyObject *str, *ret;
441 str = PyUnicode_FromString(name);
442 ret = PyObject_GenericGetAttr((PyObject *)fp, str);
443 Py_DECREF(str);
444 return ret;
445}
446
447static int
448fortran_setattr(PyFortranObject *fp, char *name, PyObject *v)
449{
450 int i, j, flag;
451 PyArrayObject *arr = NULL;
452 for (i = 0, j = 1; i < fp->len && (j = strcmp(name, fp->defs[i].name));
453 i++)
454 ;
455 if (j == 0) {
456 if (fp->defs[i].rank == -1) {
457 PyErr_SetString(PyExc_AttributeError,
458 "over-writing fortran routine");
459 return -1;
460 }
461 if (fp->defs[i].func != NULL) { /* is allocatable array */
462 npy_intp dims[F2PY_MAX_DIMS];
463 int k;
464 save_def = &fp->defs[i];
465 if (v != Py_None) { /* set new value (reallocate if needed --
466 see f2py generated code for more
467 details ) */
468 for (k = 0; k < fp->defs[i].rank; k++) dims[k] = -1;
469 if ((arr = array_from_pyobj(fp->defs[i].type, dims,
470 fp->defs[i].rank, F2PY_INTENT_IN,
471 v)) == NULL)
472 return -1;
473 (*(fp->defs[i].func))(&fp->defs[i].rank, PyArray_DIMS(arr),
474 set_data, &flag);
475 }
476 else { /* deallocate */
477 for (k = 0; k < fp->defs[i].rank; k++) dims[k] = 0;
478 (*(fp->defs[i].func))(&fp->defs[i].rank, dims, set_data,
479 &flag);
480 for (k = 0; k < fp->defs[i].rank; k++) dims[k] = -1;
481 }
482 memcpy(fp->defs[i].dims.d, dims,
483 fp->defs[i].rank * sizeof(npy_intp));
484 }
485 else { /* not allocatable array */
486 if ((arr = array_from_pyobj(fp->defs[i].type, fp->defs[i].dims.d,
487 fp->defs[i].rank, F2PY_INTENT_IN,
488 v)) == NULL)
489 return -1;
490 }
491 if (fp->defs[i].data !=
492 NULL) { /* copy Python object to Fortran array */
493 npy_intp s = PyArray_MultiplyList(fp->defs[i].dims.d,
494 PyArray_NDIM(arr));
495 if (s == -1)
496 s = PyArray_MultiplyList(PyArray_DIMS(arr), PyArray_NDIM(arr));
497 if (s < 0 || (memcpy(fp->defs[i].data, PyArray_DATA(arr),
498 s * PyArray_ITEMSIZE(arr))) == NULL) {
499 if ((PyObject *)arr != v) {
500 Py_DECREF(arr);
501 }
502 return -1;
503 }
504 if ((PyObject *)arr != v) {
505 Py_DECREF(arr);
506 }
507 }
508 else
509 return (fp->defs[i].func == NULL ? -1 : 0);
510 return 0; /* successful */
511 }
512 if (fp->dict == NULL) {
513 fp->dict = PyDict_New();
514 if (fp->dict == NULL)
515 return -1;
516 }
517 if (v == NULL) {
518 int rv = PyDict_DelItemString(fp->dict, name);
519 if (rv < 0)
520 PyErr_SetString(PyExc_AttributeError,
521 "delete non-existing fortran attribute");
522 return rv;
523 }
524 else
525 return PyDict_SetItemString(fp->dict, name, v);
526}
527
528static PyObject *
529fortran_call(PyFortranObject *fp, PyObject *arg, PyObject *kw)
530{
531 int i = 0;
532 /* printf("fortran call
533 name=%s,func=%p,data=%p,%p\n",fp->defs[i].name,
534 fp->defs[i].func,fp->defs[i].data,&fp->defs[i].data); */
535 if (fp->defs[i].rank == -1) { /* is Fortran routine */
536 if (fp->defs[i].func == NULL) {
537 PyErr_Format(PyExc_RuntimeError, "no function to call");
538 return NULL;
539 }
540 else if (fp->defs[i].data == NULL)
541 /* dummy routine */
542 return (*((fortranfunc)(fp->defs[i].func)))((PyObject *)fp, arg,
543 kw, NULL);
544 else
545 return (*((fortranfunc)(fp->defs[i].func)))(
546 (PyObject *)fp, arg, kw, (void *)fp->defs[i].data);
547 }
548 PyErr_Format(PyExc_TypeError, "this fortran object is not callable");
549 return NULL;
550}
551
552static PyObject *
553fortran_repr(PyFortranObject *fp)
554{
555 PyObject *name = NULL, *repr = NULL;
556 name = PyObject_GetAttrString((PyObject *)fp, "__name__");
557 PyErr_Clear();
558 if (name != NULL && PyUnicode_Check(name)) {
559 repr = PyUnicode_FromFormat("<fortran %U>", name);
560 }
561 else {
562 repr = PyUnicode_FromString("<fortran object>");
563 }
564 Py_XDECREF(name);
565 return repr;
566}
567
568PyTypeObject PyFortran_Type = {
569 PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fortran",
570 .tp_basicsize = sizeof(PyFortranObject),
571 .tp_dealloc = (destructor)fortran_dealloc,
572 .tp_getattr = (getattrfunc)fortran_getattr,
573 .tp_setattr = (setattrfunc)fortran_setattr,
574 .tp_repr = (reprfunc)fortran_repr,
575 .tp_call = (ternaryfunc)fortran_call,
576};
577
578/************************* f2py_report_atexit *******************************/
579
580#ifdef F2PY_REPORT_ATEXIT
581static int passed_time = 0;
582static int passed_counter = 0;
583static int passed_call_time = 0;
584static struct timeb start_time;
585static struct timeb stop_time;
586static struct timeb start_call_time;
587static struct timeb stop_call_time;
588static int cb_passed_time = 0;
589static int cb_passed_counter = 0;
590static int cb_passed_call_time = 0;
591static struct timeb cb_start_time;
592static struct timeb cb_stop_time;
593static struct timeb cb_start_call_time;
594static struct timeb cb_stop_call_time;
595
596extern void
597f2py_start_clock(void)
598{
599 ftime(&start_time);
600}
601extern void
602f2py_start_call_clock(void)
603{
604 f2py_stop_clock();
605 ftime(&start_call_time);
606}
607extern void
608f2py_stop_clock(void)
609{
610 ftime(&stop_time);
611 passed_time += 1000 * (stop_time.time - start_time.time);
612 passed_time += stop_time.millitm - start_time.millitm;
613}
614extern void
615f2py_stop_call_clock(void)
616{
617 ftime(&stop_call_time);
618 passed_call_time += 1000 * (stop_call_time.time - start_call_time.time);
619 passed_call_time += stop_call_time.millitm - start_call_time.millitm;
620 passed_counter += 1;
621 f2py_start_clock();
622}
623
624extern void
625f2py_cb_start_clock(void)
626{
627 ftime(&cb_start_time);
628}
629extern void
630f2py_cb_start_call_clock(void)
631{
632 f2py_cb_stop_clock();
633 ftime(&cb_start_call_time);
634}
635extern void
636f2py_cb_stop_clock(void)
637{
638 ftime(&cb_stop_time);
639 cb_passed_time += 1000 * (cb_stop_time.time - cb_start_time.time);
640 cb_passed_time += cb_stop_time.millitm - cb_start_time.millitm;
641}
642extern void
643f2py_cb_stop_call_clock(void)
644{
645 ftime(&cb_stop_call_time);
646 cb_passed_call_time +=
647 1000 * (cb_stop_call_time.time - cb_start_call_time.time);
648 cb_passed_call_time +=
649 cb_stop_call_time.millitm - cb_start_call_time.millitm;
650 cb_passed_counter += 1;
651 f2py_cb_start_clock();
652}
653
654static int f2py_report_on_exit_been_here = 0;
655extern void
656f2py_report_on_exit(int exit_flag, void *name)
657{
658 if (f2py_report_on_exit_been_here) {
659 fprintf(stderr, " %s\n", (char *)name);
660 return;
661 }
662 f2py_report_on_exit_been_here = 1;
663 fprintf(stderr, " /-----------------------\\\n");
664 fprintf(stderr, " < F2PY performance report >\n");
665 fprintf(stderr, " \\-----------------------/\n");
666 fprintf(stderr, "Overall time spent in ...\n");
667 fprintf(stderr, "(a) wrapped (Fortran/C) functions : %8d msec\n",
668 passed_call_time);
669 fprintf(stderr, "(b) f2py interface, %6d calls : %8d msec\n",
670 passed_counter, passed_time);
671 fprintf(stderr, "(c) call-back (Python) functions : %8d msec\n",
672 cb_passed_call_time);
673 fprintf(stderr, "(d) f2py call-back interface, %6d calls : %8d msec\n",
674 cb_passed_counter, cb_passed_time);
675
676 fprintf(stderr,
677 "(e) wrapped (Fortran/C) functions (actual) : %8d msec\n\n",
678 passed_call_time - cb_passed_call_time - cb_passed_time);
679 fprintf(stderr,
680 "Use -DF2PY_REPORT_ATEXIT_DISABLE to disable this message.\n");
681 fprintf(stderr, "Exit status: %d\n", exit_flag);
682 fprintf(stderr, "Modules : %s\n", (char *)name);
683}
684#endif
685
686/********************** report on array copy ****************************/
687
688#ifdef F2PY_REPORT_ON_ARRAY_COPY
689static void
690f2py_report_on_array_copy(PyArrayObject *arr)
691{
692 const npy_intp arr_size = PyArray_Size((PyObject *)arr);
693 if (arr_size > F2PY_REPORT_ON_ARRAY_COPY) {
694 fprintf(stderr,
695 "copied an array: size=%ld, elsize=%" NPY_INTP_FMT "\n",
696 arr_size, (npy_intp)PyArray_ITEMSIZE(arr));
697 }
698}
699static void
700f2py_report_on_array_copy_fromany(void)
701{
702 fprintf(stderr, "created an array from object\n");
703}
704
705#define F2PY_REPORT_ON_ARRAY_COPY_FROMARR \
706 f2py_report_on_array_copy((PyArrayObject *)arr)
707#define F2PY_REPORT_ON_ARRAY_COPY_FROMANY f2py_report_on_array_copy_fromany()
708#else
709#define F2PY_REPORT_ON_ARRAY_COPY_FROMARR
710#define F2PY_REPORT_ON_ARRAY_COPY_FROMANY
711#endif
712
713/************************* array_from_obj *******************************/
714
715/*
716 * File: array_from_pyobj.c
717 *
718 * Description:
719 * ------------
720 * Provides array_from_pyobj function that returns a contiguous array
721 * object with the given dimensions and required storage order, either
722 * in row-major (C) or column-major (Fortran) order. The function
723 * array_from_pyobj is very flexible about its Python object argument
724 * that can be any number, list, tuple, or array.
725 *
726 * array_from_pyobj is used in f2py generated Python extension
727 * modules.
728 *
729 * Author: Pearu Peterson <pearu@cens.ioc.ee>
730 * Created: 13-16 January 2002
731 * $Id: fortranobject.c,v 1.52 2005/07/11 07:44:20 pearu Exp $
732 */
733
734static int check_and_fix_dimensions(const PyArrayObject* arr,
735 const int rank,
736 npy_intp *dims,
737 const char *errmess);
738
739static int
740find_first_negative_dimension(const int rank, const npy_intp *dims)
741{
742 int i;
743 for (i = 0; i < rank; ++i) {
744 if (dims[i] < 0) {
745 return i;
746 }
747 }
748 return -1;
749}
750
751#ifdef DEBUG_COPY_ND_ARRAY
752void
753dump_dims(int rank, npy_intp const *dims)
754{
755 int i;
756 printf("[");
757 for (i = 0; i < rank; ++i) {
758 printf("%3" NPY_INTP_FMT, dims[i]);
759 }
760 printf("]\n");
761}
762void
763dump_attrs(const PyArrayObject *obj)
764{
765 const PyArrayObject_fields *arr = (const PyArrayObject_fields *)obj;
766 int rank = PyArray_NDIM(arr);
767 npy_intp size = PyArray_Size((PyObject *)arr);
768 printf("\trank = %d, flags = %d, size = %" NPY_INTP_FMT "\n", rank,
769 arr->flags, size);
770 printf("\tstrides = ");
771 dump_dims(rank, arr->strides);
772 printf("\tdimensions = ");
773 dump_dims(rank, arr->dimensions);
774}
775#endif
776
777#define SWAPTYPE(a, b, t) \
778 { \
779 t c; \
780 c = (a); \
781 (a) = (b); \
782 (b) = c; \
783 }
784
785static int
786swap_arrays(PyArrayObject *obj1, PyArrayObject *obj2)
787{
788 PyArrayObject_fields *arr1 = (PyArrayObject_fields *)obj1,
789 *arr2 = (PyArrayObject_fields *)obj2;
790 SWAPTYPE(arr1->data, arr2->data, char *);
791 SWAPTYPE(arr1->nd, arr2->nd, int);
792 SWAPTYPE(arr1->dimensions, arr2->dimensions, npy_intp *);
793 SWAPTYPE(arr1->strides, arr2->strides, npy_intp *);
794 SWAPTYPE(arr1->base, arr2->base, PyObject *);
795 SWAPTYPE(arr1->descr, arr2->descr, PyArray_Descr *);
796 SWAPTYPE(arr1->flags, arr2->flags, int);
797 /* SWAPTYPE(arr1->weakreflist,arr2->weakreflist,PyObject*); */
798 return 0;
799}
800
801#define ARRAY_ISCOMPATIBLE(arr,type_num) \
802 ((PyArray_ISINTEGER(arr) && PyTypeNum_ISINTEGER(type_num)) || \
803 (PyArray_ISFLOAT(arr) && PyTypeNum_ISFLOAT(type_num)) || \
804 (PyArray_ISCOMPLEX(arr) && PyTypeNum_ISCOMPLEX(type_num)) || \
805 (PyArray_ISBOOL(arr) && PyTypeNum_ISBOOL(type_num)) || \
806 (PyArray_ISSTRING(arr) && PyTypeNum_ISSTRING(type_num)))
807
808static int
809get_elsize(PyObject *obj) {
810 /*
811 get_elsize determines array itemsize from a Python object. Returns
812 elsize if successful, -1 otherwise.
813
814 Supported types of the input are: numpy.ndarray, bytes, str, tuple,
815 list.
816 */
817
818 if (PyArray_Check(obj)) {
819 return PyArray_ITEMSIZE((PyArrayObject *)obj);
820 } else if (PyBytes_Check(obj)) {
821 return PyBytes_GET_SIZE(obj);
822 } else if (PyUnicode_Check(obj)) {
823 return PyUnicode_GET_LENGTH(obj);
824 } else if (PySequence_Check(obj)) {
825 PyObject* fast = PySequence_Fast(obj, "f2py:fortranobject.c:get_elsize"); // noqa: borrowed-ref OK
826 if (fast != NULL) {
827 Py_ssize_t i, n = PySequence_Fast_GET_SIZE(fast);
828 int sz, elsize = 0;
829 for (i=0; i<n; i++) {
830 sz = get_elsize(PySequence_Fast_GET_ITEM(fast, i) /* borrowed */);
831 if (sz > elsize) {
832 elsize = sz;
833 }
834 }
835 Py_DECREF(fast);
836 return elsize;
837 }
838 }
839 return -1;
840}
841
842extern PyArrayObject *
843ndarray_from_pyobj(const int type_num,
844 const int elsize_,
845 npy_intp *dims,
846 const int rank,
847 const int intent,
848 PyObject *obj,
849 const char *errmess) {
850 /*
851 * Return an array with given element type and shape from a Python
852 * object while taking into account the usage intent of the array.
853 *
854 * - element type is defined by type_num and elsize
855 * - shape is defined by dims and rank
856 *
857 * ndarray_from_pyobj is used to convert Python object arguments
858 * to numpy ndarrays with given type and shape that data is passed
859 * to interfaced Fortran or C functions.
860 *
861 * errmess (if not NULL), contains a prefix of an error message
862 * for an exception to be triggered within this function.
863 *
864 * Negative elsize value means that elsize is to be determined
865 * from the Python object in runtime.
866 *
867 * Note on strings
868 * ---------------
869 *
870 * String type (type_num == NPY_STRING) does not have fixed
871 * element size and, by default, the type object sets it to
872 * 0. Therefore, for string types, one has to use elsize
873 * argument. For other types, elsize value is ignored.
874 *
875 * NumPy defines the type of a fixed-width string as
876 * dtype('S<width>'). In addition, there is also dtype('c'), that
877 * appears as dtype('S1') (these have the same type_num value),
878 * but is actually different (.char attribute is either 'S' or
879 * 'c', respectively).
880 *
881 * In Fortran, character arrays and strings are different
882 * concepts. The relation between Fortran types, NumPy dtypes,
883 * and type_num-elsize pairs, is defined as follows:
884 *
885 * character*5 foo | dtype('S5') | elsize=5, shape=()
886 * character(5) foo | dtype('S1') | elsize=1, shape=(5)
887 * character*5 foo(n) | dtype('S5') | elsize=5, shape=(n,)
888 * character(5) foo(n) | dtype('S1') | elsize=1, shape=(5, n)
889 * character*(*) foo | dtype('S') | elsize=-1, shape=()
890 *
891 * Note about reference counting
892 * -----------------------------
893 *
894 * If the caller returns the array to Python, it must be done with
895 * Py_BuildValue("N",arr). Otherwise, if obj!=arr then the caller
896 * must call Py_DECREF(arr).
897 *
898 * Note on intent(cache,out,..)
899 * ----------------------------
900 * Don't expect correct data when returning intent(cache) array.
901 *
902 */
903 char mess[F2PY_MESSAGE_BUFFER_SIZE];
904 PyArrayObject *arr = NULL;
905 int elsize = (elsize_ < 0 ? get_elsize(obj) : elsize_);
906 if (elsize < 0) {
907 if (errmess != NULL) {
908 strcpy(mess, errmess);
909 }
910 sprintf(mess + strlen(mess),
911 " -- failed to determine element size from %s",
912 Py_TYPE(obj)->tp_name);
913 PyErr_SetString(PyExc_SystemError, mess);
914 return NULL;
915 }
916 PyArray_Descr * descr = get_descr_from_type_and_elsize(type_num, elsize); // new reference
917 if (descr == NULL) {
918 return NULL;
919 }
920 elsize = PyDataType_ELSIZE(descr);
921 if ((intent & F2PY_INTENT_HIDE)
922 || ((intent & F2PY_INTENT_CACHE) && (obj == Py_None))
923 || ((intent & F2PY_OPTIONAL) && (obj == Py_None))
924 ) {
925 /* intent(cache), optional, intent(hide) */
926 int ineg = find_first_negative_dimension(rank, dims);
927 if (ineg >= 0) {
928 int i;
929 strcpy(mess, "failed to create intent(cache|hide)|optional array"
930 "-- must have defined dimensions but got (");
931 for(i = 0; i < rank; ++i)
932 sprintf(mess + strlen(mess), "%" NPY_INTP_FMT ",", dims[i]);
933 strcat(mess, ")");
934 PyErr_SetString(PyExc_ValueError, mess);
935 Py_DECREF(descr);
936 return NULL;
937 }
938 arr = (PyArrayObject *) \
939 PyArray_NewFromDescr(&PyArray_Type, descr, rank, dims,
940 NULL, NULL, !(intent & F2PY_INTENT_C), NULL);
941 if (arr == NULL) {
942 Py_DECREF(descr);
943 return NULL;
944 }
945 if (PyArray_ITEMSIZE(arr) != elsize) {
946 strcpy(mess, "failed to create intent(cache|hide)|optional array");
947 sprintf(mess+strlen(mess)," -- expected elsize=%d got %" NPY_INTP_FMT, elsize, (npy_intp)PyArray_ITEMSIZE(arr));
948 PyErr_SetString(PyExc_ValueError,mess);
949 Py_DECREF(arr);
950 return NULL;
951 }
952 if (!(intent & F2PY_INTENT_CACHE)) {
953 PyArray_FILLWBYTE(arr, 0);
954 }
955 return arr;
956 }
957
958 if (PyArray_Check(obj)) {
959 arr = (PyArrayObject *)obj;
960 if (intent & F2PY_INTENT_CACHE) {
961 /* intent(cache) */
962 if (PyArray_ISONESEGMENT(arr)
963 && PyArray_ITEMSIZE(arr) >= elsize) {
964 if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
965 Py_DECREF(descr);
966 return NULL;
967 }
968 if (intent & F2PY_INTENT_OUT)
969 Py_INCREF(arr);
970 Py_DECREF(descr);
971 return arr;
972 }
973 strcpy(mess, "failed to initialize intent(cache) array");
974 if (!PyArray_ISONESEGMENT(arr))
975 strcat(mess, " -- input must be in one segment");
976 if (PyArray_ITEMSIZE(arr) < elsize)
977 sprintf(mess + strlen(mess),
978 " -- expected at least elsize=%d but got "
979 "%" NPY_INTP_FMT,
980 elsize, (npy_intp)PyArray_ITEMSIZE(arr));
981 PyErr_SetString(PyExc_ValueError, mess);
982 Py_DECREF(descr);
983 return NULL;
984 }
985
986 /* here we have always intent(in) or intent(inout) or intent(inplace)
987 */
988
989 if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
990 Py_DECREF(descr);
991 return NULL;
992 }
993 /*
994 printf("intent alignment=%d\n", F2PY_GET_ALIGNMENT(intent));
995 printf("alignment check=%d\n", F2PY_CHECK_ALIGNMENT(arr, intent));
996 int i;
997 for (i=1;i<=16;i++)
998 printf("i=%d isaligned=%d\n", i, ARRAY_ISALIGNED(arr, i));
999 */
1000 if ((! (intent & F2PY_INTENT_COPY)) &&
1001 PyArray_ITEMSIZE(arr) == elsize &&
1002 ARRAY_ISCOMPATIBLE(arr,type_num) &&
1003 F2PY_CHECK_ALIGNMENT(arr, intent)) {
1004 if ((intent & F2PY_INTENT_INOUT || intent & F2PY_INTENT_INPLACE)
1005 ? ((intent & F2PY_INTENT_C) ? PyArray_ISCARRAY(arr) : PyArray_ISFARRAY(arr))
1006 : ((intent & F2PY_INTENT_C) ? PyArray_ISCARRAY_RO(arr) : PyArray_ISFARRAY_RO(arr))) {
1007 if ((intent & F2PY_INTENT_OUT)) {
1008 Py_INCREF(arr);
1009 }
1010 /* Returning input array */
1011 Py_DECREF(descr);
1012 return arr;
1013 }
1014 }
1015 if (intent & F2PY_INTENT_INOUT) {
1016 strcpy(mess, "failed to initialize intent(inout) array");
1017 /* Must use PyArray_IS*ARRAY because intent(inout) requires
1018 * writable input */
1019 if ((intent & F2PY_INTENT_C) && !PyArray_ISCARRAY(arr))
1020 strcat(mess, " -- input not contiguous");
1021 if (!(intent & F2PY_INTENT_C) && !PyArray_ISFARRAY(arr))
1022 strcat(mess, " -- input not fortran contiguous");
1023 if (PyArray_ITEMSIZE(arr) != elsize)
1024 sprintf(mess + strlen(mess),
1025 " -- expected elsize=%d but got %" NPY_INTP_FMT,
1026 elsize,
1027 (npy_intp)PyArray_ITEMSIZE(arr)
1028 );
1029 if (!(ARRAY_ISCOMPATIBLE(arr, type_num))) {
1030 sprintf(mess + strlen(mess),
1031 " -- input '%c' not compatible to '%c'",
1032 PyArray_DESCR(arr)->type, descr->type);
1033 }
1034 if (!(F2PY_CHECK_ALIGNMENT(arr, intent)))
1035 sprintf(mess + strlen(mess), " -- input not %d-aligned",
1036 F2PY_GET_ALIGNMENT(intent));
1037 PyErr_SetString(PyExc_ValueError, mess);
1038 Py_DECREF(descr);
1039 return NULL;
1040 }
1041
1042 /* here we have always intent(in) or intent(inplace) */
1043
1044 {
1045 PyArrayObject * retarr = (PyArrayObject *) \
1046 PyArray_NewFromDescr(&PyArray_Type, descr, PyArray_NDIM(arr), PyArray_DIMS(arr),
1047 NULL, NULL, !(intent & F2PY_INTENT_C), NULL);
1048 if (retarr==NULL) {
1049 Py_DECREF(descr);
1050 return NULL;
1051 }
1052 F2PY_REPORT_ON_ARRAY_COPY_FROMARR;
1053 if (PyArray_CopyInto(retarr, arr)) {
1054 Py_DECREF(retarr);
1055 return NULL;
1056 }
1057 if (intent & F2PY_INTENT_INPLACE) {
1058 if (swap_arrays(arr,retarr)) {
1059 Py_DECREF(retarr);
1060 return NULL; /* XXX: set exception */
1061 }
1062 Py_XDECREF(retarr);
1063 if (intent & F2PY_INTENT_OUT)
1064 Py_INCREF(arr);
1065 } else {
1066 arr = retarr;
1067 }
1068 }
1069 return arr;
1070 }
1071
1072 if ((intent & F2PY_INTENT_INOUT) || (intent & F2PY_INTENT_INPLACE) ||
1073 (intent & F2PY_INTENT_CACHE)) {
1074 PyErr_Format(PyExc_TypeError,
1075 "failed to initialize intent(inout|inplace|cache) "
1076 "array, input '%s' object is not an array",
1077 Py_TYPE(obj)->tp_name);
1078 Py_DECREF(descr);
1079 return NULL;
1080 }
1081
1082 {
1083 F2PY_REPORT_ON_ARRAY_COPY_FROMANY;
1084 arr = (PyArrayObject *)PyArray_FromAny(
1085 obj, descr, 0, 0,
1086 ((intent & F2PY_INTENT_C) ? NPY_ARRAY_CARRAY
1087 : NPY_ARRAY_FARRAY) |
1088 NPY_ARRAY_FORCECAST,
1089 NULL);
1090 // Warning: in the case of NPY_STRING, PyArray_FromAny may
1091 // reset descr->elsize, e.g. dtype('S0') becomes dtype('S1').
1092 if (arr == NULL) {
1093 Py_DECREF(descr);
1094 return NULL;
1095 }
1096 if (type_num != NPY_STRING && PyArray_ITEMSIZE(arr) != elsize) {
1097 // This is internal sanity tests: elsize has been set to
1098 // descr->elsize in the beginning of this function.
1099 strcpy(mess, "failed to initialize intent(in) array");
1100 sprintf(mess + strlen(mess),
1101 " -- expected elsize=%d got %" NPY_INTP_FMT, elsize,
1102 (npy_intp)PyArray_ITEMSIZE(arr));
1103 PyErr_SetString(PyExc_ValueError, mess);
1104 Py_DECREF(arr);
1105 return NULL;
1106 }
1107 if (check_and_fix_dimensions(arr, rank, dims, errmess)) {
1108 Py_DECREF(arr);
1109 return NULL;
1110 }
1111 return arr;
1112 }
1113}
1114
1115extern PyArrayObject *
1116array_from_pyobj(const int type_num,
1117 npy_intp *dims,
1118 const int rank,
1119 const int intent,
1120 PyObject *obj) {
1121 /*
1122 Same as ndarray_from_pyobj but with elsize determined from type,
1123 if possible. Provided for backward compatibility.
1124 */
1125 PyArray_Descr* descr = PyArray_DescrFromType(type_num);
1126 int elsize = PyDataType_ELSIZE(descr);
1127 Py_DECREF(descr);
1128 return ndarray_from_pyobj(type_num, elsize, dims, rank, intent, obj, NULL);
1129}
1130
1131/*****************************************/
1132/* Helper functions for array_from_pyobj */
1133/*****************************************/
1134
1135static int
1136check_and_fix_dimensions(const PyArrayObject* arr, const int rank,
1137 npy_intp *dims, const char *errmess)
1138{
1139 /*
1140 * This function fills in blanks (that are -1's) in dims list using
1141 * the dimensions from arr. It also checks that non-blank dims will
1142 * match with the corresponding values in arr dimensions.
1143 *
1144 * Returns 0 if the function is successful.
1145 *
1146 * If an error condition is detected, an exception is set and 1 is
1147 * returned.
1148 */
1149 char mess[F2PY_MESSAGE_BUFFER_SIZE];
1150 const npy_intp arr_size =
1151 (PyArray_NDIM(arr)) ? PyArray_Size((PyObject *)arr) : 1;
1152#ifdef DEBUG_COPY_ND_ARRAY
1153 dump_attrs(arr);
1154 printf("check_and_fix_dimensions:init: dims=");
1155 dump_dims(rank, dims);
1156#endif
1157 if (rank > PyArray_NDIM(arr)) { /* [1,2] -> [[1],[2]]; 1 -> [[1]] */
1158 npy_intp new_size = 1;
1159 int free_axe = -1;
1160 int i;
1161 npy_intp d;
1162 /* Fill dims where -1 or 0; check dimensions; calc new_size; */
1163 for (i = 0; i < PyArray_NDIM(arr); ++i) {
1164 d = PyArray_DIM(arr, i);
1165 if (dims[i] >= 0) {
1166 if (d > 1 && dims[i] != d) {
1167 PyErr_Format(
1168 PyExc_ValueError,
1169 "%d-th dimension must be fixed to %" NPY_INTP_FMT
1170 " but got %" NPY_INTP_FMT "\n",
1171 i, dims[i], d);
1172 return 1;
1173 }
1174 if (!dims[i])
1175 dims[i] = 1;
1176 }
1177 else {
1178 dims[i] = d ? d : 1;
1179 }
1180 new_size *= dims[i];
1181 }
1182 for (i = PyArray_NDIM(arr); i < rank; ++i)
1183 if (dims[i] > 1) {
1184 PyErr_Format(PyExc_ValueError,
1185 "%d-th dimension must be %" NPY_INTP_FMT
1186 " but got 0 (not defined).\n",
1187 i, dims[i]);
1188 return 1;
1189 }
1190 else if (free_axe < 0)
1191 free_axe = i;
1192 else
1193 dims[i] = 1;
1194 if (free_axe >= 0) {
1195 dims[free_axe] = arr_size / new_size;
1196 new_size *= dims[free_axe];
1197 }
1198 if (new_size != arr_size) {
1199 PyErr_Format(PyExc_ValueError,
1200 "unexpected array size: new_size=%" NPY_INTP_FMT
