From e6d9105eb3addbfb22ac0b3bc71f30a5eefd7ea1 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 17:51:48 -0600 Subject: [PATCH 01/16] Modify inner meson.build files --- pyoptsparse/pyALPSO/meson.build | 13 ------------- pyoptsparse/pyCONMIN/meson.build | 22 ++++------------------ pyoptsparse/pyIPOPT/meson.build | 12 +----------- pyoptsparse/pyNLPQLP/meson.build | 14 +------------- pyoptsparse/pyNSGA2/meson.build | 16 ++-------------- pyoptsparse/pyPSQP/meson.build | 22 ++++------------------ pyoptsparse/pyParOpt/meson.build | 10 ---------- pyoptsparse/pySLSQP/meson.build | 22 ++++------------------ pyoptsparse/pySNOPT/meson.build | 18 ++---------------- 9 files changed, 18 insertions(+), 131 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 b49fafe2..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' -) \ No newline at end of file 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 d26259d6..a43e5b64 100644 --- a/pyoptsparse/pyIPOPT/meson.build +++ b/pyoptsparse/pyIPOPT/meson.build @@ -45,16 +45,6 @@ if get_option('ipopt_dir') != '' or fs.is_dir('Ipopt') dependencies : [py3_dep, ipopt_dep], subdir: 'pyoptsparse/pyIPOPT', link_language: 'c', - install : false) + install: true) endif -#python_sources = [ -# '__init__.py', -# 'pyIPOPT.py', -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyIPOPT' -#) 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 e9cfdcca..4365014c 100644 --- a/pyoptsparse/pyNSGA2/meson.build +++ b/pyoptsparse/pyNSGA2/meson.build @@ -7,7 +7,7 @@ if swig.found() command: ['swig', '-o', 'pyoptsparse/pyNSGA2/nsga2_wrap.c', '-python', '-interface', 'nsga2', '@INPUT@'] ) - py3_target.extension_module('nsga2', + py3.extension_module('nsga2', 'source/allocate.c', 'source/auxiliary.c', 'source/crossover.c', @@ -31,19 +31,7 @@ if swig.found() dependencies : py3_dep, subdir: 'pyoptsparse/pyNSGA2', link_language: 'c', - install : false) + install: true) else message('SWIG was not found, therefore NSGA2 will not be built.') endif - -#python_sources = [ -# '__init__.py', -# 'pyNSGA2.py', -# 'LICENSE' -#] -# -#py3_target.install_sources( -# python_sources, -# pure: false, -# subdir: 'pyoptsparse/pyNSGA2' -#) 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 0bc4f8e6..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' -) \ No newline at end of file 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 24a97ef7..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' -#) \ No newline at end of file From d6aa7d13fe90003242167f91fa214f01a3abaa83 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 17:52:11 -0600 Subject: [PATCH 02/16] Modify outer meson.build --- meson.build | 59 +++++++++++------- pyoptsparse/meson.build | 128 +++++++++++++++------------------------- 2 files changed, 86 insertions(+), 101 deletions(-) diff --git a/meson.build b/meson.build index 1524e924..67a428b9 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 0467ed19..10ceeeb2 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -1,98 +1,64 @@ -# 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() -endif -# this creates a raw string which is useful for Windows use of '\' for paths -incdir_numpy = '''@0@'''.format(incdir_numpy) +# from scipy +# subdir('pySNOPT') subdir('pyIPOPT') 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 15314e623987c5b502b0556c8f3986d761fe2e16 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:03:38 -0600 Subject: [PATCH 03/16] setup.py -> pyproject.toml --- pyproject.toml | 41 +++++++++++++- setup.py | 143 ------------------------------------------------- 2 files changed, 39 insertions(+), 145 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index e7053196..921dddd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,40 @@ [build-system] -requires = ["setuptools>=42", "meson>=0.60.0", "ninja", "numpy>=2.0"] -build-backend = "setuptools.build_meta" +requires = ["meson-python", "setuptools", "numpy"] +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 9f068765..00000000 --- a/setup.py +++ /dev/null @@ -1,143 +0,0 @@ -import os -import re -import shutil -import setuptools -import subprocess - - -def run_meson_build(): - # check if ipopt dir is specified - ipopt_dir_opt = "" - if "IPOPT_DIR" in os.environ: - ipopt_dir = os.environ["IPOPT_DIR"] - ipopt_dir_opt = f"-Dipopt_dir={ipopt_dir}" - 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"] - # check to make sure ipopt dir isnt specified twice - if "-Dipopt_dir" in meson_args and ipopt_dir_opt != "": - raise RuntimeError("IPOPT_DIR environment variable is set and '-Dipopt_dir' in 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} {ipopt_dir_opt} {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 d7c080c2716ffbd56b072f452a788898f09d802b Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:10:08 -0600 Subject: [PATCH 04/16] remove unused helper --- meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 67a428b9..fae11eb0 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 89a5e74801d8c1ca6979db9a4beb1a66d3ae6494 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:14:26 -0600 Subject: [PATCH 05/16] 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 b23fb74a..8a327dad 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -5,6 +5,7 @@ dependencies: - ipopt - swig - meson >=1.3.2 + - meson-python - compilers - pkg-config - pip From 03ab46fe9abe270691e01e4ddc6518afe6d2ab8a Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:23:21 -0600 Subject: [PATCH 06/16] Remove extra flags --- meson.build | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index fae11eb0..e83d00bd 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 a6af4692bbde3a3521812bd9463efa6d11319d7a Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:29:21 -0600 Subject: [PATCH 07/16] Remove chdir to tools --- pyoptsparse/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 10ceeeb2..892d2a15 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 67761cb32280c10565ad5e30f2bf163d4fe7d2a9 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:37:00 -0600 Subject: [PATCH 08/16] 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 27adf08588bb922faec08fa42dc546bbede67953 Mon Sep 17 00:00:00 2001 From: Phil Chiu Date: Tue, 1 Apr 2025 23:37:08 -0600 Subject: [PATCH 09/16] 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 e83d00bd..a2574837 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project( ], ) -#