From edf41ab8c43fa9efbca7caaecf098770fd798e1c Mon Sep 17 00:00:00 2001 From: Davide Laghi Date: Tue, 2 Jun 2026 15:32:50 +0200 Subject: [PATCH 1/2] add interpolation of HTC and T bulk --- doc/htc/destination_mesh.txt | 76 +++++++++ doc/htc/htc.ipynb | 268 +++++++++++++++++++++++++++++++ doc/htc/source_data/htc_data.txt | 31 ++++ src/interpcore/config.py | 3 + src/interpcore/interpolator.py | 5 + tests/test_interpolator.py | 103 ++++++++++++ 6 files changed, 486 insertions(+) create mode 100644 doc/htc/destination_mesh.txt create mode 100644 doc/htc/htc.ipynb create mode 100644 doc/htc/source_data/htc_data.txt diff --git a/doc/htc/destination_mesh.txt b/doc/htc/destination_mesh.txt new file mode 100644 index 0000000..dfad400 --- /dev/null +++ b/doc/htc/destination_mesh.txt @@ -0,0 +1,76 @@ +Node_ID X Y Z +101 0.25 0.33 0.00 +102 0.25 1.00 0.00 +103 0.25 1.67 0.00 +104 0.25 2.33 0.00 +105 0.25 3.00 0.00 +106 0.25 3.67 0.00 +107 0.25 4.34 0.00 +108 0.25 5.00 0.00 +109 0.25 5.67 0.00 +110 0.25 6.34 0.00 +111 0.25 7.00 0.00 +112 0.25 7.67 0.00 +113 0.25 8.34 0.00 +114 0.25 9.00 0.00 +115 0.25 9.67 0.00 +116 0.75 0.33 0.00 +117 0.75 1.00 0.00 +118 0.75 1.67 0.00 +119 0.75 2.33 0.00 +120 0.75 3.00 0.00 +121 0.75 3.67 0.00 +122 0.75 4.34 0.00 +123 0.75 5.00 0.00 +124 0.75 5.67 0.00 +125 0.75 6.34 0.00 +126 0.75 7.00 0.00 +127 0.75 7.67 0.00 +128 0.75 8.34 0.00 +129 0.75 9.00 0.00 +130 0.75 9.67 0.00 +131 1.25 0.33 0.00 +132 1.25 1.00 0.00 +133 1.25 1.67 0.00 +134 1.25 2.33 0.00 +135 1.25 3.00 0.00 +136 1.25 3.67 0.00 +137 1.25 4.34 0.00 +138 1.25 5.00 0.00 +139 1.25 5.67 0.00 +140 1.25 6.34 0.00 +141 1.25 7.00 0.00 +142 1.25 7.67 0.00 +143 1.25 8.34 0.00 +144 1.25 9.00 0.00 +145 1.25 9.67 0.00 +146 1.75 0.33 0.00 +147 1.75 1.00 0.00 +148 1.75 1.67 0.00 +149 1.75 2.33 0.00 +150 1.75 3.00 0.00 +151 1.75 3.67 0.00 +152 1.75 4.34 0.00 +153 1.75 5.00 0.00 +154 1.75 5.67 0.00 +155 1.75 6.34 0.00 +156 1.75 7.00 0.00 +157 1.75 7.67 0.00 +158 1.75 8.34 0.00 +159 1.75 9.00 0.00 +160 1.75 9.67 0.00 +161 2.25 0.33 0.00 +162 2.25 1.00 0.00 +163 2.25 1.67 0.00 +164 2.25 2.33 0.00 +165 2.25 3.00 0.00 +166 2.25 3.67 0.00 +167 2.25 4.34 0.00 +168 2.25 5.00 0.00 +169 2.25 5.67 0.00 +170 2.25 6.34 0.00 +171 2.25 7.00 0.00 +172 2.25 7.67 0.00 +173 2.25 8.34 0.00 +174 2.25 9.00 0.00 +175 2.25 9.67 0.00 diff --git a/doc/htc/htc.ipynb b/doc/htc/htc.ipynb new file mode 100644 index 0000000..e722b74 --- /dev/null +++ b/doc/htc/htc.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7dbe4280", + "metadata": {}, + "source": [ + "# Heat Transfer Coefficient (HTC) Interpolation Example\n", + "\n", + "This notebook demonstrates interpolating convection boundary conditions (HTC and reference temperature) from a source mesh to a destination mesh using the DISTANCE_WEIGHTED kernel with k=3 nearest neighbors.\n", + "\n", + "HTC is a 2-component load:\n", + "- **Component 1**: Heat Transfer Coefficient (W/m²·K)\n", + "- **Component 2**: Reference (bulk fluid) temperature (K)\n", + "\n", + "The ANSYS APDL export uses `SFE,,CONV,1` for HTC and `SFE,,CONV,2` for the reference temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2a59916e", + "metadata": {}, + "outputs": [], + "source": [ + "from interpcore.interpolator import Interpolator\n", + "from interpcore.config import InterpolationConfig, QUERY_TYPE, INTERPOLATED_LOAD_TYPE\n", + "from interpcore.kernels import INTERPOLATION_KERNEL" + ] + }, + { + "cell_type": "markdown", + "id": "62a80d29", + "metadata": {}, + "source": [ + "## Configure and Run Interpolation" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0f81e052", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/1 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "source_cloud = interpolator.src_vtk[\"htc_data\"]\n", + "dest_cloud = interpolator.dest_vtk[\"htc_data\"]\n", + "\n", + "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n", + "fig.suptitle('HTC Interpolation Results', fontsize=16, fontweight='bold')\n", + "\n", + "components = [\n", + " (\"Component_0\", \"HTC (W/m²·K)\", \"coolwarm\"),\n", + " (\"Component_1\", \"Tref (K)\", \"plasma\"),\n", + "]\n", + "\n", + "for row, (scalar, title, cmap) in enumerate(components):\n", + " # Source cloud\n", + " plotter_src = pv.Plotter(off_screen=True)\n", + " plotter_src.add_mesh(source_cloud, scalars=scalar, cmap=cmap, point_size=10,\n", + " render_points_as_spheres=False, style='points')\n", + " plotter_src.add_title(f'Source — {title}', font_size=11)\n", + " plotter_src.camera_position = 'xy'\n", + " img_src = plotter_src.screenshot(return_img=True)\n", + " plotter_src.close()\n", + "\n", + " # Destination cloud\n", + " plotter_dest = pv.Plotter(off_screen=True)\n", + " plotter_dest.add_mesh(dest_cloud, scalars=scalar, cmap=cmap, point_size=10,\n", + " render_points_as_spheres=False, style='points')\n", + " plotter_dest.add_title(f'Destination — {title}', font_size=11)\n", + " plotter_dest.camera_position = 'xy'\n", + " img_dest = plotter_dest.screenshot(return_img=True)\n", + " plotter_dest.close()\n", + "\n", + " axes[row, 0].imshow(img_src)\n", + " axes[row, 0].axis('off')\n", + " axes[row, 0].set_title(f'Source — {title}', fontsize=12, fontweight='bold')\n", + "\n", + " axes[row, 1].imshow(img_dest)\n", + " axes[row, 1].axis('off')\n", + " axes[row, 1].set_title(f'Destination — {title}', fontsize=12, fontweight='bold')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "interpcore", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/htc/source_data/htc_data.txt b/doc/htc/source_data/htc_data.txt new file mode 100644 index 0000000..bb95ed5 --- /dev/null +++ b/doc/htc/source_data/htc_data.txt @@ -0,0 +1,31 @@ +Node_ID X Y Z HTC Tref +1 0.50 0.50 0.00 150.00 310.00 +2 0.50 1.50 0.00 155.00 312.00 +3 0.50 2.50 0.00 162.00 315.00 +4 0.50 3.50 0.00 170.00 318.00 +5 0.50 4.50 0.00 178.00 320.00 +6 0.50 5.50 0.00 185.00 322.00 +7 0.50 6.50 0.00 190.00 325.00 +8 0.50 7.50 0.00 196.00 328.00 +9 0.50 8.50 0.00 200.00 330.00 +10 0.50 9.50 0.00 205.00 332.00 +11 1.50 0.50 0.00 158.00 311.00 +12 1.50 1.50 0.00 163.00 313.00 +13 1.50 2.50 0.00 169.00 316.00 +14 1.50 3.50 0.00 177.00 319.00 +15 1.50 4.50 0.00 184.00 321.00 +16 1.50 5.50 0.00 191.00 323.00 +17 1.50 6.50 0.00 198.00 326.00 +18 1.50 7.50 0.00 204.00 329.00 +19 1.50 8.50 0.00 209.00 331.00 +20 1.50 9.50 0.00 214.00 333.00 +21 2.50 0.50 0.00 165.00 312.00 +22 2.50 1.50 0.00 170.00 314.00 +23 2.50 2.50 0.00 176.00 317.00 +24 2.50 3.50 0.00 183.00 320.00 +25 2.50 4.50 0.00 190.00 322.00 +26 2.50 5.50 0.00 197.00 324.00 +27 2.50 6.50 0.00 204.00 327.00 +28 2.50 7.50 0.00 210.00 330.00 +29 2.50 8.50 0.00 215.00 332.00 +30 2.50 9.50 0.00 220.00 334.00 diff --git a/src/interpcore/config.py b/src/interpcore/config.py index cd04e8d..980afeb 100644 --- a/src/interpcore/config.py +++ b/src/interpcore/config.py @@ -19,6 +19,7 @@ class INTERPOLATED_LOAD_TYPE(Enum): EM_FORCE = "EM-Force" HEAT_FLUX = "Heat Flux" HEAT_GEN = "Heat Generation" + HTC = "Heat Transfer Coefficient" class INTERPOLATION_KERNEL(Enum): @@ -85,5 +86,7 @@ def __post_init__(self): self.num_components = 1 elif self.interpolated_load == INTERPOLATED_LOAD_TYPE.HEAT_GEN: self.num_components = 1 + elif self.interpolated_load == INTERPOLATED_LOAD_TYPE.HTC: + self.num_components = 2 else: raise ValueError(f"Unsupported load type: {self.interpolated_load}") diff --git a/src/interpcore/interpolator.py b/src/interpcore/interpolator.py index f83b2cf..dc5f26c 100644 --- a/src/interpcore/interpolator.py +++ b/src/interpcore/interpolator.py @@ -243,5 +243,10 @@ def _select_template(interpolated_load: INTERPOLATED_LOAD_TYPE) -> list[str]: return [ "BFE, {}, HGEN,, {}\n", ] + elif interpolated_load == INTERPOLATED_LOAD_TYPE.HTC: + return [ + "SFE, {},, CONV, 1, {}\n", # HTC + "SFE, {},, CONV, 2, {}\n", # TEMP + ] else: raise NotImplementedError(f"Unsupported load type: {interpolated_load}") diff --git a/tests/test_interpolator.py b/tests/test_interpolator.py index 6bfcba8..0cc04bb 100644 --- a/tests/test_interpolator.py +++ b/tests/test_interpolator.py @@ -61,6 +61,20 @@ def sample_config_heat_gen(): ) +@pytest.fixture +def sample_config_htc(): + """Create a sample configuration for HTC interpolation""" + return InterpolationConfig( + method=QUERY_TYPE.K, + param=3, + max_distance=2.0, + coincidence_tolerance=1e-6, + kernel=INTERPOLATION_KERNEL.DISTANCE_WEIGHTED, + multithread=False, + interpolated_load=INTERPOLATED_LOAD_TYPE.HTC, + ) + + @pytest.fixture def create_sample_mesh_files(temp_dir): """Create sample mesh and data files for testing""" @@ -156,6 +170,38 @@ def create_sample_heat_gen_files(temp_dir): } +@pytest.fixture +def create_sample_htc_files(temp_dir): + """Create sample mesh and HTC data files for testing""" + # Create destination mesh file + dest_mesh = temp_dir / "destination_mesh.txt" + dest_content = """Node_ID X Y Z +501 0.0 0.0 0.0 +502 1.0 0.0 0.0 +503 2.0 0.0 0.0 +504 0.0 1.0 0.0 +""" + dest_mesh.write_text(dest_content) + + # Create source data folder + src_folder = temp_dir / "htc_data" + src_folder.mkdir() + + # Create source data file with HTC and reference temperature (2 components) + src_file = src_folder / "htc_001.txt" + src_content = """Node_ID X Y Z HTC Tref +1 0.5 0.5 0.0 250.0 300.0 +2 1.5 0.5 0.0 300.0 310.0 +3 0.5 1.5 0.0 275.0 305.0 +""" + src_file.write_text(src_content) + + return { + "dest_mesh": str(dest_mesh), + "src_folder": str(src_folder), + } + + class TestInterpolator: """Tests for Interpolator class""" @@ -267,6 +313,25 @@ def test_interpolate_all_with_em_force( result = interpolator.interpolated_results["force_001"] assert result["interpolated"].shape[1] == 3 # EM force has 3 components + def test_interpolate_all_with_htc(self, create_sample_htc_files, sample_config_htc): + """Test interpolation with HTC data (2 components: HTC and Tref)""" + file_idx = {"ids": 0, "dest_x": 1, "src_x": 1, "val": 4} + interpolator = Interpolator( + path_to_src_folder=create_sample_htc_files["src_folder"], + path_to_dest_mesh=create_sample_htc_files["dest_mesh"], + config=sample_config_htc, + file_idx=file_idx, + ) + + interpolator.interpolate_all() + + assert interpolator.interpolated_results is not None + result = interpolator.interpolated_results["htc_001"] + assert "interpolated" in result + assert "unmapped" in result + assert result["interpolated"].shape[0] > 0 + assert result["interpolated"].shape[1] == 2 # HTC has 2 components + def test_export_to_ansys_without_interpolation( self, create_sample_mesh_files, sample_config_heat_flux, temp_dir ): @@ -371,6 +436,34 @@ def test_export_to_ansys_heat_gen( assert "HGEN" in content assert "BF" in content + def test_export_to_ansys_htc( + self, create_sample_htc_files, sample_config_htc, temp_dir + ): + """Test exporting HTC results to ANSYS format""" + file_idx = {"ids": 0, "dest_x": 1, "src_x": 1, "val": 4} + interpolator = Interpolator( + path_to_src_folder=create_sample_htc_files["src_folder"], + path_to_dest_mesh=create_sample_htc_files["dest_mesh"], + config=sample_config_htc, + file_idx=file_idx, + ) + + interpolator.interpolate_all() + + output_dir = temp_dir / "output" + output_dir.mkdir() + interpolator.export_to_ansys(output_dir) + + # Check that output file was created + output_files = list(output_dir.glob("interpolated_*.txt")) + assert len(output_files) == 1 + assert output_files[0].name == "interpolated_htc_001.txt" + + # Check file content format (should have CONV) + content = output_files[0].read_text() + assert "CONV" in content + assert "SFE" in content + def test_build_vtk_output_without_interpolation( self, create_sample_mesh_files, sample_config_heat_flux ): @@ -500,6 +593,16 @@ def test_select_template_heat_gen(self): assert "HGEN" in templates[0] assert "BFE" in templates[0] + def test_select_template_htc(self): + """Test template selection for HTC""" + templates = _select_template(INTERPOLATED_LOAD_TYPE.HTC) + + assert len(templates) == 2 + assert "CONV" in templates[0] + assert "SFE" in templates[0] + assert "CONV" in templates[1] + assert "SFE" in templates[1] + def test_select_template_unsupported_type(self): """Test that unsupported load types raise NotImplementedError""" From f5343aba653b3ad9e597ceee85a25280d5c07000 Mon Sep 17 00:00:00 2001 From: Davide Laghi Date: Tue, 2 Jun 2026 15:36:43 +0200 Subject: [PATCH 2/2] updare doc --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01b2c4e..040a535 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A Python library for interpolating physical field data (electromagnetic forces, - **Support for multiple load types**: - EM forces (3-component vector fields) - Heat flux (scalar fields) + - Heat generation (volumetric) + - Heat Transfer Coefficient + bulk fluid temperature (convection BCs) - **Export to ANSYS APDL**: Direct export of interpolated results in APDL format - **Visualization**: Built-in VTK export for ParaView or PyVista visualization - **Efficient**: KDTree-based spatial queries for fast neighbor searches @@ -62,9 +64,10 @@ interpolator.build_vtk_output(outdir="vtk_output") Complete working examples with sample data are available in the [`doc/`](doc/) folder: -- **[Heat Flux Example](doc/heat_flux/)**: Scalar field interpolation using the AVERAGE kernel -- **[Heat Generation Example](doc/heat_gen/)**: Volumetric heat generation using the CLOSEST kernel -- **[EM Force Example](doc/em_force/)**: Vector field interpolation with glyph visualization +- **[Heat Flux Example](doc/heat_flux/heat_flux.ipynb)**: Scalar field interpolation using the AVERAGE kernel +- **[Heat Generation Example](doc/heat_gen/heat_gen.ipynb)**: Volumetric heat generation using the CLOSEST kernel +- **[EM Force Example](doc/em_force/em_force.ipynb)**: Vector field interpolation with glyph visualization +- **[HTC Example](doc/htc/htc.ipynb)**: Convection boundary condition interpolation (HTC + bulk fluid temperature) Each example includes: - Sample mesh files @@ -99,6 +102,7 @@ A value is assigned to each destination point based on source neighbours interpreted as force densities and will be multiplied by the volume. - `HEAT_FLUX`: Scalar fields for surface heat flux - `HEAT_GEN`: Scalar fields for volumetric heat generation +- `HTC`: 2-component convection boundary condition — Heat Transfer Coefficient and bulk fluid (reference) temperature. Exported as `SFE,,CONV,1` and `SFE,,CONV,2` in APDL. ## File Format