arraykit 0.10.0__cp313-cp313-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
arraykit/tri_map.c ADDED
@@ -0,0 +1,1395 @@
1
+ # include "Python.h"
2
+ # include "stdbool.h"
3
+
4
+ # define NO_IMPORT_ARRAY
5
+ # define PY_ARRAY_UNIQUE_SYMBOL AK_ARRAY_API
6
+ # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
7
+
8
+ # include "numpy/arrayobject.h"
9
+ # include "numpy/arrayscalars.h"
10
+
11
+ # include "tri_map.h"
12
+ # include "utilities.h"
13
+
14
+ static inline NPY_DATETIMEUNIT
15
+ AK_dt_unit_from_array(PyArrayObject* a) {
16
+ // This is based on get_datetime_metadata_from_dtype in the NumPy source, but that function is private. This does not check that the dtype is of the appropriate type.
17
+ PyArray_Descr* dt = PyArray_DESCR(a); // borrowed ref
18
+ PyArray_DatetimeMetaData* dma = &(((PyArray_DatetimeDTypeMetaData *)PyDataType_C_METADATA(dt))->meta);
19
+ // PyArray_DatetimeMetaData* dma = &(((PyArray_DatetimeDTypeMetaData *)PyArray_DESCR(a)->c_metadata)->meta);
20
+ return dma->base;
21
+ }
22
+
23
+ typedef struct TriMapOne {
24
+ Py_ssize_t from; // signed
25
+ Py_ssize_t to;
26
+ } TriMapOne;
27
+
28
+ typedef struct TriMapManyTo {
29
+ Py_ssize_t start;
30
+ Py_ssize_t stop;
31
+ } TriMapManyTo;
32
+
33
+ typedef struct TriMapManyFrom {
34
+ npy_intp src;
35
+ PyArrayObject* dst;
36
+ } TriMapManyFrom;
37
+
38
+ typedef struct TriMapObject {
39
+ PyObject_HEAD
40
+ Py_ssize_t src_len;
41
+ Py_ssize_t dst_len;
42
+ Py_ssize_t len;
43
+ bool is_many;
44
+ bool finalized;
45
+
46
+ PyObject* src_match; // array object
47
+ npy_bool* src_match_data; // contiguous C array
48
+ PyObject* dst_match; // array object
49
+ npy_bool* dst_match_data; // contiguous C array
50
+
51
+ PyObject* final_src_fill; // array object
52
+ PyObject* final_dst_fill; // array object
53
+
54
+ // register one
55
+ TriMapOne* src_one;
56
+ Py_ssize_t src_one_count;
57
+ Py_ssize_t src_one_capacity;
58
+
59
+ TriMapOne* dst_one;
60
+ Py_ssize_t dst_one_count;
61
+ Py_ssize_t dst_one_capacity;
62
+
63
+ // register_many
64
+ TriMapManyTo* many_to; // two integers for contiguous assignment region
65
+ TriMapManyFrom* many_from; // int and array, for src and dst (together)
66
+ Py_ssize_t many_count;
67
+ Py_ssize_t many_capacity;
68
+
69
+ } TriMapObject;
70
+
71
+ PyObject *
72
+ TriMap_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs) {
73
+ TriMapObject *self = (TriMapObject *)cls->tp_alloc(cls, 0);
74
+ if (!self) {
75
+ return NULL;
76
+ }
77
+ return (PyObject *)self;
78
+ }
79
+
80
+ PyDoc_STRVAR(
81
+ TriMap_doc,
82
+ "\n"
83
+ "A utilty for three-way join mappings."
84
+ );
85
+
86
+ // Returns 0 on success, -1 on error.
87
+ int
88
+ TriMap_init(PyObject *self, PyObject *args, PyObject *kwargs) {
89
+ Py_ssize_t src_len;
90
+ Py_ssize_t dst_len;
91
+ if (!PyArg_ParseTuple(args,
92
+ "nn:__init__",
93
+ &src_len,
94
+ &dst_len)) {
95
+ return -1;
96
+ }
97
+ TriMapObject* tm = (TriMapObject*)self;
98
+ // handle all C types
99
+ tm->src_len = src_len;
100
+ tm->dst_len = dst_len;
101
+ tm->is_many = false;
102
+ tm->finalized = false;
103
+ tm->len = 0;
104
+
105
+ // we create arrays, and also pre-extract pointers to array data for fast insertion; we keep the array for optimal summing routines
106
+ npy_intp dims_src_len[] = {src_len};
107
+ tm->src_match = PyArray_ZEROS(1, dims_src_len, NPY_BOOL, 0);
108
+ if (tm->src_match == NULL) {
109
+ return -1;
110
+ }
111
+ tm->src_match_data = (npy_bool*)PyArray_DATA((PyArrayObject*)tm->src_match);
112
+
113
+ npy_intp dims_dst_len[] = {dst_len};
114
+ tm->dst_match = PyArray_ZEROS(1, dims_dst_len, NPY_BOOL, 0);
115
+ if (tm->dst_match == NULL) {
116
+ return -1;
117
+ }
118
+ tm->dst_match_data = (npy_bool*)PyArray_DATA((PyArrayObject*)tm->dst_match);
119
+
120
+ // register one
121
+ tm->src_one_count = 0;
122
+ tm->src_one_capacity = 16;
123
+ tm->src_one = (TriMapOne*)PyMem_Malloc(
124
+ sizeof(TriMapOne) * tm->src_one_capacity);
125
+ if (tm->src_one == NULL) {
126
+ PyErr_SetNone(PyExc_MemoryError);
127
+ return -1;
128
+ }
129
+ tm->dst_one_count = 0;
130
+ tm->dst_one_capacity = 16;
131
+ tm->dst_one = (TriMapOne*)PyMem_Malloc(
132
+ sizeof(TriMapOne) * tm->dst_one_capacity);
133
+ if (tm->dst_one == NULL) {
134
+ PyErr_SetNone(PyExc_MemoryError);
135
+ return -1;
136
+ }
137
+ // register many
138
+ tm->many_count = 0;
139
+ tm->many_capacity = 16;
140
+ tm->many_to = (TriMapManyTo*)PyMem_Malloc(
141
+ sizeof(TriMapManyTo) * tm->many_capacity);
142
+ if (tm->many_to == NULL) {
143
+ PyErr_SetNone(PyExc_MemoryError);
144
+ return -1;
145
+ }
146
+ tm->many_from = (TriMapManyFrom*)PyMem_Malloc(
147
+ sizeof(TriMapManyFrom) * tm->many_capacity);
148
+ if (tm->many_from == NULL) {
149
+ PyErr_SetNone(PyExc_MemoryError);
150
+ return -1;
151
+ }
152
+
153
+ return 0;
154
+ }
155
+
156
+ void
157
+ TriMap_dealloc(TriMapObject *self) {
158
+ // NOTE: we use XDECREF incase init fails before these objects get allocated
159
+ Py_XDECREF(self->src_match);
160
+ Py_XDECREF(self->dst_match);
161
+ Py_XDECREF(self->final_src_fill);
162
+ Py_XDECREF(self->final_dst_fill);
163
+
164
+ if (self->src_one != NULL) {
165
+ PyMem_Free(self->src_one);
166
+ }
167
+ if (self->dst_one != NULL) {
168
+ PyMem_Free(self->dst_one);
169
+ }
170
+ if (self->many_to != NULL) {
171
+ PyMem_Free(self->many_to);
172
+ }
173
+ if (self->many_from != NULL) {
174
+ // decref all arrays before freeing
175
+ for (Py_ssize_t i = 0; i < self->many_count; i++) {
176
+ // NOTE: using dot to get to pointer?
177
+ Py_DECREF((PyObject*)self->many_from[i].dst);
178
+ }
179
+ PyMem_Free(self->many_from);
180
+ }
181
+ Py_TYPE(self)->tp_free((PyObject *)self);
182
+ }
183
+
184
+ PyObject *
185
+ TriMap_repr(TriMapObject *self) {
186
+ const char *is_many = self->is_many ? "true" : "false";
187
+ const char *is_finalized = self->finalized ? "true" : "false";
188
+
189
+ npy_intp src_fill;
190
+ npy_intp dst_fill;
191
+ if (self->finalized) {
192
+ src_fill = PyArray_SIZE((PyArrayObject*)self->final_src_fill);
193
+ dst_fill = PyArray_SIZE((PyArrayObject*)self->final_dst_fill);
194
+ }
195
+ else {
196
+ src_fill = -1;
197
+ dst_fill = -1;
198
+ }
199
+
200
+ return PyUnicode_FromFormat("<%s(len: %i, src_fill: %i, dst_fill: %i, is_many: %s, is_finalized: %s)>",
201
+ Py_TYPE(self)->tp_name,
202
+ self->len,
203
+ src_fill,
204
+ dst_fill,
205
+ is_many,
206
+ is_finalized);
207
+ }
208
+
209
+ // Provide the integer positions connecting the `src` to the `dst`. If there is no match to `src` or `dst`, the unmatched position can be provided with -1. From each side, a connection is documented to the current `len`. Each time this is called `len` is incremented, indicating the inrease in position in the `final`. Return NULL on error.
210
+
211
+ // Inner function for calling from C; returns 0 on success, -1 on error. Exceptions will be set on error.
212
+ static inline int
213
+ AK_TM_register_one(TriMapObject* tm, Py_ssize_t src_from, Py_ssize_t dst_from) {
214
+ bool src_matched = src_from >= 0;
215
+ bool dst_matched = dst_from >= 0;
216
+ if (src_from >= tm->src_len || dst_from >= tm->dst_len) {
217
+ PyErr_SetString(PyExc_ValueError, "Out of bounds locator");
218
+ return -1;
219
+ }
220
+ if (src_matched) {
221
+ if (AK_UNLIKELY(tm->src_one_count == tm->src_one_capacity)) {
222
+ tm->src_one_capacity <<= 1; // get 2x the capacity
223
+ tm->src_one = PyMem_Realloc(tm->src_one,
224
+ sizeof(TriMapOne) * tm->src_one_capacity);
225
+ if (tm->src_one == NULL) {
226
+ PyErr_SetNone(PyExc_MemoryError);
227
+ return -1;
228
+ }
229
+ }
230
+ tm->src_one[tm->src_one_count] = (TriMapOne){src_from, tm->len};
231
+ tm->src_one_count += 1;
232
+ }
233
+ if (dst_matched) {
234
+ if (AK_UNLIKELY(tm->dst_one_count == tm->dst_one_capacity)) {
235
+ tm->dst_one_capacity <<= 1; // get 2x the capacity
236
+ tm->dst_one = PyMem_Realloc(tm->dst_one,
237
+ sizeof(TriMapOne) * tm->dst_one_capacity);
238
+ if (tm->dst_one == NULL) {
239
+ PyErr_SetNone(PyExc_MemoryError);
240
+ return -1;
241
+ }
242
+ }
243
+ tm->dst_one[tm->dst_one_count] = (TriMapOne){dst_from, tm->len};
244
+ tm->dst_one_count += 1;
245
+ }
246
+ if (src_matched && dst_matched) {
247
+ if (!tm->is_many) {
248
+ // if we have seen this connection before, we have a many
249
+ if (tm->src_match_data[src_from] || tm->dst_match_data[dst_from]) {
250
+ tm->is_many = true;
251
+ }
252
+ }
253
+ tm->src_match_data[src_from] = NPY_TRUE;
254
+ tm->dst_match_data[dst_from] = NPY_TRUE;
255
+ }
256
+ tm->len += 1;
257
+ return 0;
258
+ }
259
+
260
+ // Public function for calling from Python.
261
+ PyObject *
262
+ TriMap_register_one(TriMapObject *self, PyObject *args) {
263
+ Py_ssize_t src_from;
264
+ Py_ssize_t dst_from;
265
+ if (!PyArg_ParseTuple(args,
266
+ "nn:register_one",
267
+ &src_from,
268
+ &dst_from)) {
269
+ return NULL;
270
+ }
271
+ if (self->finalized) {
272
+ PyErr_SetString(PyExc_RuntimeError, "Cannot register post finalization");
273
+ return NULL;
274
+ }
275
+ if (AK_TM_register_one(self, src_from, dst_from)) {
276
+ return NULL;
277
+ }
278
+ Py_RETURN_NONE;
279
+ }
280
+
281
+ PyObject *
282
+ TriMap_register_unmatched_dst(TriMapObject *self) {
283
+ if (self->finalized) {
284
+ PyErr_SetString(PyExc_RuntimeError, "Cannot register post finalization");
285
+ return NULL;
286
+ }
287
+ PyArrayObject* dst_match_array = (PyArrayObject *)self->dst_match;
288
+ PyObject* sum_scalar = PyArray_Sum(
289
+ dst_match_array,
290
+ 0,
291
+ NPY_INT64, // this converts before sum; not sure this is necessary
292
+ NULL);
293
+ if (sum_scalar == NULL) {
294
+ return NULL;
295
+ }
296
+ // for a 1D array PyArray_SUM returns a scalar
297
+ npy_int64 sum = PyArrayScalar_VAL(sum_scalar, Int64);
298
+ Py_DECREF(sum_scalar);
299
+
300
+ if (sum < self->dst_len) {
301
+ PyArrayObject* dst_unmatched = (PyArrayObject *)PyObject_CallMethod(
302
+ self->dst_match, // PyObject
303
+ "__invert__",
304
+ NULL);
305
+ if (dst_unmatched == NULL) {
306
+ return NULL;
307
+ }
308
+ // derive indices for unmatched locations, call each with register_one
309
+ PyArrayObject* indices = (PyArrayObject*)AK_nonzero_1d(dst_unmatched);
310
+ if (indices == NULL) {
311
+ Py_DECREF((PyObject*)dst_unmatched);
312
+ return NULL;
313
+ }
314
+ // borrow ref to array in 1-element tuple
315
+ npy_int64 *index_data = (npy_int64 *)PyArray_DATA(indices);
316
+ npy_intp index_len = PyArray_SIZE(indices);
317
+
318
+ for (npy_intp i = 0; i < index_len; i++) {
319
+ if (AK_TM_register_one(self, -1, index_data[i])) {
320
+ Py_DECREF((PyObject*)dst_unmatched);
321
+ Py_DECREF((PyObject*)indices);
322
+ return NULL;
323
+ }
324
+ }
325
+ Py_DECREF((PyObject*)dst_unmatched);
326
+ Py_DECREF((PyObject*)indices);
327
+ }
328
+ Py_RETURN_NONE;
329
+ }
330
+
331
+ // Given an integer (for the src) and an array of integers (for the dst), store mappings from src to final and dst to final.
332
+ PyObject *
333
+ TriMap_register_many(TriMapObject *self, PyObject *args) {
334
+ Py_ssize_t src_from;
335
+ PyArrayObject* dst_from;
336
+ if (!PyArg_ParseTuple(args,
337
+ "nO!:register_many",
338
+ &src_from,
339
+ &PyArray_Type, &dst_from)) {
340
+ return NULL;
341
+ }
342
+ if (self->finalized) {
343
+ PyErr_SetString(PyExc_RuntimeError, "Cannot register post finalization");
344
+ return NULL;
345
+ }
346
+ int dst_from_type = PyArray_TYPE(dst_from);
347
+ if (dst_from_type != NPY_INT64) {
348
+ PyErr_SetString(PyExc_ValueError, "`dst_from` must be a 64 bit integer array");
349
+ return NULL;
350
+ }
351
+ npy_intp increment = PyArray_SIZE(dst_from);
352
+
353
+ if (AK_UNLIKELY(self->many_count == self->many_capacity)) {
354
+ self->many_capacity <<= 1; // get 2x the capacity
355
+ self->many_to = PyMem_Realloc(self->many_to,
356
+ sizeof(TriMapManyTo) * self->many_capacity);
357
+ if (self->many_to == NULL) {
358
+ PyErr_SetNone(PyExc_MemoryError);
359
+ return NULL;
360
+ }
361
+ self->many_from = PyMem_Realloc(self->many_from,
362
+ sizeof(TriMapManyFrom) * self->many_capacity);
363
+ if (self->many_from == NULL) {
364
+ PyErr_SetNone(PyExc_MemoryError);
365
+ return NULL;
366
+ }
367
+ }
368
+ // define contiguous region in final to map to
369
+ self->many_to[self->many_count] = (TriMapManyTo){self->len, self->len + increment};
370
+
371
+ Py_INCREF((PyObject*)dst_from); // decrefs on dealloc
372
+ self->many_from[self->many_count] = (TriMapManyFrom){src_from, dst_from};
373
+ self->many_count += 1;
374
+
375
+ self->src_match_data[src_from] = NPY_TRUE;
376
+ // iterate over dst_from and set values to True; cannot assume that dst_from is contiguous; dst_match_data is contiguous
377
+ for (Py_ssize_t i = 0; i < increment; i++){
378
+ npy_int64 pos = *(npy_int64*)PyArray_GETPTR1(dst_from, i); // always int64
379
+ self->dst_match_data[pos] = NPY_TRUE;
380
+ }
381
+ self->len += increment;
382
+ self->is_many = true;
383
+ Py_RETURN_NONE;
384
+ }
385
+
386
+ //------------------------------------------------------------------------------
387
+ // Determine, for src and dst, which indices will need fill values, and store those indices as an integer array in final_src_fill, final_dst_fill
388
+ PyObject *
389
+ TriMap_finalize(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
390
+ TriMapObject* tm = (TriMapObject*)self;
391
+
392
+ if (self->finalized) {
393
+ PyErr_SetString(PyExc_RuntimeError, "Cannot call finalize twice");
394
+ return NULL;
395
+ }
396
+ // predefine all PyObjects to use goto error
397
+ PyObject* final_src_match = NULL;
398
+ PyObject* final_dst_match = NULL;
399
+ PyObject* final_src_unmatched = NULL;
400
+ PyObject* final_dst_unmatched = NULL;
401
+
402
+ npy_intp dims[] = {tm->len};
403
+
404
+ // initialize all to False
405
+ final_src_match = PyArray_ZEROS(1, dims, NPY_BOOL, 0);
406
+ if (final_src_match == NULL) {
407
+ goto error;
408
+ }
409
+ final_dst_match = PyArray_ZEROS(1, dims, NPY_BOOL, 0);
410
+ if (final_dst_match == NULL) {
411
+ goto error;
412
+ }
413
+
414
+ npy_bool* final_src_match_data = (npy_bool*)PyArray_DATA(
415
+ (PyArrayObject*)final_src_match);
416
+ npy_bool* final_dst_match_data = (npy_bool*)PyArray_DATA(
417
+ (PyArrayObject*)final_dst_match);
418
+
419
+ TriMapOne* o;
420
+ TriMapOne* o_end;
421
+ o = tm->src_one;
422
+ o_end = o + tm->src_one_count;
423
+ for (; o < o_end; o++) {
424
+ final_src_match_data[o->to] = NPY_TRUE;
425
+ }
426
+ o = tm->dst_one;
427
+ o_end = o + tm->dst_one_count;
428
+ for (; o < o_end; o++) {
429
+ final_dst_match_data[o->to] = NPY_TRUE;
430
+ }
431
+ // many assign from src and dst into the same final positions
432
+ npy_bool* s;
433
+ npy_bool* d;
434
+ npy_bool* end;
435
+ TriMapManyTo* m = tm->many_to;
436
+ TriMapManyTo* m_end = m + tm->many_count;
437
+
438
+ for (; m < m_end; m++) {
439
+ d = final_dst_match_data + m->start;
440
+ s = final_src_match_data + m->start;
441
+ end = final_src_match_data + m->stop;
442
+ while (s < end) {
443
+ *s++ = NPY_TRUE;
444
+ *d++ = NPY_TRUE;
445
+ }
446
+ }
447
+ // NOTE: could sum first to see if nonzero call is necessary; would skip invert and nonzero calls
448
+ final_src_unmatched = PyObject_CallMethod(
449
+ final_src_match, // PyObject
450
+ "__invert__",
451
+ NULL);
452
+ if (final_src_unmatched == NULL) {
453
+ goto error;
454
+ }
455
+
456
+ final_dst_unmatched = PyObject_CallMethod(
457
+ final_dst_match, // PyObject
458
+ "__invert__",
459
+ NULL);
460
+ if (final_dst_unmatched == NULL) {
461
+ goto error;
462
+ }
463
+ tm->final_src_fill = AK_nonzero_1d((PyArrayObject*)final_src_unmatched);
464
+ if (tm->final_src_fill == NULL) {
465
+ goto error;
466
+ }
467
+ tm->final_dst_fill = AK_nonzero_1d((PyArrayObject*)final_dst_unmatched);
468
+ if (tm->final_dst_fill == NULL) {
469
+ goto error;
470
+ }
471
+ Py_DECREF(final_src_match);
472
+ Py_DECREF(final_dst_match);
473
+ Py_DECREF(final_src_unmatched);
474
+ Py_DECREF(final_dst_unmatched);
475
+
476
+ tm->finalized = true;
477
+ Py_RETURN_NONE;
478
+ error: // all PyObject initialized to NULL, no more than 1 ref
479
+ Py_XDECREF(final_src_match);
480
+ Py_XDECREF(final_dst_match);
481
+ Py_XDECREF(final_src_unmatched);
482
+ Py_XDECREF(final_dst_unmatched);
483
+ return NULL;
484
+ }
485
+
486
+ PyObject *
487
+ TriMap_is_many(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
488
+ if (!self->finalized) {
489
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
490
+ return NULL;
491
+ }
492
+ if (self->is_many) {
493
+ Py_RETURN_TRUE;
494
+ }
495
+ Py_RETURN_FALSE;
496
+ }
497
+
498
+ // Return True if the `src` will not need a fill. This is only correct of `src` is binding to a left join or an inner join.
499
+ PyObject *
500
+ TriMap_src_no_fill(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
501
+ if (!self->finalized) {
502
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
503
+ return NULL;
504
+ }
505
+ if (PyArray_SIZE((PyArrayObject*)self->final_src_fill) == 0) {
506
+ Py_RETURN_TRUE;
507
+ }
508
+ Py_RETURN_FALSE;
509
+ }
510
+
511
+ // Return True if the `dst` will not need a fill. This is only correct of `dst` is binding to a left join or an inner join.
512
+ PyObject *
513
+ TriMap_dst_no_fill(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
514
+ if (!self->finalized) {
515
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
516
+ return NULL;
517
+ }
518
+ if (PyArray_SIZE((PyArrayObject*)self->final_dst_fill) == 0) {
519
+ Py_RETURN_TRUE;
520
+ }
521
+ Py_RETURN_FALSE;
522
+ }
523
+
524
+ # define AK_TM_TRANSFER_SCALAR(npy_type_to, npy_type_from) do { \
525
+ npy_type_to* array_to_data = (npy_type_to*)PyArray_DATA(array_to); \
526
+ TriMapOne* o = one_pairs; \
527
+ TriMapOne* o_end = o + one_count; \
528
+ for (; o < o_end; o++) { \
529
+ array_to_data[o->to] = (npy_type_to) \
530
+ *(npy_type_from*)PyArray_GETPTR1( \
531
+ array_from, o->from); \
532
+ } \
533
+ npy_type_to* t; \
534
+ npy_type_to* t_end; \
535
+ npy_type_to f; \
536
+ npy_int64 f_pos; \
537
+ npy_intp dst_pos; \
538
+ PyArrayObject* dst; \
539
+ for (Py_ssize_t i = 0; i < tm->many_count; i++) { \
540
+ t = array_to_data + tm->many_to[i].start; \
541
+ t_end = array_to_data + tm->many_to[i].stop; \
542
+ if (from_src) { \
543
+ f = (npy_type_to)*(npy_type_from*)PyArray_GETPTR1( \
544
+ array_from, tm->many_from[i].src); \
545
+ while (t < t_end) { \
546
+ *t++ = f; \
547
+ } \
548
+ } \
549
+ else { \
550
+ dst_pos = 0; \
551
+ dst = tm->many_from[i].dst; \
552
+ while (t < t_end) { \
553
+ f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos); \
554
+ *t++ = (npy_type_to) \
555
+ *(npy_type_from*)PyArray_GETPTR1( \
556
+ array_from, f_pos); \
557
+ dst_pos++; \
558
+ } \
559
+ } \
560
+ } \
561
+ } while (0) \
562
+
563
+ // Based on `tm` state, transfer from src or from dst (depending on `from_src`) to a `array_to`, a newly created contiguous array that is compatible with the values in `array_from`. Returns -1 on error. This only needs to match to / from type combinations that are possible from `resolve_dtype`, i.e., bool never goes to integer.
564
+ static inline int
565
+ AK_TM_transfer_scalar(TriMapObject* tm,
566
+ bool from_src,
567
+ PyArrayObject* array_from,
568
+ PyArrayObject* array_to) {
569
+ Py_ssize_t one_count = from_src ? tm->src_one_count : tm->dst_one_count;
570
+ TriMapOne* one_pairs = from_src ? tm->src_one : tm->dst_one;
571
+
572
+ switch(PyArray_TYPE(array_to)){
573
+ case NPY_BOOL:
574
+ AK_TM_TRANSFER_SCALAR(npy_bool, npy_bool);
575
+ return 0;
576
+ case NPY_INT64:
577
+ switch (PyArray_TYPE(array_from)) {
578
+ case NPY_INT64:
579
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int64);
580
+ return 0;
581
+ case NPY_INT32:
582
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int32);
583
+ return 0;
584
+ case NPY_INT16:
585
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int16);
586
+ return 0;
587
+ case NPY_INT8:
588
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int8);
589
+ return 0;
590
+ case NPY_UINT32:
591
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint32);
592
+ return 0;
593
+ case NPY_UINT16:
594
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint16);
595
+ return 0;
596
+ case NPY_UINT8:
597
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint8);
598
+ return 0;
599
+ }
600
+ break;
601
+ case NPY_INT32:
602
+ switch (PyArray_TYPE(array_from)) {
603
+ case NPY_INT32:
604
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int32);
605
+ return 0;
606
+ case NPY_INT16:
607
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int16);
608
+ return 0;
609
+ case NPY_INT8:
610
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int8);
611
+ return 0;
612
+ case NPY_UINT16:
613
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_uint16);
614
+ return 0;
615
+ case NPY_UINT8:
616
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_uint8);
617
+ return 0;
618
+ }
619
+ break;
620
+ case NPY_INT16:
621
+ switch (PyArray_TYPE(array_from)) {
622
+ case NPY_INT16:
623
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_int16);
624
+ return 0;
625
+ case NPY_INT8:
626
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_int8);
627
+ return 0;
628
+ case NPY_UINT8:
629
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_uint8);
630
+ return 0;
631
+ }
632
+ break;
633
+ case NPY_INT8:
634
+ AK_TM_TRANSFER_SCALAR(npy_int8, npy_int8);
635
+ return 0;
636
+ case NPY_UINT64:
637
+ switch (PyArray_TYPE(array_from)) {
638
+ case NPY_UINT64:
639
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint64);
640
+ return 0;
641
+ case NPY_UINT32:
642
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint32);
643
+ return 0;
644
+ case NPY_UINT16:
645
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint16);
646
+ return 0;
647
+ case NPY_UINT8:
648
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint8);
649
+ return 0;
650
+ }
651
+ break;
652
+ case NPY_UINT32:
653
+ switch (PyArray_TYPE(array_from)) {
654
+ case NPY_UINT32:
655
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint32);
656
+ return 0;
657
+ case NPY_UINT16:
658
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint16);
659
+ return 0;
660
+ case NPY_UINT8:
661
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint8);
662
+ return 0;
663
+ }
664
+ break;
665
+ case NPY_UINT16:
666
+ switch (PyArray_TYPE(array_from)) {
667
+ case NPY_UINT16:
668
+ AK_TM_TRANSFER_SCALAR(npy_uint16, npy_uint16);
669
+ return 0;
670
+ case NPY_UINT8:
671
+ AK_TM_TRANSFER_SCALAR(npy_uint16, npy_uint8);
672
+ return 0;
673
+ }
674
+ break;
675
+ case NPY_UINT8:
676
+ AK_TM_TRANSFER_SCALAR(npy_uint8, npy_uint8);
677
+ return 0;
678
+ case NPY_FLOAT64:
679
+ switch (PyArray_TYPE(array_from)) {
680
+ case NPY_FLOAT64:
681
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float64);
682
+ return 0;
683
+ case NPY_FLOAT32:
684
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float32);
685
+ return 0;
686
+ case NPY_FLOAT16:
687
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float16);
688
+ return 0;
689
+ case NPY_INT64:
690
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int64);
691
+ return 0;
692
+ case NPY_INT32:
693
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int32);
694
+ return 0;
695
+ case NPY_INT16:
696
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int16);
697
+ return 0;
698
+ case NPY_INT8:
699
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int8);
700
+ return 0;
701
+ case NPY_UINT64:
702
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint64);
703
+ return 0;
704
+ case NPY_UINT32:
705
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint32);
706
+ return 0;
707
+ case NPY_UINT16:
708
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint16);
709
+ return 0;
710
+ case NPY_UINT8:
711
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint8);
712
+ return 0;
713
+ }
714
+ break;
715
+ case NPY_FLOAT32:
716
+ switch (PyArray_TYPE(array_from)) {
717
+ case NPY_FLOAT32:
718
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_float32);
719
+ return 0;
720
+ case NPY_FLOAT16:
721
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_float16);
722
+ return 0;
723
+ case NPY_INT16:
724
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_int16);
725
+ return 0;
726
+ case NPY_INT8:
727
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_int8);
728
+ return 0;
729
+ case NPY_UINT16:
730
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_uint16);
731
+ return 0;
732
+ case NPY_UINT8:
733
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_uint8);
734
+ return 0;
735
+ }
736
+ break;
737
+ case NPY_FLOAT16:
738
+ switch (PyArray_TYPE(array_from)) {
739
+ case NPY_FLOAT16:
740
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_float16);
741
+ return 0;
742
+ case NPY_INT8:
743
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_int8);
744
+ return 0;
745
+ case NPY_UINT16:
746
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_uint16);
747
+ return 0;
748
+ case NPY_UINT8:
749
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_uint8);
750
+ return 0;
751
+ }
752
+ break;
753
+ case NPY_DATETIME: {
754
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int64);
755
+ return 0;
756
+ }
757
+ }
758
+ PyErr_SetString(PyExc_TypeError, "No handling for types");
759
+ return -1;
760
+ }
761
+ #undef AK_TM_TRANSFER_SCALAR
762
+
763
+ // Returns -1 on error. Specialized transfer from any type of an array to an object array.
764
+ static inline int
765
+ AK_TM_transfer_object(TriMapObject* tm,
766
+ bool from_src,
767
+ PyArrayObject* array_from,
768
+ PyArrayObject* array_to
769
+ ) {
770
+ Py_ssize_t one_count = from_src ? tm->src_one_count : tm->dst_one_count;
771
+ TriMapOne* one_pairs = from_src ? tm->src_one : tm->dst_one;
772
+
773
+ // NOTE: could use PyArray_Scalar instead of PyArray_GETITEM if we wanted to store scalars instead of Python objects; however, that is pretty uncommon for object arrays to store PyArray_Scalars
774
+ bool f_is_obj = PyArray_TYPE(array_from) == NPY_OBJECT;
775
+
776
+ // the passed in object array is contiguous and have NULL (not None) in each position
777
+ PyObject** array_to_data = (PyObject**)PyArray_DATA(array_to);
778
+ PyObject* pyo;
779
+ void* f;
780
+ TriMapOne* o = one_pairs;
781
+ TriMapOne* o_end = o + one_count;
782
+ for (; o < o_end; o++) {
783
+ f = PyArray_GETPTR1(array_from, o->from);
784
+ if (f_is_obj) {
785
+ pyo = *(PyObject**)f;
786
+ Py_INCREF(pyo);
787
+ }
788
+ else { // will convert any value to an object
789
+ pyo = PyArray_GETITEM(array_from, f);
790
+ }
791
+ array_to_data[o->to] = pyo;
792
+ }
793
+ PyObject** t;
794
+ PyObject** t_end;
795
+ npy_intp dst_pos;
796
+ npy_int64 f_pos;
797
+ PyArrayObject* dst;
798
+ for (Py_ssize_t i = 0; i < tm->many_count; i++) {
799
+ t = array_to_data + tm->many_to[i].start;
800
+ t_end = array_to_data + tm->many_to[i].stop;
801
+
802
+ if (from_src) {
803
+ f = PyArray_GETPTR1(array_from, tm->many_from[i].src);
804
+ if (f_is_obj) {
805
+ pyo = *(PyObject**)f;
806
+ Py_INCREF(pyo); // pre add new ref so equal to PyArray_GETITEM
807
+ }
808
+ else {
809
+ pyo = PyArray_GETITEM(array_from, f); // given a new ref
810
+ }
811
+ while (t < t_end) {
812
+ Py_INCREF(pyo); // one more than we need
813
+ *t++ = pyo;
814
+ }
815
+ Py_DECREF(pyo); // remove the extra ref
816
+ }
817
+ else { // from_dst, dst is an array
818
+ dst_pos = 0;
819
+ dst = tm->many_from[i].dst;
820
+ while (t < t_end) {
821
+ f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos);
822
+ f = PyArray_GETPTR1(array_from, f_pos);
823
+ if (f_is_obj) {
824
+ pyo = *(PyObject**)f;
825
+ Py_INCREF(pyo);
826
+ }
827
+ else {
828
+ pyo = PyArray_GETITEM(array_from, f);
829
+ }
830
+ *t++ = pyo;
831
+ dst_pos++;
832
+ }
833
+ }
834
+ }
835
+ return 0;
836
+ }
837
+
838
+ // Returns -1 on error. Specialized transfer from any type of an array to an object array. For usage with merge, Will only transfer if the destination is not NULL.
839
+ static inline int
840
+ AK_TM_transfer_object_if_null(TriMapObject* tm,
841
+ bool from_src,
842
+ PyArrayObject* array_from,
843
+ PyArrayObject* array_to
844
+ ) {
845
+ Py_ssize_t one_count = from_src ? tm->src_one_count : tm->dst_one_count;
846
+ TriMapOne* one_pairs = from_src ? tm->src_one : tm->dst_one;
847
+
848
+ // NOTE: could use PyArray_Scalar instead of PyArray_GETITEM if we wanted to store scalars instead of Python objects; however, that is pretty uncommon for object arrays to store PyArray_Scalars
849
+ bool f_is_obj = PyArray_TYPE(array_from) == NPY_OBJECT;
850
+
851
+ // the passed in object array is contiguous and have NULL (not None) in each position
852
+ PyObject** array_to_data = (PyObject**)PyArray_DATA(array_to);
853
+ PyObject* pyo;
854
+ void* f;
855
+ TriMapOne* o = one_pairs;
856
+ TriMapOne* o_end = o + one_count;
857
+ for (; o < o_end; o++) {
858
+ if (array_to_data[o->to] == NULL) {
859
+ f = PyArray_GETPTR1(array_from, o->from);
860
+ if (f_is_obj) {
861
+ pyo = *(PyObject**)f;
862
+ Py_INCREF(pyo);
863
+ }
864
+ else { // will convert any value to an object
865
+ pyo = PyArray_GETITEM(array_from, f);
866
+ }
867
+ array_to_data[o->to] = pyo;
868
+ }
869
+ }
870
+ PyObject** t;
871
+ PyObject** t_end;
872
+ npy_intp dst_pos;
873
+ npy_int64 f_pos;
874
+ PyArrayObject* dst;
875
+ for (Py_ssize_t i = 0; i < tm->many_count; i++) {
876
+ t = array_to_data + tm->many_to[i].start;
877
+ t_end = array_to_data + tm->many_to[i].stop;
878
+
879
+ if (from_src) {
880
+ while (t < t_end) {
881
+ if (*t == NULL) {
882
+ f = PyArray_GETPTR1(array_from, tm->many_from[i].src);
883
+ if (f_is_obj) {
884
+ pyo = *(PyObject**)f;
885
+ Py_INCREF(pyo);
886
+ }
887
+ else {
888
+ pyo = PyArray_GETITEM(array_from, f); // given a new ref
889
+ }
890
+ *t++ = pyo;
891
+ }
892
+ else {
893
+ t++;
894
+ }
895
+ }
896
+ }
897
+ else { // from_dst, dst is an array
898
+ dst_pos = 0;
899
+ dst = tm->many_from[i].dst;
900
+ while (t < t_end) {
901
+ if (*t == NULL) {
902
+ f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos);
903
+ f = PyArray_GETPTR1(array_from, f_pos);
904
+ if (f_is_obj) {
905
+ pyo = *(PyObject**)f;
906
+ Py_INCREF(pyo);
907
+ }
908
+ else {
909
+ pyo = PyArray_GETITEM(array_from, f);
910
+ }
911
+ *t++ = pyo;
912
+ dst_pos++;
913
+ }
914
+ else {
915
+ t++;
916
+ dst_pos++;
917
+ }
918
+ }
919
+ }
920
+ }
921
+ return 0;
922
+ }
923
+
924
+ // Returns -1 on error.
925
+ static inline int
926
+ AK_TM_fill_object(TriMapObject* tm,
927
+ bool from_src,
928
+ PyArrayObject* array_to,
929
+ PyObject* fill_value) {
930
+
931
+ PyArrayObject* final_fill = (PyArrayObject*)(from_src
932
+ ? tm->final_src_fill : tm->final_dst_fill);
933
+ PyObject** array_to_data = (PyObject**)PyArray_DATA(array_to);
934
+ npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
935
+ npy_int64* p_end = p + PyArray_SIZE(final_fill);
936
+ PyObject** target;
937
+ while (p < p_end) {
938
+ target = array_to_data + *p++;
939
+ Py_INCREF(fill_value);
940
+ *target = fill_value;
941
+ }
942
+ return 0;
943
+ }
944
+
945
+ #define AK_TM_TRANSFER_FLEXIBLE(c_type, from_src, array_from, array_to) do {\
946
+ Py_ssize_t one_count = from_src ? tm->src_one_count : tm->dst_one_count;\
947
+ TriMapOne* one_pairs = from_src ? tm->src_one : tm->dst_one; \
948
+ npy_intp t_element_size = PyArray_ITEMSIZE(array_to); \
949
+ npy_intp t_element_cp = t_element_size / sizeof(c_type); \
950
+ npy_intp f_element_size = PyArray_ITEMSIZE(array_from); \
951
+ c_type* array_to_data = (c_type*)PyArray_DATA(array_to); \
952
+ c_type* f; \
953
+ c_type* t; \
954
+ c_type* t_end; \
955
+ npy_intp dst_pos; \
956
+ npy_int64 f_pos; \
957
+ PyArrayObject* dst; \
958
+ TriMapOne* o = one_pairs; \
959
+ TriMapOne* o_end = o + one_count; \
960
+ for (; o < o_end; o++) { \
961
+ f = (c_type*)PyArray_GETPTR1(array_from, o->from); \
962
+ t = array_to_data + t_element_cp * o->to; \
963
+ memcpy(t, f, f_element_size); \
964
+ } \
965
+ for (Py_ssize_t i = 0; i < tm->many_count; i++) { \
966
+ t = array_to_data + t_element_cp * tm->many_to[i].start; \
967
+ t_end = array_to_data + t_element_cp * tm->many_to[i].stop; \
968
+ if (from_src) { \
969
+ f = (c_type*)PyArray_GETPTR1(array_from, tm->many_from[i].src);\
970
+ for (; t < t_end; t += t_element_cp) { \
971
+ memcpy(t, f, f_element_size); \
972
+ } \
973
+ } \
974
+ else { \
975
+ dst_pos = 0; \
976
+ dst = tm->many_from[i].dst; \
977
+ for (; t < t_end; t += t_element_cp) { \
978
+ f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos); \
979
+ f = (c_type*)PyArray_GETPTR1(array_from, f_pos); \
980
+ memcpy(t, f, f_element_size); \
981
+ dst_pos++; \
982
+ } \
983
+ } \
984
+ } \
985
+ } while (0) \
986
+
987
+ // Returns -1 on error.
988
+ static inline int
989
+ AK_TM_fill_unicode(TriMapObject* tm,
990
+ bool from_src,
991
+ PyArrayObject* array_to,
992
+ PyObject* fill_value) {
993
+ PyArrayObject* final_fill = (PyArrayObject*)(from_src
994
+ ? tm->final_src_fill : tm->final_dst_fill);
995
+
996
+ Py_UCS4* array_to_data = (Py_UCS4*)PyArray_DATA(array_to);
997
+ // code points per element
998
+ npy_intp cp = PyArray_ITEMSIZE(array_to) / UCS4_SIZE;
999
+
1000
+ bool decref_fill_value = false;
1001
+ if (PyBytes_Check(fill_value)) {
1002
+ fill_value = PyUnicode_FromEncodedObject(fill_value, "utf-8", NULL);
1003
+ if (fill_value == NULL) {
1004
+ return -1;
1005
+ }
1006
+ decref_fill_value = true;
1007
+ }
1008
+ else if (!PyUnicode_Check(fill_value)) {
1009
+ return -1;
1010
+ }
1011
+ Py_ssize_t fill_cp = PyUnicode_GET_LENGTH(fill_value) * UCS4_SIZE; // code points
1012
+ // p is the index position to fill
1013
+ npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
1014
+ npy_int64* p_end = p + PyArray_SIZE(final_fill);
1015
+ Py_UCS4* target;
1016
+ while (p < p_end) {
1017
+ target = array_to_data + (*p * cp);
1018
+ // disabling copying a null
1019
+ if (PyUnicode_AsUCS4(fill_value, target, fill_cp, 0) == NULL) {
1020
+ return -1;
1021
+ }
1022
+ p++;
1023
+ }
1024
+ if (decref_fill_value) {
1025
+ Py_DECREF(fill_value);
1026
+ }
1027
+ return 0;
1028
+ }
1029
+
1030
+ // Returns -1 on error.
1031
+ static inline int
1032
+ AK_TM_fill_string(TriMapObject* tm,
1033
+ bool from_src,
1034
+ PyArrayObject* array_to,
1035
+ PyObject* fill_value) {
1036
+ PyArrayObject* final_fill = (PyArrayObject*)(from_src
1037
+ ? tm->final_src_fill : tm->final_dst_fill);
1038
+
1039
+ char* array_to_data = (char*)PyArray_DATA(array_to);
1040
+ npy_intp cp = PyArray_ITEMSIZE(array_to);
1041
+ if (!PyBytes_Check(fill_value)) {
1042
+ return -1;
1043
+ }
1044
+ Py_ssize_t fill_cp = PyBytes_GET_SIZE(fill_value);
1045
+ const char* fill_data = PyBytes_AS_STRING(fill_value);
1046
+ // p is the index position to fill
1047
+ npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
1048
+ npy_int64* p_end = p + PyArray_SIZE(final_fill);
1049
+ char* target;
1050
+ while (p < p_end) {
1051
+ target = array_to_data + (*p++ * cp);
1052
+ memcpy(target, fill_data, fill_cp);
1053
+ }
1054
+ return 0;
1055
+ }
1056
+
1057
+ // Returns NULL on error.
1058
+ static inline PyObject *
1059
+ AK_TM_map_no_fill(TriMapObject* tm,
1060
+ bool from_src,
1061
+ PyArrayObject* array_from) {
1062
+ if (!(PyArray_NDIM(array_from) == 1)) {
1063
+ PyErr_SetString(PyExc_TypeError, "Array must be 1D");
1064
+ return NULL;
1065
+ }
1066
+ npy_intp dims[] = {tm->len};
1067
+ PyArrayObject* array_to;
1068
+ bool dtype_is_obj = PyArray_TYPE(array_from) == NPY_OBJECT;
1069
+ bool dtype_is_unicode = PyArray_TYPE(array_from) == NPY_UNICODE;
1070
+ bool dtype_is_string = PyArray_TYPE(array_from) == NPY_STRING;
1071
+
1072
+ // create to array
1073
+ if (dtype_is_obj) { // initializes values to NULL
1074
+ array_to = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_OBJECT);
1075
+ }
1076
+ else {
1077
+ PyArray_Descr* dtype = PyArray_DESCR(array_from); // borowed ref
1078
+ Py_INCREF(dtype);
1079
+ array_to = (PyArrayObject*)PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1080
+ }
1081
+ if (array_to == NULL) {
1082
+ PyErr_SetNone(PyExc_MemoryError);
1083
+ return NULL;
1084
+ }
1085
+ // transfer values
1086
+ if (dtype_is_obj) {
1087
+ if (AK_TM_transfer_object(tm, from_src, array_from, array_to)) {
1088
+ Py_DECREF((PyObject*)array_to);
1089
+ return NULL;
1090
+ }
1091
+ }
1092
+ else if (dtype_is_unicode) {
1093
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4, from_src, array_from, array_to);
1094
+ }
1095
+ else if (dtype_is_string) {
1096
+ AK_TM_TRANSFER_FLEXIBLE(char, from_src, array_from, array_to);
1097
+ }
1098
+ else {
1099
+ if (AK_TM_transfer_scalar(tm, from_src, array_from, array_to)) {
1100
+ Py_DECREF((PyObject*)array_to);
1101
+ return NULL;
1102
+ }
1103
+ }
1104
+ PyArray_CLEARFLAGS(array_to, NPY_ARRAY_WRITEABLE);
1105
+ return (PyObject*)array_to;
1106
+ }
1107
+
1108
+ PyObject *
1109
+ TriMap_map_src_no_fill(TriMapObject *self, PyObject *arg) {
1110
+ if (!PyArray_Check(arg)) {
1111
+ PyErr_SetString(PyExc_TypeError, "Must provide an array");
1112
+ return NULL;
1113
+ }
1114
+ if (!self->finalized) {
1115
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
1116
+ return NULL;
1117
+ }
1118
+ PyArrayObject* array_from = (PyArrayObject*)arg;
1119
+ bool from_src = true;
1120
+ return AK_TM_map_no_fill(self, from_src, array_from);
1121
+ }
1122
+
1123
+ PyObject *
1124
+ TriMap_map_dst_no_fill(TriMapObject *self, PyObject *arg) {
1125
+ if (!PyArray_Check(arg)) {
1126
+ PyErr_SetString(PyExc_TypeError, "Must provide an array");
1127
+ return NULL;
1128
+ }
1129
+ if (!self->finalized) {
1130
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
1131
+ return NULL;
1132
+ }
1133
+ PyArrayObject* array_from = (PyArrayObject*)arg;
1134
+ bool from_src = false;
1135
+ return AK_TM_map_no_fill(self, from_src, array_from);
1136
+ }
1137
+
1138
+ static inline PyObject *
1139
+ TriMap_map_merge(TriMapObject *tm, PyObject *args)
1140
+ {
1141
+ // both are "from_" arrays
1142
+ PyArrayObject* array_src;
1143
+ PyArrayObject* array_dst;
1144
+
1145
+ if (!PyArg_ParseTuple(args,
1146
+ "O!O!:map_merge",
1147
+ &PyArray_Type, &array_src,
1148
+ &PyArray_Type, &array_dst
1149
+ )) {
1150
+ return NULL;
1151
+ }
1152
+ if (!tm->finalized) {
1153
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
1154
+ return NULL;
1155
+ }
1156
+ if (!(PyArray_NDIM(array_src) == 1)) {
1157
+ PyErr_SetString(PyExc_TypeError, "Array src must be 1D");
1158
+ return NULL;
1159
+ }
1160
+ if (!(PyArray_NDIM(array_dst) == 1)) {
1161
+ PyErr_SetString(PyExc_TypeError, "Array dst must be 1D");
1162
+ return NULL;
1163
+ }
1164
+ // passing a borrowed refs; returns a new ref
1165
+ PyArray_Descr* dtype = AK_resolve_dtype(
1166
+ PyArray_DESCR(array_src),
1167
+ PyArray_DESCR(array_dst));
1168
+ bool dtype_is_obj = dtype->type_num == NPY_OBJECT;
1169
+ bool dtype_is_unicode = dtype->type_num == NPY_UNICODE;
1170
+ bool dtype_is_string = dtype->type_num == NPY_STRING;
1171
+
1172
+ npy_intp dims[] = {tm->len};
1173
+
1174
+ // create to array_to
1175
+ PyArrayObject* array_to;
1176
+ if (dtype_is_obj) {
1177
+ Py_DECREF(dtype); // not needed
1178
+ // will initialize to NULL, not None
1179
+ array_to = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_OBJECT);
1180
+ }
1181
+ else if (dtype_is_unicode || dtype_is_string) {
1182
+ array_to = (PyArrayObject*)PyArray_Zeros(1, dims, dtype, 0); // steals dtype ref
1183
+ }
1184
+ else {
1185
+ array_to = (PyArrayObject*)PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1186
+ }
1187
+ if (array_to == NULL) {
1188
+ PyErr_SetNone(PyExc_MemoryError);
1189
+ return NULL;
1190
+ }
1191
+
1192
+ // if we have fill values in src, we need to transfer from dst
1193
+ bool transfer_from_dst = PyArray_SIZE((PyArrayObject*)tm->final_src_fill) != 0;
1194
+
1195
+ if (dtype_is_obj) {
1196
+ if (AK_TM_transfer_object(tm, true, array_src, array_to)) {
1197
+ Py_DECREF((PyObject*)array_to);
1198
+ return NULL;
1199
+ }
1200
+ if (transfer_from_dst) {
1201
+ if (AK_TM_transfer_object_if_null(tm, false, array_dst, array_to)) {
1202
+ Py_DECREF((PyObject*)array_to);
1203
+ return NULL;
1204
+ }
1205
+ }
1206
+ }
1207
+ else if (dtype_is_unicode) {
1208
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4, true, array_src, array_to);
1209
+ if (transfer_from_dst) {
1210
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4, false, array_dst, array_to);
1211
+ }
1212
+ }
1213
+ else if (dtype_is_string) {
1214
+ AK_TM_TRANSFER_FLEXIBLE(char, true, array_src, array_to);
1215
+ if (transfer_from_dst) {
1216
+ AK_TM_TRANSFER_FLEXIBLE(char, false, array_dst, array_to);
1217
+ }
1218
+ }
1219
+ else {
1220
+ if (AK_TM_transfer_scalar(tm, true, array_src, array_to)) {
1221
+ Py_DECREF((PyObject*)array_to);
1222
+ return NULL;
1223
+ }
1224
+ if (transfer_from_dst) {
1225
+ if (AK_TM_transfer_scalar(tm, false, array_dst, array_to)) {
1226
+ Py_DECREF((PyObject*)array_to);
1227
+ return NULL;
1228
+ }
1229
+ }
1230
+ }
1231
+ return (PyObject*)array_to;
1232
+ }
1233
+
1234
+ // Returns NULL on error.
1235
+ static inline PyObject *
1236
+ AK_TM_map_fill(TriMapObject* tm,
1237
+ bool from_src,
1238
+ PyArrayObject* array_from,
1239
+ PyObject* fill_value,
1240
+ PyArray_Descr* fill_value_dtype) {
1241
+ if (!(PyArray_NDIM(array_from) == 1)) {
1242
+ PyErr_SetString(PyExc_TypeError, "Array must be 1D");
1243
+ return NULL;
1244
+ }
1245
+ // passing a borrowed ref; returns a new ref
1246
+ PyArray_Descr* dtype = AK_resolve_dtype(PyArray_DESCR(array_from), fill_value_dtype);
1247
+ bool dtype_is_obj = dtype->type_num == NPY_OBJECT;
1248
+ bool dtype_is_unicode = dtype->type_num == NPY_UNICODE;
1249
+ bool dtype_is_string = dtype->type_num == NPY_STRING;
1250
+
1251
+ npy_intp dims[] = {tm->len};
1252
+ PyArrayObject* array_to;
1253
+
1254
+ if (dtype_is_obj) {
1255
+ Py_DECREF(dtype); // not needed
1256
+ // will initialize to NULL, not None
1257
+ array_to = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_OBJECT);
1258
+ Py_INCREF(array_from); // normalize refs when casting
1259
+ }
1260
+ else if (dtype_is_unicode || dtype_is_string) {
1261
+ array_to = (PyArrayObject*)PyArray_Zeros(1, dims, dtype, 0); // steals dtype ref
1262
+ Py_INCREF(array_from); // normalize refs when casting
1263
+ }
1264
+ else {
1265
+ array_to = (PyArrayObject*)PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1266
+ if (PyArray_TYPE(array_from) == NPY_DATETIME &&
1267
+ PyArray_TYPE(array_to) == NPY_DATETIME &&
1268
+ AK_dt_unit_from_array(array_from) != AK_dt_unit_from_array(array_to)
1269
+ ) {
1270
+ // if trying to cast into a dt64 array, need to pre-convert; array_from is originally borrowed; calling cast sets it to a new ref
1271
+ dtype = PyArray_DESCR(array_to); // borrowed ref
1272
+ Py_INCREF(dtype);
1273
+ array_from = (PyArrayObject*)PyArray_CastToType(array_from, dtype, 0);
1274
+ }
1275
+ else {
1276
+ Py_INCREF(array_from); // normalize refs when casting
1277
+ }
1278
+ }
1279
+ if (array_to == NULL) {
1280
+ PyErr_SetNone(PyExc_MemoryError);
1281
+ Py_DECREF((PyObject*)array_from);
1282
+ return NULL;
1283
+ }
1284
+ // array_from, array_to inc refed and dec refed on error
1285
+ if (dtype_is_obj) {
1286
+ if (AK_TM_transfer_object(tm, from_src, array_from, array_to)) {
1287
+ goto error;
1288
+ }
1289
+ if (AK_TM_fill_object(tm, from_src, array_to, fill_value)) {
1290
+ goto error;
1291
+ }
1292
+ }
1293
+ else if (dtype_is_unicode) {
1294
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4, from_src, array_from, array_to);
1295
+ if (AK_TM_fill_unicode(tm, from_src, array_to, fill_value)) {
1296
+ goto error;
1297
+ }
1298
+ }
1299
+ else if (dtype_is_string) {
1300
+ AK_TM_TRANSFER_FLEXIBLE(char, from_src, array_from, array_to);
1301
+ if (AK_TM_fill_string(tm, from_src, array_to, fill_value)) {
1302
+ goto error;
1303
+ }
1304
+ }
1305
+ else {
1306
+ // Most simple is to fill with scalar, then overwrite values as needed; for object and flexible dtypes this is not efficient; for object dtypes, this obligates us to decref the filled value when assigning
1307
+ if (PyArray_FillWithScalar(array_to, fill_value)) { // -1 on error
1308
+ goto error;
1309
+ }
1310
+ if (AK_TM_transfer_scalar(tm, from_src, array_from, array_to)) {
1311
+ goto error;
1312
+ }
1313
+ }
1314
+ Py_DECREF((PyObject*)array_from); // ref inc for this function
1315
+ PyArray_CLEARFLAGS(array_to, NPY_ARRAY_WRITEABLE);
1316
+ return (PyObject*)array_to;
1317
+ error:
1318
+ Py_DECREF((PyObject*)array_to);
1319
+ Py_DECREF((PyObject*)array_from);
1320
+ return NULL;
1321
+ }
1322
+ #undef AK_TM_TRANSFER_FLEXIBLE
1323
+
1324
+ PyObject *
1325
+ TriMap_map_src_fill(TriMapObject *self, PyObject *args) {
1326
+ PyArrayObject* array_from;
1327
+ PyObject* fill_value;
1328
+ PyArray_Descr* fill_value_dtype;
1329
+ if (!PyArg_ParseTuple(args,
1330
+ "O!OO!:map_src_fill",
1331
+ &PyArray_Type, &array_from,
1332
+ &fill_value,
1333
+ &PyArrayDescr_Type, &fill_value_dtype
1334
+ )) {
1335
+ return NULL;
1336
+ }
1337
+ if (!self->finalized) {
1338
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
1339
+ return NULL;
1340
+ }
1341
+ bool from_src = true;
1342
+ return AK_TM_map_fill(self, from_src, array_from, fill_value, fill_value_dtype);
1343
+ }
1344
+
1345
+ PyObject *
1346
+ TriMap_map_dst_fill(TriMapObject *self, PyObject *args) {
1347
+ PyArrayObject* array_from;
1348
+ PyObject* fill_value;
1349
+ PyArray_Descr* fill_value_dtype;
1350
+ if (!PyArg_ParseTuple(args,
1351
+ "O!OO!:map_dst_fill",
1352
+ &PyArray_Type, &array_from,
1353
+ &fill_value,
1354
+ &PyArrayDescr_Type, &fill_value_dtype
1355
+ )) {
1356
+ return NULL;
1357
+ }
1358
+ if (!self->finalized) {
1359
+ PyErr_SetString(PyExc_RuntimeError, "Finalization is required");
1360
+ return NULL;
1361
+ }
1362
+ bool from_src = false;
1363
+ return AK_TM_map_fill(self, from_src, array_from, fill_value, fill_value_dtype);
1364
+ }
1365
+
1366
+
1367
+
1368
+ static PyMethodDef TriMap_methods[] = {
1369
+ {"register_one", (PyCFunction)TriMap_register_one, METH_VARARGS, NULL},
1370
+ {"register_unmatched_dst", (PyCFunction)TriMap_register_unmatched_dst, METH_NOARGS, NULL},
1371
+ {"register_many", (PyCFunction)TriMap_register_many, METH_VARARGS, NULL},
1372
+ {"finalize", (PyCFunction)TriMap_finalize, METH_NOARGS, NULL},
1373
+ {"is_many", (PyCFunction)TriMap_is_many, METH_NOARGS, NULL},
1374
+ {"src_no_fill", (PyCFunction)TriMap_src_no_fill, METH_NOARGS, NULL},
1375
+ {"dst_no_fill", (PyCFunction)TriMap_dst_no_fill, METH_NOARGS, NULL},
1376
+ {"map_src_no_fill", (PyCFunction)TriMap_map_src_no_fill, METH_O, NULL},
1377
+ {"map_dst_no_fill", (PyCFunction)TriMap_map_dst_no_fill, METH_O, NULL},
1378
+ {"map_src_fill", (PyCFunction)TriMap_map_src_fill, METH_VARARGS, NULL},
1379
+ {"map_dst_fill", (PyCFunction)TriMap_map_dst_fill, METH_VARARGS, NULL},
1380
+ {"map_merge", (PyCFunction)TriMap_map_merge, METH_VARARGS, NULL},
1381
+ {NULL},
1382
+ };
1383
+
1384
+ PyTypeObject TriMapType = {
1385
+ PyVarObject_HEAD_INIT(NULL, 0)
1386
+ .tp_basicsize = sizeof(TriMapObject), // this does not get size of struct
1387
+ .tp_dealloc = (destructor)TriMap_dealloc,
1388
+ .tp_doc = TriMap_doc,
1389
+ .tp_flags = Py_TPFLAGS_DEFAULT,
1390
+ .tp_methods = TriMap_methods,
1391
+ .tp_name = "arraykit.TriMap",
1392
+ .tp_new = TriMap_new,
1393
+ .tp_init = TriMap_init,
1394
+ .tp_repr = (reprfunc)TriMap_repr,
1395
+ };