Repository: pyswmm/swmm-python (language: python)

Issue:
OWA SWMM 5.2.4 updates
- Adds hotstart functionality
- Updates the OWA SWMM engine to 5.2.4
- Adds cross compilation workflow



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml
new file mode 100644
index 00000000..532a87da
--- /dev/null
+++ b/.github/workflows/build_wheel.yml
@@ -0,0 +1,120 @@
+name: Build Wheels
+
+# Cross compile wheels only on main branch and tags
+on:
+  pull_request:
+    branches:    
+      - master  
+  push:
+    branches:    
+      - master
+    tags:        
+      - v*
+  workflow_dispatch:
+
+jobs:
+  build_nrtest_plugin:
+    name: Build nrtest-swmm plugin
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./nrtest-swmm
+
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          submodules: true
+
+      - name: Install Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.7
+
+      - name: Build wheel
+        run: |
+            pip install wheel
+            python setup.py bdist_wheel
+      - uses: actions/upload-artifact@v3
+        with:
+          path: nrtest-swmm/dist/*.whl
+
+
+
+  build_wheels:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: [ubuntu-latest, windows-2022, macos-12]
+        pyver: [cp38, cp39, cp310, cp311]
+
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+            submodules: true
+
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.15.0
+        with:
+          package-dir: ./swmm-toolkit
+        env:          
+          CIBW_TEST_COMMAND: "pytest {package}/tests"
+          CIBW_BEFORE_TEST: pip install -r {package}/test-requirements.txt
+          # mac needs ninja to build
+          CIBW_BEFORE_BUILD_MACOS: brew install ninja
+          # configure cibuildwheel to build native archs ('auto'), and some emulated ones      
+          CIBW_ARCHS_LINUX: x86_64
+          CIBW_ARCHS_WINDOWS: AMD64 
+          CIBW_ARCHS_MACOS: x86_64  
+          # only build current supported python: https://devguide.python.org/versions/
+          # don't build pypy or musllinux to save build time. TODO: find a good way to support those archs
+          CIBW_BUILD: ${{matrix.pyver}}-*
+          CIBW_SKIP: cp36-* cp37-* cp312-* pp* *-musllinux*
+          # Will avoid testing on emulated architectures
+          # Skip trying to test arm64 builds on Intel Macs
+          CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x} *-macosx_arm64 *-macosx_universal2:arm64"
+
+      - uses: actions/upload-artifact@v3
+        with:
+          path: ./wheelhouse/*.whl
+
+  build_cross_wheels:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: [ubuntu-latest,macos-12]
+        pyver: [cp38, cp39, cp310, cp311]
+
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+            submodules: true
+
+      - name: Set up QEMU
+        if: runner.os == 'Linux'
+        uses: docker/setup-qemu-action@v2
+        with:
+          platforms: all
+
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.15.0
+        with:
+          package-dir: ./swmm-toolkit
+        env:
+          # mac needs ninja to build
+          CIBW_BEFORE_BUILD_MACOS: brew install ninja
+          # configure cibuildwheel to build native archs ('auto'), and some emulated ones      
+          CIBW_ARCHS_LINUX: aarch64
+          CIBW_ARCHS_MACOS: arm64  
+          # only build current supported python: https://devguide.python.org/versions/
+          # don't build pypy or musllinux to save build time. TODO: find a good way to support those archs
+          CIBW_BUILD: ${{matrix.pyver}}-*
+          CIBW_SKIP: cp36-* cp37-* cp312-* pp* *-musllinux*
+
+      - uses: actions/upload-artifact@v3
+        with:
+          path: ./wheelhouse/*.whl
\ No newline at end of file
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
deleted file mode 100644
index 0296cbfc..00000000
--- a/.github/workflows/python-package.yml
+++ /dev/null
@@ -1,116 +0,0 @@
-name: Build Wheels
-
-on: [push, pull_request]
-
-env:
-  DOCKER_IMAGE: dockcross/manylinux2014-x64:latest
-
-
-jobs:
-  build_nrtest_plugin:
-    name: Build nrtest-swmm plugin
-    runs-on: ubuntu-latest
-    defaults:
-      run:
-        working-directory: ./nrtest-swmm
-
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v2
-        with:
-          submodules: true
-
-      - name: Install Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.7
-
-      - name: Build wheel
-        run: |
-            pip install wheel
-            python setup.py bdist_wheel
-      - uses: actions/upload-artifact@v2
-        with:
-          path: nrtest-swmm/dist/*.whl
-
-
-
-  build_linux_wheels:
-    name: Build wheels on linux
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v2
-        with:
-          submodules: true
-
-      - name: Install
-        run: |
-          docker pull $DOCKER_IMAGE
-          docker run --rm $DOCKER_IMAGE > ./dockcross
-          chmod +x ./dockcross
-
-      - name: Script
-        run: ./dockcross swmm-toolkit/tools/build-wheels.sh
-
-      - name: Upload artifacts
-        uses: actions/upload-artifact@v2
-        with:
-          path: ./dist/*.manylinux2014_x86_64.whl
-
-
-
-  build_win-mac_wheels:
-    name: Build wheels on ${{ matrix.os }}
-    runs-on: ${{ matrix.os }}
-    defaults:
-      run:
-        working-directory: ./swmm-toolkit
-
-    strategy:
-      fail-fast: false
-      matrix:
-        os: [windows-2022, macos-12]
-        py: ["3.7", "3.8", "3.9", "3.10", "3.11"]
-        include:
-          - os: windows-2022
-            sys_pkgs: choco install swig
-            activate: ./build-env/Scripts/activate
-
-          - os: macos-12
-            sys_pkgs: brew install swig ninja
-            activate: source ./build-env/bin/activate
-
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v2
-        with:
-          submodules: true
-
-      - name: Install Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.py }}
-
-      - name: Install required system packages
-        run: ${{matrix.sys_pkgs}}
-
-      - name: Build wheel in virtual env
-        run: |
-          python -m venv --clear ./build-env
-          ${{matrix.activate}}
-          python -m pip install -r build-requirements.txt
-          python setup.py bdist_wheel
-          deactivate
-
-      - name: Test wheel
-        run: |
-          pip install -r test-requirements.txt
-          pip install --no-index --find-links=./dist swmm_toolkit
-          pytest
-
-      - name: Upload artifacts
-        uses: actions/upload-artifact@v2
-        with:
-          path: swmm-toolkit/dist/*.whl
diff --git a/swmm-toolkit/CMakeLists.txt b/swmm-toolkit/CMakeLists.txt
index 181f9ab7..0f3f608b 100644
--- a/swmm-toolkit/CMakeLists.txt
+++ b/swmm-toolkit/CMakeLists.txt
@@ -15,7 +15,7 @@ cmake_minimum_required (VERSION 3.17)
 
 project(swmm-toolkit
     VERSION
-        0.14.0
+        0.15.0
 )
 
 
@@ -36,17 +36,10 @@ cmake_policy(SET CMP0078 NEW)
 cmake_policy(SET CMP0086 NEW)
 include(${SWIG_USE_FILE})
 
-
-# If wheel build on Apple fetch and build OpenMP Library
-if (APPLE)
-    include(./extern/openmp.cmake)
-else()
-    find_package(OpenMP
-        OPTIONAL_COMPONENTS
-            C
-    )
-endif()
-
+find_package(OpenMP
+    OPTIONAL_COMPONENTS
+        C
+)
 
 # Add project subdirectories
 add_subdirectory(swmm-solver)
diff --git a/swmm-toolkit/extern/openmp.cmake b/swmm-toolkit/extern/openmp.cmake
deleted file mode 100644
index dbe2a09d..00000000
--- a/swmm-toolkit/extern/openmp.cmake
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# CMakeLists.txt - CMake configuration file for OpenMP Library on Darwin
-#
-# Created: Mar 17, 2021
-# Updated: May 19, 2021
-#
-# Author: Michael E. Tryby
-#         US EPA ORD/CESER
-#
-# Note:
-#  Need to build libomp for binary compatibility with Python.
-#
-#  OpenMP library build fails for Xcode generator. Use Ninja or Unix Makefiles
-#  instead.
-#
-
-################################################################################
-#####################    CMAKELISTS FOR OPENMP LIBRARY    ######################
-################################################################################
-
-include(FetchContent)
-
-
-FetchContent_Declare(OpenMP
-    URL
-        https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/openmp-11.1.0.src.tar.xz
-    URL_HASH
-        SHA256=d187483b75b39acb3ff8ea1b7d98524d95322e3cb148842957e9b0fbb866052e
-)
-
-set(OPENMP_STANDALONE_BUILD TRUE)
-set(LIBOMP_INSTALL_ALIASES OFF)
-
-FetchContent_MakeAvailable(OpenMP)
-set(OpenMP_AVAILABLE TRUE)
-
-
-target_link_directories(omp
-    PUBLIC
-        $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/_deps/openmp-build/runtime/src>
-        $<INSTALL_INTERFACE:${LIBRARY_DIST}>
-)
-
-install(TARGETS omp
-    LIBRARY
-    DESTINATION
-        "${LIBRARY_DIST}"
-)
diff --git a/swmm-toolkit/pyproject.toml b/swmm-toolkit/pyproject.toml
new file mode 100644
index 00000000..bf989284
--- /dev/null
+++ b/swmm-toolkit/pyproject.toml
@@ -0,0 +1,9 @@
+[build-system]
+requires = [
+    "wheel>=0.38.1",
+    "setuptools>=42",
+    "scikit-build>=0.13",
+    "cmake>=3.21",
+    "swig==4.0.2",
+]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/swmm-toolkit/setup.py b/swmm-toolkit/setup.py
index a4107512..e9c96e6f 100644
--- a/swmm-toolkit/setup.py
+++ b/swmm-toolkit/setup.py
@@ -19,7 +19,7 @@
 import os
 from skbuild import setup
 from setuptools import Command
-
+import sys
 
 # Determine platform
 platform_system = platform.system()
@@ -56,11 +56,28 @@ def run(self):
         p.wait()
 
 
-# Set up location of wheel libraries depending on build platform
-if platform_system == "Windows":
-    package_dir = {"swmm_toolkit":"bin", "swmm.toolkit": "src/swmm/toolkit"}
+# Set up location of wheel libraries depending on build platform and command
+# commands that trigger cmake from skbuild.setuptools_wrap._should_run_cmake
+commands_that_trigger_cmake = {
+        "build",
+        "build_ext",
+        "develop",
+        "install",
+        "install_lib",
+        "bdist",
+        "bdist_dumb",
+        "bdist_egg",
+        "bdist_rpm",
+        "bdist_wininst",
+        "bdist_wheel",
+        "test",
+    }
+command = sys.argv[1] if len(sys.argv) > 1 else None
+if command in commands_that_trigger_cmake:
+    swmm_toolkit_dir= "bin" if platform_system == "Windows" else "lib"
 else:
-    package_dir = {"swmm_toolkit":"lib", "swmm.toolkit": "src/swmm/toolkit"}
+    swmm_toolkit_dir= "swmm-solver"
+package_dir = {"swmm_toolkit" : swmm_toolkit_dir, "swmm.toolkit": "src/swmm/toolkit"}
 
 
 if os.environ.get('SWMM_CMAKE_ARGS') is not None:
@@ -88,10 +105,9 @@ def exclude_files(cmake_manifest):
 here = pathlib.Path(__file__).parent.resolve()
 long_description = (here / 'README.md').read_text(encoding='utf-8')
 
-
 setup(
     name = "swmm-toolkit",
-    version = "0.14.2",
+    version = "0.15.0",
 
     packages = ["swmm_toolkit", "swmm.toolkit"],
     package_dir = package_dir,
@@ -127,4 +143,4 @@ def exclude_files(cmake_manifest):
         "Programming Language :: C",
         "Development Status :: 5 - Production/Stable",
     ]
-)
+)
\ No newline at end of file
diff --git a/swmm-toolkit/src/swmm/toolkit/__init__.py b/swmm-toolkit/src/swmm/toolkit/__init__.py
index fe53e08f..444027cc 100644
--- a/swmm-toolkit/src/swmm/toolkit/__init__.py
+++ b/swmm-toolkit/src/swmm/toolkit/__init__.py
@@ -19,7 +19,7 @@
 __credits__ = "Colleen Barr, Sam Hatchett"
 __license__ = "CC0 1.0 Universal"
 
-__version__ = "0.14.2"
+__version__ = "0.15.0"
 __date__ = "June 7, 2021"
 
 __maintainer__ = "Michael Tryby"
diff --git a/swmm-toolkit/src/swmm/toolkit/shared_enum.py b/swmm-toolkit/src/swmm/toolkit/shared_enum.py
index 48a34d5d..336bfca5 100644
--- a/swmm-toolkit/src/swmm/toolkit/shared_enum.py
+++ b/swmm-toolkit/src/swmm/toolkit/shared_enum.py
@@ -741,6 +741,13 @@ class RainResult(Enum):
     SNOWFALL = 2
 
 class InletProperty(Enum):
+    """Inlet Property enum class.
+    
+    :attr:`~NUM_INLETS`
+    :attr:`~CLOG_FACTOR`
+    :attr:`~FLOW_LIMIT`
+    :attr:`~DEPRESSION_HEIGHT`
+    :attr:`~DEPRESSION_WIDTH`"""
     NUM_INLETS = 0
     CLOG_FACTOR = 1
     FLOW_LIMIT = 2
@@ -749,7 +756,23 @@ class InletProperty(Enum):
     
 
 class InletResult(Enum):
+    """Inlet Result enum class.
+
+    :attr:`~FLOW_FACTOR`
+    :attr:`~FLOW CAPTURE`
+    :attr:`~BACK_FLOW`
+    :attr:`~BLACK_FLOW_RATIO`
+    """
     FLOW_FACTOR = 0
     FLOW_CAPTURE = 1
     BACK_FLOW = 2
-    BLACK_FLOW_RATIO = 3
\ No newline at end of file
+    BLACK_FLOW_RATIO = 3
+
+class HotstartFile(Enum):
+    """Hotstart File enum class.
+
+    :attr:`~USE`
+    :attr:`~SAVE`
+    """
+    USE = 0
+    SAVE = 1
\ No newline at end of file
diff --git a/swmm-toolkit/src/swmm/toolkit/solver.i b/swmm-toolkit/src/swmm/toolkit/solver.i
index 2fc69d18..49257bc0 100644
--- a/swmm-toolkit/src/swmm/toolkit/solver.i
+++ b/swmm-toolkit/src/swmm/toolkit/solver.i
@@ -160,7 +160,8 @@
     SM_LidUOptions,
     SM_LidResult,
     SM_InletProperty,
-    SM_InletResult
+    SM_InletResult,
+    SM_HotStart
 }
 
 
diff --git a/swmm-toolkit/src/swmm/toolkit/solver_docs.i b/swmm-toolkit/src/swmm/toolkit/solver_docs.i
index d2f02a61..5a440e8b 100644
--- a/swmm-toolkit/src/swmm/toolkit/solver_docs.i
+++ b/swmm-toolkit/src/swmm/toolkit/solver_docs.i
@@ -377,6 +377,17 @@ patch: char **
 "
 ) swmm_getVersionInfo;
 
+%feature("autodoc", 
+"Load or save a hotstart file into or out of a running simulation.
+
+Parameters
+----------
+operation: SM_HotStart
+    The hotstart operation to use (i.e. save or load)
+hsfile: str
+    The path to the hotstart file to either save or load
+"
+) swmm_hotstart;
 
 %feature("autodoc",
 "Finds the index of an object given its ID.
diff --git a/swmm-toolkit/src/swmm/toolkit/solver_rename.i b/swmm-toolkit/src/swmm/toolkit/solver_rename.i
index 2d72a89b..14db8184 100644
--- a/swmm-toolkit/src/swmm/toolkit/solver_rename.i
+++ b/swmm-toolkit/src/swmm/toolkit/solver_rename.i
@@ -105,4 +105,5 @@
 
 
 %rename(swmm_version_info)                  swmm_getSemVersion;
-%rename(swmm_build_id)                      swmm_getBuildId;
\ No newline at end of file
+%rename(swmm_build_id)                      swmm_getBuildId;
+%rename(swmm_hotstart)                      swmm_hotstart;
\ No newline at end of file
diff --git a/swmm-toolkit/swmm-solver b/swmm-toolkit/swmm-solver
index 2e5b21d5..27dc6990 160000
--- a/swmm-toolkit/swmm-solver
+++ b/swmm-toolkit/swmm-solver
@@ -1,1 +1,1 @@
-Subproject commit 2e5b21d5ce268428765f78b4e1ce6601e64e3c39
+Subproject commit 27dc6990cda8bce21f6c16ddae631b37e7510ffc


Test patch:
diff --git a/.github/workflows/conda-test.yml b/.github/workflows/conda-test.yml
deleted file mode 100644
index 6890163a..00000000
--- a/.github/workflows/conda-test.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: Conda Tests
-
-on:
-  workflow_run:
-    workflows: ["Build Wheels"]
-    types:
-      - completed
-
-
-jobs:
-  test_conda_build:
-    name: Conda wheel test mac
-    runs-on: macos-latest
-    defaults:
-      run:
-        shell: bash -l {0}
-
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v2
-        with:
-          submodules: true
-
-      - uses: conda-incubator/setup-miniconda@v2
-        with:
-          auto-update-conda: true
-          channels: conda-forge
-          # Python 3.9 does not support all libs we need in swmm-python
-          python-version: 3.8
-
-      - name: Install required system packages
-        run: brew install swig ninja
-
-      - name: Install requirements
-        run: |
-          cd swmm-toolkit
-          conda create -n test-env pip
-          conda activate test-env
-          pip install -r build-requirements.txt
-          python setup.py bdist_wheel
-          conda deactivate
-
-      - name: Test wheel
-        run: |
-          cd swmm-toolkit
-          conda activate test-env
-          pip install -r test-requirements.txt
-          python -m pip install --no-index --find-links=./dist swmm_toolkit
-          pytest
-          conda deactivate
diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml
new file mode 100644
index 00000000..c496033f
--- /dev/null
+++ b/.github/workflows/unit_test.yml
@@ -0,0 +1,63 @@
+name: Unit Test
+
+on: 
+  push:
+    branches-ignore:    
+      - 'master'
+    tags-ignore:
+      - v*
+  pull_request:
+    branches-ignore:    
+      - 'master'
+
+jobs:
+  build_and_test:
+    name: Build and test on ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    defaults:
+      run:
+        working-directory: ./swmm-toolkit
+
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-2022, macos-12, ubuntu-latest]
+        include:
+          - os: windows-2022
+            sys_pkgs: choco install swig
+            activate: ./build-env/Scripts/activate
+
+          - os: macos-12
+            sys_pkgs: brew install swig ninja
+            activate: source ./build-env/bin/activate
+          
+          - os: ubuntu-latest
+            activate: source ./build-env/bin/activate
+
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          submodules: true
+
+      - name: Install Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.11"
+
+      - name: Install required system packages
+        run: ${{matrix.sys_pkgs}}
+
+      - name: Build wheel in virtual env
+        run: |
+          python -m venv --clear ./build-env
+          ${{matrix.activate}}
+          python -m pip install -r build-requirements.txt
+          python setup.py bdist_wheel
+          deactivate
+
+      - name: Test wheel
+        run: |
+          pip install -r test-requirements.txt
+          pip install --no-index --find-links=./dist swmm_toolkit
+          pytest
\ No newline at end of file
diff --git a/swmm-toolkit/test-requirements.txt b/swmm-toolkit/test-requirements.txt
index f46c81c6..cbb4f032 100644
--- a/swmm-toolkit/test-requirements.txt
+++ b/swmm-toolkit/test-requirements.txt
@@ -3,5 +3,5 @@
 
 pytest == 7.1.1
 numpy  == 1.21.6; python_version == "3.7"
-numpy  == 1.23.4; python_version >= "3.8"
+numpy  == 1.24.4; python_version >= "3.8"
 aenum  == 3.1.11
diff --git a/swmm-toolkit/tests/test_solver.py b/swmm-toolkit/tests/test_solver.py
index 6fcec9a9..766089e4 100644
--- a/swmm-toolkit/tests/test_solver.py
+++ b/swmm-toolkit/tests/test_solver.py
@@ -832,4 +832,22 @@ def test_inlet_clogging(inlet_handle):
     assert dep_height == 0.5
 
     dep_width = solver.inlet_get_parameter(index, shared_enum.InletProperty.DEPRESSION_WIDTH)
-    assert dep_width == 2
\ No newline at end of file
+    assert dep_width == 2
+
+def test_hotstart(run_inlet_sim):
+    for i in range(0, 100):
+        solver.swmm_step()
+
+    # save hotstart and pull out a node depth
+    solver.swmm_hotstart(shared_enum.HotstartFile.SAVE,'HS_FILE.hs')
+    prev_depth = solver.node_get_result(0, shared_enum.NodeResult.DEPTH)
+    solver.swmm_end()
+    solver.swmm_close()
+
+    # restart sim using hotstart file and check that node depth is the same
+    solver.swmm_open(INPUT_FILE_INLET, REPORT_FILE_INLET, OUTPUT_FILE_INLET)
+    solver.swmm_hotstart(shared_enum.HotstartFile.USE,'HS_FILE.hs')
+    solver.swmm_start(0)
+    new_depth = solver.node_get_result(0, shared_enum.NodeResult.DEPTH)
+    assert prev_depth > 0
+    assert prev_depth == pytest.approx(new_depth, 0.1)