Skip to content

Commit 754d2e0

Browse files
refactor: Move PVMergeBlocksEnhanced plugin and create the VTK filter (#129)
* Modify isAttributeGlobal function to take into account recursive multiblockdataset * Adding a VTK filter for enhanced merge block * Move and update plugin * Fix import format * Typing * Adding tests and data for test for merge blocks * Documentation * Fix docstring and typing * error handling version * adding Fails test * Capture errors from VTK * Update python-package.yml as order might matters * CI is testing vtk9.5.1 so skip the failing test for now * wrong version dispatch * Clean and formatting * Add child logger to prevent to much verbosity in parent logger --------- Co-authored-by: Jacques Franc <49998870+jafranc@users.noreply.github.com>
1 parent d4ced19 commit 754d2e0

File tree

24 files changed

+1265
-234
lines changed

24 files changed

+1265
-234
lines changed

.github/workflows/python-package.yml

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: geosPythonPackages CI
1+
name: geosPythonPackages CI
22
on: pull_request
33

44
# Cancels in-progress workflows for a PR when updated
@@ -9,13 +9,13 @@ concurrency:
99

1010
jobs:
1111
# Checks if PR title follows conventional semantics
12-
semantic_pull_request:
12+
semantic_pull_request:
1313
permissions:
14-
pull-requests: write # for amannn/action-semantic-pull-request to analyze PRs and
14+
pull-requests: write # for amannn/action-semantic-pull-request to analyze PRs and
1515
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
16-
contents: read
16+
contents: read
1717
runs-on: ubuntu-latest
18-
18+
1919
steps:
2020
- name: Check if the PR name has conventional semantics
2121
if: github.event_name == 'pull_request'
@@ -27,7 +27,7 @@ jobs:
2727
wip: true
2828
# Configure that a scope doesn't need to be provided.
2929
requireScope: false
30-
30+
3131
- name: Skip the check on main branch
3232
if: github.ref_name == 'main'
3333
run: |
@@ -40,14 +40,14 @@ jobs:
4040
max-parallel: 3
4141
matrix:
4242
python-version: ["3.10", "3.11", "3.12"]
43-
package-name:
43+
package-name:
4444
- geos-ats
45+
- geos-utils
4546
- geos-geomechanics
4647
- geos-mesh
4748
- geos-posp
4849
- geos-timehistory
4950
- geos-trame
50-
- geos-utils
5151
- geos-xml-tools
5252
- geos-xml-viewer
5353
- hdf5-wrapper
@@ -77,7 +77,7 @@ jobs:
7777
python -m pip install --upgrade pip
7878
python -m pip install pytest yapf toml
7979
80-
DEPS="${{ matrix.dependencies || '' }}"
80+
DEPS="${{ matrix.dependencies || '' }}"
8181
8282
if [ -n "$DEPS" ]; then
8383
echo "Installing additional dependencies: $DEPS"
@@ -114,20 +114,20 @@ jobs:
114114
uses: actions/checkout@v4
115115
with:
116116
fetch-depth: 0 # Fetch all history to compare with base branch
117-
117+
118118
- name: Check if GEOS integration is required
119119
id: check_changes
120120
run: |
121121
echo "Analyzing changed files to determine if GEOS integration test is required..."
122-
122+
123123
# Get list of changed files
124124
git fetch origin ${{ github.base_ref }}
125125
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
126-
126+
127127
echo "Changed files:"
128128
echo "$CHANGED_FILES"
129129
echo ""
130-
130+
131131
# Define packages that are integrated into GEOS (from GEOS/scripts/setupPythonEnvironment.bash)
132132
GEOS_INTEGRATED_PACKAGES=(
133133
"geos-utils"
@@ -137,7 +137,7 @@ jobs:
137137
"pygeos-tools"
138138
"geos-ats"
139139
)
140-
140+
141141
# Define patterns that DON'T require GEOS integration testing
142142
SKIP_PATTERNS=(
143143
"^docs/"
@@ -158,21 +158,21 @@ jobs:
158158
"^geos-trame/"
159159
"^geos-xml-viewer/"
160160
)
161-
161+
162162
# Check if label is present (overrides automatic detection)
163163
HAS_LABEL=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | grep -q "test-geos-integration" && echo "true" || echo "false")
164-
164+
165165
if [[ "$HAS_LABEL" == "true" ]]; then
166166
echo "✓ Label 'test-geos-integration' found - GEOS integration test will run"
167167
echo "required=true" >> "$GITHUB_OUTPUT"
168168
echo "skip_reason=none" >> "$GITHUB_OUTPUT"
169169
exit 0
170170
fi
171-
171+
172172
# Check if any changed file affects GEOS-integrated packages
173173
REQUIRES_GEOS_TEST=false
174174
AFFECTED_PACKAGES=""
175-
175+
176176
for file in $CHANGED_FILES; do
177177
# Check if file matches any skip pattern
178178
SHOULD_SKIP=false
@@ -182,7 +182,7 @@ jobs:
182182
break
183183
fi
184184
done
185-
185+
186186
if [[ "$SHOULD_SKIP" == "false" ]]; then
187187
# Check if file is in a GEOS-integrated package
188188
for package in "${GEOS_INTEGRATED_PACKAGES[@]}"; do
@@ -193,21 +193,21 @@ jobs:
193193
fi
194194
fi
195195
done
196-
196+
197197
# Check for CI workflow changes that affect GEOS integration
198198
if echo "$file" | grep -qE "^\.github/workflows/(python-package\.yml|test_geos_integration\.yml)$"; then
199199
REQUIRES_GEOS_TEST=true
200200
AFFECTED_PACKAGES="$AFFECTED_PACKAGES [CI-workflows]"
201201
fi
202-
202+
203203
# Check for root-level scripts that might affect integration
204204
if echo "$file" | grep -qE "^install_packages\.sh$"; then
205205
REQUIRES_GEOS_TEST=true
206206
AFFECTED_PACKAGES="$AFFECTED_PACKAGES [install-scripts]"
207207
fi
208208
fi
209209
done
210-
210+
211211
if [[ "$REQUIRES_GEOS_TEST" == "true" ]]; then
212212
echo "✓ GEOS integration test REQUIRED"
213213
echo " Affected packages/components:$AFFECTED_PACKAGES"
@@ -240,11 +240,11 @@ jobs:
240240
run: |
241241
echo "Final CI Validation"
242242
echo "==================="
243-
243+
244244
GEOS_REQUIRED="${{ needs.check_geos_integration_required.outputs.geos_integration_required }}"
245245
SKIP_REASON="${{ needs.check_geos_integration_required.outputs.skip_reason }}"
246246
GEOS_RESULT="${{ needs.geos_integration_test.result }}"
247-
247+
248248
if [[ "$GEOS_REQUIRED" == "true" ]]; then
249249
echo "GEOS integration test was required and triggered"
250250
if [[ "$GEOS_RESULT" == "success" ]]; then

