Skip to content

Commit 0129389

Browse files
committed
NumPy 2.0 build support
Signed-off-by: Tim Paine <[email protected]> Small fix for numpy2 behavior in pandas, give leak check more leeway in test Signed-off-by: Tim Paine <[email protected]> Bump conda action and micromamba version Signed-off-by: Tim Paine <[email protected]> Go through undocumented numpy behavior to handle 1.* arrays when compiled against 2.* Signed-off-by: Tim Paine <[email protected]> Final fixes for new import utility
1 parent 8c19928 commit 0129389

19 files changed

+146
-110
lines changed

.github/actions/setup-dependencies/action.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ runs:
2525

2626
################
2727
# Linux # NOTE: skip for manylinux image
28-
# - name: Linux init steps
29-
# shell: bash
30-
# run: make dependencies-vcpkg
31-
# if: ${{ runner.os == 'Linux' }} # skip
3228

3329
################
3430
# Mac
@@ -37,16 +33,6 @@ runs:
3733
run: make dependencies-mac
3834
if: ${{ runner.os == 'macOS' }}
3935

40-
# - name: Setup vcpkg cache in shell
41-
# shell: bash
42-
# run: |
43-
# which -a gcc-12
44-
# echo "CC=/usr/local/bin/gcc-12" >> $GITHUB_ENV
45-
# echo "CMAKE_C_COMPILER=/usr/local/bin/gcc-12" >> $GITHUB_ENV
46-
# echo "CXX=/usr/local/bin/g++-12" >> $GITHUB_ENV
47-
# echo "CMAKE_CXX_COMPILER=/usr/local/bin/g++-12" >> $GITHUB_ENV
48-
# if: ${{ runner.os == 'macOS' }}
49-
5036
################
5137
# Windows
5238
- name: Windows init steps (vc143)

.github/workflows/build.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,10 @@ jobs:
654654
python-version:
655655
- 3.9
656656
package:
657-
- "sqlalchemy<2"
658-
- "numpy==1.22.4" # Min supported version of pandas 2.2
659-
- "perspective-python<3"
657+
- sqlalchemy<2
658+
- perspective-python<3
659+
# Min supported version of pandas 2.2
660+
- numpy==1.22.4
660661

661662
runs-on: ${{ matrix.os }}
662663

@@ -665,6 +666,7 @@ jobs:
665666
uses: actions/checkout@v5
666667
with:
667668
submodules: recursive
669+
fetch-depth: 0
668670

669671
- name: Set up Python ${{ matrix.python-version }}
670672
uses: ./.github/actions/setup-python
@@ -688,13 +690,15 @@ jobs:
688690
python -m pip install -U *manylinux*.whl
689691
python -m pip install -U --no-deps *manylinux*.whl --target .
690692
691-
- name: Install package - ${{ matrix.package }}
693+
- name: Install packages - ${{ matrix.package }}
692694
run: python -m pip install -U "${{ matrix.package }}"
693695

696+
# Run tests to check dependencies
694697
- name: Python Test Steps
695698
run: make test TEST_ARGS="-k TestDBReader"
696699
if: ${{ contains( matrix.package, 'sqlalchemy' )}}
697700

701+
# For e.g. numpy dep changes, run all tests
698702
- name: Python Test Steps
699703
run: make test
700704
if: ${{ contains( matrix.package, 'numpy' )}}

.github/workflows/conda.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,25 @@ jobs:
4444
- name: Checkout
4545
uses: actions/checkout@v5
4646

47-
- uses: mamba-org/setup-micromamba@v1
47+
- uses: mamba-org/setup-micromamba@v2
4848
with:
49-
micromamba-version: '1.5.7-0'
49+
micromamba-version: '2.3.2-0'
5050
environment-file: conda/dev-environment-unix.yml
5151
init-shell: >-
5252
bash
53-
cache-environment: true
53+
cache-environment: false
54+
cache-downloads: false
5455
post-cleanup: 'all'
5556
if: ${{ runner.os != 'Windows' }}
5657

57-
- uses: mamba-org/setup-micromamba@v1
58+
- uses: mamba-org/setup-micromamba@v2
5859
with:
59-
micromamba-version: '1.5.7-0'
60+
micromamba-version: '2.3.2-0'
6061
environment-file: conda/dev-environment-win.yml
6162
init-shell: >-
6263
cmd.exe
63-
cache-environment: true
64+
cache-environment: false
65+
cache-downloads: false
6466
post-cleanup: 'all'
6567
if: ${{ runner.os == 'Windows' }}
6668

