From 910eb9e76daf589d48324e2f7dcb1226e50b2541 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 17:51:48 -0600 Subject: [PATCH 01/35] 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 3a9675566..000000000 --- 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 a2eb7e5c4..9d9b905f4 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 7ea72e616..a43e5b648 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 352d9bc65..862af028c 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 48faac1ae..84e5c6cb7 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 47bc59b0f..1366f6d29 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 4500d2036..000000000 --- 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 e898636bb..29717b7ce 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 61edfdb05..f9e7aace9 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/35] 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 a2219cd39..0e283f098 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 7f1d811bc..8b5f1fd34 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/35] 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 ce02acea6..7bebb7884 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 c100711eb..000000000 --- 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/35] remove unused helper --- meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 0e283f098..a138a7022 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/35] 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 bd57cd368..8c1ded0f6 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/35] Remove extra flags --- meson.build | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index a138a7022..138eb4f02 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/35] Remove chdir to tools --- pyoptsparse/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 8b5f1fd34..40bb1104c 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/35] 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 1366f6d29..1cf920ecd 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/35] 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 138eb4f02..f07f28c34 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/35] remove cycipopt from testing deps --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 975ae07c1..e88f7a330 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/35] 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 cea6c36e0..000000000 --- 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/35] 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 84e5c6cb7..219bea0fc 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/35] require ninja --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e88f7a330..81e7684b5 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/35] pre-commit --- meson.build | 1 - pyoptsparse/meson.build | 1 - 2 files changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 85799a285..abfb31c16 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 752f042b8..c2518d481 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/35] 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 bcfce24c7..b081b1ea1 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/35] 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 1af2a3893..118bc2a91 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/35] 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 118bc2a91..0f6f4149d 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/35] 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 b081b1ea1..34ddfdaa0 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 836d9a3f9..67c47731a 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 0f6f4149d..68d788f30 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 81e7684b5..7405b2758 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" From e171eca41a5a719bdec9235179ffcf78d90c4048 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 9 Jan 2026 21:59:43 -0800 Subject: [PATCH 28/35] try a newer flang? --- .github/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/environment.yml b/.github/environment.yml index ce8c294a5..0b364b569 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -10,7 +10,7 @@ dependencies: - pip - setuptools - build - - flang>=18 + - flang>=20 - libpgmath # runtime - packaging From aa7bca54893f5ad623faa32084f5ce40e1e63d3b Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:08:32 -0800 Subject: [PATCH 29/35] what if we used clang too --- .github/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/environment.yml b/.github/environment.yml index 0b364b569..10b0d3874 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -5,7 +5,7 @@ dependencies: - swig - meson >=1.3.2 - meson-python - - compilers + - clang - pkg-config - pip - setuptools From 7d4137507a8480565f45346e806d68d9e9622dad Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:38:01 -0800 Subject: [PATCH 30/35] back to compilers --- .github/environment.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/environment.yml b/.github/environment.yml index 10b0d3874..2bf9fcc81 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -5,12 +5,11 @@ dependencies: - swig - meson >=1.3.2 - meson-python - - clang + - compilers - pkg-config - pip - setuptools - build - - flang>=20 - libpgmath # runtime - packaging From e86710bcbed517de747bc22465f8a7c60e0040d1 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:39:10 -0800 Subject: [PATCH 31/35] no longer need pkg-config --- .github/environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/environment.yml b/.github/environment.yml index 2bf9fcc81..ba36c63ea 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -6,7 +6,6 @@ dependencies: - meson >=1.3.2 - meson-python - compilers - - pkg-config - pip - setuptools - build From 4e8f021bdcf44ff407e0209954bf502e1f6cb05e Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:47:53 -0800 Subject: [PATCH 32/35] try flang 5 --- .github/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/environment.yml b/.github/environment.yml index ba36c63ea..cc42f5726 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -9,6 +9,7 @@ dependencies: - pip - setuptools - build + - flang=5 - libpgmath # runtime - packaging From 89120fe2625f3d007e80860c118aec2ea12560e8 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:42:07 -0800 Subject: [PATCH 33/35] try exporting FC --- .github/environment.yml | 5 +++-- .github/workflows/windows-build.yml | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/environment.yml b/.github/environment.yml index cc42f5726..0411e61dd 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -5,11 +5,12 @@ dependencies: - swig - meson >=1.3.2 - meson-python - - compilers + - c-compiler + - cxx-compiler - pip - setuptools - build - - flang=5 + - flang=20 - libpgmath # runtime - packaging diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index ed0b70db0..cdc90df91 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -36,6 +36,8 @@ jobs: :: debug - list environment vars set + set FC=flang.exe + python -m build -n -x . pip install --no-deps --no-index --find-links dist pyoptsparse From 9eb85b0bfa853efab010b06fc964839a705688cb Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Sun, 11 Jan 2026 20:53:20 -0800 Subject: [PATCH 34/35] remove comment string --- meson.build | 2 -- pyoptsparse/meson.build | 2 -- 2 files changed, 4 deletions(-) diff --git a/meson.build b/meson.build index abfb31c16..fa48ffa39 100644 --- a/meson.build +++ b/meson.build @@ -57,8 +57,6 @@ if host_machine.system() == 'darwin' endif endif -# --!> - # install python sources install_subdir('pyoptsparse', exclude_directories: [ diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index c2518d481..6edbf6f15 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -50,8 +50,6 @@ fortranobject_dep = declare_dependency( include_directories: [inc_np, inc_f2py], ) -# --!> - subdir('pySNOPT') subdir('pySLSQP') subdir('pyCONMIN') From e2c8d9582f299261b62b58ac4071f3bf9e726e28 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Sun, 11 Jan 2026 20:58:16 -0800 Subject: [PATCH 35/35] add comments --- pyoptsparse/pyOpt_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyoptsparse/pyOpt_utils.py b/pyoptsparse/pyOpt_utils.py index 68d788f30..881ca18ef 100644 --- a/pyoptsparse/pyOpt_utils.py +++ b/pyoptsparse/pyOpt_utils.py @@ -620,9 +620,12 @@ def import_module( with _prepend_path(path): try: + # first try e.g. "import snopt" module = importlib.import_module(module_name) except ImportError as e: try: + # next try e.g. "import pyoptsparse.pySNOPT.snopt" + # this is what meson-python's import hook needs when running in editable mode full_module_name = f"pyoptsparse.{path[0].split('/')[-1]}.{module_name}" module = importlib.import_module(full_module_name) except (ImportError, IndexError):