docs/geos_mesh_docs/processing.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,20 @@ geos.mesh.processing.SplitMesh filter
4545
:undoc-members:
4646
:show-inheritance:
4747

48+
49+
geos.mesh.processing.MergeBlockEnhanced filter
50+
------------------------------------------------
51+
52+
.. automodule:: geos.mesh.processing.MergeBlockEnhanced
53+
:members:
54+
:undoc-members:
55+
:show-inheritance:
56+
57+
4858
geos.mesh.processing.ClipToMainFrame filter
4959
--------------------------------------------
5060

5161
.. automodule:: geos.mesh.processing.ClipToMainFrame
5262
:members:
5363
:undoc-members:
54-
:show-inheritance:
64+
:show-inheritance:

docs/geos_posp_docs/PVplugins.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,6 @@ PVGeomechanicsWorkflowVolumeWell plugin
6464

6565
.. automodule:: PVplugins.PVGeomechanicsWorkflowVolumeWell
6666

67-
PVplugins.PVMergeBlocksEnhanced module
68-
--------------------------------------
69-
70-
.. automodule:: PVplugins.PVMergeBlocksEnhanced
71-
7267

7368
PVMohrCirclePlot plugin
7469
---------------------------------

docs/geos_pv_docs/processing.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ PVSplitMesh
2424
.. automodule:: geos.pv.plugins.PVSplitMesh
2525