conda/dev-environment-unix.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
- compilers
1313
- cyrus-sasl
1414
- deprecated
15+
- docutils<0.22.1
1516
- exprtk
1617
- flex
1718
- graphviz
@@ -27,7 +28,7 @@ dependencies:
2728
- mdformat>=0.7.19,<0.8
2829
- mdformat-tables>=1,<1.1
2930
- ninja
30-
- numpy<2
31+
- numpy>=2
3132
- pandas<2.3 # [py<310]
3233
- pandas # [py>=310]
3334
- pillow

conda/dev-environment-win.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
- compilers
1313
- cyrus-sasl
1414
- deprecated
15+
- docutils<0.22.1
1516
- exprtk
1617
# - flex # not available on windows
1718
- graphviz
@@ -27,7 +28,7 @@ dependencies:
2728
- mdformat>=0.7.19,<0.8
2829
- mdformat-tables>=1,<1.1
2930
- ninja
30-
- numpy<2
31+
- numpy>=2
3132
- pandas<2.3 # [py<310]
3233
- pandas # [py>=310]
3334
- pillow

cpp/csp/python/NumpyConversions.cpp

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//this is included first so that we do include without NO_IMPORT_ARRAY defined, which is done in NumpyConversions.h
22
#include <numpy/ndarrayobject.h>
3+
#include <numpy/npy_2_compat.h>
34

45

56
#include <csp/core/Time.h>
@@ -8,21 +9,15 @@
89
#include <locale>
910
#include <codecvt>
1011

