diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 8f2f2174..d702a428 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -4,34 +4,111 @@ on: [push] jobs: build-and-test: - name: build ${{ matrix.python-version }} on ${{ matrix.platform || matrix.os }} + name: build py3.${{ matrix.python-version }} on ${{ matrix.platform || matrix.os.base }}-${{ matrix.os.version }} strategy: fail-fast: true + # NOTE: In the matrix below, we call out macos 13 and latest (> 13) explicitly. This is to enable running on both Intel and Apple Silicon for Mac. + # As of this writing, MacOS 13 is Intel and MacOS 14 and above is Apple Silicon. Ideally, the Apple transition to their new chip occurs fast and + # we can drop macos-13 checks and then just use 'latest'. matrix: os: - - ubuntu - - macos - - windows + - base: ubuntu + version: latest + - base: macos + version: '13' + arch: 'x86_64' + - base: macos + version: 'latest' + arch: 'arm64' + - base: windows + version: latest + arch: 'amd64' python-version: - - "3.8" - - "3.9" - - "3.10" - runs-on: ${{ format('{0}-latest', matrix.os) }} + - "10" + - "11" + include: + - os: + base: ubuntu + version: latest + platform: linux + - os: + base: windows + version: latest + ls: dir + runs-on: ${{ format('{0}-{1}', matrix.os.base, matrix.os.version) }} steps: - uses: actions/checkout@v4 + - name: set up rust uses: dtolnay/rust-toolchain@stable - - run: rustup target add aarch64-apple-darwin - if: matrix.os == 'macos' + + - name: install rust target for MacOS 14 (Apple Silicon) + run: rustup target add aarch64-apple-darwin x86_64-apple-darwin + if: matrix.os.base == 'macos' && matrix.os.version == '14' + + - name: install rust target for MacOS 13 (Intel Mac) + run: rustup target add x86_64-apple-darwin aarch64-apple-darwin + if: matrix.os.base == 'macos' && matrix.os.version == '13' + + - name: Update minimum supported MacOS for Wheels for MacOS 13 (Intel Mac) + run: echo "MACOSX_DEPLOYMENT_TARGET=10.12" >> $GITHUB_ENV + if: matrix.os.base == 'macos' && matrix.os.version == '13' + + - name: run cargo tests + run: cargo test + - name: set up python - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 + # uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: "3.10" + - name: install python dependencies - run: python -m pip install --upgrade pip setuptools wheel pytest - - name: run cargo tests - run: cargo test + run: python -m pip install --upgrade pip setuptools wheel twine cibuildwheel pytest + + - name: build source distribution + if: matrix.os.base == 'ubuntu' && matrix.python-version == '10' + run: | + python -m pip install -U setuptools-rust + python -c "import setuptools; setuptools.setup()" sdist + - name: install FASTSim python - run: pip install -e .[dev] + run: python -m pip install -e .[dev] + - name: run pytest run: pytest -v + + - name: build ${{ matrix.platform || matrix.os.base }} binaries + run: cibuildwheel --output-dir dist + env: + CIBW_BUILD: "cp3${{ matrix.python-version }}-*" + CIBW_SKIP: "*-win32 *-musllinux* *i686 *ppc64le *s390x *aarch64" + CIBW_PLATFORM: ${{ matrix.platform || matrix.os.base }} + # TODO: why doesn't pytest work with cibuildwheel? + # CIBW_TEST_COMMAND: "pytest -v {project}/python/fastsim/tests" + CIBW_TEST_COMMAND: "python -m unittest discover {project}/python/fastsim/tests" + CIBW_ARCHS_MACOS: "${{ matrix.os.arch }}" + # see https://cibuildwheel.readthedocs.io/en/stable/faq/#universal2 + CIBW_TEST_SKIP: "*_universal2:arm64" + CIBW_ENVIRONMENT: 'PATH="$HOME/.cargo/bin:$PATH"' + CIBW_ENVIRONMENT_WINDOWS: 'PATH="$UserProfile\.cargo\bin;$PATH"' + CIBW_MANYLINUX_X86_64_IMAGE: "manylinux2014" + CIBW_MANYLINUX_I686_IMAGE: "manylinux2014" + CIBW_BEFORE_BUILD: > + pip install -U setuptools-rust && + rustup default stable && + rustup show + CIBW_BEFORE_BUILD_LINUX: > + yum -y install openssl openssl-devel && + pip install -U setuptools-rust && + curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=nightly --profile=minimal -y && + rustup show + + - name: list dist files + run: ${{ matrix.ls || 'ls -lh' }} dist/ + + - uses: actions/upload-artifact@v4 + with: + name: fastsim-${{ matrix.platform || matrix.os.base }}-${{ matrix.os.version }}-py3.${{ matrix.python }}-${{ strategy.job-index }} + path: ./dist/* + diff --git a/pyproject.toml b/pyproject.toml index 6a8f386b..8247945d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [{ name = "NREL/MTES/CIMS/MBAP Group", email = "fastsim@nrel.gov" }] description = "Tool for modeling vehicle powertrains" readme = "README.md" license = { file = "LICENSE.md" } -requires-python = ">=3.8,<3.11" +requires-python = ">=3.10,<3.12" classifiers = [ "Programming Language :: Python :: 3", "License :: Other/Proprietary License", diff --git a/python/fastsim/tests/test_speedup.py b/python/fastsim/tests/test_speedup.py index e9784b3f..af10a55e 100644 --- a/python/fastsim/tests/test_speedup.py +++ b/python/fastsim/tests/test_speedup.py @@ -1,147 +1,148 @@ import time import numpy as np import fastsim as fsim -import pytest +import os n_iters = 5 +run_tests = os.environ.get("RUN_SPEEDUP", "false").lower() == "true" -@pytest.mark.skip(reason = "Ignoring for now") -def test_hev_speedup(): - # minimum allowed f3 / f2 speed ratio - min_speed_ratio_si_none = 2 - min_speed_ratio_si_1 = 1.4 +if run_tests: + def test_hev_speedup(): + # minimum allowed f3 / f2 speed ratio + min_speed_ratio_si_none = 2 + min_speed_ratio_si_1 = 1.4 - # load 2016 Toyota Prius Two from file - veh = fsim.Vehicle.from_resource("2016_TOYOTA_Prius_Two.yaml") - - # Set `save_interval` at vehicle level -- cascades to all sub-components with time-varying states - fsim.set_param_from_path(veh, "save_interval", 1) - - # load cycle from file - cyc = fsim.Cycle.from_resource("udds.csv") - - # instantiate `SimDrive` simulation object - sd0 = fsim.SimDrive(veh, cyc) - - t_fsim2_list = [] - t_fsim3_list = [] - t_fsim3_no_save_list = [] - - for i in range(n_iters): - sd = sd0.copy() - # simulation start time - t0 = time.perf_counter() - sd.walk() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim3_list.append(t1 - t0) - - sd_no_save = sd0.copy() - fsim.set_param_from_path(sd_no_save, "veh.save_interval", None) - # simulation start time - t0 = time.perf_counter() - sd_no_save.walk() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim3_no_save_list.append(t1 - t0) - - sd2 = sd0.to_fastsim2() - # simulation start time - t0 = time.perf_counter() - sd2.sim_drive() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim2_list.append(t1 - t0) - - t_fsim2_mean = np.mean(t_fsim2_list) - t_fsim3_mean = np.mean(t_fsim3_list) - t_fsim3_no_save_mean = np.mean(t_fsim3_no_save_list) - t_fsim2_median = np.median(t_fsim2_list) - t_fsim3_median = np.median(t_fsim3_list) - t_fsim3_no_save_median = np.median(t_fsim3_no_save_list) - - assert t_fsim2_mean / t_fsim3_mean > min_speed_ratio_si_1, \ - f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, mean achieved ratio: {(t_fsim2_mean / t_fsim3_mean):.3G}" - assert t_fsim2_median / t_fsim3_median > min_speed_ratio_si_1, \ - f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, median achieved ratio: {(t_fsim2_median / t_fsim3_median):.3G}" - - assert t_fsim2_mean / t_fsim3_no_save_mean > min_speed_ratio_si_none, \ - f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, mean achieved ratio: {(t_fsim2_mean / t_fsim3_no_save_mean):.3G}" - assert t_fsim2_median / t_fsim3_no_save_median > min_speed_ratio_si_none, \ - f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, median achieved ratio: {(t_fsim2_median / t_fsim3_no_save_median):.3G}" - -@pytest.mark.skip(reason = "Ignoring for now") -def test_conv_speedup(): - # minimum allowed f3 / f2 speed ratio - # there is some wiggle room on these but we're trying to get 10x speedup - # relative to fastsim-2 with `save_interval` of `None` - min_speed_ratio_si_none = 3.8 - min_speed_ratio_si_1 = 2.0 + # load 2016 Toyota Prius Two from file + veh = fsim.Vehicle.from_resource("2016_TOYOTA_Prius_Two.yaml") + + # Set `save_interval` at vehicle level -- cascades to all sub-components with time-varying states + fsim.set_param_from_path(veh, "save_interval", 1) + + # load cycle from file + cyc = fsim.Cycle.from_resource("udds.csv") + + # instantiate `SimDrive` simulation object + sd0 = fsim.SimDrive(veh, cyc) + + t_fsim2_list = [] + t_fsim3_list = [] + t_fsim3_no_save_list = [] + + for i in range(n_iters): + sd = sd0.copy() + # simulation start time + t0 = time.perf_counter() + sd.walk() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim3_list.append(t1 - t0) + + sd_no_save = sd0.copy() + fsim.set_param_from_path(sd_no_save, "veh.save_interval", None) + # simulation start time + t0 = time.perf_counter() + sd_no_save.walk() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim3_no_save_list.append(t1 - t0) + + sd2 = sd0.to_fastsim2() + # simulation start time + t0 = time.perf_counter() + sd2.sim_drive() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim2_list.append(t1 - t0) + + t_fsim2_mean = np.mean(t_fsim2_list) + t_fsim3_mean = np.mean(t_fsim3_list) + t_fsim3_no_save_mean = np.mean(t_fsim3_no_save_list) + t_fsim2_median = np.median(t_fsim2_list) + t_fsim3_median = np.median(t_fsim3_list) + t_fsim3_no_save_median = np.median(t_fsim3_no_save_list) + + assert t_fsim2_mean / t_fsim3_mean > min_speed_ratio_si_1, \ + f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, mean achieved ratio: {(t_fsim2_mean / t_fsim3_mean):.3G}" + assert t_fsim2_median / t_fsim3_median > min_speed_ratio_si_1, \ + f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, median achieved ratio: {(t_fsim2_median / t_fsim3_median):.3G}" + + assert t_fsim2_mean / t_fsim3_no_save_mean > min_speed_ratio_si_none, \ + f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, mean achieved ratio: {(t_fsim2_mean / t_fsim3_no_save_mean):.3G}" + assert t_fsim2_median / t_fsim3_no_save_median > min_speed_ratio_si_none, \ + f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, median achieved ratio: {(t_fsim2_median / t_fsim3_no_save_median):.3G}" + + + def test_conv_speedup(): + # minimum allowed f3 / f2 speed ratio + # there is some wiggle room on these but we're trying to get 10x speedup + # relative to fastsim-2 with `save_interval` of `None` + min_speed_ratio_si_none = 3.8 + min_speed_ratio_si_1 = 2.0 - # load 2016 Toyota Prius Two from file - veh = fsim.Vehicle.from_resource("2012_Ford_Fusion.yaml") - - # Set `save_interval` at vehicle level -- cascades to all sub-components with time-varying states - fsim.set_param_from_path(veh, "save_interval", 1) - - # load cycle from file - cyc = fsim.Cycle.from_resource("udds.csv") - - # instantiate `SimDrive` simulation object - sd0 = fsim.SimDrive(veh, cyc) - - t_fsim2_list = [] - t_fsim3_list = [] - t_fsim3_no_save_list = [] - - for i in range(n_iters): - sd = sd0.copy() - # simulation start time - t0 = time.perf_counter() - sd.walk() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim3_list.append(t1 - t0) - - sd_no_save = sd0.copy() - fsim.set_param_from_path(sd_no_save, "veh.save_interval", None) - # simulation start time - t0 = time.perf_counter() - sd_no_save.walk() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim3_no_save_list.append(t1 - t0) - - sd2 = sd0.to_fastsim2() - # simulation start time - t0 = time.perf_counter() - sd2.sim_drive() - # simulation end time - t1 = time.perf_counter() - if i > 1: - t_fsim2_list.append(t1 - t0) - - t_fsim2_mean = np.mean(t_fsim2_list) - t_fsim3_mean = np.mean(t_fsim3_list) - t_fsim3_no_save_mean = np.mean(t_fsim3_no_save_list) - t_fsim2_median = np.median(t_fsim2_list) - t_fsim3_median = np.median(t_fsim3_list) - t_fsim3_no_save_median = np.median(t_fsim3_no_save_list) - - assert t_fsim2_mean / t_fsim3_mean > min_speed_ratio_si_1, \ - f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, achieved ratio: {(t_fsim2_mean / t_fsim3_mean):.3G}" - assert t_fsim2_median / t_fsim3_median > min_speed_ratio_si_1, \ - f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, achieved ratio: {(t_fsim2_median / t_fsim3_median):.3G}" - - assert t_fsim2_mean / t_fsim3_no_save_mean > min_speed_ratio_si_none, \ - f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, achieved ratio: {(t_fsim2_mean / t_fsim3_no_save_mean):.3G}" - assert t_fsim2_median / t_fsim3_no_save_median > min_speed_ratio_si_none, \ - f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, achieved ratio: {(t_fsim2_median / t_fsim3_no_save_median):.3G}" + # load 2016 Toyota Prius Two from file + veh = fsim.Vehicle.from_resource("2012_Ford_Fusion.yaml") + + # Set `save_interval` at vehicle level -- cascades to all sub-components with time-varying states + fsim.set_param_from_path(veh, "save_interval", 1) + + # load cycle from file + cyc = fsim.Cycle.from_resource("udds.csv") + + # instantiate `SimDrive` simulation object + sd0 = fsim.SimDrive(veh, cyc) + + t_fsim2_list = [] + t_fsim3_list = [] + t_fsim3_no_save_list = [] + + for i in range(n_iters): + sd = sd0.copy() + # simulation start time + t0 = time.perf_counter() + sd.walk() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim3_list.append(t1 - t0) + + sd_no_save = sd0.copy() + fsim.set_param_from_path(sd_no_save, "veh.save_interval", None) + # simulation start time + t0 = time.perf_counter() + sd_no_save.walk() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim3_no_save_list.append(t1 - t0) + + sd2 = sd0.to_fastsim2() + # simulation start time + t0 = time.perf_counter() + sd2.sim_drive() + # simulation end time + t1 = time.perf_counter() + if i > 1: + t_fsim2_list.append(t1 - t0) + + t_fsim2_mean = np.mean(t_fsim2_list) + t_fsim3_mean = np.mean(t_fsim3_list) + t_fsim3_no_save_mean = np.mean(t_fsim3_no_save_list) + t_fsim2_median = np.median(t_fsim2_list) + t_fsim3_median = np.median(t_fsim3_list) + t_fsim3_no_save_median = np.median(t_fsim3_no_save_list) + + assert t_fsim2_mean / t_fsim3_mean > min_speed_ratio_si_1, \ + f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, achieved ratio: {(t_fsim2_mean / t_fsim3_mean):.3G}" + assert t_fsim2_median / t_fsim3_median > min_speed_ratio_si_1, \ + f"`min_speed_ratio_si_1`: {min_speed_ratio_si_1:.3G}, achieved ratio: {(t_fsim2_median / t_fsim3_median):.3G}" + + assert t_fsim2_mean / t_fsim3_no_save_mean > min_speed_ratio_si_none, \ + f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, achieved ratio: {(t_fsim2_mean / t_fsim3_no_save_mean):.3G}" + assert t_fsim2_median / t_fsim3_no_save_median > min_speed_ratio_si_none, \ + f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, achieved ratio: {(t_fsim2_median / t_fsim3_no_save_median):.3G}" if __name__ == "__main__": import pytest