From 3ef46defb102e17824d8f70d15de856ea2973ce3 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:41:48 +0200 Subject: [PATCH 1/9] feat/cicd: update to latest upstream HGS-CVRP and add cibuildwheels and PyPI upload as GitHub action; bump version to 0.0.0.11 --- .github/workflows/buildpublish.yml | 64 ++++++++++++++ .github/workflows/ci.yml | 5 +- .github/workflows/pypi.yml | 74 ---------------- CMakeLists.txt | 36 ++++++++ README.md | 14 ++- hygese/hygese.py | 12 +++ pyproject.toml | 15 +++- setup.py | 134 ----------------------------- 8 files changed, 134 insertions(+), 220 deletions(-) create mode 100644 .github/workflows/buildpublish.yml delete mode 100644 .github/workflows/pypi.yml create mode 100644 CMakeLists.txt delete mode 100644 setup.py diff --git a/.github/workflows/buildpublish.yml b/.github/workflows/buildpublish.yml new file mode 100644 index 0000000..d73f5f2 --- /dev/null +++ b/.github/workflows/buildpublish.yml @@ -0,0 +1,64 @@ +name: Build wheels+sdist and publish to PyPI + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: + +jobs: + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build SDist + run: pipx run build --sdist + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + - uses: pypa/cibuildwheel@v2.23 + env: + CIBW_ARCHS: auto64 + CIBW_SKIP: "*musllinux* pp*" + CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux_2_34 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }} + path: wheelhouse/*.whl + publish_all: + name: Publish on PyPI + needs: [build_wheels, make_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + attestations: write + contents: read + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + - name: Generate artifact attestations + uses: actions/attest-build-provenance@v2.2.3 + with: + subject-path: "dist/*" + - name: Upload to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a70af40..0308f2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,9 @@ jobs: - name: Install and Build run: | - pip install . - python setup.py build + pip install build + python -m build + pip install dist/hygese*.whl - name: Test with pytest run: | diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml deleted file mode 100644 index 493dca0..0000000 --- a/.github/workflows/pypi.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Publish Python 🐍 source distribution to PyPI - -on: push - -jobs: - build: - name: Build source distribution 📦 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install pypa/build - run: python3 -m pip install build --user - - name: Build a source tarball (sdist) only - run: python3 -m build --sdist - - name: Store the distribution package - uses: actions/upload-artifact@v4 - with: - name: python-package-sdist - path: dist/ - - publish-to-pypi: - name: Publish Python 🐍 source distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/') - needs: build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/project/ # Replace with your PyPI project name. - permissions: - id-token: write - steps: - - name: Download the sdist artifact - uses: actions/download-artifact@v4 - with: - name: python-package-sdist - path: dist/ - - name: Publish source distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - github-release: - name: Sign the Python 🐍 source distribution with Sigstore and create GitHub Release - needs: publish-to-pypi - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - name: Download the sdist artifact - uses: actions/download-artifact@v4 - with: - name: python-package-sdist - path: dist/ - - name: Sign the sdist with Sigstore - uses: sigstore/gh-action-sigstore-python@v3.0.0 - with: - inputs: | - ./dist/*.tar.gz - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --notes "" - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - gh release upload "$GITHUB_REF_NAME" dist/** --repo "$GITHUB_REPOSITORY" diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5aba2d8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.15) + +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR} CACHE PATH "install prefix" FORCE) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +if(WIN32) + # MSBUILD toolchain names the shared library as hgscvrp.dll + # this forces it to use libhgscvrp.dll + set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "lib") +endif() + +project( + ${SKBUILD_PROJECT_NAME} + VERSION ${SKBUILD_PROJECT_VERSION} + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 17) + +include(FetchContent) +FetchContent_Declare( + hgscvrp + GIT_REPOSITORY https://github.com/vidalt/HGS-CVRP.git + GIT_TAG 1a927955cd2861a29d978f0d359d6e647db9319c +) +FetchContent_MakeAvailable(hgscvrp) + +# runtime library +if(CMAKE_HOST_WIN32) + install(TARGETS lib RUNTIME DESTINATION hygese) +else() + install(TARGETS lib LIBRARY DESTINATION hygese) +endif() diff --git a/README.md b/README.md index 6b3cea0..602bf30 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,10 @@ This package provides a simple Python wrapper for the Hybrid Genetic Search solver for Capacitated Vehicle Routing Problems [(HGS-CVRP)](https://github.com/vidalt/HGS-CVRP). -The installation requires `gcc`, `make`, and `cmake` to build. -On Windows, for example, you can install them by `scoop install gcc make cmake` using [Scoop](scoop.sh). -Then, install the PyHygese package: +## Installation ``` pip install hygese ``` - - ## CVRP Example (random) ```python @@ -148,9 +142,13 @@ class AlgorithmParameters: lambda_: int = 40 nbElite: int = 4 nbClose: int = 5 + nbIterPenaltyManagement: int = 100 targetFeasible: float = 0.2 - seed: int = 1 + penaltyDecrease: float = 0.85 + penaltyIncrease: float = 1.2 + seed: int = 0 nbIter: int = 20000 + nbIterTraces: int = 500 timeLimit: float = 0.0 useSwapStar: bool = True ``` diff --git a/hygese/hygese.py b/hygese/hygese.py index 53c763a..415f1ca 100644 --- a/hygese/hygese.py +++ b/hygese/hygese.py @@ -47,9 +47,13 @@ class CAlgorithmParameters(Structure): ("lambda", c_int), ("nbElite", c_int), ("nbClose", c_int), + ("nbIterPenaltyManagement", c_int), ("targetFeasible", c_double), + ("penaltyDecrease", c_double), + ("penaltyIncrease", c_double), ("seed", c_int), ("nbIter", c_int), + ("nbIterTraces", c_int), ("timeLimit", c_double), ("useSwapStar", c_int), ] @@ -62,9 +66,13 @@ class AlgorithmParameters: lambda_: int = 40 nbElite: int = 4 nbClose: int = 5 + nbIterPenaltyManagement: int = 100 targetFeasible: float = 0.2 + penaltyDecrease: float = 0.85 + penaltyIncrease: float = 1.2 seed: int = 0 nbIter: int = 20000 + nbIterTraces: int = 500 timeLimit: float = 0.0 useSwapStar: bool = True @@ -76,9 +84,13 @@ def ctypes(self) -> CAlgorithmParameters: self.lambda_, self.nbElite, self.nbClose, + self.nbIterPenaltyManagement, self.targetFeasible, + self.penaltyDecrease, + self.penaltyIncrease, self.seed, self.nbIter, + self.nbIterTraces, self.timeLimit, int(self.useSwapStar), ) diff --git a/pyproject.toml b/pyproject.toml index 739e7ad..8704ab9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,14 @@ +[project] +name = "hygese" +version = "0.0.0.11" +authors = [{name = "Changhyun Kwon"}] +description = "Wrapper for the Hybrid Genetic Search algorithm for Capacitated Vehicle Routing Problems (HGS-CVRP)" +readme = {file = "README.md", content-type = "text/markdown"} +requires-python=">=3.10" + [build-system] -requires = ["setuptools", "cmake"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +cmake.build-type = "Release" diff --git a/setup.py b/setup.py deleted file mode 100644 index 992447f..0000000 --- a/setup.py +++ /dev/null @@ -1,134 +0,0 @@ -from setuptools import setup, find_packages -from setuptools.command.build_ext import build_ext as _build_ext -from setuptools.command.build_py import build_py as _build_py -import subprocess -import os -import platform -from os.path import exists, join as pjoin -import shutil - -import urllib.request - -urlretrieve = urllib.request.urlretrieve - -# try: -# import urllib.request -# urlretrieve = urllib.request.urlretrieve -# except ImportError: # python 2 -# from urllib import urlretrieve - - -# read the contents of your README file -from pathlib import Path - -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - - -def _run(cmd, cwd): - subprocess.check_call(cmd, shell=True, cwd=cwd) - - -def _safe_makedirs(*paths): - for path in paths: - try: - os.makedirs(path) - except os.error: - pass - - -HGS_VERSION = "2.0.0" -HGS_SRC = f"https://github.com/vidalt/HGS-CVRP/archive/v{HGS_VERSION}.tar.gz" - -LIB_DIR = "lib" -BUILD_DIR = "lib/build" -BIN_DIR = "lib/bin" - - -def get_lib_filename(): - if platform.system() == "Linux": - lib_ext = "so" - elif platform.system() == "Darwin": - lib_ext = "dylib" - elif platform.system() == "Windows": - lib_ext = "dll" - else: - lib_ext = "so" - return f"libhgscvrp.{lib_ext}" - - -LIB_FILENAME = get_lib_filename() - - -def download_build_hgs(): - _safe_makedirs(LIB_DIR) - _safe_makedirs(BUILD_DIR) - hgs_src_tarball_name = "{}.tar.gz".format(HGS_VERSION) - hgs_src_path = pjoin(LIB_DIR, hgs_src_tarball_name) - urlretrieve(HGS_SRC, hgs_src_path) - _run(f"tar xzvf {hgs_src_tarball_name}", LIB_DIR) - _run( - f'cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" ../HGS-CVRP-{HGS_VERSION}', - BUILD_DIR, - ) - _run("make lib", BUILD_DIR) - - shutil.copyfile(f"{BUILD_DIR}/{LIB_FILENAME}", f"hygese/{LIB_FILENAME}") - - -# LIB_VERSION = "0.0.1" -# HGS_CVRP_WIN = f"https://github.com/chkwon/Libhgscvrp_jll.jl/releases/download/libhgscvrp-v{LIB_VERSION}%2B0/" + \ -# f"libhgscvrp.v{LIB_VERSION}.x86_64-w64-mingw32-cxx11.tar.gz" - -# def download_binary_hgs(): -# print(HGS_CVRP_WIN) - -# _safe_makedirs(LIB_DIR) -# dll_tarball_name = "win_bin.tar.gz" -# hgs_bin_path = pjoin(LIB_DIR, dll_tarball_name) -# urlretrieve(HGS_CVRP_WIN, hgs_bin_path) -# _run(f"tar xzvf {dll_tarball_name}", LIB_DIR) -# shutil.copyfile(f"{BIN_DIR}/{LIB_FILENAME}", f"hygese/{LIB_FILENAME}") - - -class BuildPyCommand(_build_py): - def run(self): - print("Build!!!!!! Run!!!!") - - if platform.system() == "Windows": - # download_binary_hgs() - download_build_hgs() - else: - download_build_hgs() - - _build_py.run(self) - - -setup( - name="hygese", - version="0.0.0.10", - description="A Python wrapper for the HGS-CVRP solver", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/chkwon/PyHygese", - author="Changhyun Kwon", - author_email="chkwon@gmail.com", - project_urls={ - "Bug Tracker": "https://github.com/chkwon/PyHygese/issues", - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - package_dir={"": "."}, - packages=find_packages(), - python_requires=">=3.6", - cmdclass={ - "build_py": BuildPyCommand, - }, - package_data={ - "": ["libhgscvrp.*"], - }, - install_requires=["numpy"], -) From 61f0889d1787d1d200b4d50997e9454a67ab1751 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:00:49 +0200 Subject: [PATCH 2/9] cicd: install numpy before running tests (ci.yml) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0308f2e..9ae3e9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: - name: Test with pytest run: | - pip install pytest-cov + pip install pytest-cov numpy pytest -s --cov=hygese --cov-report=xml - name: Upload coverage to Codecov From 41a74266d5589ea0173f0066e913945466350629 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:51:19 +0200 Subject: [PATCH 3/9] cicd: attempt at resolving automated test error on ci.yml action --- .github/workflows/ci.yml | 8 +++----- hygese/hygese.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ae3e9e..2f19669 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest - # if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install build flake8 pytest pytest-cov numpy - name: Lint with flake8 run: | @@ -42,13 +41,12 @@ jobs: - name: Install and Build run: | - pip install build python -m build - pip install dist/hygese*.whl + ls -l dist + python -m pip install dist/hygese*.whl - name: Test with pytest run: | - pip install pytest-cov numpy pytest -s --cov=hygese --cov-report=xml - name: Upload coverage to Codecov diff --git a/hygese/hygese.py b/hygese/hygese.py index 415f1ca..70de3c5 100644 --- a/hygese/hygese.py +++ b/hygese/hygese.py @@ -29,7 +29,7 @@ def get_lib_filename(): # basedir = os.path.abspath(os.path.dirname(__file__)) -basedir = os.path.dirname(os.path.realpath(__file__)) +basedir = os.path.dirname(os.path.realpath(__file__, strict=True)) # os.add_dll_directory(basedir) HGS_LIBRARY_FILEPATH = os.path.join(basedir, get_lib_filename()) From 781f86f9a58fd84261c7627a64011eebb0a19c47 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:56:11 +0200 Subject: [PATCH 4/9] cicd: fix automated test error (ci.yml) --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f19669..c9ac66a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,9 +41,7 @@ jobs: - name: Install and Build run: | - python -m build - ls -l dist - python -m pip install dist/hygese*.whl + python -m pip install . - name: Test with pytest run: | From 9c776428cf3381f94ceb3fe43c99cfcd6402c4ae Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:11:53 +0200 Subject: [PATCH 5/9] cicd: force pytest to use installed version of hygese, the only one with the compiled libs (ci.yml) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9ac66a..8f4145f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: - name: Test with pytest run: | - pytest -s --cov=hygese --cov-report=xml + pytest -s --import-mode=importlib --cov=hygese --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From b71af7dd898059d2d5b2dbf03a099be2cc763348 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:16:54 +0200 Subject: [PATCH 6/9] cicd: one more attempt at preventing pytest from loading hygese from source (ci.yml) --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f4145f..bbefc93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,9 @@ jobs: - name: Test with pytest run: | - pytest -s --import-mode=importlib --cov=hygese --cov-report=xml + cd hygese/tests + pytest -s --cov=hygese --cov-report=xml + cd ../.. - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From a6e4ced9677b222c2856e75809625f494db1fdf4 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:45:09 +0200 Subject: [PATCH 7/9] cicd: use pytest --pyargs hygese.tests (ci.yml) --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbefc93..703c783 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,9 +45,7 @@ jobs: - name: Test with pytest run: | - cd hygese/tests - pytest -s --cov=hygese --cov-report=xml - cd ../.. + pytest -s --cov=hygese --cov-report=xml --pyargs hygese.tests - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From a2aa079ee69b5bc0c16de31ef355747ce5dcfde4 Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:29:39 +0200 Subject: [PATCH 8/9] cicd: move hygese to src/hygese to enable `pytest --cov=hygese` to use the installed package instead of local folder --- {hygese => src/hygese}/__init__.py | 0 {hygese => src/hygese}/hygese.py | 0 {hygese => src/hygese}/tests/__init__.py | 0 {hygese => src/hygese}/tests/test_cvrp.py | 0 {hygese => src/hygese}/tests/test_tsp.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {hygese => src/hygese}/__init__.py (100%) rename {hygese => src/hygese}/hygese.py (100%) rename {hygese => src/hygese}/tests/__init__.py (100%) rename {hygese => src/hygese}/tests/test_cvrp.py (100%) rename {hygese => src/hygese}/tests/test_tsp.py (100%) diff --git a/hygese/__init__.py b/src/hygese/__init__.py similarity index 100% rename from hygese/__init__.py rename to src/hygese/__init__.py diff --git a/hygese/hygese.py b/src/hygese/hygese.py similarity index 100% rename from hygese/hygese.py rename to src/hygese/hygese.py diff --git a/hygese/tests/__init__.py b/src/hygese/tests/__init__.py similarity index 100% rename from hygese/tests/__init__.py rename to src/hygese/tests/__init__.py diff --git a/hygese/tests/test_cvrp.py b/src/hygese/tests/test_cvrp.py similarity index 100% rename from hygese/tests/test_cvrp.py rename to src/hygese/tests/test_cvrp.py diff --git a/hygese/tests/test_tsp.py b/src/hygese/tests/test_tsp.py similarity index 100% rename from hygese/tests/test_tsp.py rename to src/hygese/tests/test_tsp.py From 9b95974474d76b576c62a2230588da99cb6943dc Mon Sep 17 00:00:00 2001 From: Mauricio Souza de Alencar <856825+mdealencar@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:19:52 +0200 Subject: [PATCH 9/9] cicd: make the coverage report's paths relative to project root (pyproject.toml) --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8704ab9..2d02cee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,6 @@ build-backend = "scikit_build_core.build" [tool.scikit-build] cmake.build-type = "Release" + +[tool.coverage.paths] +source = ["src", "*/site-packages"]