12+
static int _npy_inited = PyArray_ImportNumPyAPI();
13+
1114
namespace csp::python
1215
{
1316

14-
static bool numpy_initialized = false;
15-
1617
PyObject * valuesAtIndexToNumpy( ValueType valueType, const csp::TimeSeriesProvider * ts, int32_t startIndex, int32_t endIndex,
1718
autogen::TimeIndexPolicy startPolicy, autogen::TimeIndexPolicy endPolicy,
1819
DateTime startDt, DateTime endDt )
1920
{
20-
if( !numpy_initialized )
21-
{
22-
import_array()
23-
numpy_initialized = true;
24-
}
25-
2621
return switchCspType( ts -> type(),
2722
[ valueType, ts, startIndex, endIndex, startPolicy, endPolicy, startDt, endDt ]( auto tag )
2823
{
@@ -35,40 +30,78 @@ int64_t scalingFromNumpyDtUnit( NPY_DATETIMEUNIT base )
3530
{
3631
switch( base )
3732
{
38-
case NPY_FR_ns:
39-
return 1;
40-
case NPY_FR_us:
41-
return csp::TimeDelta::fromMicroseconds(1).asNanoseconds();
42-
case NPY_FR_ms:
43-
return csp::TimeDelta::fromMilliseconds(1).asNanoseconds();
44-
case NPY_FR_s:
45-
return csp::TimeDelta::fromSeconds(1).asNanoseconds();
46-
case NPY_FR_m:
47-
return csp::TimeDelta::fromMinutes(1).asNanoseconds();
48-
case NPY_FR_h:
49-
return csp::TimeDelta::fromHours(1).asNanoseconds();
50-
case NPY_FR_D:
51-
return csp::TimeDelta::fromDays(1).asNanoseconds();
33+
// https://github.com/numpy/numpy/blob/v2.0.0/numpy/__init__.pxd#L794
34+
case NPY_FR_Y:
35+
return csp::TimeDelta::fromDays(365).asNanoseconds();
36+
case NPY_FR_M:
37+
return csp::TimeDelta::fromDays(30).asNanoseconds();
5238
case NPY_FR_W:
5339
return csp::TimeDelta::fromDays(7).asNanoseconds();
40+
#ifdef NPY_FR_B
41+
case NPY_FR_B:
42+
return csp::TimeDelta::fromDays(5).asNanoseconds();
43+
#endif
44+
case NPY_FR_D:
45+
return csp::TimeDelta::fromDays(1).asNanoseconds();
46+
case NPY_FR_h:
47+
return csp::TimeDelta::fromHours(1).asNanoseconds();
48+
case NPY_FR_m:
49+
return csp::TimeDelta::fromMinutes(1).asNanoseconds();
50+
case NPY_FR_s:
51+
return csp::TimeDelta::fromSeconds(1).asNanoseconds();
52+
case NPY_FR_ms:
53+
return csp::TimeDelta::fromMilliseconds(1).asNanoseconds();
54+
case NPY_FR_us:
55+
return csp::TimeDelta::fromMicroseconds(1).asNanoseconds();
56+
case NPY_FR_ns:
57+
return 1;
58+
// unsupported or invalid units
59+
// enumerated here for clarity in error messages
60+
case NPY_FR_ps:
61+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_ps" );
62+
return 0;
63+
case NPY_FR_fs:
64+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_fs" );
65+
return 0;
66+
case NPY_FR_GENERIC:
67+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_generic" );
68+
return 0;
69+
case NPY_FR_ERROR:
70+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_error" );
71+
return 0;
5472
default:
55-
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value " << base );
56-
return 0; // never reached, but keeps compiler happy
73+
if(static_cast<int>(base) == 3) {
74+
// NPY_FR_B was removed in numpy 1.20
75+
return csp::TimeDelta::fromDays(5).asNanoseconds();
76+
}
77+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value " << static_cast<int32_t>(base) );
78+
return 0;
5779
}
5880
}
5981

6082
NPY_DATETIMEUNIT datetimeUnitFromDescr( PyArray_Descr* descr )
6183
{
62-
PyArray_DatetimeDTypeMetaData* dtypeMeta = (PyArray_DatetimeDTypeMetaData*)(descr -> c_metadata);
63-
PyArray_DatetimeMetaData* dtMeta = &(dtypeMeta -> meta);
64-
return dtMeta -> base;
84+
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION)
85+
{
86+
// NumPy 2.x way
87+
PyArray_DatetimeDTypeMetaData* dtypeMeta = ( PyArray_DatetimeDTypeMetaData * ) ( ( (_PyArray_LegacyDescr *) descr ) -> c_metadata );
88+
PyArray_DatetimeMetaData* dtMeta = &( dtypeMeta -> meta );
89+
return dtMeta -> base;
90+
}
91+
else
92+
{
93+
// NumPy 1.x way
94+
PyArray_DatetimeDTypeMetaData* dtypeMeta = ( PyArray_DatetimeDTypeMetaData * ) ( ( (PyArray_DescrProto *) descr ) -> c_metadata );
95+
PyArray_DatetimeMetaData* dtMeta = &( dtypeMeta -> meta );
96+
return dtMeta -> base;
97+
}
6598
}
6699

67100
static std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> wstr_converter;
68101

69102
void stringFromNumpyStr( void* data, std::string& out, char numpy_type, int elem_size_bytes )
70103
{
71-
// strings from numpy arrays are fixed width and zero filled.
104+
// strings from numpy arrays are fixed width and zero filled.
72105
// if the last char is 0, can treat as null terminated, else use full width
73106

74107
if( numpy_type == NPY_UNICODELTR)
@@ -87,7 +120,7 @@ void stringFromNumpyStr( void* data, std::string& out, char numpy_type, int elem
87120
out = wstr_converter.to_bytes( wstr );
88121
}
89122
}
90-
else if( numpy_type == NPY_STRINGLTR || numpy_type == NPY_STRINGLTR2 )
123+
else if( numpy_type == NPY_STRINGLTR )
91124
{
92125
const char * const raw_value = (const char *) data;
93126

@@ -120,11 +153,11 @@ void validateNumpyTypeVsCspType( const CspTypePtr & type, char numpy_type_char )
120153
case NPY_INTLTR:
121154
case NPY_UINTLTR:
122155
case NPY_LONGLTR:
156+
case NPY_LONGLONGLTR:
123157
if( cspType != csp::CspType::Type::INT64 )
124158
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " requires int output type" );
125159
break;
126160
case NPY_ULONGLTR:
127-
case NPY_LONGLONGLTR:
128161
case NPY_ULONGLONGLTR:
129162
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " (int type that can't cleanly convert to long) not supported" );
130163
case NPY_HALFLTR:
@@ -144,7 +177,6 @@ void validateNumpyTypeVsCspType( const CspTypePtr & type, char numpy_type_char )
144177
// everything works as object
145178
break;
146179
case NPY_STRINGLTR:
147-
case NPY_STRINGLTR2:
148180
case NPY_UNICODELTR:
149181
case NPY_CHARLTR:
150182
if( cspType != csp::CspType::Type::STRING )

cpp/csp/python/NumpyConversions.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ inline PyObject * createNumpyArray( ValueType valueType, const csp::TimeSeriesPr
204204
T lastValue;
205205
if( ts -> valid() )
206206
lastValue = ts -> lastValueTyped<T>();
207-
207+
208208
DateTime lastTime = ( ts -> valid() ? ts -> lastTime() : DateTime() );
209209
switch( valueType )
210210
{
@@ -219,7 +219,7 @@ inline PyObject * createNumpyArray( ValueType valueType, const csp::TimeSeriesPr
219219
case ValueType::TIMESTAMP_VALUE_TUPLE:
220220
{
221221
PyObject * tuple = PyTuple_New( 2 );
222-
PyTuple_SET_ITEM( tuple, 0, adjustStartAndEndTime( as_nparray( ts, ts -> timeline(), lastTime, startIndex,
222+
PyTuple_SET_ITEM( tuple, 0, adjustStartAndEndTime( as_nparray( ts, ts -> timeline(), lastTime, startIndex,
223223
endIndex, extrapolateEnd ), startPolicy, endPolicy, startDt, endDt ) );
224224
PyTuple_SET_ITEM( tuple, 1, as_nparray( ts, ts -> dataline<T>(), lastValue, startIndex, endIndex, extrapolateEnd ) );
225225
return tuple;

cpp/csp/python/NumpyInputAdapter.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ class NumpyCurveAccessor
2929
m_descr = nullptr;
3030
}
3131

32-
NumpyCurveAccessor( PyArrayObject * arr )
32+
NumpyCurveAccessor( PyArrayObject * arr )
3333
{
3434
m_nd = PyArray_NDIM( arr );
3535
if( m_nd < 2 )
3636
CSP_THROW( csp::TypeError, "NumpyCurveAccessor is inefficient for a 1-D Numpy array: use PyArray_GETPTR1 to access indexed values" );
37-
37+
3838
// Preprocess strides and dimensions
3939
npy_intp* strides = PyArray_STRIDES( arr );
4040
npy_intp* dims = PyArray_DIMS( arr );
4141
m_outerStride = strides[0];
4242
m_outerDim = dims[0];
43-
m_innerStrides = strides + 1;
43+
m_innerStrides = strides + 1;
4444
m_innerDims = dims + 1;
4545

4646
m_arr = arr;
@@ -58,7 +58,7 @@ class NumpyCurveAccessor
5858
{
5959
if( index >= m_outerDim )
6060
CSP_THROW( csp::TypeError, "Requested data index out of range in NumpyCurveAccessor" );
61-
61+
6262
// Create a view to the (n-1) dimensional array with (n-1) potentially unnatural strides
6363
/*
6464
A note on reference counting for the subarray: NewFromDescr will *steal* a reference to the type descr,
@@ -87,7 +87,7 @@ class NumpyCurveAccessor
8787
private:
8888
char* m_data;
8989
int m_nd;
90-
90+
9191
npy_intp m_outerStride;
9292
npy_intp m_outerDim;
9393
npy_intp* m_innerStrides;
@@ -103,7 +103,7 @@ class NumpyInputAdapter : public PullInputAdapter<T>
103103
using PyArrayObjectPtr = PyPtr<PyArrayObject>;
104104

105105
public:
106-
NumpyInputAdapter( Engine * engine, CspTypePtr & type, PyArrayObject * datetimes,
106+
NumpyInputAdapter( Engine * engine, CspTypePtr & type, PyArrayObject * datetimes,
107107
PyArrayObject * values ) : PullInputAdapter<T>( engine, type, PushMode::LAST_VALUE ),
108108
m_datetimes( PyArrayObjectPtr::incref( datetimes ) ),
109109
m_values( PyArrayObjectPtr::incref( values ) ),
@@ -113,7 +113,7 @@ class NumpyInputAdapter : public PullInputAdapter<T>
113113
PyArray_Descr* vals_descr = PyArray_DESCR(m_values.ptr());
114114

115115
m_size = static_cast<int>(PyArray_SIZE( datetimes ));
116-
m_elem_size = vals_descr -> elsize;
116+
m_elem_size = PyDataType_ELSIZE(vals_descr);
117117
m_val_type = vals_descr -> type;
118118

119119
char out_type = m_val_type;
@@ -123,7 +123,7 @@ class NumpyInputAdapter : public PullInputAdapter<T>
123123
m_valueAccessor = std::make_unique<NumpyCurveAccessor>( m_values.ptr() );
124124
}
125125
validateNumpyTypeVsCspType( type, out_type );
126-
126+
127127

128128
auto dt_type = dts_descr -> type;
129129
if( dt_type != NPY_DATETIMELTR && dt_type != NPY_OBJECTLTR )
@@ -166,7 +166,7 @@ class NumpyInputAdapter : public PullInputAdapter<T>
166166

167167
++m_index;
168168
}
169-
169+
170170
PullInputAdapter<T>::start( start, end );
171171
}
172172

@@ -290,6 +290,12 @@ void NumpyInputAdapter<int64_t>::setValue( int64_t & value, void * data )
290290
value = static_cast<int64_t>(*val);
291291
break;
292292
}
293+
case NPY_LONGLONGLTR:
294+
{
295+
const long long * const val = (const long long*)data;
296+
value = static_cast<int64_t>(*val);
297+
break;
298+
}
293299
default:
294300
CSP_THROW( ValueError, "NumpyInputAdapter<int64_t>::setValue sees invalid numpy type " << m_val_type );
295301
}

0 commit comments

Comments
 (0)