2626

27+
PVMergeBlocksEnhanced module
28+
--------------------------------------
29+
30+
.. automodule:: geos.pv.plugins.PVMergeBlocksEnhanced
31+
32+
2733
PVClipToMainFrame
2834
----------------------------------
2935

geos-mesh/src/geos/mesh/processing/CreateConstantAttributePerRegion.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
33
# SPDX-FileContributor: Romain Baville
44
import numpy as np
5-
import numpy.typing as npt
5+
import logging
66

7+
import numpy.typing as npt
78
from typing import Union, Any
89
from typing_extensions import Self
910

@@ -13,7 +14,7 @@
1314
vtkDataSet,
1415
)
1516

16-
from geos.utils.Logger import ( getLogger, Logger, logging, CountWarningHandler )
17+
from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler )
1718
from geos.mesh.utils.arrayHelpers import ( getArrayInObject, getComponentNames, getNumberOfComponents,
1819
getVtkDataTypeInObject, isAttributeGlobal, getAttributePieceInfo,
1920
checkValidValuesInDataSet, checkValidValuesInMultiBlock )
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
3+
# SPDX-FileContributor: Paloma Martinez
4+
# ruff: noqa: E402 # disable Module level import not at top of file
5+
import logging
6+
7+
from typing_extensions import Self
8+
9+
from geos.utils.Logger import Logger, getLogger
10+
from geos.mesh.utils.multiblockModifiers import mergeBlocks
11+
12+
from vtkmodules.vtkCommonDataModel import (
13+
vtkMultiBlockDataSet,
14+
vtkUnstructuredGrid,
15+
)
16+
17+
__doc__ = """
18+
Merge Blocks Keeping Partial Attributes is a filter that allows to merge blocks from a multiblock dataset while keeping partial attributes.
19+
20+
Input is a vtkMultiBlockDataSet and output is a vtkUnstructuredGrid.
21+
22+
.. Note::
23+
- You may encounter issues if two datasets of the input multiblock dataset have duplicated cell IDs.
24+
- Partial attributes are filled with default values depending on their types.
25+
- 0 for uint data.
26+
- -1 for int data.
27+
- nan for float data.
28+
29+
30+
To use it:
31+
32+
.. code-block:: python
33+
34+
from geos.mesh.processing.MergeBlockEnhanced import MergeBlockEnhanced
35+
import logging
36+
from geos.utils.Errors import VTKError
37+
38+
# Define filter inputs
39+
multiblockdataset: vtkMultiblockDataSet
40+
speHandler: bool # optional
41+
42+
# Instantiate the filter
43+
filter: MergeBlockEnhanced = MergeBlockEnhanced( multiblockdataset, speHandler )
44+
45+
# Use your own handler (if speHandler is True)
46+
yourHandler: logging.Handler
47+
filter.setLoggerHandler( yourHandler )
48+
49+
# Do calculations
50+
try:
51+
filter.applyFilter()
52+
except VTKError:
53+
logging.error("Something went wrong in VTK")
54+
55+
# Get the merged mesh
56+
filter.getOutput()
57+
"""
58+
59+
loggerTitle: str = "Merge Block Enhanced"
60+
61+
62+
class MergeBlockEnhanced:
63+
64+
def __init__(
65+
self: Self,
66+
inputMesh: vtkMultiBlockDataSet,
67+
speHandler: bool = False,
68+
) -> None:
69+
"""Merge a multiblock dataset and keep the partial attributes in the output mesh.
70+
71+
Partial attributes are filled with default values depending on the data type such that:
72+
- 0 for uint data.
73+
- -1 for int data.
74+
- nan for float data.
75+
76+
Args:
77+
inputMesh (vtkMultiBlockDataSet): The input multiblock dataset to merge.
78+
speHandler (bool, optional) : True to use a specific handler, False to use the internal handler.
79+
Defaults to False.
80+
"""
81+
self.inputMesh: vtkMultiBlockDataSet = inputMesh
82+
self.outputMesh: vtkUnstructuredGrid = vtkUnstructuredGrid()
83+
84+
# Logger
85+
self.logger: Logger
86+
if not speHandler:
87+
self.logger = getLogger( loggerTitle, True )
88+
else:
89+
self.logger = logging.getLogger( loggerTitle )
90+
self.logger.setLevel( logging.INFO )
91+
92+
def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
93+
"""Set a specific handler for the filter logger.
94+
95+
In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels.
96+
97+
Args:
98+
handler (logging.Handler): The handler to add.
99+
"""
100+
if not self.logger.hasHandlers():
101+
self.logger.addHandler( handler )
102+
else:
103+
self.logger.warning(
104+
"The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
105+
)
106+
107+
def applyFilter( self: Self ) -> None:
108+
"""Merge the blocks of a multiblock dataset mesh.
109+
110+
Returns:
111+
bool: True if the blocks were successfully merged, False otherwise.
112+
113+
Raises:
114+
VTKError (geos.utils.Errors) : error captured if any from the VTK log
115+
"""
116+
self.logger.info( f"Applying filter { self.logger.name }." )
117+
118+
outputMesh: vtkUnstructuredGrid
119+
outputMesh = mergeBlocks( self.inputMesh, keepPartialAttributes=True, logger=self.logger )
120+
self.outputMesh = outputMesh
121+
122+
def getOutput( self: Self ) -> vtkUnstructuredGrid:
123+
"""Get the merged mesh.
124+
125+
Returns:
126+
vtkUnstructuredGrid: The merged mesh.
127+
"""
128+
return self.outputMesh

