Skip to content

Add Python bindings to Btwxt via SWIG - #44

Open
jmarrec wants to merge 22 commits into
bigladder:mainfrom
jmarrec:python_bindings
Open

Add Python bindings to Btwxt via SWIG#44
jmarrec wants to merge 22 commits into
bigladder:mainfrom
jmarrec:python_bindings

Conversation

@jmarrec

@jmarrec jmarrec commented Jan 27, 2025

Copy link
Copy Markdown

I was struggling to understand what EnergyPlus was doing with my Table:Lookup and Table:IndependentVariables, and wanted a way to calculate and plot what was going on (and compare with scipy.interpolate.RegularGridInterpolator).

Instead of using a debugger, or tweaking E+ source code to spit out the values I needed, I figured it would be much better to add Python bindings.

Since I had done that, I figured it could be useful for other people (at least some E+ devs, and yourselves as btwxt devs for inspection/validation purposes), so I'm proposing it for inclusion.

This PR:

Find a working workflow with artifacts here: https://github.com/jmarrec/btwxt/actions/runs/12986553478

python_bindings: true
arch: arm64
- os: macos
os_ver: "11"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macos-11 was removed from GHA a long time ago.

arch: x86_64
- os: macos
os_ver: "12"
os_ver: "14"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macos-12 is removed.

actions/runner-images#10721

I uses 13 (x86_64) and 14 (arm64, M1)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CMakeLists.txt
Comment on lines +3 to +4
cmake_policy(SET CMP0078 NEW)
cmake_policy(SET CMP0086 NEW)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of SWIG-related policies

Comment thread CMakeLists.txt
add_subdirectory("include/${PROJECT_NAME}")
add_subdirectory(src)
add_subdirectory(vendor)
add_subdirectory(python)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add subdir. Did not change the other directories.

