Skip to content

Commit d655460

Browse files
authored
Merge pull request #328 from Point72/tkp/np2
NumPy 2.0 build support
2 parents 8108401 + bff713f commit d655460

19 files changed

+142
-94
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
@@ -637,9 +637,10 @@ jobs:
637637
python-version:
638638
- 3.9
639639
package:
640-
- "sqlalchemy<2"
641-
- "numpy==1.22.4" # Min supported version of pandas 2.2
642-
- "perspective-python<3"
640+
- sqlalchemy<2
641+
- perspective-python<3
642+
# Min supported version of pandas 2.2
643+
- numpy==1.22.4
643644

644645
runs-on: ${{ matrix.os }}
645646

@@ -648,6 +649,7 @@ jobs:
648649
uses: actions/checkout@v5
649650
with:
650651
submodules: recursive
652+
fetch-depth: 0
651653

652654
- name: Set up Python ${{ matrix.python-version }}
653655
uses: ./.github/actions/setup-python
@@ -671,13 +673,15 @@ jobs:
671673
python -m pip install -U *manylinux*.whl
672674
python -m pip install -U --no-deps *manylinux*.whl --target .
673675
674-
- name: Install package - ${{ matrix.package }}
676+
- name: Install packages - ${{ matrix.package }}
675677
run: python -m pip install -U "${{ matrix.package }}"
676678

679+
# Run tests to check dependencies
677680
- name: Python Test Steps
678681
run: make test TEST_ARGS="-k TestDBReader"
679682
if: ${{ contains( matrix.package, 'sqlalchemy' )}}
680683

684+
# For e.g. numpy dep changes, run all tests
681685
- name: Python Test Steps
682686
run: make test
683687
if: ${{ contains( matrix.package, 'numpy' )}}

.github/workflows/conda.yml

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

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

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

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: 68 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,21 @@
89
#include <locale>
910
#include <codecvt>
1011

11-
namespace csp::python
12+
static void * init_nparray()
1213
{
14+
csp::python::AcquireGIL gil;
15+
import_array();
16+
return nullptr;
17+
}
18+
static void * s_init_array = init_nparray();
1319

14-
static bool numpy_initialized = false;
20+
namespace csp::python
21+
{
1522

1623
PyObject * valuesAtIndexToNumpy( ValueType valueType, const csp::TimeSeriesProvider * ts, int32_t startIndex, int32_t endIndex,
1724
autogen::TimeIndexPolicy startPolicy, autogen::TimeIndexPolicy endPolicy,
1825
DateTime startDt, DateTime endDt )
1926
{
20-
if( !numpy_initialized )
21-
{
22-
import_array()
23-
numpy_initialized = true;
24-
}
25-
2627
return switchCspType( ts -> type(),
2728
[ valueType, ts, startIndex, endIndex, startPolicy, endPolicy, startDt, endDt ]( auto tag )
2829
{
@@ -35,40 +36,77 @@ int64_t scalingFromNumpyDtUnit( NPY_DATETIMEUNIT base )
3536
{
3637
switch( base )
3738
{
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();
39+
// https://github.com/numpy/numpy/blob/v2.0.0/numpy/__init__.pxd#L794
40+
case NPY_FR_Y:
41+
return csp::TimeDelta::fromDays(365).asNanoseconds();
42+
case NPY_FR_M:
43+
return csp::TimeDelta::fromDays(30).asNanoseconds();
5244
case NPY_FR_W:
5345
return csp::TimeDelta::fromDays(7).asNanoseconds();
46+
#ifdef NPY_FR_B
47+
case NPY_FR_B:
48+
return csp::TimeDelta::fromDays(5).asNanoseconds();
49+
#endif
50+
case NPY_FR_D:
51+
return csp::TimeDelta::fromDays(1).asNanoseconds();
52+
case NPY_FR_h:
53+
return csp::TimeDelta::fromHours(1).asNanoseconds();
54+
case NPY_FR_m:
55+
return csp::TimeDelta::fromMinutes(1).asNanoseconds();
56+
case NPY_FR_s:
57+
return csp::TimeDelta::fromSeconds(1).asNanoseconds();
58+
case NPY_FR_ms:
59+
return csp::TimeDelta::fromMilliseconds(1).asNanoseconds();
60+
case NPY_FR_us:
61+
return csp::TimeDelta::fromMicroseconds(1).asNanoseconds();
62+
case NPY_FR_ns:
63+
return 1;
64+
// unsupported or invalid units
65+
// enumerated here for clarity in error messages
66+
case NPY_FR_ps:
67+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_ps" );
68+
return 0;
69+
case NPY_FR_fs:
70+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_fs" );
71+
return 0;
72+
case NPY_FR_GENERIC:
73+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_generic" );
74+
return 0;
75+
case NPY_FR_ERROR:
76+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value NPY_FR_error" );
77+
return 0;
5478
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
79+
if(static_cast<int>(base) == 3) {
80+
// NPY_FR_B was removed in numpy 1.20
81+
return csp::TimeDelta::fromDays(5).asNanoseconds();
82+
}
83+
CSP_THROW(csp::NotImplemented, "datetime resolution not supported or invalid - saw NPY_DATETIMEUNIT value " << static_cast<int32_t>(base) );
84+
return 0;
5785
}
5886
}
5987

6088
NPY_DATETIMEUNIT datetimeUnitFromDescr( PyArray_Descr* descr )
6189
{
62-
PyArray_DatetimeDTypeMetaData* dtypeMeta = (PyArray_DatetimeDTypeMetaData*)(descr -> c_metadata);
63-
PyArray_DatetimeMetaData* dtMeta = &(dtypeMeta -> meta);
90+
PyArray_DatetimeDTypeMetaData* dtypeMeta;
91+
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION)
92+
{
93+
// NumPy 2.x way
94+
dtypeMeta = ( PyArray_DatetimeDTypeMetaData * ) ( ( (_PyArray_LegacyDescr *) descr ) -> c_metadata );
95+
}
96+
else
97+
{
98+
// NumPy 1.x way
99+
dtypeMeta = ( PyArray_DatetimeDTypeMetaData * ) ( ( (PyArray_DescrProto *) descr ) -> c_metadata );
100+
}
101+
PyArray_DatetimeMetaData* dtMeta = &( dtypeMeta -> meta );
64102
return dtMeta -> base;
65103
}
66104

67105
static std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> wstr_converter;
68106

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

74112
if( numpy_type == NPY_UNICODELTR)
@@ -87,7 +125,7 @@ void stringFromNumpyStr( void* data, std::string& out, char numpy_type, int elem
87125
out = wstr_converter.to_bytes( wstr );
88126
}
89127
}
90-
else if( numpy_type == NPY_STRINGLTR || numpy_type == NPY_STRINGLTR2 )
128+
else if( numpy_type == NPY_STRINGLTR )
91129
{
92130
const char * const raw_value = (const char *) data;
93131

@@ -120,13 +158,13 @@ void validateNumpyTypeVsCspType( const CspTypePtr & type, char numpy_type_char )
120158
case NPY_INTLTR:
121159
case NPY_UINTLTR:
122160
case NPY_LONGLTR:
161+
case NPY_LONGLONGLTR:
123162
if( cspType != csp::CspType::Type::INT64 )
124163
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " requires int output type" );
125164
break;
126165
case NPY_ULONGLTR:
127-
case NPY_LONGLONGLTR:
128166
case NPY_ULONGLONGLTR:
129-
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " (int type that can't cleanly convert to long) not supported" );
167+
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " (int type that can't cleanly convert to int64) not supported" );
130168
case NPY_HALFLTR:
131169
CSP_THROW( ValueError, "numpy type " << numpy_type_char << " (numpy half float) not supported" );
132170
case NPY_FLOATLTR:
@@ -144,7 +182,6 @@ void validateNumpyTypeVsCspType( const CspTypePtr & type, char numpy_type_char )
144182
// everything works as object
145183
break;
146184
case NPY_STRINGLTR:
147-
case NPY_STRINGLTR2:
148185
case NPY_UNICODELTR:
149186
case NPY_CHARLTR:
150187
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;

0 commit comments

Comments
 (0)