Skip to content

Commit 1d4afc3

Browse files
committed
Use 3.7 + patch
1 parent 4ccde87 commit 1d4afc3

File tree

5 files changed

+246
-11
lines changed

5 files changed

+246
-11
lines changed

.github/workflows/build.yml

+5-6
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ jobs:
2222
strategy:
2323
fail-fast: False
2424
matrix:
25-
# python-version: [ 3.8, 3.9, '3.10', '3.11' ]
26-
python-version: [ '3.11' ]
25+
python-version: [ '3.7' ]
2726
steps:
2827
- name: Checkout repository
2928
uses: actions/checkout@v4
@@ -62,15 +61,15 @@ jobs:
6261
with:
6362
submodules: true
6463

65-
- name: Setup Python 3.6
64+
- name: Setup Python 3.7
6665
uses: actions/setup-python@v2
6766
with:
68-
python-version: '3.6'
67+
python-version: '3.7'
6968

7069
- name: Build wheels
7170
env:
72-
# only build CPython-3.6+ and skip 32-bit builds and skip windows
73-
CIBW_BUILD: cp36-*
71+
# only build CPython-3.7+ and skip 32-bit builds and skip windows
72+
CIBW_BUILD: cp37-*
7473
CIBW_SKIP: "*-win* *-manylinux_i686 *-musllinux*"
7574
# use latest build
7675
CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux2014_x86_64

extensions.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ def build_extension(self, ext: Extension):
4343
ext_dir = Path(self.get_ext_fullpath(ext.name)).parent.absolute()
4444
_ed = ext_dir.as_posix()
4545

46+
# patch nlopt-python.i for abi3
47+
nlopt_python_i_src = ext.source_dir / "extern" / "nlopt-python.i"
48+
nlopt_python_i_dest = ext.source_dir / "extern" / "nlopt" / "src" / "swig" / "nlopt-python.i"
49+
if not nlopt_python_i_src.exists():
50+
raise RuntimeError(f"{nlopt_python_i_src} does not exist")
51+
if not nlopt_python_i_dest.exists():
52+
raise RuntimeError(f"{nlopt_python_i_dest} does not exist")
53+
shutil.copy2(nlopt_python_i_src, nlopt_python_i_dest)
54+
4655
build_dir = create_directory(Path(self.build_temp))
4756

4857
# package builds in 2 steps, first to compile the nlopt package and second to build the DLL
@@ -58,10 +67,10 @@ def build_extension(self, ext: Extension):
5867
]
5968

6069
if platform.system() == "Windows":
61-
cmd += "-DPYTHON_EXTENSION_MODULE_SUFFIX=.abi3.so"
70+
cmd.append("-DPYTHON_EXTENSION_MODULE_SUFFIX=.abi3.so")
6271
else:
63-
cmd += "-DPYTHON_EXTENSION_MODULE_SUFFIX=.abi3.pyd"
64-
abi3_flag = "-DPy_LIMITED_API=0x03060000"
72+
cmd.append("-DPYTHON_EXTENSION_MODULE_SUFFIX=.abi3.pyd")
73+
abi3_flag = "-DPy_LIMITED_API=0x03070000"
6574

6675
if platform.system() == "Windows":
6776
cmd.insert(2, f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{self.config.upper()}={_ed}")

extern/nlopt-python.i

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// -*- C++ -*-
2+
3+
//////////////////////////////////////////////////////////////////////////////
4+
// Converting NLopt/C++ exceptions to Python exceptions
5+
6+
%{
7+
#ifndef Py_LIMITED_API
8+
9+
#define ExceptionSubclass(EXCNAME, EXCDOC) \
10+
static PyTypeObject MyExc_ ## EXCNAME = { \
11+
PyVarObject_HEAD_INIT(NULL, 0) \
12+
"nlopt." # EXCNAME, \
13+
sizeof(PyBaseExceptionObject), \
14+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
15+
Py_TPFLAGS_DEFAULT, \
16+
PyDoc_STR(EXCDOC) \
17+
}; \
18+
static void init_ ## EXCNAME(PyObject *m) { \
19+
MyExc_ ## EXCNAME .tp_base = (PyTypeObject *) PyExc_Exception; \
20+
PyType_Ready(&MyExc_ ## EXCNAME); \
21+
Py_INCREF(&MyExc_ ## EXCNAME); \
22+
PyModule_AddObject(m, # EXCNAME, (PyObject *) &MyExc_ ## EXCNAME); \
23+
}
24+
25+
26+
ExceptionSubclass(ForcedStop,
27+
"Python version of nlopt::forced_stop exception.")
28+
29+
ExceptionSubclass(RoundoffLimited,
30+
"Python version of nlopt::roundoff_limited exception.")
31+
32+
#endif
33+
%}
34+
35+
%init %{
36+
#ifndef Py_LIMITED_API
37+
init_ForcedStop(m);
38+
init_RoundoffLimited(m);
39+
#endif
40+
%}
41+
%pythoncode %{
42+
try:
43+
ForcedStop = _nlopt.ForcedStop
44+
RoundoffLimited = _nlopt.RoundoffLimited
45+
except AttributeError:
46+
ForcedStop = RuntimeError
47+
RoundoffLimited = RuntimeError
48+
__version__ = str(_nlopt.version_major())+'.'+str(_nlopt.version_minor())+'.'+str(_nlopt.version_bugfix())
49+
%}
50+
51+
%typemap(throws) std::bad_alloc %{
52+
PyErr_SetString(PyExc_MemoryError, ($1).what());
53+
SWIG_fail;
54+
%}
55+
56+
%typemap(throws) nlopt::forced_stop %{
57+
if (!PyErr_Occurred())
58+
#ifndef Py_LIMITED_API
59+
PyErr_SetString((PyObject*)&MyExc_ForcedStop, "NLopt forced stop");
60+
#else
61+
PyErr_SetString(PyExc_RuntimeError, "NLopt forced stop");
62+
#endif
63+
SWIG_fail;
64+
%}
65+
66+
%typemap(throws) nlopt::roundoff_limited %{
67+
if (!PyErr_Occurred())
68+
#ifndef Py_LIMITED_API
69+
PyErr_SetString((PyObject*)&MyExc_RoundoffLimited, "NLopt roundoff-limited");
70+
#else
71+
PyErr_SetString(PyExc_RuntimeError, "NLopt roundoff-limited");
72+
#endif
73+
SWIG_fail;
74+
%}
75+
76+
//////////////////////////////////////////////////////////////////////////////
77+
78+
%{
79+
#define SWIG_FILE_WITH_INIT
80+
%}
81+
%include "numpy.i"
82+
%init %{
83+
import_array();
84+
%}
85+
%numpy_typemaps(double, NPY_DOUBLE, unsigned)
86+
87+
//////////////////////////////////////////////////////////////////////////////
88+
// numpy.i does not include maps for std::vector<double>, so I add them here,
89+
// taking advantage of the conversion functions provided by numpy.i
90+
91+
// Typemap for input arguments of type const std::vector<double> &
92+
%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Macros")
93+
const std::vector<double> &
94+
{
95+
$1 = is_array($input) || PySequence_Check($input);
96+
}
97+
%typemap(in, fragment="NumPy_Fragments")
98+
const std::vector<double> &
99+
(PyArrayObject* array=NULL, int is_new_object=0, std::vector<double> arrayv)
100+
{
101+
npy_intp size[1] = { -1 };
102+
array = obj_to_array_allow_conversion($input, NPY_DOUBLE, &is_new_object);
103+
if (!array || !require_dimensions(array, 1) ||
104+
!require_size(array, size, 1)) SWIG_fail;
105+
arrayv = std::vector<double>(array_size(array,0));
106+
$1 = &arrayv;
107+
{
108+
double *arr_data = (double *) array_data(array);
109+
int arr_i, arr_s = array_stride(array,0) / sizeof(double);
110+
int arr_sz = array_size(array,0);
111+
for (arr_i = 0; arr_i < arr_sz; ++arr_i)
112+
arrayv[arr_i] = arr_data[arr_i * arr_s];
113+
}
114+
}
115+
%typemap(freearg)
116+
const std::vector<double> &
117+
{
118+
if (is_new_object$argnum && array$argnum)
119+
{ Py_DECREF(array$argnum); }
120+
}
121+
122+
// Typemap for return values of type std::vector<double>
123+
%typemap(out, fragment="NumPy_Fragments") std::vector<double>
124+
{
125+
npy_intp sz = $1.size();
126+
$result = PyArray_SimpleNew(1, &sz, NPY_DOUBLE);
127+
std::memcpy(array_data($result), $1.empty() ? NULL : &$1[0],
128+
sizeof(double) * sz);
129+
}
130+
131+
//////////////////////////////////////////////////////////////////////////////
132+
// Wrapper for objective function callbacks
133+
134+
%{
135+
static void *free_pyfunc(void *p) { Py_DECREF((PyObject*) p); return p; }
136+
static void *dup_pyfunc(void *p) { Py_INCREF((PyObject*) p); return p; }
137+
138+
#if NPY_API_VERSION < 0x00000007
139+
# define NPY_ARRAY_C_CONTIGUOUS NPY_C_CONTIGUOUS
140+
# define NPY_ARRAY_ALIGNED NPY_ALIGNED
141+
#endif
142+
143+
static double func_python(unsigned n, const double *x, double *grad, void *f)
144+
{
145+
npy_intp sz = npy_intp(n), sz0 = 0, stride1 = sizeof(double);
146+
PyObject *xpy = PyArray_New(&PyArray_Type, 1, &sz, NPY_DOUBLE, &stride1,
147+
const_cast<double*>(x), // not NPY_WRITEABLE
148+
0, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED, NULL);
149+
PyObject *gradpy = grad
150+
? PyArray_SimpleNewFromData(1, &sz, NPY_DOUBLE, grad)
151+
: PyArray_SimpleNew(1, &sz0, NPY_DOUBLE);
152+
153+
PyObject *arglist = Py_BuildValue("OO", xpy, gradpy);
154+
PyObject *result = PyEval_CallObject((PyObject *) f, arglist);
155+
Py_DECREF(arglist);
156+
157+
Py_DECREF(gradpy);
158+
Py_DECREF(xpy);
159+
160+
double val = HUGE_VAL;
161+
if (PyErr_Occurred()) {
162+
Py_XDECREF(result);
163+
throw nlopt::forced_stop(); // just stop, don't call PyErr_Clear()
164+
}
165+
else if (result && PyFloat_Check(result)) {
166+
val = PyFloat_AsDouble(result);
167+
Py_DECREF(result);
168+
}
169+
else if (result && PyLong_Check(result)) {
170+
val = PyLong_AsUnsignedLong(result);
171+
Py_DECREF(result);
172+
}
173+
else {
174+
Py_XDECREF(result);
175+
throw std::invalid_argument("invalid result passed to nlopt");
176+
}
177+
return val;
178+
}
179+
180+
static void mfunc_python(unsigned m, double *result,
181+
unsigned n, const double *x, double *grad, void *f)
182+
{
183+
npy_intp nsz = npy_intp(n), msz = npy_intp(m);
184+
npy_intp mnsz[2] = {msz, nsz};
185+
npy_intp sz0 = 0, stride1 = sizeof(double);
186+
PyObject *xpy = PyArray_New(&PyArray_Type, 1, &nsz, NPY_DOUBLE, &stride1,
187+
const_cast<double*>(x), // not NPY_WRITEABLE
188+
0, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED, NULL);
189+
PyObject *rpy = PyArray_SimpleNewFromData(1, &msz, NPY_DOUBLE, result);
190+
PyObject *gradpy = grad
191+
? PyArray_SimpleNewFromData(2, mnsz, NPY_DOUBLE, grad)
192+
: PyArray_SimpleNew(1, &sz0, NPY_DOUBLE);
193+
194+
PyObject *arglist = Py_BuildValue("OOO", rpy, xpy, gradpy);
195+
PyObject *res = PyEval_CallObject((PyObject *) f, arglist);
196+
Py_XDECREF(res);
197+
Py_DECREF(arglist);
198+
199+
Py_DECREF(gradpy);
200+
Py_DECREF(rpy);
201+
Py_DECREF(xpy);
202+
203+
if (PyErr_Occurred()) {
204+
throw nlopt::forced_stop(); // just stop, don't call PyErr_Clear()
205+
}
206+
}
207+
%}
208+
209+
%typemap(in)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) {
210+
$1 = func_python;
211+
$2 = dup_pyfunc((void*) $input);
212+
$3 = free_pyfunc;
213+
$4 = dup_pyfunc;
214+
}
215+
%typecheck(SWIG_TYPECHECK_POINTER)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) {
216+
$1 = PyCallable_Check($input);
217+
}
218+
219+
%typemap(in)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) {
220+
$1 = mfunc_python;
221+
$2 = dup_pyfunc((void*) $input);
222+
$3 = free_pyfunc;
223+
$4 = dup_pyfunc;
224+
}
225+
%typecheck(SWIG_TYPECHECK_POINTER)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) {
226+
$1 = PyCallable_Check($input);
227+
}

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ classifiers =
3434
Topic :: Scientific/Engineering
3535

3636
[options]
37-
python_requires = >= 3.8
37+
python_requires = >= 3.7

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def get_tag(self):
2525

2626
if python.startswith("cp"):
2727
# on CPython, our wheels are abi3 and compatible back to 3.6
28-
return "cp36", "abi3", plat
28+
return "cp37", "abi3", plat
2929

3030
return python, abi, plat
3131

0 commit comments

Comments
 (0)