Comment thread python/CMakeLists.txt
Comment on lines +85 to +89
add_custom_target(python_package
COMMAND ${CMAKE_COMMAND} -E rm -Rf "build/" "dist/" "btwxt.egg-info/"
COMMAND ${CMAKE_COMMAND} -E echo "To create the package, cd 'python/'"
COMMAND ${CMAKE_COMMAND} -E echo "${Python_EXECUTABLE} setup.py bdist_wheel"
COMMAND ${Python_EXECUTABLE} setup.py bdist_wheel

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build the python wheel (.whl) with this custom target

Comment thread python/CMakeLists.txt
Comment on lines +90 to +91
COMMAND ${CMAKE_COMMAND} -E echo "To upload the package, cd 'python/'"
COMMAND ${CMAKE_COMMAND} -E echo "${Python_EXECUTABLE} -m twine upload --verbose --skip-existing --repository ${PYTHON_PIP_REPOSITORY} dist/*"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not upload, but tells you how

Comment thread python/CMakeLists.txt
Comment on lines +99 to +109
if (${PROJECT_NAME}_BUILD_TESTING)
set(test_case_name btwxt_python)
add_test(
NAME "${test_case_name}"
COMMAND ${Python_EXECUTABLE} -m pytest --verbose test_btwxt.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test
)
# Pytest Exit code 5 means all tests were deselected
set_tests_properties("${test_case_name}" PROPERTIES SKIP_RETURN_CODE 5)
set_tests_properties("${test_case_name}" PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}/btwxt")
endif()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Register a pytest file as a CTest.

Comment thread test/test_btwxt.py
Comment on lines +1 to +66
import btwxt
import pytest
from itertools import product

def test_grid_axis():
axis_x = btwxt.GridAxis()
assert axis_x.get_length() == 0

xs = [30, 35, 40, 45, 50, 55]
axis_x = btwxt.GridAxis(
[30, 35, 40, 45, 50, 55], # Values
btwxt.InterpolationMethod_cubic, # interpolation method
btwxt.ExtrapolationMethod_linear, # extrapolation_method
(20.0, 60.0), # extrapolation_limits
"LWT", # name
)
assert axis_x.get_length() == 6
assert set(axis_x.get_values()) == set(xs)
assert axis_x.get_interpolation_method() == btwxt.InterpolationMethod_cubic
assert axis_x.get_extrapolation_method() == btwxt.ExtrapolationMethod_linear
assert axis_x.get_extrapolation_limits() == (20.0, 60.0)


def F(x, y):
return x**2 + y**2

def test_interpolator():
x0 = [1.0, 2.0, 3.0]
x1 = [10.0, 20.0]
# Not using numpy on purpose
ys = [F(x,y) for (x, y) in product(x0, x1)]

axis_x0 = btwxt.GridAxis(
x0, # Values
btwxt.InterpolationMethod_cubic, # interpolation method
btwxt.ExtrapolationMethod_linear, # extrapolation_method
(0, 5), # extrapolation_limits
"x0", # name
)
axis_x1 = btwxt.GridAxis(
x1, # Values
btwxt.InterpolationMethod_cubic, # interpolation method
btwxt.ExtrapolationMethod_linear, # extrapolation_method
(0, 30), # extrapolation_limits
"x1", # name
)
grid_point_y = btwxt.GridPointDataSet(ys, "y")

interpolator = btwxt.RegularGridInterpolator([axis_x0, axis_x1], [grid_point_y])

target_normalization = [2, 10]
normalizationDivisor = 1.0
normalizationDivisor = interpolator.normalize_grid_point_data_set_at_target(
0, target_normalization, normalizationDivisor
) * normalizationDivisor
assert normalizationDivisor == 104.0

interp_x0s = [float(x) for x in range(0, 6, 1)]
interp_oat_dbs = [float(x) for x in range(0, 31, 5)]

test_points = [(x,y) for (x, y) in product(interp_x0s, interp_oat_dbs)]
true_values = [F(x, y) for (x, y) in test_points]
interp_values = [
interpolator.get_value_at_target((x0, x1), 0) * normalizationDivisor
for (x0, x1) in test_points
]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest file to test out the python bindings

Comment on lines +114 to +158
- name: Set up Python 3.12
if: ${{ matrix.python_bindings }}
id: setup-python
uses: actions/setup-python@v5
with:
python-version: 3.12

- name: Build Python Wheel
if: ${{ matrix.python_bindings }}
working-directory: ./build/
run: |
begin_group() { echo -e "::group::\033[93m$1\033[0m"; }

begin_group "Install other python dependencies"
python -m pip install --upgrade pip
# Ubuntu 22.04 from apt will get swig that's too old to support attributes like [[ nodiscard ]], so get from PyPi
pip install setuptools wheel twine requests packaging pytest swig
echo -e "::endgroup::"

begin_group "Build target"
cmake -DBUILD_PYTHON_BINDINGS:BOOL=ON -DPython_REQUIRED_VERSION:STRING=${{ steps.setup-python.outputs.python-version }} -DPython_ROOT_DIR:PATH=$RUNNER_TOOL_CACHE/Python/${{ steps.setup-python.outputs.python-version }}/${{ matrix.arch }}/ .
cmake --build . --config ${{ matrix.config }} --target python_package
echo -e "::endgroup::"

- name: Python CTest
if: ${{ matrix.python_bindings }}
working-directory: ./build/
run: |
ctest -C ${{ matrix.config }} -R python -VV

- name: Zip the wheel to maintain case sensitivy and file permissions
if: ${{ matrix.python_bindings }}
working-directory: ./build/python
shell: bash
run: |
TGZ_STEM="btwxt-python-${{ matrix.os }}-${{ matrix.os_ver }}-${{ matrix.arch }}"
echo "TGZ_STEM=$TGZ_STEM" >> $GITHUB_ENV
tar -cvzf "$TGZ_STEM.tar.gz" dist/

- name: Upload .whl to artifact
if: ${{ matrix.python_bindings }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.TGZ_STEM }}
path: "./build/python/${{ env.TGZ_STEM }}.tar.gz"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New workflow bits to build the python bindings after the regular build is done.

Runs the python ctest, and uploads the wheel as artifacts

@nealkruis

Copy link
Copy Markdown
Member

@jmarrec, thanks for putting this together! We've always had the intention to provide bindings for our C++ libraries, but we haven't yet settled on the best approach. We also haven't gotten very far in investigating the options, and I really don't know much about SWIG other than seeing some mixed reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants