From 3e89750d16831f5378bd2e08bb62585c92db2365 Mon Sep 17 00:00:00 2001 From: Michael Greminger Date: Thu, 7 Jan 2021 12:22:38 -0600 Subject: [PATCH] PKG Add nlopt package (#1034) --- docs/building_from_sources.md | 2 + packages/nlopt/meta.yaml | 23 +++++ packages/nlopt/src/setup.py | 159 ++++++++++++++++++++++++++++++++++ packages/nlopt/test_nlopt.py | 50 +++++++++++ 4 files changed, 234 insertions(+) create mode 100644 packages/nlopt/meta.yaml create mode 100644 packages/nlopt/src/setup.py create mode 100644 packages/nlopt/test_nlopt.py diff --git a/docs/building_from_sources.md b/docs/building_from_sources.md index 58baf8f2e..da483fac2 100644 --- a/docs/building_from_sources.md +++ b/docs/building_from_sources.md @@ -19,6 +19,7 @@ Additional build prerequisites are: - PyYAML - FreeType 2 development libraries to compile Matplotlib. - Cython to compile SciPy +- SWIG to compile NLopt - gfortran (GNU Fortran 95 compiler) - [f2c](http://www.netlib.org/f2c/) - [ccache](https://ccache.samba.org) (optional) *highly* recommended for much faster rebuilds. @@ -30,6 +31,7 @@ On Mac, you will also need: - coreutils for md5sum and other essential Unix utilities (`brew install coreutils`) - cmake (`brew install cmake`) - Cython to compile SciPy (`brew install cython`) +- SWIG to compile NLopt (`brew install swig`) - pkg-config (`brew install pkg-config`) - openssl (`brew install openssl`) - gfortran (`brew cask install gfortran`) diff --git a/packages/nlopt/meta.yaml b/packages/nlopt/meta.yaml new file mode 100644 index 000000000..fcf8e8b5f --- /dev/null +++ b/packages/nlopt/meta.yaml @@ -0,0 +1,23 @@ +package: + name: nlopt + version: 2.7.0 + +source: + url: https://github.com/stevengj/nlopt/archive/v2.7.0.tar.gz + sha256: b881cc2a5face5139f1c5a30caf26b7d3cb43d69d5e423c9d78392f99844499f + + extras: + - + - src/setup.py + - ./setup.py + +requirements: + run: + - numpy + +build: + cxxflags: -std=c++11 + +test: + imports: + - nlopt diff --git a/packages/nlopt/src/setup.py b/packages/nlopt/src/setup.py new file mode 100644 index 000000000..3ec9b9026 --- /dev/null +++ b/packages/nlopt/src/setup.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +import os +import re +from pathlib import Path +from subprocess import check_call +from setuptools import setup, Extension +from setuptools.command.build_py import build_py +from numpy import get_include + + +def create_pkg_directory(): + with open("CMakeLists.txt") as f: + content = f.read() + version = [] + for s in ("MAJOR", "MINOR", "BUGFIX"): + m = re.search(f"NLOPT_{s}_VERSION *['\"](.+)['\"]", content) + version.append(m.group(1)) + version = ".".join(version) + + pkg_folder = Path("nlopt") + pkg_folder.mkdir(exist_ok=True) + with (pkg_folder / "__init__.py").open("w") as f: + f.write( + f""" +from .nlopt import * + +__version__ = '{version}' + """.strip() + + "\n" + ) + + return version + + +def configure_with_cmake(): + # There are 2 header files that are created by cmake (nlopt_config.h + # and nlopt.hpp) + # cmake is used to configure only, actual compile will be handled + # by setuptools build_ext + cmd = [ + "emcmake", + "cmake", + "-LAH", + "-DNLOPT_GUILE=OFF", + "-DNLOPT_MATLAB=OFF", + "-DNLOPT_OCTAVE=OFF", + ".", + ] + + check_call(cmd, env=os.environ) + + # Need to generate nlopt.hpp + cmd = [ + "emcmake", + "cmake", + "-DAPI_SOURCE_DIR=./src/api", + "-P", + "./cmake/generate-cpp.cmake", + ] + + check_call(cmd, env=os.environ) + + +version = create_pkg_directory() + + +class build_py_after_build_ext(build_py): + def run(self): + configure_with_cmake() + self.run_command("build_ext") + return super().run() + + +setup( + name="nlopt", + version=version, + packages=["nlopt"], + install_requires=["numpy >=1.14"], + ext_modules=[ + Extension( + "nlopt._nlopt", + [ + "src/algs/direct/DIRect.c", + "src/algs/direct/direct_wrap.c", + "src/algs/direct/DIRserial.c", + "src/algs/direct/DIRsubrout.c", + "src/algs/cdirect/cdirect.c", + "src/algs/cdirect/hybrid.c", + "src/algs/praxis/praxis.c", + "src/algs/luksan/plis.c", + "src/algs/luksan/plip.c", + "src/algs/luksan/pnet.c", + "src/algs/luksan/mssubs.c", + "src/algs/luksan/pssubs.c", + "src/algs/crs/crs.c", + "src/algs/mlsl/mlsl.c", + "src/algs/mma/mma.c", + "src/algs/mma/ccsa_quadratic.c", + "src/algs/cobyla/cobyla.c", + "src/algs/newuoa/newuoa.c", + "src/algs/neldermead/nldrmd.c", + "src/algs/neldermead/sbplx.c", + "src/algs/auglag/auglag.c", + "src/algs/bobyqa/bobyqa.c", + "src/algs/isres/isres.c", + "src/algs/slsqp/slsqp.c", + "src/algs/esch/esch.c", + "src/api/general.c", + "src/api/options.c", + "src/api/optimize.c", + "src/api/deprecated.c", + "src/api/f77api.c", + "src/util/mt19937ar.c", + "src/util/sobolseq.c", + "src/util/timer.c", + "src/util/stop.c", + "src/util/redblack.c", + "src/util/qsort_r.c", + "src/util/rescale.c", + "src/algs/stogo/global.cc", + "src/algs/stogo/linalg.cc", + "src/algs/stogo/local.cc", + "src/algs/stogo/stogo.cc", + "src/algs/stogo/tools.cc", + "src/algs/ags/evolvent.cc", + "src/algs/ags/solver.cc", + "src/algs/ags/local_optimizer.cc", + "src/algs/ags/ags.cc", + "src/swig/nlopt.i", + ], + include_dirs=[ + "./src/util", + "./", + "./src/api", + "./src/algs/praxis", + "./src/algs/direct", + "./src/algs/stogo", + "./src/algs/ags", + "./src/algs/cdirect", + "./src/algs/luksan", + "./src/algs/crs", + "./src/algs/mlsl", + "./src/algs/mma", + "./src/algs/cobyla", + "./src/algs/newuoa", + "./src/algs/neldermead", + "./src/algs/auglag", + "./src/algs/bobyqa", + "./src/algs/isres", + "./src/algs/esch", + "./src/algs/slsqp", + get_include(), + ], + swig_opts=["-c++", "-interface", "_nlopt", "-outdir", "./nlopt"], + ) + ], + zip_safe=False, + cmdclass={"build_py": build_py_after_build_ext}, +) diff --git a/packages/nlopt/test_nlopt.py b/packages/nlopt/test_nlopt.py new file mode 100644 index 000000000..7d683703e --- /dev/null +++ b/packages/nlopt/test_nlopt.py @@ -0,0 +1,50 @@ +def test_nlopt(selenium): + selenium.load_package("nlopt") + assert selenium.run( + """ +import numpy as np +import nlopt + +# objective function +def f(x, grad): + x0 = x[0] + x1 = x[1] + y = 67.8306620138889-13.5689721666667*x0-3.83269458333333*x1+\ + 0.720841066666667*x0**2+0.3427605*x0*x1+\ + 0.0640322916666664*x1**2 + + grad[0] = 1.44168213333333*x0 + 0.3427605*x1 - 13.5689721666667 + grad[1] = 0.3427605*x0 + 0.128064583333333*x1 - 3.83269458333333 + + return y + +# inequality constraint (constrained to be <= 0) +def h(x, grad): + x0 = x[0] + x1 = x[1] + z = -3.72589930555515+128.965158333333*x0+0.341479166666643*x1-\ + 0.19642666666667*x0**2+2.78692500000002*x0*x1-\ + 0.0000104166666686543*x1**2-468.897287036862 + + grad[0] = -0.39285333333334*x0 + 2.78692500000002*x1 + 128.965158333333 + grad[1] = 2.78692500000002*x0 - 2.08333333373086e-5*x1 + 0.341479166666643 + + return z + +opt = nlopt.opt(nlopt.LD_SLSQP, 2) +opt.set_min_objective(f) + +opt.set_lower_bounds(np.array([2.5, 7])) +opt.set_upper_bounds(np.array([7.5, 15])) + +opt.add_inequality_constraint(h) + +opt.set_ftol_rel(1.0e-6) + +x0 = np.array([5, 11]) + +xopt = opt.optimize(x0) + +np.linalg.norm(xopt - np.array([2.746310775, 15.0])) < 1e-7 +""" + )