arraykit 0.6.1__tar.gz → 0.6.3__tar.gz

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.
Files changed (27) hide show
  1. {arraykit-0.6.1/arraykit.egg-info → arraykit-0.6.3}/PKG-INFO +1 -1
  2. {arraykit-0.6.1 → arraykit-0.6.3}/README.rst +14 -0
  3. {arraykit-0.6.1 → arraykit-0.6.3/arraykit.egg-info}/PKG-INFO +1 -1
  4. {arraykit-0.6.1 → arraykit-0.6.3}/setup.py +1 -1
  5. {arraykit-0.6.1 → arraykit-0.6.3}/src/_arraykit.c +278 -297
  6. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_nonzero_1d.py +16 -1
  7. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_tri_map.py +15 -1
  8. {arraykit-0.6.1 → arraykit-0.6.3}/LICENSE.txt +0 -0
  9. {arraykit-0.6.1 → arraykit-0.6.3}/MANIFEST.in +0 -0
  10. {arraykit-0.6.1 → arraykit-0.6.3}/arraykit.egg-info/SOURCES.txt +0 -0
  11. {arraykit-0.6.1 → arraykit-0.6.3}/arraykit.egg-info/dependency_links.txt +0 -0
  12. {arraykit-0.6.1 → arraykit-0.6.3}/arraykit.egg-info/requires.txt +0 -0
  13. {arraykit-0.6.1 → arraykit-0.6.3}/arraykit.egg-info/top_level.txt +0 -0
  14. {arraykit-0.6.1 → arraykit-0.6.3}/setup.cfg +0 -0
  15. {arraykit-0.6.1 → arraykit-0.6.3}/src/__init__.py +0 -0
  16. {arraykit-0.6.1 → arraykit-0.6.3}/src/__init__.pyi +0 -0
  17. {arraykit-0.6.1 → arraykit-0.6.3}/src/py.typed +0 -0
  18. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_array_go.py +0 -0
  19. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_block_index.py +0 -0
  20. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_delimited_to_arrays.py +0 -0
  21. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_delimited_to_arrays_integration.py +0 -0
  22. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_delimited_to_arrays_property.py +0 -0
  23. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_nonzero_1d_property.py +0 -0
  24. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_pyi.py +0 -0
  25. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_split_after_count.py +0 -0
  26. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_type_discovery.py +0 -0
  27. {arraykit-0.6.1 → arraykit-0.6.3}/test/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arraykit
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Array utilities for StaticFrame
5
5
  Home-page: https://github.com/static-frame/arraykit
6
6
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
@@ -37,6 +37,20 @@ ArrayKit requires the following:
37
37
  What is New in ArrayKit
38
38
  -------------------------
39
39
 
40
+ 0.6.3
41
+ ............
42
+
43
+ Optimized memory allocation strategy for ``nonzero_1d()``.
44
+
45
+
46
+ 0.6.2
47
+ ............
48
+
49
+ Extended ``nonzero_1d()`` to support non-contiguous arrays.
50
+
51
+ Optimizations to ``TriMap`` when mapping to object and flexible dtypes.
52
+
53
+
40
54
  0.6.1
41
55
  ............
42
56
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arraykit
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Array utilities for StaticFrame
5
5
  Home-page: https://github.com/static-frame/arraykit
6
6
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
@@ -5,7 +5,7 @@ from setuptools import Extension # type: ignore
5
5
  from setuptools import setup
6
6
  from pathlib import Path
7
7
 
8
- AK_VERSION = '0.6.1'
8
+ AK_VERSION = '0.6.3'
9
9
 
10
10
  def get_long_description() -> str:
11
11
  return '''The ArrayKit library provides utilities for creating and transforming NumPy arrays, implementing performance-critical StaticFrame operations as Python C extensions.
@@ -836,9 +836,9 @@ AK_TP_ResolveLineResetField(AK_TypeParser* tp,
836
836
  static char* TRUE_LOWER = "true";
837
837
  static char* TRUE_UPPER = "TRUE";
838
838
 
839
- #define ERROR_NO_DIGITS 1
840
- #define ERROR_OVERFLOW 2
841
- #define ERROR_INVALID_CHARS 3
839
+ #define AK_ERROR_NO_DIGITS 1
840
+ #define AK_ERROR_OVERFLOW 2
841
+ #define AK_ERROR_INVALID_CHARS 3
842
842
 
843
843
  // Convert a Py_UCS4 array to a signed integer. Extended from pandas/_libs/src/parser/tokenizer.c. Sets `error` to values greater than 0 on error; never sets error on success.
844
844
  static inline npy_int64
@@ -866,7 +866,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
866
866
 
867
867
  // Check that there is a first digit.
868
868
  if (!AK_is_digit(*p)) {
869
- *error = ERROR_NO_DIGITS;
869
+ *error = AK_ERROR_NO_DIGITS;
870
870
  return 0;
871
871
  }
872
872
  if (isneg) {
@@ -891,7 +891,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
891
891
  if (p >= end) return number;
892
892
  d = *p;
893
893
  } else {
894
- *error = ERROR_OVERFLOW;
894
+ *error = AK_ERROR_OVERFLOW;
895
895
  return 0;
896
896
  }
897
897
  }
@@ -904,7 +904,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
904
904
  if (p >= end) return number;
905
905
  d = *p;
906
906
  } else {
907
- *error = ERROR_OVERFLOW;
907
+ *error = AK_ERROR_OVERFLOW;
908
908
  return 0;
909
909
  }
910
910
  }
@@ -931,7 +931,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
931
931
  if (p >= end) return number;
932
932
  d = *p;
933
933
  } else {
934
- *error = ERROR_OVERFLOW;
934
+ *error = AK_ERROR_OVERFLOW;
935
935
  return 0;
936
936
  }
937
937
  }
@@ -944,7 +944,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
944
944
  if (p >= end) return number;
945
945
  d = *p;
946
946
  } else {
947
- *error = ERROR_OVERFLOW;
947
+ *error = AK_ERROR_OVERFLOW;
948
948
  return 0;
949
949
  }
950
950
  }
@@ -952,7 +952,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
952
952
  }
953
953
  while (p < end) {
954
954
  if (!AK_is_space(*p)) {
955
- *error = ERROR_INVALID_CHARS;
955
+ *error = AK_ERROR_INVALID_CHARS;
956
956
  return 0;
957
957
  }
958
958
  p++;
@@ -975,7 +975,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
975
975
  if (p >= end) return number;
976
976
  }
977
977
  if (*p == '-') {
978
- *error = ERROR_INVALID_CHARS;
978
+ *error = AK_ERROR_INVALID_CHARS;
979
979
  return 0;
980
980
  } else if (*p == '+') {
981
981
  p++;
@@ -984,7 +984,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
984
984
 
985
985
  // Check that there is a first digit.
986
986
  if (!AK_is_digit(*p)) {
987
- *error = ERROR_NO_DIGITS;
987
+ *error = AK_ERROR_NO_DIGITS;
988
988
  return 0;
989
989
  }
990
990
  // If number is less than pre_max, at least one more digit can be processed without overflowing.
@@ -1006,7 +1006,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
1006
1006
  if (p >= end) return number;
1007
1007
  d = *p;
1008
1008
  } else {
1009
- *error = ERROR_OVERFLOW;
1009
+ *error = AK_ERROR_OVERFLOW;
1010
1010
  return 0;
1011
1011
  }
1012
1012
  }
@@ -1019,14 +1019,14 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
1019
1019
  if (p >= end) return number;
1020
1020
  d = *p;
1021
1021
  } else {
1022
- *error = ERROR_OVERFLOW;
1022
+ *error = AK_ERROR_OVERFLOW;
1023
1023
  return 0;
1024
1024
  }
1025
1025
  }
1026
1026
  }
1027
1027
  while (p < end) {
1028
1028
  if (!AK_is_space(*p)) {
1029
- *error = ERROR_INVALID_CHARS;
1029
+ *error = AK_ERROR_INVALID_CHARS;
1030
1030
  return 0;
1031
1031
  }
1032
1032
  p++;
@@ -3535,21 +3535,9 @@ resolve_dtype_iter(PyObject *Py_UNUSED(m), PyObject *arg) {
3535
3535
  //------------------------------------------------------------------------------
3536
3536
  // general utility
3537
3537
 
3538
- #define NONZERO_APPEND_INDEX { \
3539
- if (AK_UNLIKELY(count == capacity)) { \
3540
- capacity <<= 1; \
3541
- indices = (npy_int64*)realloc(indices, sizeof(npy_int64) * capacity);\
3542
- if (indices == NULL) { \
3543
- return NULL; \
3544
- } \
3545
- } \
3546
- indices[count++] = p - p_start; \
3547
- } \
3548
-
3549
- // Given a Boolean, contiguous 1D array, return the index positions in an int64 array.
3538
+ // Given a Boolean, contiguous 1D array, return the index positions in an int64 array. Through experimentation it has been verified that doing full-size allocation of memory provides the best performance at all scales. Using NpyIter, or using, bit masks does not improve performance over pointer arithmetic. Prescanning for all empty is very effective. Note that NumPy benefits from first counting the nonzeros, then allocating only enough data for the expexted number of indices.
3550
3539
  static inline PyObject*
3551
3540
  AK_nonzero_1d(PyArrayObject* array) {
3552
- // the maxiumum number of indices we could return is the size of the array; if this is under a certain number, probably better to just allocate that rather than reallocate
3553
3541
  PyObject* final;
3554
3542
  npy_intp count_max = PyArray_SIZE(array);
3555
3543
 
@@ -3562,116 +3550,75 @@ AK_nonzero_1d(PyArrayObject* array) {
3562
3550
  lldiv_t size_div = lldiv((long long)count_max, 8); // quot, rem
3563
3551
 
3564
3552
  Py_ssize_t count = 0;
3565
- // the maximum number of collected integers is equal to or less than count_max; for small count_max, we can just set that value; for large size, we set it to half the size
3566
- Py_ssize_t capacity = count_max < 1024 ? count_max : count_max / 8;
3553
+ // the maximum number of collected integers is equal to or less than count_max
3554
+ Py_ssize_t capacity = count_max;
3567
3555
  npy_int64* indices = (npy_int64*)malloc(sizeof(npy_int64) * capacity);
3568
3556
 
3569
- // array is contiguous, 1d, boolean
3570
- npy_bool* p_start = (npy_bool*)PyArray_DATA(array);
3571
- npy_bool* p = p_start;
3572
- npy_bool* p_end = p + count_max;
3573
- npy_bool* p_end_roll = p_end - size_div.rem;
3574
-
3575
3557
  NPY_BEGIN_THREADS_DEF;
3576
3558
  NPY_BEGIN_THREADS;
3577
- // Through experimentation it has been verified that doing full-size allocation of memory does not permit outperforming NumPy at 10_000_000 scale; but doing less optimizations does help.
3578
- // Using bit masks does not improve perforamnce over pointer arithmetic.
3579
- // Prescanning for all empty is very effective.
3580
3559
 
3581
- while (p < p_end_roll) {
3582
- if (*(npy_uint64*)p == 0) {
3583
- p += 8; // no true within this 8 byte roll region
3584
- continue;
3560
+ if (PyArray_IS_C_CONTIGUOUS(array)) {
3561
+ npy_bool* p_start = (npy_bool*)PyArray_DATA(array);
3562
+ npy_bool* p = p_start;
3563
+ npy_bool* p_end = p + count_max;
3564
+ npy_bool* p_end_roll = p_end - size_div.rem;
3565
+
3566
+ while (p < p_end_roll) {
3567
+ if (*(npy_uint64*)p == 0) {
3568
+ p += 8; // no true within this 8 byte roll region
3569
+ continue;
3570
+ }
3571
+ if (*p) {indices[count++] = p - p_start;}
3572
+ p++;
3573
+ if (*p) {indices[count++] = p - p_start;}
3574
+ p++;
3575
+ if (*p) {indices[count++] = p - p_start;}
3576
+ p++;
3577
+ if (*p) {indices[count++] = p - p_start;}
3578
+ p++;
3579
+ if (*p) {indices[count++] = p - p_start;}
3580
+ p++;
3581
+ if (*p) {indices[count++] = p - p_start;}
3582
+ p++;
3583
+ if (*p) {indices[count++] = p - p_start;}
3584
+ p++;
3585
+ if (*p) {indices[count++] = p - p_start;}
3586
+ p++;
3587
+ }
3588
+ while (p < p_end) {
3589
+ if (*p) {indices[count++] = p - p_start;}
3590
+ p++;
3585
3591
  }
3586
- if (*p) {NONZERO_APPEND_INDEX;}
3587
- p++;
3588
- if (*p) {NONZERO_APPEND_INDEX;}
3589
- p++;
3590
- if (*p) {NONZERO_APPEND_INDEX;}
3591
- p++;
3592
- if (*p) {NONZERO_APPEND_INDEX;}
3593
- p++;
3594
- if (*p) {NONZERO_APPEND_INDEX;}
3595
- p++;
3596
- if (*p) {NONZERO_APPEND_INDEX;}
3597
- p++;
3598
- if (*p) {NONZERO_APPEND_INDEX;}
3599
- p++;
3600
- if (*p) {NONZERO_APPEND_INDEX;}
3601
- p++;
3602
3592
  }
3603
- while (p < p_end) {
3604
- if (*p) {NONZERO_APPEND_INDEX;}
3605
- p++;
3593
+ else {
3594
+ npy_intp i = 0; // position within Boolean array
3595
+ npy_intp i_end = count_max;
3596
+ npy_intp i_end_roll = count_max - size_div.rem;
3597
+ while (i < i_end_roll) {
3598
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3599
+ i++;
3600
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3601
+ i++;
3602
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3603
+ i++;
3604
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3605
+ i++;
3606
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3607
+ i++;
3608
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3609
+ i++;
3610
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3611
+ i++;
3612
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3613
+ i++;
3614
+ }
3615
+ while (i < i_end) {
3616
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3617
+ i++;
3618
+ }
3606
3619
  }
3607
3620
  NPY_END_THREADS;
3608
3621
 
3609
- // npy_uint64 roll;
3610
- // while (p < p_end_roll) {
3611
- // roll = *(npy_uint64*)p;
3612
- // if (roll == 0) {
3613
- // p += 8; // no true within this 8 byte roll region
3614
- // continue;
3615
- // }
3616
- // // this order depends on byte order
3617
- // if (roll & 0xFF) {NONZERO_APPEND_OFFSET(0);}
3618
- // if (roll & 0xFF00) {NONZERO_APPEND_OFFSET(1);}
3619
- // if (roll & 0xFF0000) {NONZERO_APPEND_OFFSET(2);}
3620
- // if (roll & 0xFF000000) {NONZERO_APPEND_OFFSET(3);}
3621
- // if (roll & 0xFF00000000) {NONZERO_APPEND_OFFSET(4);}
3622
- // if (roll & 0xFF0000000000) {NONZERO_APPEND_OFFSET(5);}
3623
- // if (roll & 0xFF000000000000) {NONZERO_APPEND_OFFSET(6);}
3624
- // if (roll & 0xFF00000000000000) {NONZERO_APPEND_OFFSET(7);}
3625
- // p += 8;
3626
- // }
3627
- // while (p < p_end) {
3628
- // if (*p) {NONZERO_APPEND_OFFSET(0);}
3629
- // p++;
3630
- // }
3631
-
3632
-
3633
- // while (p < p_end_roll) {
3634
- // if (*(npy_uint64*)p == 0) {
3635
- // p += 8; // no true within this roll region
3636
- // continue;
3637
- // }
3638
- // if (AK_UNLIKELY(count + 8 >= capacity)) {
3639
- // capacity <<= 1;
3640
- // indices = (npy_int64*)realloc(indices, sizeof(npy_int64) * capacity);
3641
- // if (indices == NULL) {
3642
- // return NULL;
3643
- // }
3644
- // }
3645
- // if (*p) {indices[count++] = p - p_start;}
3646
- // p++;
3647
- // if (*p) {indices[count++] = p - p_start;}
3648
- // p++;
3649
- // if (*p) {indices[count++] = p - p_start;}
3650
- // p++;
3651
- // if (*p) {indices[count++] = p - p_start;}
3652
- // p++;
3653
- // if (*p) {indices[count++] = p - p_start;}
3654
- // p++;
3655
- // if (*p) {indices[count++] = p - p_start;}
3656
- // p++;
3657
- // if (*p) {indices[count++] = p - p_start;}
3658
- // p++;
3659
- // if (*p) {indices[count++] = p - p_start;}
3660
- // p++;
3661
- // }
3662
- // // at most three more indices remain
3663
- // if (AK_UNLIKELY(count + 7 >= capacity)) {
3664
- // capacity <<= 1;
3665
- // indices = (npy_int64*)realloc(indices, sizeof(npy_int64) * capacity);
3666
- // if (indices == NULL) {
3667
- // return NULL;
3668
- // }
3669
- // }
3670
- // while (p < p_end) {
3671
- // if (*p) {indices[count++] = p - p_start;}
3672
- // p++;
3673
- // }
3674
-
3675
3622
  npy_intp dims = {count};
3676
3623
  final = PyArray_SimpleNewFromData(1, &dims, NPY_INT64, (void*)indices);
3677
3624
  if (!final) {
@@ -3683,7 +3630,6 @@ AK_nonzero_1d(PyArrayObject* array) {
3683
3630
  PyArray_CLEARFLAGS((PyArrayObject*)final, NPY_ARRAY_WRITEABLE);
3684
3631
  return final;
3685
3632
  }
3686
- #undef NONZERO_APPEND_INDEX
3687
3633
 
3688
3634
  static PyObject*
3689
3635
  nonzero_1d(PyObject *Py_UNUSED(m), PyObject *a) {
@@ -3697,10 +3643,6 @@ nonzero_1d(PyObject *Py_UNUSED(m), PyObject *a) {
3697
3643
  PyErr_SetString(PyExc_ValueError, "Array must be of type bool");
3698
3644
  return NULL;
3699
3645
  }
3700
- if (!PyArray_IS_C_CONTIGUOUS(array)) {
3701
- PyErr_SetString(PyExc_ValueError, "Array must be contiguous");
3702
- return NULL;
3703
- }
3704
3646
  return AK_nonzero_1d(array);
3705
3647
  }
3706
3648
 
@@ -5869,8 +5811,6 @@ typedef struct TriMapObject {
5869
5811
  Py_ssize_t src_len;
5870
5812
  Py_ssize_t dst_len;
5871
5813
  Py_ssize_t len;
5872
- // Py_ssize_t src_connected;
5873
- // Py_ssize_t dst_connected;
5874
5814
  bool is_many;
5875
5815
  bool finalized;
5876
5816
 
@@ -5932,8 +5872,6 @@ TriMap_init(PyObject *self, PyObject *args, PyObject *kwargs) {
5932
5872
  tm->is_many = false;
5933
5873
  tm->finalized = false;
5934
5874
  tm->len = 0;
5935
- // tm->src_connected = 0;
5936
- // tm->dst_connected = 0;
5937
5875
 
5938
5876
  // we create arrays, and also pre-extract pointers to array data for fast insertion; we keep the array for optimal summing routines
5939
5877
  npy_intp dims_src_len[] = {src_len};
@@ -6039,7 +5977,7 @@ TriMap_repr(TriMapObject *self) {
6039
5977
  is_finalized);
6040
5978
  }
6041
5979
 
6042
- // 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 of th `final`. Return NULL on error
5980
+ // 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.
6043
5981
 
6044
5982
  // Inner function for calling from C; returns 0 on success, -1 on error. Exceptions will be set on error.
6045
5983
  static inline int
@@ -6062,7 +6000,6 @@ AK_TM_register_one(TriMapObject* tm, Py_ssize_t src_from, Py_ssize_t dst_from) {
6062
6000
  }
6063
6001
  tm->src_one[tm->src_one_count] = (TriMapOne){src_from, tm->len};
6064
6002
  tm->src_one_count += 1;
6065
- // tm->src_connected += 1;
6066
6003
  }
6067
6004
  if (dst_matched) {
6068
6005
  if (AK_UNLIKELY(tm->dst_one_count == tm->dst_one_capacity)) {
@@ -6076,7 +6013,6 @@ AK_TM_register_one(TriMapObject* tm, Py_ssize_t src_from, Py_ssize_t dst_from) {
6076
6013
  }
6077
6014
  tm->dst_one[tm->dst_one_count] = (TriMapOne){dst_from, tm->len};
6078
6015
  tm->dst_one_count += 1;
6079
- // tm->dst_connected += 1;
6080
6016
  }
6081
6017
  if (src_matched && dst_matched) {
6082
6018
  if (!tm->is_many) {
@@ -6141,14 +6077,12 @@ TriMap_register_unmatched_dst(TriMapObject *self) {
6141
6077
  return NULL;
6142
6078
  }
6143
6079
  // derive indices for unmatched locations, call each with register_one
6144
- // PyObject* nonzero = PyArray_Nonzero(dst_unmatched);
6145
6080
  PyArrayObject* indices = (PyArrayObject*)AK_nonzero_1d(dst_unmatched);
6146
6081
  if (indices == NULL) {
6147
6082
  Py_DECREF((PyObject*)dst_unmatched);
6148
6083
  return NULL;
6149
6084
  }
6150
6085
  // borrow ref to array in 1-element tuple
6151
- // PyArrayObject *indices = (PyArrayObject*)PyTuple_GET_ITEM(nonzero, 0);
6152
6086
  npy_int64 *index_data = (npy_int64 *)PyArray_DATA(indices);
6153
6087
  npy_intp index_len = PyArray_SIZE(indices);
6154
6088
 
@@ -6215,8 +6149,6 @@ TriMap_register_many(TriMapObject *self, PyObject *args) {
6215
6149
  npy_int64 pos = *(npy_int64*)PyArray_GETPTR1(dst_from, i); // always int64
6216
6150
  self->dst_match_data[pos] = NPY_TRUE;
6217
6151
  }
6218
- // self->src_connected += increment;
6219
- // self->dst_connected += increment;
6220
6152
  self->len += increment;
6221
6153
  self->is_many = true;
6222
6154
  Py_RETURN_NONE;
@@ -6359,7 +6291,7 @@ TriMap_dst_no_fill(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
6359
6291
  Py_RETURN_FALSE;
6360
6292
  }
6361
6293
 
6362
- # define TRANSFER_SCALARS(npy_type_to, npy_type_from) { \
6294
+ # define AK_TM_TRANSFER_SCALAR(npy_type_to, npy_type_from) do { \
6363
6295
  npy_type_to* array_to_data = (npy_type_to*)PyArray_DATA(array_to); \
6364
6296
  TriMapOne* o = one_pairs; \
6365
6297
  TriMapOne* o_end = o + one_count; \
@@ -6396,57 +6328,11 @@ TriMap_dst_no_fill(TriMapObject *self, PyObject *Py_UNUSED(unused)) {
6396
6328
  } \
6397
6329
  } \
6398
6330
  } \
6399
- } \
6400
-
6401
- // The elsize of the `to` array will be equal to or greater than the from array
6402
- #define TRANSFER_FLEXIBLE(c_type) { \
6403
- npy_intp t_element_size = PyArray_DESCR(array_to)->elsize; \
6404
- npy_intp t_element_cp = t_element_size / sizeof(c_type); \
6405
- npy_intp f_element_size = PyArray_DESCR(array_from)->elsize; \
6406
- npy_intp f_element_cp = f_element_size / sizeof(c_type); \
6407
- npy_intp gap = t_element_size - f_element_size; \
6408
- c_type* array_to_data = (c_type*)PyArray_DATA(array_to); \
6409
- c_type* f; \
6410
- c_type* t; \
6411
- c_type* t_end; \
6412
- npy_intp dst_pos; \
6413
- npy_int64 f_pos; \
6414
- PyArrayObject* dst; \
6415
- TriMapOne* o = one_pairs; \
6416
- TriMapOne* o_end = o + one_count; \
6417
- for (; o < o_end; o++) { \
6418
- f = (c_type*)PyArray_GETPTR1(array_from, o->from); \
6419
- t = array_to_data + t_element_cp * o->to; \
6420
- memcpy(t, f, f_element_size); \
6421
- memset(t + f_element_cp, '\0', gap); \
6422
- } \
6423
- for (Py_ssize_t i = 0; i < tm->many_count; i++) { \
6424
- t = array_to_data + t_element_cp * tm->many_to[i].start; \
6425
- t_end = array_to_data + t_element_cp * tm->many_to[i].stop; \
6426
- if (from_src) { \
6427
- f = (c_type*)PyArray_GETPTR1(array_from, tm->many_from[i].src);\
6428
- for (; t < t_end; t += t_element_cp) { \
6429
- memcpy(t, f, f_element_size); \
6430
- memset(t + f_element_cp, '\0', gap); \
6431
- } \
6432
- } \
6433
- else { \
6434
- dst_pos = 0; \
6435
- dst = tm->many_from[i].dst; \
6436
- for (; t < t_end; t += t_element_cp) { \
6437
- f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos); \
6438
- f = (c_type*)PyArray_GETPTR1(array_from, f_pos); \
6439
- memcpy(t, f, f_element_size); \
6440
- memset(t + f_element_cp, '\0', gap); \
6441
- dst_pos++; \
6442
- } \
6443
- } \
6444
- } \
6445
- } \
6331
+ } while (0) \
6446
6332
 
6447
6333
  // 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.
6448
6334
  static inline int
6449
- AK_TM_transfer(TriMapObject* tm,
6335
+ AK_TM_transfer_scalar(TriMapObject* tm,
6450
6336
  bool from_src,
6451
6337
  PyArrayObject* array_from,
6452
6338
  PyArrayObject* array_to) {
@@ -6455,206 +6341,194 @@ AK_TM_transfer(TriMapObject* tm,
6455
6341
 
6456
6342
  switch(PyArray_TYPE(array_to)){
6457
6343
  case NPY_BOOL:
6458
- TRANSFER_SCALARS(npy_bool, npy_bool);
6344
+ AK_TM_TRANSFER_SCALAR(npy_bool, npy_bool);
6459
6345
  return 0;
6460
6346
  case NPY_INT64:
6461
6347
  switch (PyArray_TYPE(array_from)) {
6462
6348
  case NPY_INT64:
6463
- TRANSFER_SCALARS(npy_int64, npy_int64);
6349
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int64);
6464
6350
  return 0;
6465
6351
  case NPY_INT32:
6466
- TRANSFER_SCALARS(npy_int64, npy_int32);
6352
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int32);
6467
6353
  return 0;
6468
6354
  case NPY_INT16:
6469
- TRANSFER_SCALARS(npy_int64, npy_int16);
6355
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int16);
6470
6356
  return 0;
6471
6357
  case NPY_INT8:
6472
- TRANSFER_SCALARS(npy_int64, npy_int8);
6358
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int8);
6473
6359
  return 0;
6474
6360
  case NPY_UINT32:
6475
- TRANSFER_SCALARS(npy_int64, npy_uint32);
6361
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint32);
6476
6362
  return 0;
6477
6363
  case NPY_UINT16:
6478
- TRANSFER_SCALARS(npy_int64, npy_uint16);
6364
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint16);
6479
6365
  return 0;
6480
6366
  case NPY_UINT8:
6481
- TRANSFER_SCALARS(npy_int64, npy_uint8);
6367
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_uint8);
6482
6368
  return 0;
6483
6369
  }
6484
6370
  break;
6485
6371
  case NPY_INT32:
6486
6372
  switch (PyArray_TYPE(array_from)) {
6487
6373
  case NPY_INT32:
6488
- TRANSFER_SCALARS(npy_int32, npy_int32);
6374
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int32);
6489
6375
  return 0;
6490
6376
  case NPY_INT16:
6491
- TRANSFER_SCALARS(npy_int32, npy_int16);
6377
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int16);
6492
6378
  return 0;
6493
6379
  case NPY_INT8:
6494
- TRANSFER_SCALARS(npy_int32, npy_int8);
6380
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_int8);
6495
6381
  return 0;
6496
6382
  case NPY_UINT16:
6497
- TRANSFER_SCALARS(npy_int32, npy_uint16);
6383
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_uint16);
6498
6384
  return 0;
6499
6385
  case NPY_UINT8:
6500
- TRANSFER_SCALARS(npy_int32, npy_uint8);
6386
+ AK_TM_TRANSFER_SCALAR(npy_int32, npy_uint8);
6501
6387
  return 0;
6502
6388
  }
6503
6389
  break;
6504
6390
  case NPY_INT16:
6505
6391
  switch (PyArray_TYPE(array_from)) {
6506
6392
  case NPY_INT16:
6507
- TRANSFER_SCALARS(npy_int16, npy_int16);
6393
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_int16);
6508
6394
  return 0;
6509
6395
  case NPY_INT8:
6510
- TRANSFER_SCALARS(npy_int16, npy_int8);
6396
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_int8);
6511
6397
  return 0;
6512
6398
  case NPY_UINT8:
6513
- TRANSFER_SCALARS(npy_int16, npy_uint8);
6399
+ AK_TM_TRANSFER_SCALAR(npy_int16, npy_uint8);
6514
6400
  return 0;
6515
6401
  }
6516
6402
  break;
6517
6403
  case NPY_INT8:
6518
- TRANSFER_SCALARS(npy_int8, npy_int8);
6404
+ AK_TM_TRANSFER_SCALAR(npy_int8, npy_int8);
6519
6405
  return 0;
6520
6406
  case NPY_UINT64:
6521
6407
  switch (PyArray_TYPE(array_from)) {
6522
6408
  case NPY_UINT64:
6523
- TRANSFER_SCALARS(npy_uint64, npy_uint64);
6409
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint64);
6524
6410
  return 0;
6525
6411
  case NPY_UINT32:
6526
- TRANSFER_SCALARS(npy_uint64, npy_uint32);
6412
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint32);
6527
6413
  return 0;
6528
6414
  case NPY_UINT16:
6529
- TRANSFER_SCALARS(npy_uint64, npy_uint16);
6415
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint16);
6530
6416
  return 0;
6531
6417
  case NPY_UINT8:
6532
- TRANSFER_SCALARS(npy_uint64, npy_uint8);
6418
+ AK_TM_TRANSFER_SCALAR(npy_uint64, npy_uint8);
6533
6419
  return 0;
6534
6420
  }
6535
6421
  break;
6536
6422
  case NPY_UINT32:
6537
6423
  switch (PyArray_TYPE(array_from)) {
6538
6424
  case NPY_UINT32:
6539
- TRANSFER_SCALARS(npy_uint32, npy_uint32);
6425
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint32);
6540
6426
  return 0;
6541
6427
  case NPY_UINT16:
6542
- TRANSFER_SCALARS(npy_uint32, npy_uint16);
6428
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint16);
6543
6429
  return 0;
6544
6430
  case NPY_UINT8:
6545
- TRANSFER_SCALARS(npy_uint32, npy_uint8);
6431
+ AK_TM_TRANSFER_SCALAR(npy_uint32, npy_uint8);
6546
6432
  return 0;
6547
6433
  }
6548
6434
  break;
6549
6435
  case NPY_UINT16:
6550
6436
  switch (PyArray_TYPE(array_from)) {
6551
6437
  case NPY_UINT16:
6552
- TRANSFER_SCALARS(npy_uint16, npy_uint16);
6438
+ AK_TM_TRANSFER_SCALAR(npy_uint16, npy_uint16);
6553
6439
  return 0;
6554
6440
  case NPY_UINT8:
6555
- TRANSFER_SCALARS(npy_uint16, npy_uint8);
6441
+ AK_TM_TRANSFER_SCALAR(npy_uint16, npy_uint8);
6556
6442
  return 0;
6557
6443
  }
6558
6444
  break;
6559
6445
  case NPY_UINT8:
6560
- TRANSFER_SCALARS(npy_uint8, npy_uint8);
6446
+ AK_TM_TRANSFER_SCALAR(npy_uint8, npy_uint8);
6561
6447
  return 0;
6562
6448
  case NPY_FLOAT64:
6563
6449
  switch (PyArray_TYPE(array_from)) {
6564
6450
  case NPY_FLOAT64:
6565
- TRANSFER_SCALARS(npy_float64, npy_float64);
6451
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float64);
6566
6452
  return 0;
6567
6453
  case NPY_FLOAT32:
6568
- TRANSFER_SCALARS(npy_float64, npy_float32);
6454
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float32);
6569
6455
  return 0;
6570
6456
  case NPY_FLOAT16:
6571
- TRANSFER_SCALARS(npy_float64, npy_float16);
6457
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_float16);
6572
6458
  return 0;
6573
6459
  case NPY_INT64:
6574
- TRANSFER_SCALARS(npy_float64, npy_int64);
6460
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int64);
6575
6461
  return 0;
6576
6462
  case NPY_INT32:
6577
- TRANSFER_SCALARS(npy_float64, npy_int32);
6463
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int32);
6578
6464
  return 0;
6579
6465
  case NPY_INT16:
6580
- TRANSFER_SCALARS(npy_float64, npy_int16);
6466
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int16);
6581
6467
  return 0;
6582
6468
  case NPY_INT8:
6583
- TRANSFER_SCALARS(npy_float64, npy_int8);
6469
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_int8);
6584
6470
  return 0;
6585
6471
  case NPY_UINT64:
6586
- TRANSFER_SCALARS(npy_float64, npy_uint64);
6472
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint64);
6587
6473
  return 0;
6588
6474
  case NPY_UINT32:
6589
- TRANSFER_SCALARS(npy_float64, npy_uint32);
6475
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint32);
6590
6476
  return 0;
6591
6477
  case NPY_UINT16:
6592
- TRANSFER_SCALARS(npy_float64, npy_uint16);
6478
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint16);
6593
6479
  return 0;
6594
6480
  case NPY_UINT8:
6595
- TRANSFER_SCALARS(npy_float64, npy_uint8);
6481
+ AK_TM_TRANSFER_SCALAR(npy_float64, npy_uint8);
6596
6482
  return 0;
6597
6483
  }
6598
6484
  break;
6599
6485
  case NPY_FLOAT32:
6600
6486
  switch (PyArray_TYPE(array_from)) {
6601
6487
  case NPY_FLOAT32:
6602
- TRANSFER_SCALARS(npy_float32, npy_float32);
6488
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_float32);
6603
6489
  return 0;
6604
6490
  case NPY_FLOAT16:
6605
- TRANSFER_SCALARS(npy_float32, npy_float16);
6491
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_float16);
6606
6492
  return 0;
6607
6493
  case NPY_INT16:
6608
- TRANSFER_SCALARS(npy_float32, npy_int16);
6494
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_int16);
6609
6495
  return 0;
6610
6496
  case NPY_INT8:
6611
- TRANSFER_SCALARS(npy_float32, npy_int8);
6497
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_int8);
6612
6498
  return 0;
6613
6499
  case NPY_UINT16:
6614
- TRANSFER_SCALARS(npy_float32, npy_uint16);
6500
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_uint16);
6615
6501
  return 0;
6616
6502
  case NPY_UINT8:
6617
- TRANSFER_SCALARS(npy_float32, npy_uint8);
6503
+ AK_TM_TRANSFER_SCALAR(npy_float32, npy_uint8);
6618
6504
  return 0;
6619
6505
  }
6620
6506
  break;
6621
6507
  case NPY_FLOAT16:
6622
6508
  switch (PyArray_TYPE(array_from)) {
6623
6509
  case NPY_FLOAT16:
6624
- TRANSFER_SCALARS(npy_float16, npy_float16);
6510
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_float16);
6625
6511
  return 0;
6626
6512
  case NPY_INT8:
6627
- TRANSFER_SCALARS(npy_float16, npy_int8);
6513
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_int8);
6628
6514
  return 0;
6629
6515
  case NPY_UINT16:
6630
- TRANSFER_SCALARS(npy_float16, npy_uint16);
6516
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_uint16);
6631
6517
  return 0;
6632
6518
  case NPY_UINT8:
6633
- TRANSFER_SCALARS(npy_float16, npy_uint8);
6519
+ AK_TM_TRANSFER_SCALAR(npy_float16, npy_uint8);
6634
6520
  return 0;
6635
6521
  }
6636
6522
  break;
6637
- case NPY_UNICODE: {
6638
- TRANSFER_FLEXIBLE(Py_UCS4);
6639
- return 0;
6640
- }
6641
- case NPY_STRING: {
6642
- TRANSFER_FLEXIBLE(char);
6643
- return 0;
6644
- }
6645
6523
  case NPY_DATETIME: {
6646
- TRANSFER_SCALARS(npy_int64, npy_int64);
6524
+ AK_TM_TRANSFER_SCALAR(npy_int64, npy_int64);
6647
6525
  return 0;
6648
6526
  }
6649
6527
  }
6650
- // AK_DEBUG_MSG_OBJ("array_to", (PyObject*)array_to);
6651
- // AK_DEBUG_MSG_OBJ("array_from", (PyObject*)array_from);
6652
6528
  PyErr_SetString(PyExc_TypeError, "No handling for types");
6653
6529
  return -1;
6654
6530
  }
6655
-
6656
- #undef TRANSFER_SCALARS
6657
- #undef TRANSFER_FLEXIBLE
6531
+ #undef AK_TM_TRANSFER_SCALAR
6658
6532
 
6659
6533
  // Returns -1 on error. Specialized transfer from any type of an array to an object array.
6660
6534
  static inline int
@@ -6740,12 +6614,10 @@ AK_TM_fill_object(TriMapObject* tm,
6740
6614
 
6741
6615
  PyArrayObject* final_fill = (PyArrayObject*)(from_src
6742
6616
  ? tm->final_src_fill : tm->final_dst_fill);
6743
-
6744
6617
  PyObject** array_to_data = (PyObject**)PyArray_DATA(array_to);
6745
6618
  npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
6746
6619
  npy_int64* p_end = p + PyArray_SIZE(final_fill);
6747
6620
  PyObject** target;
6748
- // npy_int64 pos;
6749
6621
  while (p < p_end) {
6750
6622
  target = array_to_data + *p++;
6751
6623
  Py_INCREF(fill_value);
@@ -6754,29 +6626,117 @@ AK_TM_fill_object(TriMapObject* tm,
6754
6626
  return 0;
6755
6627
  }
6756
6628
 
6757
- // TODO: AK_TM_fill_flexible
6758
- // this manually inserts string
6759
- // if (t_is_flexible) {
6760
- // // insert fill values
6761
- // Py_UCS4* t = (Py_UCS4*)PyArray_DATA(array_to);
6762
- // npy_intp t_cp = PyArray_DESCR(array_to)->elsize / UCS4_SIZE;
6763
- // Py_ssize_t len = PyUnicode_GET_LENGTH(fill_value) * UCS4_SIZE; // code points
6764
- // Py_ssize_t count = from_src ? tm->src_len : tm->dst_len;
6765
- // // NOTE: matches do not tell where a fill is needed
6766
- // npy_bool* d = from_src ? tm->src_match_data : tm->dst_match_data;
6767
- // npy_bool* d_end = d + count;
6768
- // while (d < d_end) {
6769
- // if (*d == NPY_FALSE) {
6770
- // if (PyUnicode_AsUCS4(fill_value, t, len, 0) == NULL) {
6771
- // Py_DECREF((PyObject*)array_to);
6772
- // return NULL;
6773
- // }
6774
- // }
6775
- // t += t_cp;
6776
- // d++;
6777
- // }
6778
- // }
6629
+ #define AK_TM_TRANSFER_FLEXIBLE(c_type) do { \
6630
+ Py_ssize_t one_count = from_src ? tm->src_one_count : tm->dst_one_count;\
6631
+ TriMapOne* one_pairs = from_src ? tm->src_one : tm->dst_one; \
6632
+ npy_intp t_element_size = PyArray_DESCR(array_to)->elsize; \
6633
+ npy_intp t_element_cp = t_element_size / sizeof(c_type); \
6634
+ npy_intp f_element_size = PyArray_DESCR(array_from)->elsize; \
6635
+ c_type* array_to_data = (c_type*)PyArray_DATA(array_to); \
6636
+ c_type* f; \
6637
+ c_type* t; \
6638
+ c_type* t_end; \
6639
+ npy_intp dst_pos; \
6640
+ npy_int64 f_pos; \
6641
+ PyArrayObject* dst; \
6642
+ TriMapOne* o = one_pairs; \
6643
+ TriMapOne* o_end = o + one_count; \
6644
+ for (; o < o_end; o++) { \
6645
+ f = (c_type*)PyArray_GETPTR1(array_from, o->from); \
6646
+ t = array_to_data + t_element_cp * o->to; \
6647
+ memcpy(t, f, f_element_size); \
6648
+ } \
6649
+ for (Py_ssize_t i = 0; i < tm->many_count; i++) { \
6650
+ t = array_to_data + t_element_cp * tm->many_to[i].start; \
6651
+ t_end = array_to_data + t_element_cp * tm->many_to[i].stop; \
6652
+ if (from_src) { \
6653
+ f = (c_type*)PyArray_GETPTR1(array_from, tm->many_from[i].src);\
6654
+ for (; t < t_end; t += t_element_cp) { \
6655
+ memcpy(t, f, f_element_size); \
6656
+ } \
6657
+ } \
6658
+ else { \
6659
+ dst_pos = 0; \
6660
+ dst = tm->many_from[i].dst; \
6661
+ for (; t < t_end; t += t_element_cp) { \
6662
+ f_pos = *(npy_int64*)PyArray_GETPTR1(dst, dst_pos); \
6663
+ f = (c_type*)PyArray_GETPTR1(array_from, f_pos); \
6664
+ memcpy(t, f, f_element_size); \
6665
+ dst_pos++; \
6666
+ } \
6667
+ } \
6668
+ } \
6669
+ } while (0) \
6670
+
6671
+ // Returns -1 on error.
6672
+ static inline int
6673
+ AK_TM_fill_unicode(TriMapObject* tm,
6674
+ bool from_src,
6675
+ PyArrayObject* array_to,
6676
+ PyObject* fill_value) {
6677
+ PyArrayObject* final_fill = (PyArrayObject*)(from_src
6678
+ ? tm->final_src_fill : tm->final_dst_fill);
6679
+
6680
+ Py_UCS4* array_to_data = (Py_UCS4*)PyArray_DATA(array_to);
6681
+ // code points per element
6682
+ npy_intp cp = PyArray_DESCR(array_to)->elsize / UCS4_SIZE;
6683
+
6684
+ bool decref_fill_value = false;
6685
+ if (PyBytes_Check(fill_value)) {
6686
+ fill_value = PyUnicode_FromEncodedObject(fill_value, "utf-8", NULL);
6687
+ if (fill_value == NULL) {
6688
+ return -1;
6689
+ }
6690
+ decref_fill_value = true;
6691
+ }
6692
+ else if (!PyUnicode_Check(fill_value)) {
6693
+ return -1;
6694
+ }
6695
+ Py_ssize_t fill_cp = PyUnicode_GET_LENGTH(fill_value) * UCS4_SIZE; // code points
6696
+ // p is the index position to fill
6697
+ npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
6698
+ npy_int64* p_end = p + PyArray_SIZE(final_fill);
6699
+ Py_UCS4* target;
6700
+ while (p < p_end) {
6701
+ target = array_to_data + (*p * cp);
6702
+ // disabling copying a null
6703
+ if (PyUnicode_AsUCS4(fill_value, target, fill_cp, 0) == NULL) {
6704
+ return -1;
6705
+ }
6706
+ p++;
6707
+ }
6708
+ if (decref_fill_value) {
6709
+ Py_DECREF(fill_value);
6710
+ }
6711
+ return 0;
6712
+ }
6713
+
6714
+ // Returns -1 on error.
6715
+ static inline int
6716
+ AK_TM_fill_string(TriMapObject* tm,
6717
+ bool from_src,
6718
+ PyArrayObject* array_to,
6719
+ PyObject* fill_value) {
6720
+ PyArrayObject* final_fill = (PyArrayObject*)(from_src
6721
+ ? tm->final_src_fill : tm->final_dst_fill);
6779
6722
 
6723
+ char* array_to_data = (char*)PyArray_DATA(array_to);
6724
+ npy_intp cp = PyArray_DESCR(array_to)->elsize;
6725
+ if (!PyBytes_Check(fill_value)) {
6726
+ return -1;
6727
+ }
6728
+ Py_ssize_t fill_cp = PyBytes_GET_SIZE(fill_value);
6729
+ const char* fill_data = PyBytes_AS_STRING(fill_value);
6730
+ // p is the index position to fill
6731
+ npy_int64* p = (npy_int64*)PyArray_DATA(final_fill);
6732
+ npy_int64* p_end = p + PyArray_SIZE(final_fill);
6733
+ char* target;
6734
+ while (p < p_end) {
6735
+ target = array_to_data + (*p++ * cp);
6736
+ memcpy(target, fill_data, fill_cp);
6737
+ }
6738
+ return 0;
6739
+ }
6780
6740
 
6781
6741
  // Returns NULL on error.
6782
6742
  static inline PyObject*
@@ -6790,6 +6750,9 @@ AK_TM_map_no_fill(TriMapObject* tm,
6790
6750
  npy_intp dims[] = {tm->len};
6791
6751
  PyArrayObject* array_to;
6792
6752
  bool dtype_is_obj = PyArray_TYPE(array_from) == NPY_OBJECT;
6753
+ bool dtype_is_unicode = PyArray_TYPE(array_from) == NPY_UNICODE;
6754
+ bool dtype_is_string = PyArray_TYPE(array_from) == NPY_STRING;
6755
+
6793
6756
  // create to array
6794
6757
  if (dtype_is_obj) { // initializes values to NULL
6795
6758
  array_to = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_OBJECT);
@@ -6810,8 +6773,14 @@ AK_TM_map_no_fill(TriMapObject* tm,
6810
6773
  return NULL;
6811
6774
  }
6812
6775
  }
6776
+ else if (dtype_is_unicode) {
6777
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4);
6778
+ }
6779
+ else if (dtype_is_string) {
6780
+ AK_TM_TRANSFER_FLEXIBLE(char);
6781
+ }
6813
6782
  else {
6814
- if (AK_TM_transfer(tm, from_src, array_from, array_to)) {
6783
+ if (AK_TM_transfer_scalar(tm, from_src, array_from, array_to)) {
6815
6784
  Py_DECREF((PyObject*)array_to);
6816
6785
  return NULL;
6817
6786
  }
@@ -6864,15 +6833,22 @@ AK_TM_map_fill(TriMapObject* tm,
6864
6833
  // passing a borrowed ref; returns a new ref
6865
6834
  PyArray_Descr* dtype = AK_resolve_dtype(PyArray_DESCR(array_from), fill_value_dtype);
6866
6835
  bool dtype_is_obj = dtype->type_num == NPY_OBJECT;
6836
+ bool dtype_is_unicode = dtype->type_num == NPY_UNICODE;
6837
+ bool dtype_is_string = dtype->type_num == NPY_STRING;
6867
6838
 
6868
6839
  npy_intp dims[] = {tm->len};
6869
6840
  PyArrayObject* array_to;
6841
+
6870
6842
  if (dtype_is_obj) {
6871
6843
  Py_DECREF(dtype); // not needed
6872
6844
  // will initialize to NULL, not None
6873
6845
  array_to = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_OBJECT);
6874
6846
  Py_INCREF(array_from); // normalize refs when casting
6875
6847
  }
6848
+ else if (dtype_is_unicode || dtype_is_string) {
6849
+ array_to = (PyArrayObject*)PyArray_Zeros(1, dims, dtype, 0); // steals dtype ref
6850
+ Py_INCREF(array_from); // normalize refs when casting
6851
+ }
6876
6852
  else {
6877
6853
  array_to = (PyArrayObject*)PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
6878
6854
  if (PyArray_TYPE(array_from) == NPY_DATETIME &&
@@ -6893,44 +6869,51 @@ AK_TM_map_fill(TriMapObject* tm,
6893
6869
  Py_DECREF((PyObject*)array_from);
6894
6870
  return NULL;
6895
6871
  }
6872
+ // array_from, array_to inc refed and dec refed on error
6896
6873
  if (dtype_is_obj) {
6897
6874
  if (AK_TM_transfer_object(tm, from_src, array_from, array_to)) {
6898
- Py_DECREF((PyObject*)array_to);
6899
- Py_DECREF((PyObject*)array_from);
6900
- return NULL;
6875
+ goto error;
6901
6876
  }
6902
6877
  if (AK_TM_fill_object(tm, from_src, array_to, fill_value)) {
6903
- Py_DECREF((PyObject*)array_to);
6904
- Py_DECREF((PyObject*)array_from);
6905
- return NULL;
6878
+ goto error;
6879
+ }
6880
+ }
6881
+ else if (dtype_is_unicode) {
6882
+ AK_TM_TRANSFER_FLEXIBLE(Py_UCS4);
6883
+ if (AK_TM_fill_unicode(tm, from_src, array_to, fill_value)) {
6884
+ goto error;
6885
+ }
6886
+ }
6887
+ else if (dtype_is_string) {
6888
+ AK_TM_TRANSFER_FLEXIBLE(char);
6889
+ if (AK_TM_fill_string(tm, from_src, array_to, fill_value)) {
6890
+ goto error;
6906
6891
  }
6907
6892
  }
6908
- // TODO: add special hanldig for unicode/bytes
6909
6893
  else {
6910
6894
  // 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 obbligates us to decref the filled value when assigning
6911
6895
  if (PyArray_FillWithScalar(array_to, fill_value)) { // -1 on error
6912
- Py_DECREF((PyObject*)array_to);
6913
- Py_DECREF((PyObject*)array_from);
6914
- return NULL;
6896
+ goto error;
6915
6897
  }
6916
- if (AK_TM_transfer(tm, from_src, array_from, array_to)) {
6917
- Py_DECREF((PyObject*)array_to);
6918
- Py_DECREF((PyObject*)array_from);
6919
- return NULL;
6898
+ if (AK_TM_transfer_scalar(tm, from_src, array_from, array_to)) {
6899
+ goto error;
6920
6900
  }
6921
6901
  }
6922
-
6923
6902
  Py_DECREF((PyObject*)array_from); // ref inc for this function
6924
6903
  PyArray_CLEARFLAGS(array_to, NPY_ARRAY_WRITEABLE);
6925
6904
  return (PyObject*)array_to;
6905
+ error:
6906
+ Py_DECREF((PyObject*)array_to);
6907
+ Py_DECREF((PyObject*)array_from);
6908
+ return NULL;
6926
6909
  }
6910
+ #undef AK_TM_TRANSFER_FLEXIBLE
6927
6911
 
6928
6912
  static PyObject*
6929
6913
  TriMap_map_src_fill(TriMapObject *self, PyObject *args) {
6930
6914
  PyArrayObject* array_from;
6931
6915
  PyObject* fill_value;
6932
6916
  PyArray_Descr* fill_value_dtype;
6933
-
6934
6917
  if (!PyArg_ParseTuple(args,
6935
6918
  "O!OO!:map_src_fill",
6936
6919
  &PyArray_Type, &array_from,
@@ -6952,7 +6935,6 @@ TriMap_map_dst_fill(TriMapObject *self, PyObject *args) {
6952
6935
  PyArrayObject* array_from;
6953
6936
  PyObject* fill_value;
6954
6937
  PyArray_Descr* fill_value_dtype;
6955
-
6956
6938
  if (!PyArg_ParseTuple(args,
6957
6939
  "O!OO!:map_dst_fill",
6958
6940
  &PyArray_Type, &array_from,
@@ -6969,7 +6951,6 @@ TriMap_map_dst_fill(TriMapObject *self, PyObject *args) {
6969
6951
  return AK_TM_map_fill(self, from_src, array_from, fill_value, fill_value_dtype);
6970
6952
  }
6971
6953
 
6972
-
6973
6954
  static PyMethodDef TriMap_methods[] = {
6974
6955
  {"register_one", (PyCFunction)TriMap_register_one, METH_VARARGS, NULL},
6975
6956
  {"register_unmatched_dst", (PyCFunction)TriMap_register_unmatched_dst, METH_NOARGS, NULL},
@@ -83,4 +83,19 @@ class TestUnit(unittest.TestCase):
83
83
  a1[999] = True
84
84
  self.assertEqual(nonzero_1d(a1).tolist(), [999, 9_999_999])
85
85
  a1[0] = True
86
- self.assertEqual(nonzero_1d(a1).tolist(), [0, 999, 9_999_999])
86
+ self.assertEqual(nonzero_1d(a1).tolist(), [0, 999, 9_999_999])
87
+
88
+ def test_nonzero_1d_f(self) -> None:
89
+ # non-contiguous
90
+ a1 = np.arange(40).reshape(10, 4) % 3 == 0
91
+ a2 = a1[:, 3]
92
+ self.assertEqual(nonzero_1d(a2).tolist(), [0, 3, 6, 9])
93
+
94
+ a3 = a1[:, 1]
95
+ self.assertEqual(nonzero_1d(a3).tolist(), [2, 5, 8])
96
+
97
+ def test_nonzero_1d_g(self) -> None:
98
+ a1 = np.arange(20).reshape(4, 5) % 3 == 0
99
+ a2 = a1[:, 4]
100
+ # array([False, True, False, False])
101
+ self.assertEqual(nonzero_1d(a2).tolist(), [1])
@@ -975,6 +975,21 @@ class TestUnit(unittest.TestCase):
975
975
  post_dst = tm.map_dst_no_fill(dst)
976
976
  self.assertEqual(post_dst.tolist(), [b'a', b'bbb', b'cc', b'cc', b'dddd', b'a'])
977
977
 
978
+ def test_tri_map_map_bytes_a(self) -> None:
979
+ src = np.array([b'a', b'bbb', b'cc'], dtype=np.bytes_)
980
+ dst = np.array([b'cc', b'dddd', b'eee'], dtype=np.bytes_)
981
+
982
+ tm = TriMap(len(src), len(dst))
983
+ tm.register_one(0, -1)
984
+ tm.register_one(1, -1)
985
+ tm.register_one(2, 0)
986
+ tm.register_unmatched_dst()
987
+ tm.finalize()
988
+
989
+ post_src = tm.map_src_fill(src, b'--', np.dtype(np.bytes_))
990
+ post_dst = tm.map_dst_fill(dst, b'--', np.dtype(np.bytes_))
991
+ self.assertEqual(post_src.tolist(), [b'a', b'bbb', b'cc', b'--', b'--'])
992
+ self.assertEqual(post_dst.tolist(), [b'--', b'--', b'cc', b'dddd', b'eee'])
978
993
  #---------------------------------------------------------------------------
979
994
 
980
995
  def test_tri_map_map_unicode_a(self) -> None:
@@ -994,7 +1009,6 @@ class TestUnit(unittest.TestCase):
994
1009
  post_dst = tm.map_dst_fill(dst, '====', np.array('====').dtype)
995
1010
  self.assertEqual(post_dst.tolist(), ['a', 'a', 'a', '====', 'cc', 'cc', '===='])
996
1011
 
997
-
998
1012
  def test_tri_map_map_unicode_b(self) -> None:
999
1013
  src = np.array(['a', 'bbb', 'cc', 'dddd'])
1000
1014
  dst = np.array(['cc', 'a', 'a', 'a', 'cc'])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes