From d2db08259b432e629734dfa944a57e03510b36d8 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:33:36 +0100 Subject: [PATCH 01/11] Got started on interoperability tutorial --- .../generate_tutorial_notebooks.yaml | 42 +++++++++++++++++++ tutorials/scripts/interoperability.py | 41 ++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 .github/workflows/generate_tutorial_notebooks.yaml create mode 100644 tutorials/scripts/interoperability.py diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml new file mode 100644 index 0000000000..e0dc24318e --- /dev/null +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -0,0 +1,42 @@ +name: Generate tutorial notebooks + +on: push + +jobs: + generate_notebooks: + runs-on: ubuntu-latest + timeout-minutes: 120 + name: Setup + steps: + - name: Harden Runner + uses: step-security/harden-runner + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout + + - name: Setup MPI + uses: mpi4py/setup-mpi + with: + mpi: openmpi + + - name: Use Python ${{ matrix.py-version }} + uses: actions/setup-python + with: + python-version: ${{ matrix.py-version }} + architecture: x64 + + - name: Test + run: | + pip install jupytext + python tutorials/interoperability.py + - name: Run each .py file individually + run: | + set -e + find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do + base="$(basename "$file" .py)" + echo "Running $file and converting to ${base}.ipynb" + mpirun -np 2 python "$file" + mpirun -np 2 jupytext --to ipynb --execute $file -o tutorials/notebooks/${base}.ipynb + done diff --git a/tutorials/scripts/interoperability.py b/tutorials/scripts/interoperability.py new file mode 100644 index 0000000000..d8e1e35996 --- /dev/null +++ b/tutorials/scripts/interoperability.py @@ -0,0 +1,41 @@ +# %% [markdown] +# Interoperability of Heat DNDarray with other python array libraries +# =================================================================== + +# %% +import heat as ht +import torch +data_heat = ht.arange(4 * 3).reshape(4, 3).resplit(None) +data_heat_split = ht.arange(4 * 3).reshape(4, 3).resplit(0) + +print(f'Running with {ht.comm.size} tasks') + +# %% [markdown] +# NumPy +# ----- +# We begin with unsplit data, which we can simply cast back and forth between numpy and heat: + +# %% +import numpy as np +data_numpy = np.arange(4 * 3).reshape(4, 3) +assert np.allclose(data_numpy, np.array(data_heat)) +assert ht.allclose(data_heat, ht.array(data_numpy)) + +# %% [markdown] +# With the split data, we need to be more careful, because if while casting from heat to numpy, we get only the local data on each process. +# We can use the `chunk` method of heat communicators to compute the shape and slices of local data given a heat array of any shape and split. +# Note that you should use this slice on the numpy data in order to extract the process local data. You should not use this on the heat data, because you never want to use different slices on each task in heat, as heat takes care of distributing the data among processes itself. + +# %% +global_shape, split = data_heat_split.shape, data_heat_split.split +offset, local_shape, slices = data_heat_split.comm.chunk(global_shape, split) +assert np.allclose(data_numpy[*slices], np.array(data_heat_split)) + +# %% [markdown] +# Going from process-local numpy data to a heat array is a bit more complicated. +# We need to access the `larray` attribute of the heat array, which is a torch tensor, so we cast to torch and then assign this to the local heat data. + +# %% +_data_heat_split = ht.empty(global_shape, split=split, dtype=ht.int) +_data_heat_split.larray[...] = torch.from_numpy(data_numpy[*slices]) +assert ht.allclose(_data_heat_split, data_heat_split) From cea0a58e3a5a06959300f6aff0ba8b78f2a151e7 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:39:22 +0100 Subject: [PATCH 02/11] Add hex codes --- .github/workflows/generate_tutorial_notebooks.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index e0dc24318e..0018715c62 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -9,20 +9,20 @@ jobs: name: Setup steps: - name: Harden Runner - uses: step-security/harden-runner + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup MPI - uses: mpi4py/setup-mpi + uses: mpi4py/setup-mpi@3969f247e8fceef153418744f9d9ee6fdaeda29f # v1.2.0 with: mpi: openmpi - name: Use Python ${{ matrix.py-version }} - uses: actions/setup-python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.py-version }} architecture: x64 From 7d5765700d4102364190fa7e52bdd7f5ca1c1427 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:41:36 +0100 Subject: [PATCH 03/11] Fix path --- .github/workflows/generate_tutorial_notebooks.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 0018715c62..cf9cdfcac7 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -6,7 +6,7 @@ jobs: generate_notebooks: runs-on: ubuntu-latest timeout-minutes: 120 - name: Setup + name: Generate Notebooks steps: - name: Harden Runner uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 @@ -21,10 +21,10 @@ jobs: with: mpi: openmpi - - name: Use Python ${{ matrix.py-version }} + - name: Use Python 3.13 uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: - python-version: ${{ matrix.py-version }} + python-version: 3.13 architecture: x64 - name: Test @@ -34,7 +34,7 @@ jobs: - name: Run each .py file individually run: | set -e - find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do + find ../tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do base="$(basename "$file" .py)" echo "Running $file and converting to ${base}.ipynb" mpirun -np 2 python "$file" From f259485b2c9931185be6bb84798d9db40f2ef66b Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:44:50 +0100 Subject: [PATCH 04/11] Remove old thing --- .github/workflows/generate_tutorial_notebooks.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index cf9cdfcac7..3776f8f870 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -30,13 +30,12 @@ jobs: - name: Test run: | pip install jupytext - python tutorials/interoperability.py - name: Run each .py file individually run: | set -e - find ../tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do + find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do base="$(basename "$file" .py)" - echo "Running $file and converting to ${base}.ipynb" + echo "Running $file and converting to tutorials/notebooks/${base}.ipynb" mpirun -np 2 python "$file" mpirun -np 2 jupytext --to ipynb --execute $file -o tutorials/notebooks/${base}.ipynb done From 47ad88fb089ac37d098f4277929444f9ab0bbeca Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:46:44 +0100 Subject: [PATCH 05/11] Install heat --- .github/workflows/generate_tutorial_notebooks.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 3776f8f870..0cb26dd886 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -21,16 +21,18 @@ jobs: with: mpi: openmpi - - name: Use Python 3.13 + - name: Setup Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: 3.13 architecture: x64 - - name: Test + - name: Install heat and jupytext run: | + pip install '.[hdf5,netcdf,pandas,zarr]' pip install jupytext - - name: Run each .py file individually + + - name: Run and convert python files run: | set -e find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do From 316099f9b4212bcc892885c8a1ea42d7c20e2bed Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:51:04 +0100 Subject: [PATCH 06/11] Installing also jupyter --- .github/workflows/generate_tutorial_notebooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 0cb26dd886..464dde0685 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -30,7 +30,7 @@ jobs: - name: Install heat and jupytext run: | pip install '.[hdf5,netcdf,pandas,zarr]' - pip install jupytext + pip install jupytext notebook jupyter_client - name: Run and convert python files run: | From 82796f87386294df43f6f660a92d268ed6478f4a Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:56:57 +0100 Subject: [PATCH 07/11] Installing jupyter kernel --- .github/workflows/generate_tutorial_notebooks.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 464dde0685..9cff1085fb 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -32,6 +32,9 @@ jobs: pip install '.[hdf5,netcdf,pandas,zarr]' pip install jupytext notebook jupyter_client + - name: Install jupyter kernel + run: python -m ipykernel install --name heat_kernel --user + - name: Run and convert python files run: | set -e From 8a4d0036a6c0bffc3b4fdb47a2e0814cd9bcc73f Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:00:57 +0100 Subject: [PATCH 08/11] Use relative paths --- .github/workflows/generate_tutorial_notebooks.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 9cff1085fb..14a399bc13 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -32,7 +32,7 @@ jobs: pip install '.[hdf5,netcdf,pandas,zarr]' pip install jupytext notebook jupyter_client - - name: Install jupyter kernel + - name: Install Jupyter kernel run: python -m ipykernel install --name heat_kernel --user - name: Run and convert python files @@ -40,7 +40,7 @@ jobs: set -e find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do base="$(basename "$file" .py)" - echo "Running $file and converting to tutorials/notebooks/${base}.ipynb" + echo "Running $file and converting to ./tutorials/notebooks/${base}.ipynb" mpirun -np 2 python "$file" - mpirun -np 2 jupytext --to ipynb --execute $file -o tutorials/notebooks/${base}.ipynb + mpirun -np 2 jupytext --to ipynb --execute $file -o ./tutorials/notebooks/${base}.ipynb done From 183c1a2bcfe5f345e5f41c1a844583c5e51c4e37 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:04:03 +0100 Subject: [PATCH 09/11] Create notbooks directory --- .github/workflows/generate_tutorial_notebooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 14a399bc13..82571b4567 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -37,7 +37,7 @@ jobs: - name: Run and convert python files run: | - set -e + mkdir -p .tutorials/notebooks find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do base="$(basename "$file" .py)" echo "Running $file and converting to ./tutorials/notebooks/${base}.ipynb" From 6ba7fa6ab218f0cb1bd3ffc424b6c2ee02e82476 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:06:31 +0100 Subject: [PATCH 10/11] Fix paths and upload notebooks --- .github/workflows/generate_tutorial_notebooks.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate_tutorial_notebooks.yaml b/.github/workflows/generate_tutorial_notebooks.yaml index 82571b4567..e7073f564e 100644 --- a/.github/workflows/generate_tutorial_notebooks.yaml +++ b/.github/workflows/generate_tutorial_notebooks.yaml @@ -37,10 +37,16 @@ jobs: - name: Run and convert python files run: | - mkdir -p .tutorials/notebooks + mkdir -p ./tutorials/notebooks find ./tutorials/scripts/ -maxdepth 1 -type f -name "*.py" | while read -r file; do base="$(basename "$file" .py)" echo "Running $file and converting to ./tutorials/notebooks/${base}.ipynb" mpirun -np 2 python "$file" mpirun -np 2 jupytext --to ipynb --execute $file -o ./tutorials/notebooks/${base}.ipynb done + + - name: Upload notebooks + uses: actions/upload-artifact@v4 + with: + name: Tutorial_Notebooks + path: ./tutorials/notebooks/ From 70d76f4917526500820e0cca699724781015ef92 Mon Sep 17 00:00:00 2001 From: Thomas Baumann <39156931+brownbaerchen@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:31:42 +0100 Subject: [PATCH 11/11] Improved interoperability tutorial a bit --- tutorials/scripts/interoperability.py | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tutorials/scripts/interoperability.py b/tutorials/scripts/interoperability.py index d8e1e35996..35a2df0f39 100644 --- a/tutorials/scripts/interoperability.py +++ b/tutorials/scripts/interoperability.py @@ -2,13 +2,24 @@ # Interoperability of Heat DNDarray with other python array libraries # =================================================================== +# This tutorial will show how to use data generated in heat with other libraries and vice versa. +# Do have a look if you plan on migrating your code from one of the discussed libraries to heat! + +# This tutorial assumes that you understand the use of the `split` attribute in heat, which determines the distribution of the data. +# If you don't feel comfortable with this yet, please check out a basic tutorial on heat first. + +# Before discussing how to interoperate with other libraries, we import heat and set up some data. +# Some things are different when the data is distributed, which is why we generate split and unsplit data. +# Note that you need to run this tutorial with multiple MPI tasks for the split attribute to matter as otherwise the data will not be distributed regardless of the split attribute. + # %% import heat as ht -import torch + data_heat = ht.arange(4 * 3).reshape(4, 3).resplit(None) data_heat_split = ht.arange(4 * 3).reshape(4, 3).resplit(0) print(f'Running with {ht.comm.size} tasks') +print(f'`data_heat_split` is{' not' if not data_heat_split.is_distributed() else ''} distributed') # %% [markdown] # NumPy @@ -17,25 +28,30 @@ # %% import numpy as np + data_numpy = np.arange(4 * 3).reshape(4, 3) -assert np.allclose(data_numpy, np.array(data_heat)) -assert ht.allclose(data_heat, ht.array(data_numpy)) + +assert np.allclose(data_numpy, np.array(data_heat)) # convert heat to numpy +assert ht.allclose(data_heat, ht.array(data_numpy)) # convert numpy to heat # %% [markdown] -# With the split data, we need to be more careful, because if while casting from heat to numpy, we get only the local data on each process. -# We can use the `chunk` method of heat communicators to compute the shape and slices of local data given a heat array of any shape and split. -# Note that you should use this slice on the numpy data in order to extract the process local data. You should not use this on the heat data, because you never want to use different slices on each task in heat, as heat takes care of distributing the data among processes itself. +# With the split data, we need to be more careful, because while casting from heat to numpy, we get only the local data on each process. +# We can use the `chunk` method of heat communicators to compute the shape and slices of local data given an array of any shape and split. + +# Note that you should use this slice on the numpy data in order to extract the process local data. +# You should not use this on the heat data, because you never want to use different slices on each task in heat, as this would conflict with heat taking care of distributing the data itself. # %% global_shape, split = data_heat_split.shape, data_heat_split.split offset, local_shape, slices = data_heat_split.comm.chunk(global_shape, split) -assert np.allclose(data_numpy[*slices], np.array(data_heat_split)) +assert np.allclose(data_numpy[*slices], np.array(data_heat_split)) # convert from heat to numpy # %% [markdown] # Going from process-local numpy data to a heat array is a bit more complicated. # We need to access the `larray` attribute of the heat array, which is a torch tensor, so we cast to torch and then assign this to the local heat data. # %% +import torch _data_heat_split = ht.empty(global_shape, split=split, dtype=ht.int) _data_heat_split.larray[...] = torch.from_numpy(data_numpy[*slices]) -assert ht.allclose(_data_heat_split, data_heat_split) +assert ht.allclose(_data_heat_split, data_heat_split) # convert from numpy to heat