From 910eb9e76daf589d48324e2f7dcb1226e50b2541 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 17:51:48 -0600 Subject: [PATCH 01/27] Modify inner meson.build files --- pyoptsparse/pyALPSO/meson.build | 13 ------- pyoptsparse/pyCONMIN/meson.build | 22 ++--------- pyoptsparse/pyIPOPT/meson.build | 60 ++++++++++++++++++++++++----- pyoptsparse/pyNLPQLP/meson.build | 14 +------ pyoptsparse/pyNSGA2/meson.build | 65 ++++++++++++++------------------ pyoptsparse/pyPSQP/meson.build | 22 ++--------- pyoptsparse/pyParOpt/meson.build | 10 ----- pyoptsparse/pySLSQP/meson.build | 22 ++--------- pyoptsparse/pySNOPT/meson.build | 18 +-------- 9 files changed, 93 insertions(+), 153 deletions(-) delete mode 100644 pyoptsparse/pyALPSO/meson.build delete mode 100644 pyoptsparse/pyParOpt/meson.build diff --git a/pyoptsparse/pyALPSO/meson.build b/pyoptsparse/pyALPSO/meson.build deleted file mode 100644 index 3a967556..00000000 --- a/pyoptsparse/pyALPSO/meson.build +++ /dev/null @@ -1,13 +0,0 @@ -python_sources = [ - '__init__.py', - 'alpso.py', - 'alpso_ext.py', - 'pyALPSO.py', - 'LICENSE' -] - -py3_target.install_sources( - python_sources, - pure: true, - subdir: 'pyoptsparse/pyALPSO' -) diff --git a/pyoptsparse/pyCONMIN/meson.build b/pyoptsparse/pyCONMIN/meson.build index a2eb7e5c..9d9b905f 100644 --- a/pyoptsparse/pyCONMIN/meson.build +++ b/pyoptsparse/pyCONMIN/meson.build @@ -2,11 +2,11 @@ conmin_source = custom_target('conminmodule.c', input : ['source/f2py/conmin.pyf', ], output : ['conminmodule.c', 'conmin-f2pywrappers.f'], - command: [py3_command, '-m', 'numpy.f2py', '@INPUT@', + command: [py3, '-m', 'numpy.f2py', '@INPUT@', '--lower', '--build-dir', 'pyoptsparse/pyCONMIN'] ) -py3_target.extension_module('conmin', +py3.extension_module('conmin', 'source/openunit.f', 'source/cnmn00.f', 'source/cnmn01.f', @@ -21,21 +21,7 @@ py3_target.extension_module('conmin', 'source/conmin.f', 'source/closeunit.f', conmin_source, - fortranobject_c, - include_directories: [inc_np, inc_f2py], - dependencies : py3_dep, + dependencies: [fortranobject_dep], subdir: 'pyoptsparse/pyCONMIN', - install : false, + install: true, build_rpath: '') - -#python_sources = [ -# '__init__.py', -# 'pyCONMIN.py', -# 'LICENSE' -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyCONMIN' -#) diff --git a/pyoptsparse/pyIPOPT/meson.build b/pyoptsparse/pyIPOPT/meson.build index 7ea72e61..a43e5b64 100644 --- a/pyoptsparse/pyIPOPT/meson.build +++ b/pyoptsparse/pyIPOPT/meson.build @@ -1,10 +1,50 @@ -# python_sources = [ -# '__init__.py', -# 'pyIPOPT.py', -# ] - -# py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyIPOPT' -# ) +fs = import('fs') + +if get_option('ipopt_dir') != '' or fs.is_dir('Ipopt') + + ipopt_dir = '' + + if get_option('ipopt_dir') != '' + ipopt_dir = get_option('ipopt_dir') + elif fs.is_dir('Ipopt') + ipopt_dir = fs.is_dir('Ipopt') + endif + + ipopt_lib = [] + ipopt_idir = '' + + # Ipopt installs differently on some systems (i.e. Fedora) + if fs.is_dir(ipopt_dir / 'lib') + ipopt_lib = [ipopt_dir / 'lib'] + elif fs.is_dir(ipopt_dir / 'lib64') + ipopt_lib = [ipopt_dir / 'lib64'] + endif + + + if fs.is_dir(ipopt_dir / 'include' / 'coin-or') + ipopt_idir = ipopt_dir / 'include' / 'coin-or' + elif fs.is_dir(ipopt_dir / 'include' / 'coin') + ipopt_idir = ipopt_dir / 'include' / 'coin' + endif + + ipopt_dep = cc.find_library('ipopt-3', required: false, dirs: ipopt_lib) # only relevant on windows + if not ipopt_dep.found() + ipopt_dep = cc.find_library('ipopt', required: true, dirs: ipopt_lib) + endif + + if fs.is_dir(ipopt_idir) + ipopt_inc = include_directories(ipopt_idir) + else + error('IPOPT include directory not found: ', ipopt_dir) + endif + + py3_target.extension_module('pyipoptcore', + 'src/callback.c', + 'src/pyipoptcoremodule.c', + include_directories: [inc_np, 'src', ipopt_inc], + dependencies : [py3_dep, ipopt_dep], + subdir: 'pyoptsparse/pyIPOPT', + link_language: 'c', + install: true) +endif + diff --git a/pyoptsparse/pyNLPQLP/meson.build b/pyoptsparse/pyNLPQLP/meson.build index 352d9bc6..862af028 100644 --- a/pyoptsparse/pyNLPQLP/meson.build +++ b/pyoptsparse/pyNLPQLP/meson.build @@ -24,18 +24,6 @@ if HAS_NLPQLP include_directories: [inc_np, inc_f2py], dependencies : py3_dep, subdir: 'pyoptsparse/pyNLPQLP', - install : false + install: true ) endif - -#python_sources = [ -# '__init__.py', -# 'pyNLPQLP.py', -# 'LICENSE' -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyNLPQLP' -#) diff --git a/pyoptsparse/pyNSGA2/meson.build b/pyoptsparse/pyNSGA2/meson.build index 48faac1a..84e5c6cb 100644 --- a/pyoptsparse/pyNSGA2/meson.build +++ b/pyoptsparse/pyNSGA2/meson.build @@ -6,40 +6,31 @@ nsga2_source = custom_target('nsga2_wrap.c', command: ['swig', '-o', 'pyoptsparse/pyNSGA2/nsga2_wrap.c', '-python', '-interface', 'nsga2', '@INPUT@'] ) -py3_target.extension_module('nsga2', - 'source/allocate.c', - 'source/auxiliary.c', - 'source/crossover.c', - 'source/crowddist.c', - 'source/decode.c', - 'source/dominance.c', - 'source/eval.c', - 'source/fillnds.c', - 'source/initialize.c', - 'source/list.c', - 'source/merge.c', - 'source/mutation.c', - 'source/nsga2.c', - 'source/rand.c', - 'source/rank.c', - 'source/report.c', - 'source/sort.c', - 'source/tourselect.c', - nsga2_source, - include_directories: 'source', - dependencies : py3_dep, - subdir: 'pyoptsparse/pyNSGA2', - link_language: 'c', - install : false) - -#python_sources = [ -# '__init__.py', -# 'pyNSGA2.py', -# 'LICENSE' -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyNSGA2' -#) + py3.extension_module('nsga2', + 'source/allocate.c', + 'source/auxiliary.c', + 'source/crossover.c', + 'source/crowddist.c', + 'source/decode.c', + 'source/dominance.c', + 'source/eval.c', + 'source/fillnds.c', + 'source/initialize.c', + 'source/list.c', + 'source/merge.c', + 'source/mutation.c', + 'source/nsga2.c', + 'source/rand.c', + 'source/rank.c', + 'source/report.c', + 'source/sort.c', + 'source/tourselect.c', + nsga2_source, + include_directories: 'source', + dependencies : py3_dep, + subdir: 'pyoptsparse/pyNSGA2', + link_language: 'c', + install: true) +else + message('SWIG was not found, therefore NSGA2 will not be built.') +endif diff --git a/pyoptsparse/pyPSQP/meson.build b/pyoptsparse/pyPSQP/meson.build index 47bc59b0..1366f6d2 100644 --- a/pyoptsparse/pyPSQP/meson.build +++ b/pyoptsparse/pyPSQP/meson.build @@ -1,11 +1,11 @@ psqp_source = custom_target('psqpmodule.c', input : ['source/f2py/psqp.pyf'], output : ['psqpmodule.c', 'psqp-f2pywrappers.f'], - command: [py3_command, '-m', 'numpy.f2py', '@INPUT@', + command: [py3, '-m', 'numpy.f2py', '@INPUT@', '--lower', '--build-dir', 'pyoptsparse/pyPSQP', '--no-wrap-functions'] ) -py3_target.extension_module('psqp', +py3.extension_module('psqp', 'source/closeunit.f', 'source/mqsubs.f', 'source/openunit.f', @@ -13,20 +13,6 @@ py3_target.extension_module('psqp', 'source/psqp.f', 'source/psqp_wrap.f90', psqp_source, - fortranobject_c, - include_directories: [inc_np, inc_f2py], - dependencies : py3_dep, + dependencies: [fortranobject_dep], subdir: 'pyoptsparse/pyPSQP', - install : false) - -#python_sources = [ -# '__init__.py', -# 'pyPSQP.py', -# 'LICENSE' -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyPSQP' -#) + install: false) diff --git a/pyoptsparse/pyParOpt/meson.build b/pyoptsparse/pyParOpt/meson.build deleted file mode 100644 index 4500d203..00000000 --- a/pyoptsparse/pyParOpt/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -python_sources = [ - '__init__.py', - 'ParOpt.py', -] - -py3_target.install_sources( - python_sources, - pure: true, - subdir: 'pyoptsparse/pyParOpt' -) diff --git a/pyoptsparse/pySLSQP/meson.build b/pyoptsparse/pySLSQP/meson.build index e898636b..29717b7c 100644 --- a/pyoptsparse/pySLSQP/meson.build +++ b/pyoptsparse/pySLSQP/meson.build @@ -1,11 +1,11 @@ slsqp_source = custom_target('slsqpmodule.c', input : ['source/f2py/slsqp.pyf'], output : ['slsqpmodule.c'], - command: [py3_command, '-m', 'numpy.f2py', '@INPUT@', + command: [py3, '-m', 'numpy.f2py', '@INPUT@', '--lower', '--build-dir', 'pyoptsparse/pySLSQP'] ) -py3_target.extension_module('slsqp', +py3.extension_module('slsqp', 'source/closeunit.f', 'source/daxpy.f', 'source/dcopy.f', @@ -24,20 +24,6 @@ py3_target.extension_module('slsqp', 'source/slsqp.f', 'source/slsqpb.f', slsqp_source, - fortranobject_c, - include_directories: [inc_np, inc_f2py], - dependencies : py3_dep, + dependencies: [fortranobject_dep], subdir: 'pyoptsparse/pySLSQP', - install : false) - -#python_sources = [ -# '__init__.py', -# 'pySLSQP.py', -# 'LICENSE' -#] - -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pySLSQP' -#) + install: true) diff --git a/pyoptsparse/pySNOPT/meson.build b/pyoptsparse/pySNOPT/meson.build index 61edfdb0..f9e7aace 100644 --- a/pyoptsparse/pySNOPT/meson.build +++ b/pyoptsparse/pySNOPT/meson.build @@ -20,24 +20,10 @@ if HAS_SNOPT py3_target.extension_module('snopt', snopt_source, - fortranobject_c, snopt_source_files, - include_directories: [inc_np, inc_f2py], - dependencies : py3_dep, + dependencies : [fortranobject_dep], subdir: 'pyoptsparse/pySNOPT', - install : false, + install: true, fortran_args: '-ffixed-line-length-80' ) endif - -#python_sources = [ -# '__init__.py', -# 'pySNOPT.py', -# 'LICENSE' -#] - -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pySNOPT' -#) From 936256f6dec736fec7cfe798c39c9bbcb0435c65 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 17:52:11 -0600 Subject: [PATCH 02/27] Modify outer meson.build --- meson.build | 58 ++++++++++++------ pyoptsparse/meson.build | 132 +++++++++++++++------------------------- 2 files changed, 88 insertions(+), 102 deletions(-) diff --git a/meson.build b/meson.build index a2219cd3..0e283f09 100644 --- a/meson.build +++ b/meson.build @@ -1,24 +1,34 @@ -# Much of this is from SciPy - project( 'pyoptsparse', 'c', 'cpp', -# unnecessary metadata commented out until Meson supports PEP517 and installation with pip -# version: 'x.x.x', -# license: 'GPL-3', - meson_version: '>= 0.60', + meson_version: '>= 0.64', default_options: [ 'buildtype=debugoptimized', - 'c_std=c99', - 'cpp_std=c++14', + 'b_ndebug=if-release', + 'c_std=c17', + 'cpp_std=c++17', ], ) -fortranobject_c = '../fortranobject.c' +# from scipy +# + +install_subdir('pyoptsparse', install_dir: py3.get_install_dir()) subdir('pyoptsparse') + diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 7f1d811b..8b5f1fd3 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -1,97 +1,63 @@ -# NumPy include directory - needed in all submodules -incdir_numpy = get_option('incdir_numpy') -if incdir_numpy == '' - incdir_numpy = run_command(py3_target, - [ - '-c', - 'import os; os.chdir(".."); import numpy; print(numpy.get_include())' - ], - check: true - ).stdout().strip() +# from scipy +# subdir('pySNOPT') subdir('pySLSQP') subdir('pyCONMIN') subdir('pyNLPQLP') -subdir('pyNSGA2') +# subdir('pyNSGA2') # mostly working with issues, C implicit warning subdir('pyPSQP') -#subdir('pyALPSO') -#subdir('pyParOpt') -#subdir('postprocessing') - -# test imports -# envdata = environment() -# python_paths = [join_paths(meson.current_build_dir(), '..')] -# envdata.prepend('PYTHONPATH', python_paths) - -# progs = [['SLSQP', 'pySLSQP', 'slsqp'], -# ['CONMIN', 'pyCONMIN', 'conmin'], -# ['PSQP', 'pyPSQP', 'psqp'], -# ['NSGA2', 'pyNSGA2', 'nsga2']] - -# foreach p : progs -# import_command = 'from pyoptsparse.' + p[1] + ' import '+p[2]+'; print('+p[2]+'.__file__)' -# test( -# 'import test for '+p[0], -# py3_command, -# args: ['-c', import_command], -# env: envdata -# ) -# endforeach From 4c978a9afe291316196c3289312da45b77cc82b6 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:03:38 -0600 Subject: [PATCH 03/27] setup.py -> pyproject.toml --- pyproject.toml | 41 ++++++++++++++- setup.py | 135 ------------------------------------------------- 2 files changed, 39 insertions(+), 137 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index ce02acea..7bebb788 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,40 @@ [build-system] -requires = ["setuptools>=42", "meson>=0.60.0", "ninja", "numpy>=2.0", "swig"] -build-backend = "setuptools.build_meta" +requires = ["meson-python", "setuptools", "numpy>=2.0", "swig"] +build-backend = "mesonpy" + +[project] +name = "pyoptsparse" +description = "Python package for formulating and solving nonlinear constrained optimization problems" +version = "0.0.0" +readme = "README.md" +license = {file = "LICENSE"} +requires-python = ">=3.9" +dependencies = [ + "packaging", + "sqlitedict>=1.6", + "numpy>=1.21", + "scipy>=1.7", + "mdolab-baseclasses>=1.3.1" +] + +[project.optional-dependencies] +optview = [ + "dash", + "plotly", + "matplotlib" +] +docs = [ + "sphinx", + "sphinx_rtd_theme" +] +testing = [ + "testflo>=1.4.5", + "parameterized" +] + +[project.urls] +Homepage = "https://github.com/mdolab/pyoptsparse" + +[project.scripts] +optview = "pyoptsparse.postprocessing.OptView:main" +optview_dash = "pyoptsparse.postprocessing.OptView_dash:main" diff --git a/setup.py b/setup.py deleted file mode 100644 index c100711e..00000000 --- a/setup.py +++ /dev/null @@ -1,135 +0,0 @@ -import os -import re -import shutil -import setuptools -import subprocess - - -def run_meson_build(): - prefix = os.path.join(os.getcwd(), staging_dir) - purelibdir = "." - - # check if meson extra args are specified - meson_args = "" - if "MESON_ARGS" in os.environ: - meson_args = os.environ["MESON_ARGS"] - - # configure - meson_path = shutil.which("meson") - meson_call = ( - f"{meson_path} setup {staging_dir} --prefix={prefix} " - + f"-Dpython.purelibdir={purelibdir} -Dpython.platlibdir={purelibdir} {meson_args}" - ) - sysargs = meson_call.split(" ") - sysargs = [arg for arg in sysargs if arg != ""] - p1 = subprocess.run(sysargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - print(p1.stdout.decode()) - setup_log = os.path.join(staging_dir, "setup.log") - with open(setup_log, "wb") as f: - f.write(p1.stdout) - if p1.returncode != 0: - raise OSError(sysargs, f"The meson setup command failed! Check the log at {setup_log} for more information.") - - # build - meson_call = f"{meson_path} compile -C {staging_dir}" - sysargs = meson_call.split(" ") - p2 = subprocess.run(sysargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - print(p2.stdout.decode()) - compile_log = os.path.join(staging_dir, "compile.log") - with open(compile_log, "wb") as f: - f.write(p2.stdout) - if p2.returncode != 0: - raise OSError( - sysargs, f"The meson compile command failed! Check the log at {compile_log} for more information." - ) - - -def copy_shared_libraries(): - build_path = os.path.join(staging_dir, "pyoptsparse") - for root, _dirs, files in os.walk(build_path): - for file in files: - # move pyoptsparse to just under staging_dir - if file.endswith((".so", ".lib", ".pyd", ".pdb", ".dylib")): - if ".so.p" in root or ".pyd.p" in root: # excludes intermediate object files - continue - file_path = os.path.join(root, file) - new_path = str(file_path) - match = re.search(staging_dir, new_path) - new_path = new_path[match.span()[1] + 1 :] - print(f"Copying build file {file_path} -> {new_path}") - shutil.copy(file_path, new_path) - - -if __name__ == "__main__": - # This is where the meson build system will install to, it is then - # used as the sources for setuptools - staging_dir = "meson_build" - - # this keeps the meson build system from running more than once - if "dist" not in str(os.path.abspath(__file__)) and not os.path.isdir(staging_dir): - cwd = os.getcwd() - run_meson_build() - os.chdir(cwd) - copy_shared_libraries() - - docs_require = "" - req_txt = os.path.join("doc", "requirements.txt") - if os.path.isfile(req_txt): - with open(req_txt) as f: - docs_require = f.read().splitlines() - - init_file = os.path.join("pyoptsparse", "__init__.py") - __version__ = re.findall( - r"""__version__ = ["']+([0-9\.]*)["']+""", - open(init_file).read(), - )[0] - - setuptools.setup( - name="pyoptsparse", - version=__version__, - description="Python package for formulating and solving nonlinear constrained optimization problems", - long_description="pyOptSparse is a Python package for formulating and solving nonlinear constrained optimization problems", - platforms=["Linux"], - keywords="optimization", - install_requires=[ - "packaging", - "sqlitedict>=1.6", - "numpy>=1.21", - "scipy>=1.7", - "mdolab-baseclasses>=1.3.1", - ], - extras_require={ - "optview": [ - "dash", - "plotly", - "matplotlib", - ], - "docs": docs_require, - "testing": ["testflo>=1.4.5", "parameterized"], - }, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python", - "Topic :: Scientific/Engineering", - "Topic :: Software Development", - "Topic :: Education", - ], - package_dir={"": "."}, - packages=setuptools.find_packages(where="."), - package_data={ - "": ["*.so", "*.lib", "*.pyd", "*.pdb", "*.dylib", "assets/*", "LICENSE"], - }, - python_requires=">=3.9", - entry_points={ - "gui_scripts": [ - "optview = pyoptsparse.postprocessing.OptView:main", - "optview_dash = pyoptsparse.postprocessing.OptView_dash:main", - ] - }, - ) From 30e2bab2d7756497e64f87dad40978722d5c1418 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:10:08 -0600 Subject: [PATCH 04/27] remove unused helper --- meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 0e283f09..a138a702 100644 --- a/meson.build +++ b/meson.build @@ -27,8 +27,6 @@ add_project_arguments(_global_c_args, language : 'c') py3 = import('python').find_installation(pure: false) py3_dep = py3.dependency() -generate_f2pymod = find_program('tools/generate_f2pymod.py') - # We need -lm for all C code (assuming it uses math functions, which is safe to # assume for SciPy). For C++ it isn't needed, because libstdc++/libc++ is # guaranteed to depend on it. For Fortran code, Meson already adds `-lm`. From 70be944532e01b0c1786490982b16cbc643008ee Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:14:26 -0600 Subject: [PATCH 05/27] Add meson-python dep to win GHA --- .github/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/environment.yml b/.github/environment.yml index bd57cd36..8c1ded0f 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -4,6 +4,7 @@ dependencies: - numpy >=2.0 - swig - meson >=1.3.2 + - meson-python - compilers - pkg-config - pip From 8c0ec4be264c497fce1e3086120ee58d929e0838 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:23:21 -0600 Subject: [PATCH 06/27] Remove extra flags --- meson.build | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index a138a702..138eb4f0 100644 --- a/meson.build +++ b/meson.build @@ -10,20 +10,11 @@ project( ], ) -# from scipy -# +# install python sources install_subdir('pyoptsparse', install_dir: py3.get_install_dir()) + +# install non-python sources subdir('pyoptsparse') From 0f54b3372a130a4be064b60510832d85d7a32b4c Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:29:21 -0600 Subject: [PATCH 07/27] Remove chdir to tools --- pyoptsparse/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 8b5f1fd3..40bb1104 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -7,7 +7,6 @@ if incdir_numpy == 'not-given' [ '-c', '''import os -os.chdir(os.path.join("..", "tools")) import numpy as np try: incdir = os.path.relpath(np.get_include()) From 8805551f3600af08a1714527500c359ab8be397b Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:37:00 -0600 Subject: [PATCH 08/27] Missed install: true for pyPSQP --- pyoptsparse/pyPSQP/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoptsparse/pyPSQP/meson.build b/pyoptsparse/pyPSQP/meson.build index 1366f6d2..1cf920ec 100644 --- a/pyoptsparse/pyPSQP/meson.build +++ b/pyoptsparse/pyPSQP/meson.build @@ -15,4 +15,4 @@ py3.extension_module('psqp', psqp_source, dependencies: [fortranobject_dep], subdir: 'pyoptsparse/pyPSQP', - install: false) + install: true) From fad6c612d88f71f1573865f4400fd5aefbc7604d Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:37:08 -0600 Subject: [PATCH 09/27] Add permalink to scipy snippets --- meson.build | 2 +- pyoptsparse/meson.build | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 138eb4f0..f07f28c3 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project( ], ) -# # install python sources -install_subdir('pyoptsparse', install_dir: py3.get_install_dir()) +install_subdir('pyoptsparse', + exclude_directories: [ + 'pyCONMIN/source', + 'pyCONMIN/README', + 'pyNLPQLP/source', + 'pyNLPQLP/README', + 'pyNSGA2/source', + 'pyNSGA2/README', + 'pyPSQP/source', + 'pyPSQP/README', + 'pySLSQP/source', + 'pySLSQP/README', + 'pySNOPT/source', + 'pySNOPT/README' + ], + install_dir: py3.get_install_dir()) # install non-python sources subdir('pyoptsparse') From 80f94a0c280a1f0000fdd12e7f1137b27648289a Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:25:39 -0700 Subject: [PATCH 19/27] remove cycipopt from testing deps --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 975ae07c..e88f7a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ docs = [ testing = [ "testflo>=1.4.5", "parameterized", - "cyipopt" ] [project.urls] From 257b9f6d75e54f539d171005e687186cb2c0a739 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:30:30 -0700 Subject: [PATCH 20/27] missed IPOPT in rebase --- pyoptsparse/pyIPOPT/meson.build | 50 --------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 pyoptsparse/pyIPOPT/meson.build diff --git a/pyoptsparse/pyIPOPT/meson.build b/pyoptsparse/pyIPOPT/meson.build deleted file mode 100644 index cea6c36e..00000000 --- a/pyoptsparse/pyIPOPT/meson.build +++ /dev/null @@ -1,50 +0,0 @@ -fs = import('fs') - -if get_option('ipopt_dir') != '' or fs.is_dir('Ipopt') - - ipopt_dir = '' - - if get_option('ipopt_dir') != '' - ipopt_dir = get_option('ipopt_dir') - elif fs.is_dir('Ipopt') - ipopt_dir = fs.is_dir('Ipopt') - endif - - ipopt_lib = [] - ipopt_idir = '' - - # Ipopt installs differently on some systems (i.e. Fedora) - if fs.is_dir(ipopt_dir / 'lib') - ipopt_lib = [ipopt_dir / 'lib'] - elif fs.is_dir(ipopt_dir / 'lib64') - ipopt_lib = [ipopt_dir / 'lib64'] - endif - - - if fs.is_dir(ipopt_dir / 'include' / 'coin-or') - ipopt_idir = ipopt_dir / 'include' / 'coin-or' - elif fs.is_dir(ipopt_dir / 'include' / 'coin') - ipopt_idir = ipopt_dir / 'include' / 'coin' - endif - - ipopt_dep = cc.find_library('ipopt-3', required: false, dirs: ipopt_lib) # only relevant on windows - if not ipopt_dep.found() - ipopt_dep = cc.find_library('ipopt', required: true, dirs: ipopt_lib) - endif - - if fs.is_dir(ipopt_idir) - ipopt_inc = include_directories(ipopt_idir) - else - error('IPOPT include directory not found: ', ipopt_dir) - endif - - py3.extension_module('pyipoptcore', - 'src/callback.c', - 'src/pyipoptcoremodule.c', - include_directories: [inc_np, 'src', ipopt_inc], - dependencies : [py3_dep, ipopt_dep], - subdir: 'pyoptsparse/pyIPOPT', - link_language: 'c', - install: true) -endif - From 309f3592ec40003c7bfa8c603525ecfec2b4573e Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:33:15 -0700 Subject: [PATCH 21/27] fix nsga2 --- pyoptsparse/pyNSGA2/meson.build | 61 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/pyoptsparse/pyNSGA2/meson.build b/pyoptsparse/pyNSGA2/meson.build index 84e5c6cb..219bea0f 100644 --- a/pyoptsparse/pyNSGA2/meson.build +++ b/pyoptsparse/pyNSGA2/meson.build @@ -1,36 +1,33 @@ swig = find_program('swig', required: true) nsga2_source = custom_target('nsga2_wrap.c', - input : ['source/swig/nsga2.i'], - output : ['nsga2_wrap.c'], - command: ['swig', '-o', 'pyoptsparse/pyNSGA2/nsga2_wrap.c', '-python', '-interface', 'nsga2', '@INPUT@'] - ) + input : ['source/swig/nsga2.i'], + output : ['nsga2_wrap.c'], + command: ['swig', '-o', 'pyoptsparse/pyNSGA2/nsga2_wrap.c', '-python', '-interface', 'nsga2', '@INPUT@'] + ) - py3.extension_module('nsga2', - 'source/allocate.c', - 'source/auxiliary.c', - 'source/crossover.c', - 'source/crowddist.c', - 'source/decode.c', - 'source/dominance.c', - 'source/eval.c', - 'source/fillnds.c', - 'source/initialize.c', - 'source/list.c', - 'source/merge.c', - 'source/mutation.c', - 'source/nsga2.c', - 'source/rand.c', - 'source/rank.c', - 'source/report.c', - 'source/sort.c', - 'source/tourselect.c', - nsga2_source, - include_directories: 'source', - dependencies : py3_dep, - subdir: 'pyoptsparse/pyNSGA2', - link_language: 'c', - install: true) -else - message('SWIG was not found, therefore NSGA2 will not be built.') -endif +py3.extension_module('nsga2', + 'source/allocate.c', + 'source/auxiliary.c', + 'source/crossover.c', + 'source/crowddist.c', + 'source/decode.c', + 'source/dominance.c', + 'source/eval.c', + 'source/fillnds.c', + 'source/initialize.c', + 'source/list.c', + 'source/merge.c', + 'source/mutation.c', + 'source/nsga2.c', + 'source/rand.c', + 'source/rank.c', + 'source/report.c', + 'source/sort.c', + 'source/tourselect.c', + nsga2_source, + include_directories: 'source', + dependencies : py3_dep, + subdir: 'pyoptsparse/pyNSGA2', + link_language: 'c', + install: true) From c2f20aada00609188413820e059d9ae548b5f370 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:44:08 -0700 Subject: [PATCH 22/27] require ninja --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e88f7a33..81e7684b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["meson-python", "numpy>=2.0", "swig"] +requires = ["meson-python", "ninja", "numpy>=2.0", "swig"] build-backend = "mesonpy" [project] From 0e93e710419ee5e3cc3f8ebcd97a0da177bf5d2c Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:15:22 -0700 Subject: [PATCH 23/27] pre-commit --- meson.build | 1 - pyoptsparse/meson.build | 1 - 2 files changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 85799a28..abfb31c1 100644 --- a/meson.build +++ b/meson.build @@ -79,4 +79,3 @@ install_subdir('pyoptsparse', # install non-python sources subdir('pyoptsparse') - diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 752f042b..c2518d48 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -58,4 +58,3 @@ subdir('pyCONMIN') subdir('pyNLPQLP') subdir('pyNSGA2') subdir('pyPSQP') - From 078d5aa4f9c29f1e8c3d689f284f4d7d38c67634 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 8 Oct 2025 00:43:43 -0700 Subject: [PATCH 24/27] update docs to reflect editable install instructions --- doc/contribute.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/contribute.rst b/doc/contribute.rst index bcfce24c..b081b1ea 100644 --- a/doc/contribute.rst +++ b/doc/contribute.rst @@ -8,6 +8,20 @@ If you have an issue with pyOptSparse, a bug to report, or a feature to request, This lets other users know about the issue. If you are comfortable fixing the issue, please do so and submit a pull request. +Editable Installs +----------------- +Due to the use of ``meson-python`` as the backend, the typical process of using ``pip install -e .`` to generate an editable install cannot be used. +Instead, based on the instructions `here `__, +you must first install the `build dependencies` yourself. +This can be done by looking at the ``requires`` field of the ``[build-system]`` section of the ``pyproject.toml`` file. +Then, do the following: + +.. prompt:: bash + + pip install --no-build-isolation --editable . + +To run tests, ensure that the testing dependencies specified in the ``pyproject.toml`` file are also installed. + Coding style ------------ We use `ruff `_ and `pre-commit `_ for linting and formatting. From 36551185f6ade3932f6d5134f4aa83a8798e030f Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:09:58 -0800 Subject: [PATCH 25/27] handle meson editable installs which have a separate builddir --- pyoptsparse/pyOpt_utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyoptsparse/pyOpt_utils.py b/pyoptsparse/pyOpt_utils.py index 1af2a389..118bc2a9 100644 --- a/pyoptsparse/pyOpt_utils.py +++ b/pyoptsparse/pyOpt_utils.py @@ -621,9 +621,13 @@ def import_module( with _prepend_path(path): try: module = importlib.import_module(module_name) - except ImportError as e: - if on_error.lower() == "raise": - raise e - else: - module = e + except ImportError: + try: + full_module_name = f"pyoptsparse.{path[0].split('/')[-1]}.{module_name}" + module = importlib.import_module(full_module_name) + except ImportError as e: + if on_error.lower() == "raise": + raise e + else: + module = e return module From 7d4df1f5a1c6fa545b30ff51a3e688ff2b88c125 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:11:57 -0800 Subject: [PATCH 26/27] handle case of empth tuple --- pyoptsparse/pyOpt_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyoptsparse/pyOpt_utils.py b/pyoptsparse/pyOpt_utils.py index 118bc2a9..0f6f4149 100644 --- a/pyoptsparse/pyOpt_utils.py +++ b/pyoptsparse/pyOpt_utils.py @@ -621,13 +621,13 @@ def import_module( with _prepend_path(path): try: module = importlib.import_module(module_name) - except ImportError: + except ImportError as e1: try: full_module_name = f"pyoptsparse.{path[0].split('/')[-1]}.{module_name}" module = importlib.import_module(full_module_name) - except ImportError as e: + except (ImportError, IndexError): if on_error.lower() == "raise": - raise e + raise e1 from e1 else: - module = e + module = e1 return module From e5cb8a6d9fe9e21ec96a6b2fabd444271cf420e0 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:25:18 -0800 Subject: [PATCH 27/27] add [dev] --- doc/contribute.rst | 7 ++++++- doc/install.rst | 6 +----- pyoptsparse/pyOpt_utils.py | 6 +++--- pyproject.toml | 8 ++++++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/doc/contribute.rst b/doc/contribute.rst index b081b1ea..34ddfdaa 100644 --- a/doc/contribute.rst +++ b/doc/contribute.rst @@ -13,7 +13,9 @@ Editable Installs Due to the use of ``meson-python`` as the backend, the typical process of using ``pip install -e .`` to generate an editable install cannot be used. Instead, based on the instructions `here `__, you must first install the `build dependencies` yourself. -This can be done by looking at the ``requires`` field of the ``[build-system]`` section of the ``pyproject.toml`` file. +This can be done by looking at the ``requires`` field of the ``[build-system]`` section of the ``pyproject.toml`` file, or via +``pip install .[dev]`` + Then, do the following: .. prompt:: bash @@ -63,6 +65,9 @@ When you add code or functionality, add tests that cover the new or modified cod These may be units tests for individual components or regression tests for entire models that use the new functionality. All the existing tests can be found under the ``test`` folder. +To run tests, ensure that the testing dependencies have been installed (see `pyproject.toml`). + + Pull requests ------------- Finally, after adding or modifying code, and making sure the steps above are followed, submit a pull request via the GitHub interface. diff --git a/doc/install.rst b/doc/install.rst index 836d9a3f..67c47731 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -98,11 +98,7 @@ If you encounter a ``no module named tkinter`` error when trying to run optview, Testing ------- pyOptSparse provides a set of unit and regression tests to verify the installation. -To run these tests, first install ``testflo`` which is a testing framework developed by the OpenMDAO team: - -.. prompt:: bash - - pip install testflo +To run these tests, first install testing dependencies via ``pip install .[testing]``. Then, in the project root directory, type: diff --git a/pyoptsparse/pyOpt_utils.py b/pyoptsparse/pyOpt_utils.py index 0f6f4149..68d788f3 100644 --- a/pyoptsparse/pyOpt_utils.py +++ b/pyoptsparse/pyOpt_utils.py @@ -621,13 +621,13 @@ def import_module( with _prepend_path(path): try: module = importlib.import_module(module_name) - except ImportError as e1: + except ImportError as e: try: full_module_name = f"pyoptsparse.{path[0].split('/')[-1]}.{module_name}" module = importlib.import_module(full_module_name) except (ImportError, IndexError): if on_error.lower() == "raise": - raise e1 from e1 + raise e from e else: - module = e1 + module = e return module diff --git a/pyproject.toml b/pyproject.toml index 81e7684b..7405b275 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,14 @@ testing = [ "testflo>=1.4.5", "parameterized", ] +dev = [ + "meson-python", + "ninja", + "numpy", + "swig", + "testflo>=1.4.5", + "parameterized", +] [project.urls] Homepage = "https://github.com/mdolab/pyoptsparse"