geos-mesh/src/geos/mesh/utils/arrayHelpers.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,6 @@ def isAttributeGlobal( multiBlockDataSet: vtkMultiBlockDataSet, attributeName: s
677677
dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSet.GetDataSet( blockIndex ) )
678678
if not isAttributeInObjectDataSet( dataSet, attributeName, onPoints ):
679679
return False
680-
681680
return True
682681

683682

geos-mesh/src/geos/mesh/utils/arrayModifiers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,13 +762,13 @@ def transferAttributeToDataSetWithElementMap(
762762

763763
for idElementTo in range( nbElementsTo ):
764764
valueToTransfer: Any = defaultValue
765-
idElementFrom: int = elementMap[ flatIdDataSetTo ][ idElementTo ][ 1 ]
765+
idElementFrom: int = int( elementMap[ flatIdDataSetTo ][ idElementTo ][ 1 ] )
766766
if idElementFrom != -1:
767767
dataFrom: Union[ vtkPointData, vtkCellData ]
768768
if isinstance( meshFrom, vtkDataSet ):
769769
dataFrom = meshFrom.GetPointData() if onPoints else meshFrom.GetCellData()
770770
elif isinstance( meshFrom, vtkMultiBlockDataSet ):
771-
flatIdDataSetFrom: int = elementMap[ flatIdDataSetTo ][ idElementTo ][ 0 ]
771+
flatIdDataSetFrom: int = int( elementMap[ flatIdDataSetTo ][ idElementTo ][ 0 ] )
772772
dataSetFrom: vtkDataSet = vtkDataSet.SafeDownCast( meshFrom.GetDataSet( flatIdDataSetFrom ) )
773773
dataFrom = dataSetFrom.GetPointData() if onPoints else dataSetFrom.GetCellData()
774774

0 commit comments

Comments
 (0)