diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 54d08e11b..f533a1369 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -158,7 +158,7 @@ jobs:
check_force_integration_label:
runs-on: ubuntu-latest
- needs: [build]
+ # needs: [build]
outputs:
has_geos_integration_force_label: ${{ steps.set-label.outputs.has_label }}
steps:
diff --git a/.github/workflows/test_geos_integration.yml b/.github/workflows/test_geos_integration.yml
index 9671e5194..5edaafd85 100644
--- a/.github/workflows/test_geos_integration.yml
+++ b/.github/workflows/test_geos_integration.yml
@@ -25,7 +25,7 @@ jobs:
container:
# using this image to get access to python 3.10+
- image: geosx/ubuntu22.04-gcc12:${{ needs.setup.outputs.tag }}
+ image: geosx/ubuntu24.04-gcc13:${{ needs.setup.outputs.tag }}
steps:
- name: Checkout geosPythonPackages
@@ -44,22 +44,25 @@ jobs:
- name: Install Build Dependencies
run: |
apt-get update
- apt-get install -y make python3-numpy python3-dev python3-venv python3-pip
+ apt-get install -y make python3-numpy python3-dev python3-venv python3-setuptools
# Ensure pip installs scripts to /usr/local/bin
export PATH="/usr/local/bin:$PATH"
echo "PATH=/usr/local/bin:$PATH" >> $GITHUB_ENV
+ # For Ubuntu 24.04 with Python 3.12+, setuptools is required (distutils was removed)
+ # Upgrade setuptools to latest versions
+ python3 -m pip install --break-system-packages --upgrade setuptools
+
# Set environment variables to handle setuptools/distutils issues
- echo "SETUPTOOLS_USE_DISTUTILS=stdlib" >> $GITHUB_ENV
echo "PIP_DISABLE_PIP_VERSION_CHECK=1" >> $GITHUB_ENV
echo "PYTHONDONTWRITEBYTECODE=1" >> $GITHUB_ENV
-
+
- name: Setup test environment
run: |
echo "Setting up test environment..."
GEOS_ROOT="$(pwd)/GEOS"
- SETUP_PYTHON_ENVIRONMENT_SCRIPT="$GEOS_ROOT/scripts/setupPythonEnvironment.bash"
+ SETUP_PYTHON_ENVIRONMENT_SCRIPT="${GEOS_ROOT}/scripts/setupPythonEnvironment.bash"
if [ -n "${{ github.head_ref }}" ]; then
CURRENT_GEOSPYTHONPACKAGES_BRANCH_NAME="${{ github.head_ref }}"
@@ -100,7 +103,7 @@ jobs:
-DENABLE_YAPF=OFF \
-DGEOS_ENABLE_TESTS=OFF \
-DGEOS_ENABLE_CONTACT=OFF \
- -DGEOS_ENABLE_FLUIDFLOW=ON \
+ -DGEOS_ENABLE_FLUIDFLOW=OFF \
-DGEOS_ENABLE_INDUCEDSEISMICITY=OFF \
-DGEOS_ENABLE_MULTIPHYSICS=OFF \
-DGEOS_ENABLE_SIMPLEPDE=OFF \
@@ -119,6 +122,11 @@ jobs:
run: |
echo "=== Test 1: Direct setupPythonEnvironment.bash execution ==="
mkdir -p bin_direct
+
+ # # encapsulate env
+ python3 -m venv geos-venv
+ . geos-venv/bin/activate
+ python -m pip install --upgrade pip setuptools wheel
# The setup script searches specific paths but pip installs to /usr/local/bin
# We need to patch the setup script to also search /usr/local/bin
@@ -130,8 +138,22 @@ jobs:
# Add /usr/local/bin to the MOD_SEARCH_PATH array right after the python bin directory
# We insert /usr/local/bin as the second entry
- sed -i '/^declare -a MOD_SEARCH_PATH=.*PYTHON_TARGET)"/a\ "/usr/local/bin"' "$TEMP_SETUP_SCRIPT"
-
+ # sed -i '/^declare -a MOD_SEARCH_PATH=.*PYTHON_TARGET)"/a\ "/usr/local/bin"' "$TEMP_SETUP_SCRIPT"
+ # Only add --break-system-packages for Python >= 3.12
+ # PYTHON_VERSION=$($(which python3) -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
+ # if [[ "$PYTHON_VERSION" == "3.12" || "$PYTHON_VERSION" > "3.12" ]]; then
+ # sed -i 's/${PIP_CMD} install/${PIP_CMD} install --break-system-packages/g' "$TEMP_SETUP_SCRIPT"
+ # sed -i 's/$PYTHON_TARGET -m pip install --upgrade pip setuptools wheel/$PYTHON_TARGET -m pip install --upgrade setuptools/' "$TEMP_SETUP_SCRIPT"
+ # fi
+ cat "$TEMP_SETUP_SCRIPT"
+ export PIP_NO_BUILD_ISOLATION=1
+ bash "$TEMP_SETUP_SCRIPT" \
+ -p $(which python) \
+ -b "$(pwd)/bin_direct" \
+ --python-pkg-branch "$CURRENT_GEOSPYTHONPACKAGES_BRANCH_NAME" \
+ --verbose
+ cat "$TEMP_SETUP_SCRIPT"
+ export PIP_NO_BUILD_ISOLATION=1
bash "$TEMP_SETUP_SCRIPT" \
-p $(which python3) \
-b "$(pwd)/bin_direct" \
@@ -210,6 +232,8 @@ jobs:
echo "Creating symlink from PyGEOSX virtual environment..."
ln -s "$(pwd)/lib/PYGEOSX/bin/test_geosx_xml_tools" python/geosx/bin/test_geosx_xml_tools
else
+ # If the real test script is not found, create a placeholder so the test step can proceed.
+ # This ensures the workflow does not fail due to a missing test script, but signals that a real test should be provided.
echo "Creating placeholder test script..."
echo '#!/usr/bin/env python3' > python/geosx/bin/test_geosx_xml_tools
echo 'import sys' >> python/geosx/bin/test_geosx_xml_tools
@@ -252,6 +276,16 @@ jobs:
# Ensure geosx_python_tools is built (provides format_xml)
echo "Building geosx_python_tools dependency..."
make geosx_python_tools -j8
+
+ # The geosx_format_all_xml_files target has a bug - it depends on 'geosx_xml_tools' which doesn't exist
+ # It should depend on 'geosx_python_tools'. Since we can't modify the CMakeLists.txt,
+ # we'll run the formatting command directly instead
+ # To solve the bug in cmake, we would have to change:
+ # add_custom_target( geosx_format_all_xml_files
+ # COMMAND bash ${CMAKE_SOURCE_DIR}/../scripts/formatXMLFiles.bash -g ${CMAKE_BINARY_DIR}/bin/format_xml ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/../examples
+ # WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ # DEPENDS geosx_python_tools # ← Change this!
+ # )
echo "Running XML formatting directly (bypassing broken make target)..."
if [ -f "bin/format_xml" ] && [ -f "$GEOS_ROOT/scripts/formatXMLFiles.bash" ]; then
@@ -274,7 +308,16 @@ jobs:
run: |
if [[ "${{ needs.test_geos_integration.result }}" == "success" ]]; then
echo "All integration tests passed successfully!" >> $GITHUB_STEP_SUMMARY
- else
+ elif [[ "${{ needs.test_geos_integration.result }}" == "failure" ]]; then
echo "Integration tests failed. Please review the logs." >> $GITHUB_STEP_SUMMARY
exit 1
- fi
\ No newline at end of file
+ elif [[ "${{ needs.test_geos_integration.result }}" == "cancelled" ]]; then
+ echo "Integration tests were cancelled." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ elif [[ "${{ needs.test_geos_integration.result }}" == "skipped" ]]; then
+ echo "Integration tests were skipped." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ else
+ echo "Integration tests ended with unknown status: ${{ needs.test_geos_integration.result }}" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml
index 940fb9fc4..b84824688 100644
--- a/.github/workflows/typing-check.yml
+++ b/.github/workflows/typing-check.yml
@@ -30,7 +30,7 @@ jobs:
# working-directory: ./${{ matrix.package-name }}
run: |
python -m pip install --upgrade pip
- python -m pip install mypy ruff types-PyYAML types-pytz
+ python -m pip install mypy ruff types-PyYAML types-paramiko types-pytz
- name: Typing check with mypy
# working-directory: ./${{ matrix.package-name }}
diff --git a/.gitignore b/.gitignore
index 5f08477d3..6275d935c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,9 @@ MANIFEST
*.manifest
*.spec
+#env (do not port user-specs)
+.env
+
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
@@ -165,4 +168,4 @@ cython_debug/
#.idea/
# VSCode
-.vscode
\ No newline at end of file
+.vscode
diff --git a/geos-trame/README.rst b/geos-trame/README.rst
index a1c54deb2..6e519cdad 100644
--- a/geos-trame/README.rst
+++ b/geos-trame/README.rst
@@ -21,7 +21,21 @@ Build and install the Vue components
cd vue-components
npm i
npm run build
- cd -
+ cd ..
+
+then configure the .env
+
+ sh configure.sh
+
+this will generate a `dotenv` environement file defining useful path to trame,
+
+.. code-block:: console
+
+ cat .env
+ TEMPLATE_DIR=/path/to/geosPythonPackages/geos-trame/src/geos/trame/io/jinja_t
+ ASSETS_DIR=/path/to/geosPythonPackages/geos-trame/src/geos/trame/assets
+
+those will have lower precedence than local environement variables if defined
Install the application
diff --git a/geos-trame/configure.sh b/geos-trame/configure.sh
new file mode 100644
index 000000000..f46d8ff9a
--- /dev/null
+++ b/geos-trame/configure.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+echo "TEMPLATE_DIR=${PWD}/src/geos/trame/app/io/jinja_t/" >> ${PWD}/src/geos/trame/assets/.env
+echo "ASSETS_DIR=${PWD}/src/geos/trame/assets/" >> ${PWD}/src/geos/trame/assets/.env
\ No newline at end of file
diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml
index 73d7a028d..e751b49b0 100644
--- a/geos-trame/pyproject.toml
+++ b/geos-trame/pyproject.toml
@@ -31,18 +31,19 @@ keywords = [
dependencies = [
"setuptools",
- "typing-extensions==4.12.2",
"trame==3.6.5",
- "trame-vuetify==2.7.1",
+ "trame-vuetify==3.1.0",
"trame-code==1.0.1",
"trame-server==3.2.3",
- "trame-client==3.5.0",
+ "trame-client==3.11.2",
"trame-simput==2.4.3",
- "trame-vtk>=2.8.14",
+ "trame-vtk==2.10.0",
"matplotlib==3.9.4",
"trame-matplotlib==2.0.3",
"trame-components==2.4.2",
+ "python-dotenv>=1.2.1",
"mpld3<0.5.11",
+ "paramiko==4.0.0",
"xsdata[cli]>=25.4",
"xsdata-pydantic[lxml]==24.5",
"pyvista==0.45.2",
@@ -51,7 +52,7 @@ dependencies = [
"funcy==2.0",
"pytz==2025.2",
"typing_inspect==0.9.0",
- "typing_extensions>=4.12",
+ "typing_extensions>=4.15.0",
"PyYAML",
]
@@ -73,7 +74,8 @@ test = [
"pixelmatch==0.3.0",
"Pillow==11.0.0",
"pytest-mypy==0.10.3",
- "pytest-xprocess==1.0.2"
+ "pytest-xprocess==1.0.2",
+ "playwright==1.59.0"
]
[project.readme]
@@ -94,7 +96,7 @@ include-package-data = true
# include = ['geos-trame*']
[tool.setuptools.package-data]
-"*" = ["*.js", "*.css"]
+"*" = ["*.js", "*.css","assets/*","*.jinja","*.json",".env"]
[tool.pytest.ini_options]
addopts = [
diff --git a/geos-trame/src/geos/trame/app/core.py b/geos-trame/src/geos/trame/app/core.py
index 3baf7d387..7452782ec 100644
--- a/geos-trame/src/geos/trame/app/core.py
+++ b/geos-trame/src/geos/trame/app/core.py
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Lionel Untereiner, Jacques Franc
-
+# ignore context collapsing as it is clearer this way
+# ruff: noqa: SIM117
from trame.ui.vuetify3 import VAppLayout
from trame.decorators import TrameApp
from trame.widgets import html, simput
@@ -24,6 +25,9 @@
from geos.trame.app.ui.viewer.viewer import DeckViewer
from geos.trame.app.components.alertHandler import AlertHandler
+from geos.trame.app.io.simulation import Simulation
+from geos.trame.app.ui.simulation_view import define_simulation_view
+
import sys
@@ -38,10 +42,12 @@ def __init__( self, server: Server, file_name: str ) -> None:
self.deckEditor: DeckEditor | None = None
self.timelineEditor: TimelineEditor | None = None
self.deckInspector: DeckInspector | None = None
+ self.simulationLauncher: Simulation | None = None
self.server = server
server.enable_module( module )
self.state.input_file = file_name
+ self.state.user_id = None
# TODO handle hot_reload
@@ -67,6 +73,9 @@ def __init__( self, server: Server, file_name: str ) -> None:
self.region_viewer = RegionViewer()
self.well_viewer = WellViewer( 5, 5 )
+ ######## Simulation runner
+ self.simulation = Simulation( server=server )
+
# Data loader
self.data_loader = DataLoader( self.tree, self.region_viewer, self.well_viewer, trame_server=server )
@@ -177,24 +186,6 @@ def build_ui( self ) -> None:
):
vuetify.VIcon( "mdi-content-save-outline" )
- with html.Div(
- style=
- "height: 100%; width: 300px; display: flex; align-items: center; justify-content: space-between;",
- v_if=( "tab_idx == 1", ),
- ):
- vuetify.VBtn(
- "Run",
- style="z-index: 1;",
- )
- vuetify.VBtn(
- "Kill",
- style="z-index: 1;",
- )
- vuetify.VBtn(
- "Clear",
- style="z-index: 1;",
- )
-
# input file editor
with vuetify.VCol( v_show=( "tab_idx == 0", ), classes="flex-grow-1 pa-0 ma-0" ):
if self.tree.input_file is not None:
@@ -208,3 +199,16 @@ def build_ui( self ) -> None:
"The file " + self.state.input_file + " cannot be parsed.",
file=sys.stderr,
)
+
+ with vuetify.VCol( v_show=( "tab_idx == 1" ), classes="flex-grow-1 pa-0 ma-0" ):
+ if self.simulation is not None:
+ define_simulation_view( self.server )
+ else:
+ self.ctrl.on_add_error(
+ "Error",
+ "The execution context " + self.state.exec_context + " is not consistent.",
+ )
+ print(
+ "The execution context " + self.state.exec_context + " is not consistent.",
+ file=sys.stderr,
+ )
diff --git a/geos-trame/src/geos/trame/app/io/hpc_tools.py b/geos-trame/src/geos/trame/app/io/hpc_tools.py
new file mode 100644
index 000000000..99ffec48a
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/hpc_tools.py
@@ -0,0 +1,99 @@
+from geos.trame.app.io.ssh_tools import SimulationConstant
+
+
+class SuggestDecomposition:
+
+ def __init__( self, selected_cluster: SimulationConstant, n_unknowns: int, job_type: str = 'cpu' ) -> None:
+ """Initialize the decomposition hinter for HPC."""
+ self.selected_cluster: SimulationConstant = selected_cluster
+ self.n_unknowns: int = n_unknowns
+ self.job_type: str = job_type #TODO should be an enum
+ self.sd: list[ dict ] = []
+
+ @staticmethod
+ def compute( n_unknowns: int,
+ memory_per_unknown_bytes: int,
+ node_memory_gb: int,
+ cores_per_node: int,
+ min_unknowns_per_rank: int = 10000,
+ strong_scaling: bool = True ) -> list[ dict ]:
+ """Suggests node/rank distribution for a cluster computation.
+
+ Parameters:
+ - n_unknowns: total number of unknowns
+ - memory_per_unknown_bytes: estimated memory per unknown
+ - node_memory_gb: available memory per node
+ - cores_per_node: cores available per node
+ - min_unknowns_per_rank: minimum for efficiency
+ - strong_scaling: True if problem size is fixed
+
+ Note:
+ - 10,000-100,000 unknowns per rank is often a sweet spot for many PDE solvers
+ - Use power-of-2 decompositions when possible (helps with communication patterns)
+ - For 3D problems, try to maintain cubic subdomains (minimizes surface-to-volume ratio, reducing communication)
+ - Don't oversubscribe: avoid using more ranks than provide parallel efficiency
+
+ """
+ # Memory constraint
+ node_memory_bytes = node_memory_gb * 1e9
+ max_unknowns_per_node = int( 0.8 * node_memory_bytes / memory_per_unknown_bytes )
+
+ # Compute minimum nodes needed
+ min_nodes = max( 1, ( n_unknowns + max_unknowns_per_node - 1 ) // max_unknowns_per_node )
+
+ # Determine ranks per node
+ unknowns_per_node = n_unknowns // min_nodes
+ unknowns_per_rank = max( min_unknowns_per_rank, unknowns_per_node // cores_per_node )
+
+ # Calculate total ranks needed
+ n_ranks = max( 1, n_unknowns // unknowns_per_rank )
+
+ # Distribute across nodes
+ ranks_per_node = min( cores_per_node, ( n_ranks + min_nodes - 1 ) // min_nodes )
+ n_nodes = ( n_ranks + ranks_per_node - 1 ) // ranks_per_node
+
+ return [
+ {
+ 'id': 1,
+ 'nodes': n_nodes,
+ 'ranks_per_node': ranks_per_node,
+ 'total_ranks': n_nodes * ranks_per_node,
+ 'unknowns_per_rank': n_unknowns // ( n_nodes * ranks_per_node )
+ },
+ {
+ 'id': 2,
+ 'nodes': n_nodes * 2,
+ 'ranks_per_node': ranks_per_node // 2,
+ 'total_ranks': n_nodes * ranks_per_node,
+ 'unknowns_per_rank': n_unknowns // ( n_nodes * ranks_per_node )
+ },
+ ]
+
+ def get_sd( self ) -> list[ dict ]:
+ """Get the suggested decomposition popoulated."""
+ if self.job_type == 'cpu' and self.selected_cluster: #make it an enum
+ self.sd = SuggestDecomposition.compute( self.n_unknowns, 64, self.selected_cluster.mem_per_node,
+ self.selected_cluster.cores_per_node )
+ self.sd = [ {
+ **item, 'label': f"{self.selected_cluster.name} : {item['nodes']} x {item['ranks_per_node']}"
+ } for item in self.sd ]
+ else:
+ self.sd = [
+ {
+ 'id': -1,
+ 'label': 'No: 0x0',
+ 'nodes': 0,
+ 'ranks_per_node': 0,
+ 'total_ranks': 0,
+ 'unknowns_per_rank': 0
+ },
+ ]
+ # elif job_type == 'gpu':
+ # selected_cluster['n_nodes']*selected_cluster['gpu']['per_node']
+
+ return self.sd
+
+ # def to_list( self ) -> list[ str ]:
+ # """Pretty printer to list of string for display in UI."""
+ # sd = self.get_sd()
+ # return [ f"{self.selected_cluster.name} : {sd_item['nodes']} x {sd_item['ranks_per_node']}" for sd_item in sd ]
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/local_copyback.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/local_copyback.jinja
new file mode 100644
index 000000000..fb3605832
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/local_copyback.jinja
@@ -0,0 +1,15 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --error=job_GEOS_%j.err
+#SBATCH --dependency=afterok:{{ dep_job_id }}
+
+srun tar cfz {{ dep_job_id }}.tgz Outputs_{{ dep_job_id }}/ log_{{ dep_job_id }}.out
+srun mkdir -p {{ target_dl_path }} && mv -v {{ dep_job_id }}.tgz {{ target_dl_path }}
\ No newline at end of file
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/local_slurm.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/local_slurm.jinja
new file mode 100644
index 000000000..21f6ff6fa
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/local_slurm.jinja
@@ -0,0 +1,27 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --error=job_GEOS_%j.err
+
+ulimit -s unlimited
+ulimit -c unlimited
+
+module purge
+module use {{ geos_module }}
+module load {{ geos_load_list }}
+
+export HDF5_USE_FILE_LOCKING=FALSE
+export OMP_NUM_THREADS=1
+export EXEC={{ geos_path }}
+
+srun --hint=nomultithread \
+ -n {{ ntasks }} ${EXEC} \
+ -o Outputs_${SLURM_JOBID} \
+ -i {{ input_file | default('geosDeck.xml') }} | tee Outputs_${SLURM_JOBID}/log_${SLURM_JOBID}.out
\ No newline at end of file
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/p4_copyback.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/p4_copyback.jinja
new file mode 100644
index 000000000..35cc2b790
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/p4_copyback.jinja
@@ -0,0 +1,15 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --err=job_GEOS_%j.err
+#SBATCH --dependency=afterok:{{ dep_job_id }}
+
+srun tar cfz {{ dep_job_id }}.tgz Outputs_{{ dep_job_id }}/ log_{{ dep_job_id }}.out
+srun mkdir -p {{ target_dl_path }} && mv -v {{ dep_job_id }}.tgz {{ target_dl_path }}
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/p4_slurm.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/p4_slurm.jinja
new file mode 100644
index 000000000..bb331e457
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/p4_slurm.jinja
@@ -0,0 +1,27 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --error=job_GEOS_%j.err
+
+ulimit -s unlimited
+ulimit -c unlimited
+
+module purge
+module use {{ geos_module }}
+module load {{ geos_load_list }}
+
+export HDF5_USE_FILE_LOCKING=FALSE
+export OMP_NUM_THREADS=1
+export EXEC={{ geos_path }}
+
+srun --mpi=pmix_v3 --hint=nomultithread \
+ -n {{ ntasks }} ${EXEC} \
+ -o Outputs_${SLURM_JOBID} \
+ -i {{ input_file | default('geosDeck.xml') }} | tee log_${SLURM_JOBID}.out
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/pine_copyback.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/pine_copyback.jinja
new file mode 100644
index 000000000..e849c07d2
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/pine_copyback.jinja
@@ -0,0 +1,15 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --error=job_GEOS_%j.err
+#SBATCH --dependency=afterok:{{ dep_job_id }}
+
+srun tar cfz {{ dep_job_id }}.tgz Outputs_{{ dep_job_id }}/ log_{{ dep_job_id }}.out
+srun mkdir -p {{ target_dl_path }} && mv -v {{ dep_job_id }}.tgz {{ target_dl_path }}
diff --git a/geos-trame/src/geos/trame/app/io/jinja_t/pine_slurm.jinja b/geos-trame/src/geos/trame/app/io/jinja_t/pine_slurm.jinja
new file mode 100644
index 000000000..0c47ae296
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/jinja_t/pine_slurm.jinja
@@ -0,0 +1,28 @@
+#!/bin/sh
+#SBATCH --job-name="{{ job_name }}"
+#SBATCH --ntasks={{ ntasks }}
+#SBATCH --partition={{ partition }}
+#SBATCH --comment={{ comment_gr }}
+#SBATCH --account={{ account }}
+#SBATCH --nodes={{ nodes }}
+#SBATCH --time={{ time | default('00:10:00') }}
+#SBATCH --mem={{ mem }}
+#SBATCH --output=job_GEOS_%j.out
+#SBATCH --error=job_GEOS_%j.err
+
+ulimit -s unlimited
+ulimit -c unlimited
+
+module purge
+module use {{ geos_module }}
+module load {{ geos_load_list }}
+
+export HDF5_USE_FILE_LOCKING=FALSE
+export OMP_NUM_THREADS=1
+export EXEC={{ geos_path }}
+
+mkdir -p Outputs_${SLURM_JOBID} && touch log_${SLURM_JOBID}.out
+mpirun -mca coll_hcoll_enable 0 -x UCX_RNDV_THRESH=131072 \
+ -n {{ ntasks }} ${EXEC} \
+ -o Outputs_${SLURM_JOBID} \
+ -i {{ input_file | default('geosDeck.xml') }} | tee Outputs_${SLURM_JOBID}/log_${SLURM_JOBID}.out
diff --git a/geos-trame/src/geos/trame/app/io/simulation.py b/geos-trame/src/geos/trame/app/io/simulation.py
new file mode 100644
index 000000000..e4afac10a
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/simulation.py
@@ -0,0 +1,311 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Jacques Franc
+
+from pathlib import Path
+from enum import Enum, unique, auto
+from typing import Optional, Any
+from trame_server.core import Server
+
+from geos.trame.app.io.ssh_tools import Authentificator
+from geos.trame.app.utils.async_file_watcher import AsyncPeriodicRunner
+
+from jinja2 import Environment, FileSystemLoader
+import paramiko
+import re
+import os
+
+
+@unique
+class SimulationStatus( Enum ):
+ SCHEDULED = auto()
+ RUNNING = auto()
+ COMPLETING = auto()
+ COPY_BACK = auto()
+ DONE = auto()
+ NOT_RUN = auto()
+ UNKNOWN = auto()
+
+
+@unique
+class SlurmJobStatus( Enum ):
+ PENDING = "PEND"
+ RUNNING = "R"
+ COMPLETING = "CG"
+ COMPLETED = "CD"
+ SUSPENDED = "S"
+ UNKNOWN = "UNKNOWN"
+
+
+class Simulation:
+ """Simulation component.
+
+ Fills the UI with the screenshot as read from the simulation outputs folder and a graph with the time series
+ from the simulation.
+ Requires a simulation runner providing information on the output path of the simulation to monitor and ways to
+ trigger the simulation.
+ """
+
+ def __init__( self, server: Server, sim_info_dir: Optional[ Path ] = None ) -> None:
+ """Initialize the Simulation object with logging and sim triggers among other callbacks."""
+ self._server = server
+ controller = server.controller
+ self._sim_info_dir = sim_info_dir
+ server.state.job_ids = []
+ server.state.selected_cluster = None
+ server.state.nunknowns = 1
+
+ server.state.status_colors = {
+ "PENDING": "#4CAF50", #PD
+ "RUNNING": "#3F51B5", #R
+ "CANCELLED": "#FFC107", #CA
+ "COMPLETED": "#484B45", #CD
+ "FAILED": "#E53935", #F
+ }
+ self._job_status_watcher: Optional[ AsyncPeriodicRunner ] = None
+ self._job_status_watcher_period_ms = 2000
+
+ #define triggers
+ @controller.trigger( "run_try_login" )
+ def run_try_login() -> None:
+
+ # if server.state.key:
+ Authentificator.ssh_client = Authentificator._create_ssh_client(
+ Authentificator.get_cluster( server.state.selected_cluster_name ).host, #test
+ Authentificator.get_cluster( server.state.selected_cluster_name ).port,
+ server.state.login,
+ key=Authentificator.get_key( server.state.login, server.state.password, server.state.key_path,
+ server.state.selected_cluster_name ) )
+
+ if Authentificator.ssh_client:
+ server.state.access_granted = True
+
+ @controller.trigger( "run_simulation" )
+ def run_simulation() -> None:
+
+ # if server.state.access_granted and server.state.sd and server.state.simulation_xml_filename:
+ if server.state.access_granted and server.state.simulation_xml_filename and server.state.decomposition:
+ if Authentificator.ssh_client:
+ # create remote path
+ try:
+ sftp = Authentificator.ssh_client.open_sftp()
+ sftp.stat( server.state.simulation_remote_path )
+ except FileNotFoundError:
+ import posixpath
+ jpart = '/'
+ for part in server.state.simulation_remote_path.split( '/' )[ 1: ]:
+ try:
+ jpart = posixpath.join( jpart, part )
+ sftp.stat( str( jpart ) ) # exists?
+ except FileNotFoundError:
+ sftp.mkdir( str( jpart ) )
+ except PermissionError:
+ print( f"Permission error creating root folder at {jpart}" )
+ raise
+ except:
+ print( f"Error creating root folder at {jpart}" )
+ raise
+
+ # create local path
+ os.makedirs( server.state.simulation_dl_path, exist_ok=True )
+
+ Authentificator._sftp_copy_tree( Authentificator.ssh_client,
+ Simulation.gen_tree( server.state.simulation_xml_filename ),
+ server.state.simulation_remote_path )
+
+ cluster_name = Authentificator.get_cluster( server.state.selected_cluster_name ).name
+ cluster_part = Authentificator.get_cluster( server.state.selected_cluster_name ).partition
+ cluster_trans_part = Authentificator.get_cluster(
+ server.state.selected_cluster_name ).partition_transfert
+ run_id: str = Simulation.render_and_run(
+ f'{cluster_name}_slurm.jinja',
+ 'job.slurm',
+ server,
+ job_name=server.state.simulation_job_name,
+ input_file=[
+ item for item in server.state.simulation_xml_filename if item.get( 'type' ) == 'text/xml'
+ ][ 0 ].get( 'name' ),
+ nodes=server.state.decomposition[ 'nodes' ],
+ ntasks=server.state.decomposition[ 'nodes' ] * server.state.decomposition[ 'ranks_per_node' ],
+ geos_module=Authentificator.get_cluster( server.state.selected_cluster_name ).geos_module,
+ geos_load_list=" ".join(
+ Authentificator.get_cluster( server.state.selected_cluster_name ).geos_load_list ),
+ geos_path=Authentificator.get_cluster( server.state.selected_cluster_name ).geos_path,
+ mem="0",
+ comment_gr=server.state.slurm_comment,
+ partition=cluster_part,
+ account=server.state.slurm_comment )
+
+ Simulation.render_and_run( f'{cluster_name}_copyback.jinja',
+ 'copyback.slurm',
+ server,
+ job_name=server.state.simulation_job_name,
+ input_file=[
+ item for item in server.state.simulation_xml_filename
+ if item.get( 'type' ) == 'text/xml'
+ ][ 0 ].get( 'name' ),
+ nodes=1,
+ ntasks=1,
+ mem="0",
+ dep_job_id=run_id,
+ target_dl_path=server.state.simulation_dl_path,
+ comment_gr=server.state.slurm_comment,
+ partition=cluster_trans_part,
+ account=server.state.slurm_comment )
+
+ self._start_result_streams()
+
+ else:
+ raise paramiko.SSHException
+
+ @controller.trigger( "kill_all_simulations" )
+ def kill_all_simulations() -> None:
+ # exec scancel jobid
+ for jobs in server.state.job_ids:
+ Authentificator.kill_job( jobs[ 'job_id' ] )
+
+ def __del__( self ) -> None:
+ """Clean up running streams on destruction."""
+ self._stop_result_streams()
+
+ def set_status_watcher_period_ms( self, period_ms: int ) -> None:
+ """Set the watcher period in ms."""
+ self._job_status_watcher_period_ms = period_ms
+ if self._job_status_watcher:
+ self._job_status_watcher.set_period_ms( period_ms )
+
+ def _stop_result_streams( self ) -> None:
+ if self._job_status_watcher is not None:
+ self._job_status_watcher.stop()
+
+ def _start_result_streams( self ) -> None:
+ self._stop_result_streams()
+ self._job_status_watcher = AsyncPeriodicRunner( self.check_jobs, period_ms=self._job_status_watcher_period_ms )
+
+ def check_jobs( self ) -> None:
+ """Check on running jobs and update their names and progresses."""
+ if Authentificator.ssh_client:
+ jid = self._server.state.job_ids
+ for index, job in enumerate( jid ):
+ job_id = job[ 'job_id' ]
+ _, sout, _ = Authentificator._execute_remote_command(
+ Authentificator.ssh_client, f'sacct -j {job_id} -o JobID,JobName,State --noheader' )
+ job_line = sout.strip().split( "\n" )[ -1 ]
+
+ jid[ index ][ 'status' ] = job_line.split()[ 2 ]
+ jid[ index ][ 'name' ] = job_line.split()[ 1 ]
+
+ if ( jid[ index ][ 'status' ] == 'RUNNING' ):
+ _, sout, _ = Authentificator._execute_remote_command(
+ Authentificator.ssh_client,
+ f"sacct -j {job_id} -o ElapsedRaw,TimelimitRaw --noheader --parsable2 | head -n 1 " )
+ progress_line = sout.strip().split( "|" )
+ jid[ index ][ 'slprogress' ] = str(
+ float( progress_line[ 0 ] ) / float( progress_line[ 1 ] ) / 60 * 100 )
+
+ # getthe completed status
+ pattern = re.compile( r'\((\d+(?:\.\d+)?)%\s*completed\)' )
+ _, sout, _ = Authentificator._execute_remote_command(
+ Authentificator.ssh_client,
+ f"grep \"completed\" {self._server.state.simulation_remote_path}/job_GEOS_{job_id}.out | tail -1"
+ )
+ m = pattern.search( sout.strip() )
+ if m:
+ jid[ index ][ 'simprogress' ] = str( m.group( 1 ) )
+
+ print(
+ f"{job_line}-{job_id}\n job id:{jid[index]['job_id']}\n status:{jid[index]['status']}\n name:{jid[index]['name']} \n --- \n"
+ )
+ self._server.state.job_ids = jid
+ self._server.state.dirty( "job_ids" )
+ self._server.state.flush()
+
+ return None
+
+ @staticmethod
+ def render_and_run( template_name: str, dest_name: str, server: Server, **kwargs: Any ) -> str:
+ """Render the slurm template and run it. Return it job_id."""
+ if server.state.access_granted and server.state.simulation_xml_filename:
+ template = Environment(
+ loader=FileSystemLoader( f'{os.getenv("TEMPLATE_DIR")}' ) ).get_template( template_name )
+ rendered = template.render( kwargs )
+
+ if Authentificator.ssh_client:
+ #write slurm directly on remote
+ try:
+ sftp = Authentificator.ssh_client.open_sftp()
+ remote_path = Path( server.state.simulation_remote_path ) / Path( dest_name )
+ with sftp.file( str( remote_path ), 'w' ) as f:
+ f.write( rendered )
+
+ except PermissionError as e:
+ print( f"Permission error: {e}" )
+ except IOError as e:
+ print( f"Error accessing remote file or path: {e}" )
+ except Exception as e:
+ print( f"An error occurred during SFTP: {e}" )
+
+ _, sout, _ = Authentificator._execute_remote_command(
+ Authentificator.ssh_client, f'cd {server.state.simulation_remote_path} && sbatch {dest_name}' )
+ job_lines = sout.strip()
+ job_id = re.search( r"Submitted batch job (\d+)", job_lines )
+ if job_id:
+ server.state.job_ids.append( { 'job_id': job_id.group( 1 ) } )
+ return job_id.group( 1 )
+ else:
+ return "-1"
+ else:
+ return "-1"
+ else:
+ return "-1"
+
+ @staticmethod
+ def gen_tree( xml_filename: Any ) -> dict:
+ """Generate file tree to be copied on remote from files uploaded."""
+ import re
+ xml_pattern = re.compile( r"\.xml$", re.IGNORECASE )
+ mesh_pattern = re.compile( r"\.(vtu|vtm|pvtu|pvtm)$", re.IGNORECASE )
+ table_pattern = re.compile( r"\.(txt|dat|csv|geos)$", re.IGNORECASE )
+ xml_matches = []
+ mesh_matches = []
+ table_matches = []
+
+ pattern_file = r"[\w\-.]+\.(?:vtu|pvtu|dat|txt|xml|geos)\b" # all files
+ pattern_xml_path = r"\"(.*/)*([\w\-.]+\.(?:xml))\b"
+ pattern_mesh_path = r"\"(.*/)*([\w\-.]+\.(?:vtu|pvtu|vtm|pvtm))\b"
+ pattern_table_curly_path = r"((?:[\w\-/]+/)+)*([\w\-.]+\.(?:geos|csv|dat|txt))"
+
+ for file in xml_filename:
+ if xml_pattern.search( file.get( "name", "" ) ):
+ xml_matches.append( file )
+ elif mesh_pattern.search( file.get( "name", "" ) ):
+ mesh_matches.append( file )
+ elif table_pattern.search( file.get( "name", "" ) ):
+ table_matches.append( file )
+
+ #assume the first XML is the main xml
+ xml_expected_file_matches = re.findall( pattern_file, xml_matches[ 0 ][ 'content' ].decode( "utf-8" ) )
+
+ #TODO all the needed files
+ test_assert = { item.get( "name" ) for item in xml_filename }.intersection( set( xml_expected_file_matches ) )
+ assert test_assert
+
+ decoded = re.sub( pattern_xml_path, r'"\2', xml_matches[ 0 ][ 'content' ].decode( "utf-8" ) )
+ decoded = re.sub( pattern_mesh_path, r'"mesh/\2', decoded )
+ decoded = re.sub( pattern_table_curly_path, r"tables/\2", decoded )
+
+ xml_matches[ 0 ][ 'content' ] = decoded.encode( "utf-8" )
+
+ FILE_TREE = {
+ 'root': '.',
+ "structure": {
+ "files": xml_matches,
+ "subfolders": {
+ "mesh": mesh_matches,
+ "tables": table_matches
+ }
+ }
+ }
+
+ print( f"Generated FILE_TREE: {FILE_TREE}" )
+ return FILE_TREE
diff --git a/geos-trame/src/geos/trame/app/io/ssh_tools.py b/geos-trame/src/geos/trame/app/io/ssh_tools.py
new file mode 100644
index 000000000..ff5ef6549
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/io/ssh_tools.py
@@ -0,0 +1,307 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Jacques Franc
+
+from typing import Optional
+from pathlib import Path
+import paramiko
+import os
+import json
+from dataclasses import dataclass
+
+
+@dataclass
+class SimulationConstant:
+ name: str
+ host: str
+ partition: str
+ partition_transfert: str
+ port: int
+ geos_path: str
+ geos_module: str
+ geos_load_list: list
+ remote_home_base: str # for ssh key
+ simulation_default_filename: str
+ simulation_remote_path: str
+ simulation_dl_default_path: str
+ simulation_information_default_path: str
+ n_nodes: int
+ cores_per_node: int
+ mem_per_node: int
+
+
+#If proxyJump are needed
+#
+# proxy_cmd = "ssh -W {host}:{port} proxyuser@bastion.example.com".format(
+# host=ssh_host, port=ssh_port
+# )
+# from paramiko import ProxyCommand
+# sock = ProxyCommand(proxy_cmd)
+
+# client = paramiko.SSHClient()
+# client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+# client.connect(
+# hostname=ssh_host,
+# port=ssh_port,
+# username=username,
+# key_filename=keyfile,
+# sock=sock, # <— tunnel created by ProxyCommand
+# )
+
+
+class Authentificator:
+
+ ssh_client: Optional[ paramiko.SSHClient ] = None
+
+ sim_constants: list = []
+
+ @staticmethod
+ def reload_simconstants() -> None:
+ """Reload the cluster configuration from cluster.json file."""
+ Authentificator.sim_constants = [
+ SimulationConstant( **item )
+ for item in json.load( open( f'{os.getenv("ASSETS_DIR")}/cluster.json', 'r' ) ) # noqa: SIM115
+ ]
+
+ @staticmethod
+ def get_cluster( name: str ) -> Optional[ SimulationConstant ]:
+ """Return the structured meta for cluster selected."""
+ match = next( ( item for item in Authentificator.sim_constants if item.name == name ), None )
+ return match
+
+ @staticmethod
+ def _sftp_copy_tree( ssh_client: paramiko.SSHClient, file_tree: dict, remote_root: str ) -> None:
+ """Copy the file tree at remote root using ssh_client."""
+ # Connect to remote server
+ sftp = ssh_client.open_sftp()
+
+ Authentificator.dfs_tree( file_tree[ "structure" ], file_tree[ "root" ], sftp=sftp, remote_root=remote_root )
+
+ sftp.close()
+
+ @staticmethod
+ def dfs_tree( node: list | dict, path: str, sftp: paramiko.SFTPClient, remote_root: str ) -> None:
+ """Create the tree represented by node at local path in remote pointed by sftp client at remote_root."""
+ if path is None or remote_root is None:
+ return # type:ignore[unreachable]
+
+ lp = Path( path )
+ rp = Path( remote_root ) / lp
+
+ if isinstance( node, list ):
+ for file in node:
+ print( f"copying {lp/Path(file.get('name'))} to {rp/Path(file.get('name'))}" )
+ try:
+ content = file.get( 'content' )
+ mode = "wb" if isinstance( content, ( bytes, bytearray ) ) else "w"
+ with sftp.file( str( rp / Path( file.get( 'name' ) ) ), mode ) as f:
+ f.write( content )
+ except Exception as e:
+ print( f"Error copying {lp/Path(file.get('name'))} to {rp/Path(file.get('name'))}: {e}" )
+ raise
+ elif isinstance( node, dict ):
+ if "files" in node:
+ files = node[ 'files' ]
+ for file in files:
+ print( f"copying {lp/Path(file.get('name'))} to {rp/Path(file.get('name'))}" )
+ try:
+ content = file.get( 'content' )
+ mode = "wb" if isinstance( content, ( bytes, bytearray ) ) else "w"
+ with sftp.file( str( rp / Path( file.get( 'name' ) ) ), mode ) as f:
+ f.write( content )
+ except Exception as e:
+ print( f"Error copying {lp/Path(file.get('name'))} to {rp/Path(file.get('name'))}: {e}" )
+ raise
+ if "subfolders" in node:
+ for subfolder, content in node[ "subfolders" ].items():
+ try:
+ sftp.stat( str( rp / Path( subfolder ) ) )
+ except FileNotFoundError:
+ try:
+ print( f"creating {rp/Path(subfolder)}" )
+ sftp.mkdir( str( rp / Path( subfolder ) ) )
+ except:
+ print( f"Error creating {rp/Path(subfolder)} on remote." )
+ raise
+ Authentificator.dfs_tree( content, str( lp / Path( subfolder ) ), sftp, remote_root )
+
+ for folder, content in node.items():
+ if folder not in [ "files", "subfolders" ]:
+ try:
+ sftp.stat( str( rp / Path( folder ) ) )
+ except FileNotFoundError:
+ print( f"creating {rp/Path(folder)}" )
+ try:
+ sftp.mkdir( str( rp / Path( folder ) ) )
+ except:
+ print( f"Error creating {rp/Path(subfolder)} on remote." )
+ raise
+ Authentificator.dfs_tree( content, str( lp / Path( folder ) ), sftp, remote_root )
+
+ @staticmethod
+ def kill_job( id: int ) -> None:
+ """Cancel job identified by id in slurm schedulder."""
+ if Authentificator.ssh_client:
+ Authentificator._execute_remote_command( Authentificator.ssh_client, f"scancel {id}" )
+ return None
+
+ @staticmethod
+ def get_key( id: str, pword: str, key_path: str, cluster_name: str ) -> paramiko.RSAKey:
+ """Return the ssh key if found or create and dispatch one."""
+ try:
+ PRIVATE_KEY = paramiko.RSAKey.from_private_key_file( key_path )
+ return PRIVATE_KEY
+ except paramiko.SSHException as e:
+ print( f"Error loading private key: {e}\n" )
+ raise paramiko.SSHException( f"Error loading private key: {e}\n" ) from e
+ except FileNotFoundError as e:
+ print( f"Private key not found: {e}\n Generating key at ... {key_path}" )
+ PRIVATE_KEY = Authentificator.gen_key( key_path )
+ temp_client = paramiko.SSHClient()
+ temp_client.set_missing_host_key_policy( paramiko.AutoAddPolicy() )
+
+ clusterByName = Authentificator.get_cluster( cluster_name )
+ if clusterByName is None:
+ raise ValueError( f"Cluster '{cluster_name}' not found in configuration." ) from e
+
+ host = clusterByName.host
+ port = clusterByName.port
+ temp_client.connect( host, port, username=id, password=pword, timeout=10 )
+ remote_base = clusterByName.remote_home_base
+ Authentificator._transfer_file_sftp( temp_client, f"{key_path.split('/')[-1]}.pub",
+ f"{remote_base}/{id}/.ssh/{key_path.split('/')[-1]}.pub" )
+ Authentificator._execute_remote_command(
+ temp_client,
+ f" cat {remote_base}/.ssh/{key_path.split('/')[-1]}.pub | tee -a {clusterByName.remote_home_base}/.ssh/authorized_keys"
+ )
+
+ return PRIVATE_KEY
+
+ @staticmethod
+ def gen_key( key_path: str ) -> paramiko.RSAKey:
+ """Generate RSAKey for SSH protocol."""
+ # home = os.environ.get( "HOME" )
+ # file_path = f"{home}/.ssh/id_trame"
+ key = paramiko.RSAKey.generate( bits=4096 )
+ key.write_private_key_file( key_path )
+
+ # Get public key in OpenSSH format
+ public_key = f"{key.get_name()} {key.get_base64()}"
+ with open( key_path + ".pub", "w" ) as pub_file:
+ pub_file.write( public_key )
+
+ suffix = key_path.split( '/' )[ -1 ]
+ print( f"SSH key pair generated: {suffix} (private), {suffix}.pub (public)" )
+
+ return key
+
+ @staticmethod
+ def _create_ssh_client( host: str,
+ port: int,
+ username: str,
+ password: str | None = None,
+ key: paramiko.RSAKey | None = None ) -> paramiko.SSHClient | None:
+ """Initializes and returns an SSH client connection.
+
+ Uses context manager for automatic cleanup.
+ """
+ client = paramiko.SSHClient()
+ # Automatically adds the hostname and new host keys to the host files (~/.ssh/known_hosts)
+ client.set_missing_host_key_policy( paramiko.AutoAddPolicy() )
+
+ try:
+ print( f"Connecting to {host} using key-based authentication..." )
+ client.connect( host, port, username, pkey=key, timeout=10 )
+
+ return client
+ except paramiko.AuthenticationException:
+ print( "Authentication failed. Check your credentials or key." )
+ return None
+ except paramiko.SSHException as e:
+ print( f"Could not establish SSH connection: {e}" )
+ return None
+ except Exception as e:
+ print( f"An unexpected error occurred: {e}" )
+ return None
+
+ @staticmethod
+ def _execute_remote_command( client: paramiko.SSHClient, command: str ) -> tuple[ int, str, str ]:
+ """Executes a single command on the remote server and prints the output."""
+ if not client:
+ return ( -1, "", "" )
+
+ print( f"\n--- Executing Command: '{command}' ---" )
+ try:
+ # Executes the command. stdin, stdout, and stderr are file-like objects.
+ # Ensure command ends with a newline character for some shell environments.
+ stdin, stdout, stderr = client.exec_command( command )
+
+ # Wait for the command to finish and read the output
+ exit_status = stdout.channel.recv_exit_status()
+
+ # Print standard output
+ stdout_data = stdout.read().decode().strip()
+ if stdout_data:
+ print( "STDOUT:" )
+ print( stdout_data )
+
+ # Print standard error (if any)
+ stderr_data = stderr.read().decode().strip()
+ if stderr_data:
+ print( "STDERR:" )
+ print( stderr_data )
+
+ print( f"Command exited with status: {exit_status}" )
+ return ( exit_status, stdout_data, stderr_data )
+
+ except PermissionError as e:
+ print( f"Permission error: {e}" )
+ return ( -1, "", "" )
+ except IOError as e:
+ print( f"Error accessing remote file or path: {e}" )
+ return ( -1, "", "" )
+ except Exception as e:
+ print( f"An error occurred during SFTP: {e}" )
+ return ( -1, "", "" )
+
+ @staticmethod
+ def _transfer_file_sftp( client: paramiko.SSHClient,
+ local_path: str,
+ remote_path: str,
+ direction: str = "put" ) -> Optional[ bool ]:
+ """Transfers a file using SFTP (Secure File Transfer Protocol).
+
+ Direction can be 'put' (upload) or 'get' (download).
+ """
+ if not client:
+ return None
+
+ print( f"\n--- Starting SFTP Transfer ({direction.upper()}) ---" )
+
+ try:
+ # Establish an SFTP connection session
+ sftp = client.open_sftp()
+
+ if direction == "put":
+ print( f"Uploading '{local_path}' to '{remote_path}'..." )
+ sftp.put( local_path, remote_path )
+ print( "Upload complete." )
+ elif direction == "get":
+ print( f"Downloading '{remote_path}' to '{local_path}'..." )
+ sftp.get( remote_path, local_path )
+ print( "Download complete." )
+ else:
+ print( "Invalid transfer direction. Use 'put' or 'get'." )
+
+ sftp.close()
+ return True
+
+ except FileNotFoundError:
+ print( f"Error: Local file '{local_path}' not found." )
+ return False
+ except IOError as e:
+ print( f"Error accessing remote file or path: {e}" )
+ return False
+ except Exception as e:
+ print( f"An error occurred during SFTP: {e}" )
+ return False
diff --git a/geos-trame/src/geos/trame/app/main.py b/geos-trame/src/geos/trame/app/main.py
index 2ad3b293a..cada4beec 100644
--- a/geos-trame/src/geos/trame/app/main.py
+++ b/geos-trame/src/geos/trame/app/main.py
@@ -1,13 +1,17 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Lionel Untereiner
+# SPDX-FileContributor: Lionel Untereiner, Jacques Franc
from pathlib import Path
from typing import Any
+from dotenv import load_dotenv
+import os
from trame.app import get_server # type: ignore
from trame_server import Server
+#do not override if existing
from geos.trame.app.core import GeosTrame
+from geos.trame.app.io.ssh_tools import Authentificator
def main( server: Server = None, **kwargs: Any ) -> None:
@@ -24,13 +28,23 @@ def main( server: Server = None, **kwargs: Any ) -> None:
# parse args
parser = server.cli
- parser.add_argument( "-I", "--input", help="Input file (.xml)" )
+ parser.add_argument( "-I", "--input", help="Input file (.xml)", required=True )
+ parser.add_argument( "-e", "--env", help="dot_env file", required=False )
( args, _unknown ) = parser.parse_known_args()
- if args.input is None:
- print( "Usage: \n\tgeos-trame -I /path/to/input/file" )
- return
+ if args.env:
+ if not load_dotenv( dotenv_path=Path( args.env ) ):
+ raise SystemExit( f"Failed to load environment file: {args.env}" )
+ else:
+ env_path = Path( __file__ ).parent.parent / "assets/.env"
+ if not load_dotenv( dotenv_path=env_path ):
+ raise SystemExit( f"Failed to load environment file: {env_path}" )
+
+ Authentificator.reload_simconstants()
+
+ print( f"TEMPLATE_DIR .. {os.getenv('TEMPLATE_DIR')}" )
+ print( f"ASSETS_DIR .. {os.getenv('ASSETS_DIR')}" )
file_name = str( Path( args.input ).absolute() )
diff --git a/geos-trame/src/geos/trame/app/ui/plotting.py b/geos-trame/src/geos/trame/app/ui/plotting.py
index 1ca2bbb9f..790203c27 100644
--- a/geos-trame/src/geos/trame/app/ui/plotting.py
+++ b/geos-trame/src/geos/trame/app/ui/plotting.py
@@ -57,7 +57,7 @@ def _figure_size( self ) -> dict:
"dpi": dpi,
}
- def _permeability( self, **kwargs: Any ) -> Figure:
+ def _permeability( self, **kwargs: Any ) -> type[ Figure ]:
# read data
assert self.source.input_file is not None
if self.source.input_file is None:
diff --git a/geos-trame/src/geos/trame/app/ui/simulation_view.py b/geos-trame/src/geos/trame/app/ui/simulation_view.py
new file mode 100644
index 000000000..627b47578
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/ui/simulation_view.py
@@ -0,0 +1,410 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Jacques Franc
+# ignore context collapsing as it is clearer this way
+# ruff: noqa: SIM117
+from typing import Any
+
+from trame.widgets import html
+from trame.widgets import vuetify3 as vuetify
+from trame_server import Server
+
+from geos.trame.app.io.simulation import Authentificator
+from geos.trame.app.io.hpc_tools import SuggestDecomposition
+
+#rough estimate of n unknowns would be better from GEOS's dry-run
+# unknowns (oncell,onpoint)
+# for now do not take into account wells as dep on the num of wells (neg vs matrix elmts)
+# for now do not take into account frac as dep on the num of frac elmts (prob neg vs matrix elmts)
+solvers_to_unknowns = {
+ "CompositionalMultiphaseFVM": ( 3, 0 ),
+ "CompositionalMultiphaseHybridFVM": ( 4, 0 ),
+ "CompositionalMultiphaseReservoirPoromechanics": ( 3, 3 ),
+ "CompositionalMultiphaseReservoirPoromechanicsConformingFractures": ( 3, 6 ),
+ "CompositionalMultiphaseWell": ( 3, 0 ),
+ "ElasticFirstOrderSEM": ( 0, 3 ),
+ "ElasticSEM": ( 0, 3 ),
+ "ImmiscibleMultiphaseFlow": ( 3, 0 ),
+ "LaplaceFEM": ( 0, 3 ),
+ "MultiphasePoromechanics": ( 3, 3 ),
+ "MultiphasePoromechanicsReservoir": ( 3, 3 ), #??
+ "MultiphasePoromechanicsConformingFractures": ( 3, 6 ),
+ "SinglePhaseFVM": ( 2, 0 ),
+ "SinglePhaseHybridFVM": ( 3, 0 ),
+ "SinglePhasePoromechanics": ( 2, 3 ),
+ "SinglePhasePoromechanicsConformingFractures": ( 2, 3 ),
+ "SinglePhasePoromechanicsConformingFracturesALM": ( 2, 3 ),
+ "SinglePhaseWell": ( 2, 0 ),
+ "SolidMechanicsEmbeddedFractures": ( 0, 3 ),
+ "SolidMechanicsAugmentedLagrangianContact": ( 0, 3 ),
+ "SolidMechanicsLagrangeContact": ( 0, 3 ),
+ "SolidMechanicsLagrangeContactBubbleStab": ( 0, 3 ),
+ "SolidMechanicsLagrangianFEM": ( 0, 3 )
+}
+
+
+# helpers
+def _what_solver( bcontent: dict ) -> tuple[ int, int ]:
+ from xml.etree.ElementTree import Element, fromstring
+ sim_xml: Element = fromstring( bcontent[ 'content' ] )
+ solver = sim_xml.find( 'Solvers' )
+ nunk: list[ tuple[ int, int ] ] = [ solvers_to_unknowns.get( elt.tag, ( 1, 0 ) )
+ for elt in solver ] if solver else [ ( 0, 0 ) ]
+ return max( nunk )
+
+
+def _how_many_cells( bcontent: dict ) -> tuple[ int, int ]:
+ import vtk
+ name = bcontent[ 'name' ]
+ if name.endswith( ".vtp" ):
+ reader = vtk.vtkXMLPolyDataReader()
+ elif name.endswith( ".vtu" ):
+ reader = vtk.vtkXMLUnstructuredGridReader()
+ elif name.endswith( ".vtm" ):
+ reader = vtk.vtkXMLMultiBlockDataReader()
+ else:
+ raise ValueError( "Unsupported kind (use 'vtp', 'vtu', or 'vtm')." )
+
+ reader.SetReadFromInputString( 1 )
+ reader.SetInputString( bcontent[ 'content' ] )
+ reader.Update()
+ output = reader.GetOutput()
+ return ( output.GetNumberOfCells(), output.GetNumberOfPoints() )
+
+
+def _has_internalMesh( bcontent: dict ) -> bool:
+ from xml.etree.ElementTree import Element, fromstring
+ sim_xml: Element = fromstring( bcontent[ 'content' ] )
+ return bool( sim_xml.find( 'Mesh/InternalMesh' ) is not None )
+
+
+def _what_internalMesh( bcontent: dict ) -> tuple[ int, int ]:
+ from xml.etree.ElementTree import Element, fromstring
+ import re
+ sim_xml: Element = fromstring( bcontent[ 'content' ] )
+
+ mesh = sim_xml.find( 'Mesh/InternalMesh' )
+
+ def _parse_sum( value: str | None ) -> int:
+ if value is None:
+ return 0
+ return sum( int( el ) for el in re.findall( r'-?\d+(?:\.\d+)?', value ) )
+
+ if mesh is None:
+ nx = ny = nz = 0
+ else:
+ nx = _parse_sum( mesh.get( 'nx' ) )
+ ny = _parse_sum( mesh.get( 'ny' ) )
+ nz = _parse_sum( mesh.get( 'nz' ) )
+
+ return ( nx * ny * nz, ( nx + 1 ) * ( ny + 1 ) * ( nz + 1 ) )
+
+
+#TODO a class from it
+def define_simulation_view( server: Server ) -> None:
+ """Functional definition of UI elements."""
+
+ @server.state.change( "other_widget_selected_file" )
+ def on_other_widget_file_ready( other_widget_selected_file: dict, **_: Any ) -> None:
+ if not other_widget_selected_file:
+ return
+
+ current = list( server.state.simulation_xml_filename )
+ existing_names = { f.get( "name" ) for f in current }
+
+ if other_widget_selected_file.get( "name" ) not in existing_names:
+ current.append( other_widget_selected_file )
+ server.state.simulation_xml_filename = current
+
+ @server.state.change( "selected_cluster_name" )
+ def on_cluster_change( selected_cluster_name: str, **_: Any ) -> None:
+ print( f"selecting {selected_cluster_name}" )
+ cluster = Authentificator.get_cluster( selected_cluster_name )
+ if cluster is None:
+ print( f"Error: Cluster '{selected_cluster_name}' not found in configuration." )
+ return
+
+ server.state.decompositions = SuggestDecomposition( cluster, server.state.nunknowns ).get_sd()
+
+ server.state.simulation_remote_path = cluster.simulation_remote_path
+
+ server.state.simulation_dl_path = cluster.simulation_dl_default_path
+
+ # @server.state.change( "decomposition" )
+ # def on_decomposition_selected( decomposition: str, **_: Any ) -> None:
+ # = SuggestDecomposition( Authentificator.get_cluster( server.state.selected_cluster_name ), server.state.nunknowns ).get_sd()
+ # # if server.state.decomposition:
+ # except:
+ # server.state.sd = { 'nodes': 0, 'total_ranks': 0 }
+
+ @server.state.change( "simulation_xml_temp" )
+ def on_temp_change( simulation_xml_temp: list, **_: Any ) -> None:
+ current_list = server.state.simulation_xml_filename
+ new_list = current_list + simulation_xml_temp
+
+ server.state.simulation_xml_filename = new_list
+ server.state.simulation_xml_temp = []
+
+ @server.state.change( "nunknowns" )
+ def on_nunknowns_change( nunknowns: int, **_: Any ) -> None:
+ #re-gen list
+ if len( server.state.decompositions ) > 0:
+ server.state.decompositions = SuggestDecomposition(
+ Authentificator.get_cluster( server.state.selected_cluster_name ), nunknowns ).get_sd()
+ print( f'unknowns changed : {server.state.nunknowns} -> {nunknowns}' )
+ server.state.nunknowns = nunknowns
+
+ @server.state.change( "simulation_xml_filename" )
+ def on_simfiles_change( simulation_xml_filename: list, **_: Any ) -> None:
+ import re
+ has_xml = [ file.get( "type", "" ) == 'text/xml' for file in simulation_xml_filename ]
+
+ has_external_mesh = [
+ bool( file.get( "name", "" ).endswith( ( ".vtu", ".vtm", ".vtp" ) ) ) for file in simulation_xml_filename
+ ]
+
+ has_internal_mesh = False
+ for i, _ in enumerate( has_xml ):
+ if has_xml[ i ]:
+ has_internal_mesh = _has_internalMesh( simulation_xml_filename[ i ] )
+
+ if any( has_xml ):
+ uc = up = nc = np = 0
+ # compute unknowns and cells only for xml files, if external mesh do not take into account internal mesh info even if present, if no external mesh try to take into account internal mesh info if present
+ # useful for decomposition suggestion
+ for i, _ in enumerate( has_xml ):
+ if has_external_mesh[ i ]:
+ nc, np = _how_many_cells( simulation_xml_filename[ i ] )
+ elif has_xml[ i ]:
+ uc, up = _what_solver( simulation_xml_filename[ i ] )
+ if has_internal_mesh:
+ nc, np = _what_internalMesh( simulation_xml_filename[ i ] )
+
+ if all( i is not None for i in ( uc, nc, up, np ) ):
+ server.state.nunknowns = uc * nc + up * np
+
+ if any( has_xml ):
+ xml_pattern = re.compile( r"\.xml$", re.IGNORECASE )
+ mesh_pattern = re.compile( r"\.(vtu|vtm|pvtu|pvtm)$", re.IGNORECASE )
+ table_pattern = re.compile( r"\.(txt|dat|csv|geos)$", re.IGNORECASE )
+
+ xml_matches, mesh_matches, table_matches = [], [], []
+
+ pattern_file = r"[\w\-.]+\.(?:vtu|pvtu|dat|txt|xml|geos)\b"
+
+ # Fix: use enumerate instead of .index() to handle duplicates safely
+ for i, file in enumerate( simulation_xml_filename ):
+ if not has_xml[ i ]:
+ continue
+ name = file.get( "name", "" )
+ if xml_pattern.search( name ):
+ xml_matches.append( file )
+ elif mesh_pattern.search( name ):
+ mesh_matches.append( file )
+ elif table_pattern.search( name ):
+ table_matches.append( file )
+
+ if xml_matches:
+ already_have = { file.get( "name", "" ) for file in simulation_xml_filename }
+ required = set( re.findall( pattern_file, xml_matches[ 0 ][ 'content' ].decode( "utf-8" ) ) )
+ required -= already_have
+ # Fix: store as list of dicts so the UI can use {{ file.name }}
+ server.state.simulation_xml_required = [ { "name": f } for f in sorted( required ) ]
+ else:
+ server.state.simulation_xml_required = []
+
+ server.state.is_valid_jobfiles = any( has_xml )
+ server.state.all_req_files = any( has_xml ) and len( server.state.simulation_xml_required ) == 0
+
+ def kill_job( index_to_remove: int ) -> None:
+ # for now just check there is an xml
+ jid = list( server.state.job_ids )
+ if 0 <= index_to_remove < len( jid ):
+ removed_id = jid[ index_to_remove ][ 'job_id' ]
+ Authentificator.kill_job( removed_id )
+ del jid[ index_to_remove ]
+
+ server.state.job_ids = jid
+ print( f"Job {removed_id} kill. Still running: {len(jid)}" )
+ else:
+ print( f"Error: supress index does not exist ({index_to_remove})." )
+
+ def run_remove_jobfile( index_to_remove: int ) -> None:
+ current_files = list( server.state.simulation_xml_filename )
+ if 0 <= index_to_remove < len( current_files ):
+ del current_files[ index_to_remove ]
+
+ server.state.simulation_xml_filename = current_files
+ print( f"File at {index_to_remove} deleted. New files: {len(current_files)}" )
+ else:
+ print( f"Erreur: Wrong deletion index ({index_to_remove})." )
+
+ with vuetify.VContainer():
+ with vuetify.VRow():
+ with vuetify.VCol( cols=4 ):
+ vuetify.VTextField( v_model=(
+ "login",
+ None,
+ ),
+ label="Login",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ prepend_icon="mdi-login" )
+ with vuetify.VCol( cols=4 ):
+ vuetify.VTextField( v_model=(
+ "password",
+ None,
+ ),
+ label="Password",
+ type="password",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ prepend_icon="mdi-onepassword" )
+
+ #
+ server.state.access_granted = False
+ server.state.is_valid_jobfiles = False
+ server.state.simulation_xml_filename = []
+ server.state.simulation_xml_required = []
+ server.state.selected_cluster_names = [ cluster.name for cluster in Authentificator.sim_constants ]
+ # server.state.decompositions = []
+
+ # --------------------------- auth block -----------------------#
+ vuetify.VDivider( vertical=True, thickness=5, classes="mx-4" )
+ with vuetify.VCol( cols=1 ):
+ vuetify.VSelect( label="Cluster",
+ items=( "selected_cluster_names", ),
+ v_model=( "selected_cluster_name", 'p4' ) )
+ vuetify.VDivider( vertical=True, thickness=5, classes="mx-4" )
+ with vuetify.VCol( cols=1 ):
+ vuetify.VSelect( label="Decomposition",
+ items=( "decompositions", [] ),
+ v_model=( "decomposition", None ),
+ item_title="label",
+ item_value="id",
+ return_object=True )
+
+ with vuetify.VRow():
+ with vuetify.VCol( cols=8 ):
+ vuetify.VTextField( v_model=(
+ "key_path",
+ "/users/$USER/.ssh/id_trame",
+ ),
+ label="Path to ssh key",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ prepend_icon="mdi-key-chain-variant" )
+
+ #
+ vuetify.VDivider( vertical=True, thickness=5, classes="mx-4" )
+ with vuetify.VCol( cols=1 ):
+ vuetify.VBtn( "Log in", click="trigger('run_try_login')",
+ disabled=( "access_granted", ) ) # type: ignore
+ #
+ vuetify.VDivider( vertical=True, thickness=5, classes="mx-4" )
+ with vuetify.VCol( cols=1 ):
+ vuetify.VTextField(
+ v_model=( "slurm_comment", None ),
+ label="Comment to slurm",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ ) # type: ignore
+
+ # --------------------------- simulation block -----------------------#
+ vuetify.VDivider( thickness=5, classes="my-4" )
+
+ with vuetify.VRow():
+ with vuetify.VCol( cols=4 ):
+ vuetify.VFileUpload(
+ v_model=( "simulation_xml_temp", [] ),
+ title="Simulation file name",
+ density='comfortable',
+ hide_details=True,
+ # clearable=True,
+ multiple=True,
+ filter_by_type='.xml,.vtu,.vtm,.pvtu,.pvtm,.dat,.csv,.txt,.geos,.vtk',
+ # readonly=True,
+ disabled=( "!access_granted", ) )
+ with vuetify.VCol( cols=4 ), vuetify.VList():
+ with vuetify.VListItem( v_for=( "(file,i) in simulation_xml_filename" ),
+ key="i",
+ value="file",
+ prepend_icon="mdi-minus-circle-outline",
+ click=( run_remove_jobfile, "[i]" ) ):
+ vuetify.VListItemTitle( "{{ file.name }}" )
+ vuetify.VListItemSubtitle( "{{ file.size ? (file.size / 1024).toFixed(1) + ' KB' : 'URL' }}" )
+ vuetify.VDivider( thickness=2, classes="my-2" )
+
+ with vuetify.VListItem(
+ v_for=( "(file,i) in simulation_xml_required" ),
+ key="i",
+ value="file",
+ classes="bg-red-lighten-4 text-red-darken-4",
+ # base_color="red-lighten-4",
+ # style="background-color: rgb(var(--v-theme-error-lighten-4));",
+ prepend_icon="mdi-alert-circle-outline" ):
+ vuetify.VListItemTitle( "{{ file.name }} (required)" )
+
+ with vuetify.VRow(), vuetify.VCol():
+ vuetify.VTextField( v_model=( "simulation_remote_path", None ),
+ label="Path where to write files and launch code",
+ prepend_icon="mdi-upload",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ disabled=( "!access_granted", )
+ # TODO callback validation of path
+ )
+
+ with vuetify.VRow(), vuetify.VCol():
+ vuetify.VTextField( v_model=( "simulation_dl_path", None ),
+ label="Simulation download path",
+ dense=True,
+ clearable=True,
+ prepend_icon="mdi-download",
+ disabled=( "!access_granted", )
+ # TODO callback validation of path
+ )
+
+ with vuetify.VRow():
+ with vuetify.VCol( cols=4 ):
+ vuetify.VTextField( v_model=( "simulation_job_name", "geosJob" ),
+ label="Job Name",
+ dense=True,
+ hide_details=True,
+ clearable=True,
+ disabled=( "!access_granted", ) )
+
+ vuetify.VSpacer()
+ with vuetify.VCol( cols=1 ):
+ vuetify.VBtn( "Run",
+ click="trigger('run_simulation')",
+ disabled=( "!is_valid_jobfiles || !all_req_files", ),
+ classes="ml-auto" ), # type: ignore
+
+ # ------------------------------- Status block ----------------------------- #
+ vuetify.VDivider( thickness=5, classes="my-4" )
+
+ with vuetify.VRow():
+ vuetify.VSpacer()
+ with vuetify.VCol( cols=1 ):
+ vuetify.VBtn( "Kill All", click="trigger('kill_all_simulations')" ), # type: ignore
+
+ color_expression = "status_colors[job_ids[i].status] || '#607D8B'"
+ with vuetify.VRow(), vuetify.VCol( cols=4 ), vuetify.VList():
+ with vuetify.VListItem( v_for=( "(jobs,i) in job_ids" ),
+ key="i",
+ value="jobs",
+ base_color=( color_expression, ),
+ prepend_icon="mdi-minus-circle-outline",
+ click=( kill_job, "[i]" ) ):
+ vuetify.VListItemTitle( "{{ jobs.status }} -- {{ jobs.name }} -- {{ jobs.job_id }}" )
+ vuetify.VProgressLinear( v_model=( "jobs.simprogress", "0" ), )
+ vuetify.VProgressLinear( v_model=( "jobs.slprogress", "0" ), )
+
+ with vuetify.VRow( v_if="simulation_error" ):
+ html.Div( "An error occurred while running simulation :
{{simulation_error}}", style="color:red;" )
diff --git a/geos-trame/src/geos/trame/app/ui/viewer/boxViewer.py b/geos-trame/src/geos/trame/app/ui/viewer/boxViewer.py
index e51f5035f..4b5310e15 100644
--- a/geos-trame/src/geos/trame/app/ui/viewer/boxViewer.py
+++ b/geos-trame/src/geos/trame/app/ui/viewer/boxViewer.py
@@ -4,6 +4,7 @@
import pyvista as pv
from geos.trame.schema_generated.schema_mod import Box
+from typing import Any
import re
@@ -102,7 +103,7 @@ def _compute_intersected_cell( self ) -> None:
if len( saved_ids ) > 0:
self._extracted_cells = self._mesh.extract_cells( saved_ids )
- def _check_cell_inside_box( self, cell: pv.Cell, box_bounds: list[ float ] ) -> bool:
+ def _check_cell_inside_box( self, cell: pv.Cell, box_bounds: Any ) -> bool:
"""Check if the cell is inside the box bounds.
A cell is considered inside the box if his bounds are completely
diff --git a/geos-trame/src/geos/trame/app/ui/viewer/viewer.py b/geos-trame/src/geos/trame/app/ui/viewer/viewer.py
index 4ea3c705a..46ceddb38 100644
--- a/geos-trame/src/geos/trame/app/ui/viewer/viewer.py
+++ b/geos-trame/src/geos/trame/app/ui/viewer/viewer.py
@@ -401,8 +401,8 @@ def _update_box( self, active_block: Box, show_obj: bool ) -> None:
return
if self.box_engine is not None and active_block.name in self.box_engine:
- box_polydata_actor: pv.Actor = self.box_engine[ active_block.name ].get_box_polydata_actor()
- extracted_cell_actor: pv.Actor = self.box_engine[ active_block.name ].get_extracted_cells_actor()
+ box_polydata_actor: pv.BasePlotter = self.box_engine[ active_block.name ].get_box_polydata_actor()
+ extracted_cell_actor: pv.BasePlotter = self.box_engine[ active_block.name ].get_extracted_cells_actor()
self.plotter.remove_actor( box_polydata_actor )
self.plotter.remove_actor( extracted_cell_actor )
del self.box_engine[ active_block.name ]
diff --git a/geos-trame/src/geos/trame/app/utils/async_file_watcher.py b/geos-trame/src/geos/trame/app/utils/async_file_watcher.py
new file mode 100644
index 000000000..f8960e580
--- /dev/null
+++ b/geos-trame/src/geos/trame/app/utils/async_file_watcher.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Jacques Franc
+
+import asyncio
+from asyncio import CancelledError, ensure_future
+from typing import Callable
+
+from trame_server.utils import asynchronous
+
+
+class AsyncPeriodicRunner:
+ """While started, runs given callback at given period."""
+
+ def __init__( self, callback: Callable, period_ms: int = 100 ) -> None:
+ """Init the async watcher object."""
+ self.last_m_time = None
+ self.callback = callback
+ self.period_ms = period_ms
+ self.task = None
+ self.start()
+
+ def __del__( self ) -> None:
+ """Clean up async watch on destruction."""
+ self.stop()
+
+ def set_period_ms( self, period_ms: int ) -> None:
+ """Set the async watch period.
+
+ :params:period_ms period in ms
+ """
+ self.period_ms = period_ms
+
+ def start( self ) -> None:
+ """Stop existing async watch and start a new stream."""
+ self.stop()
+ self.task = asynchronous.create_task( self._runner() )
+
+ def stop( self ) -> None:
+ """Stop the async watch."""
+ if not self.task:
+ return
+
+ ensure_future( self._wait_for_cancel() ) # type:ignore[unreachable]
+
+ async def _wait_for_cancel( self ) -> None:
+ """Cancel and await cancel error for the task.
+
+ If cancel is done outside async, it may raise warnings as cancelled exception may be triggered outside async
+ loop.
+ """
+ if not self.task or self.task.done() or self.task.cancelled(): # type:ignore[unreachable]
+ self.task = None
+ return
+
+ try: # type:ignore[unreachable]
+ self.task.cancel()
+ await self.task
+ except CancelledError:
+ self.task = None
+
+ async def _runner( self ) -> None:
+ while True:
+ self.callback()
+ await asyncio.sleep( self.period_ms / 1000.0 )
diff --git a/geos-trame/src/geos/trame/assets/cluster.json b/geos-trame/src/geos/trame/assets/cluster.json
new file mode 100644
index 000000000..221dd11d1
--- /dev/null
+++ b/geos-trame/src/geos/trame/assets/cluster.json
@@ -0,0 +1,68 @@
+[
+ {
+ "name": "p4",
+ "host": "p4log01",
+ "partition": "p4_general",
+ "partition_transfert": "p4_transfer",
+ "port": 22,
+ "geos_path": "/workrd/users/$USER/GEOS/build-spack-generated-debug/bin/geosx",
+ "geos_module": "/workrd/users/$USER/modulesRHEL88",
+ "geos_load_list": [
+ "geos-daily-rhel88"
+ ],
+ "remote_home_base": "/users/$USER",
+ "simulation_default_filename": "geosDeck.xml",
+ "simulation_remote_path": "/workrd/users/$USER/Example",
+ "simulation_dl_default_path": "/users/$USER/Example",
+ "simulation_information_default_path": "/users/$USER/.trame-logs",
+ "n_nodes": 212,
+ "cores_per_node": 192,
+ "mem_per_node": 747
+ },
+ {
+ "name": "pine",
+ "host": "pine-1",
+ "partition": "pine",
+ "partition_transfert": "pine",
+ "port": 22,
+ "geos_path": "/shared/data1/Users/$USER/codes/GEOS-2025-11-03/build-pine-1.pine.cluster-linux-rocky9-zen4-gcc@11.4.1-release/bin/geosx",
+ "geos_module": "/apps/modules/modulefiles3",
+ "geos_load_list": [
+ "genesis",
+ "common",
+ "proxy",
+ "slurm",
+ "gcc/11.4.1",
+ "openmpi-gcc/5.0.5",
+ "cmake/3.27.9"
+ ],
+ "remote_home_base": "/home/$USER",
+ "simulation_default_filename": "geosDeck.xml",
+ "simulation_remote_path": "/shared/data1/Users/$USER/Example",
+ "simulation_dl_default_path": "/shared/data1/Users/$USER/Example",
+ "simulation_information_default_path": "/home/$USER/.trame-logs",
+ "n_nodes": 48,
+ "cores_per_node": 64,
+ "mem_per_node": 768
+ },
+ {
+ "name": "local",
+ "host": "127.0.0.1",
+ "partition": "debug",
+ "partition_transfert": "debug",
+ "port": 22,
+ "geos_path": "/opt/GEOS/build-spack-generated-debug/bin/geosx",
+ "geos_module": "/workrd/users/$USER/geos-generated",
+ "geos_load_list": [
+ "geos-toolchains"
+ ],
+ "remote_home_base": "/home/$USER",
+ "simulation_default_filename": "geosDeck.xml",
+ "simulation_remote_path": "/work/",
+ "simulation_dl_default_path": "/data/",
+ "simulation_information_default_path": "/home/.trame-logs",
+ "n_nodes": 1,
+ "cores_per_node": 8,
+ "mem_per_node": 32
+ }
+]
\ No newline at end of file
diff --git a/install_packages.sh b/install_packages.sh
index 3ded06357..3f9a454a4 100755
--- a/install_packages.sh
+++ b/install_packages.sh
@@ -11,8 +11,10 @@ python -m pip install --upgrade ./mesh-doctor
python -m pip install --upgrade ./pygeos-tools
python -m pip install --upgrade ./geos-pv
#! trame install requires npm
-# cd ./geos-trame/vue-components
-# npm i
-# npm run build
-# cd ../../
-# python -m pip install ./geos-trame
\ No newline at end of file
+cd ./geos-trame/vue-components
+npm i
+npm run build
+cd ..
+sh configure.sh
+cd ..
+python -m pip install ./geos-trame
diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/convertMD2SG.py b/mesh-doctor/src/geos/mesh_doctor/actions/convertMD2SG.py
new file mode 100644
index 000000000..0e90fe103
--- /dev/null
+++ b/mesh-doctor/src/geos/mesh_doctor/actions/convertMD2SG.py
@@ -0,0 +1,438 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: GitHub Copilot, Jacques Franc
+from dataclasses import dataclass
+from typing import Optional, Union
+
+from vtkmodules.vtkCommonDataModel import VTK_TRIANGLE, VTK_TRIANGLE_STRIP, VTK_QUAD, VTK_POLYGON, vtkPolyData, vtkKdTree, vtkCell
+from vtkmodules.vtkCommonCore import vtkIdTypeArray, vtkPoints, reference, vtkUnsignedIntArray, vtkDataArray, vtkIdList
+from vtkmodules.vtkCommonDataModel import vtkSelection, vtkSelectionNode, vtkUnstructuredGrid, vtkMultiBlockDataSet
+from vtkmodules.vtkFiltersCore import vtkAppendPolyData, vtkCleanPolyData
+from vtkmodules.vtkFiltersExtraction import vtkExtractSelection
+from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter
+
+from geos.mesh_doctor.parsing.cliParsing import setupLogger
+from geos.mesh.io.vtkIO import readMesh, VtkOutput, writeMesh
+
+
+@dataclass( frozen=True )
+class Options:
+ meshVtkOutput: VtkOutput
+ attrs: tuple[ int, ...] = ()
+ skipCleanCollocated: bool = False
+ skipFilterVolumeCells: bool = False
+
+
+@dataclass( frozen=True )
+class Result:
+ outputMesh: Optional[ vtkUnstructuredGrid ]
+ bounds: tuple[ float, float, float, float, float, float ]
+ numPoints: int
+ numCells: int
+ attrs: tuple[ int, ...]
+ skipCleanCollocated: bool
+ nCleanCollocated: int
+ skipFilterVolumeCells: bool
+ nFilterVolumeCells: int
+ nColors: int
+
+
+TOLERANCE = 1e-6
+
+
+def is_surface_cell_type( t: int ) -> bool:
+ """Checks if the given VTK cell type is a surface cell type (2D)."""
+ surface_types = {
+ VTK_TRIANGLE,
+ VTK_QUAD,
+ VTK_POLYGON,
+ VTK_TRIANGLE_STRIP,
+ }
+ return t in surface_types
+
+
+def __process_block( block: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid, vtkPolyData ],
+ append_filter: vtkAppendPolyData, attrs: list[ int ] ) -> None:
+ """Recursively processes a block of the multi-block dataset, extracting surface cells that match the given attributes and adding them to the append filter."""
+ if isinstance( block, vtkMultiBlockDataSet ):
+ for i in range( block.GetNumberOfBlocks() ):
+ child: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid,
+ vtkPolyData ] = block.GetBlock( i ) # type: ignore[assignment]
+ if child is not None:
+ __process_block( child, append_filter, attrs )
+ return
+
+ if not hasattr( block, "GetNumberOfCells" ):
+ return
+ if block.GetNumberOfCells() == 0:
+ return
+
+ cell_types = set()
+ for i in range( block.GetNumberOfCells() ):
+ cell_types.add( block.GetCellType( i ) )
+
+ if all( is_surface_cell_type( ct ) for ct in cell_types ):
+ cell_attributes = block.GetCellData().GetArray( "attribute" )
+ if len( attrs ) == 0 or ( cell_attributes is not None and cell_attributes.GetTuple1( 0 ) in attrs ):
+ if isinstance( block, vtkPolyData ):
+ append_filter.AddInputData( block )
+ else:
+ gf = vtkGeometryFilter()
+ gf.SetInputData( block )
+ gf.Update()
+ append_filter.AddInputData( gf.GetOutput() )
+
+
+def __extract_first_vol( block: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid ] ) -> Optional[ vtkUnstructuredGrid ]:
+ """Recursively searches for the first volumetric block in the multi-block dataset."""
+ if isinstance( block, vtkMultiBlockDataSet ):
+ for i in range( block.GetNumberOfBlocks() ):
+ child: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid ] = block.GetBlock( i ) # type: ignore[assignment]
+ if child is not None:
+ found = __extract_first_vol( child )
+ if found is not None:
+ return found
+ return None
+
+ if not hasattr( block, "GetNumberOfCells" ):
+ return None
+ if block.GetNumberOfCells() == 0:
+ return None
+
+ is_vol = False
+ for i in range( block.GetNumberOfCells() ):
+ cell_type = block.GetCellType( i )
+ is_vol = not is_surface_cell_type( cell_type )
+ if is_vol:
+ break
+ return block if is_vol else None
+
+
+def _filterVolumeCells( mesh: vtkUnstructuredGrid,
+ attrs: list[ int ] ) -> tuple[ vtkUnstructuredGrid, vtkUnstructuredGrid, int ]:
+ """Filters out volume cells from the input mesh, keeping only surface cells that match the given attributes. Returns the filtered volume mesh, the extracted surface mesh, and the number of cells removed."""
+ volumeIds = vtkIdTypeArray()
+ surfaceIds = vtkIdTypeArray()
+ nVolume = nSurface = nOther = 0
+
+ cell_attributes = mesh.GetCellData().GetArray( "attribute" )
+ assert cell_attributes is not None, "Input mesh must have a 'attribute' cell data array."
+ for i in range( mesh.GetNumberOfCells() ):
+ dim = mesh.GetCell( i ).GetCellDimension()
+ if dim == 3:
+ volumeIds.InsertNextValue( i )
+ nVolume += 1
+ elif dim == 2:
+ if cell_attributes.GetTuple1( i ) in attrs or len( attrs ) == 0:
+ surfaceIds.InsertNextValue( i )
+ nSurface += 1
+ else:
+ nOther += 1
+
+ if cell_attributes is not None:
+ setupLogger.info(
+ f"Mesh contains {nVolume} volume cells, {nSurface} surface cells matching attributes {attrs}, and {nOther} other cells."
+ )
+ else:
+ setupLogger.info(
+ f"Mesh contains {nVolume} volume cells, {nSurface} surface cells, and {nOther} other cells (no 'attribute' array for filtering)."
+ )
+
+ if nSurface == 0 and nOther == 0:
+ setupLogger.info( "No filtering needed (all cells are 3D)" )
+ return mesh, mesh.NewInstance(), nSurface + nOther
+
+ sn = vtkSelectionNode()
+ sn.SetFieldType( vtkSelectionNode.CELL )
+ sn.SetContentType( vtkSelectionNode.INDICES )
+ sn.SetSelectionList( volumeIds )
+ Esn = vtkSelectionNode()
+ Esn.SetFieldType( vtkSelectionNode.CELL )
+ Esn.SetContentType( vtkSelectionNode.INDICES )
+ Esn.SetSelectionList( surfaceIds )
+
+ sel = vtkSelection()
+ sel.AddNode( sn )
+ Esel = vtkSelection()
+ Esel.AddNode( Esn )
+
+ ext = vtkExtractSelection()
+ ext.SetInputData( 0, mesh )
+ ext.SetInputData( 1, sel )
+ ext.Update()
+ Eext = vtkExtractSelection()
+ Eext.SetInputData( 0, mesh )
+ Eext.SetInputData( 1, Esel )
+ Eext.Update()
+
+ setupLogger.info( f"Filtered → {nVolume} cells (removed {nSurface + nOther})" )
+
+ if nVolume > 0:
+ if nSurface > 0:
+ return ext.GetOutput(), Eext.GetOutput(), nSurface + nOther
+ return ext.GetOutput(), mesh.NewInstance(), nSurface + nOther
+
+ return mesh.NewInstance(), mesh.NewInstance(), nSurface + nOther
+
+
+def __clean_collocated( main: vtkUnstructuredGrid ) -> tuple[ vtkUnstructuredGrid, int ]:
+ """Cleans collocated points in the input mesh, returning a new mesh with unique points and updated cell connectivity, as well as the number of points cleaned."""
+ clean_point_set: dict[ tuple[ float, float, float ], int ] = {}
+ reverse_map: dict[ int, int ] = {}
+
+ for pid in range( main.GetNumberOfPoints() ):
+ pt = main.GetPoints().GetPoint( pid )
+ reverse_map[ pid ] = clean_point_set.get( pt, pid )
+ clean_point_set.setdefault( pt, pid )
+
+ old_to_new: dict[ int, int ] = {}
+ clean_points = vtkPoints()
+ for pt, ids in clean_point_set.items():
+ old_to_new[ ids ] = clean_points.InsertNextPoint( pt )
+
+ rewrite_mesh = vtkUnstructuredGrid()
+ rewrite_mesh.SetPoints( clean_points )
+
+ for cell_id in range( main.GetNumberOfCells() ):
+ cell: vtkCell = main.GetCell( cell_id )
+ new_ids: list[ int ] = []
+ for i in range( cell.GetNumberOfPoints() ):
+ pid = cell.GetPointId( i )
+ new_ids.append( old_to_new.get( reverse_map.get( pid, pid ), pid ) )
+ rewrite_mesh.InsertNextCell( cell.GetCellType(), len( new_ids ), new_ids )
+
+ rewrite_mesh.GetCellData().ShallowCopy( main.GetCellData() )
+
+ for array_i in range( main.GetPointData().GetNumberOfArrays() ):
+ arr = main.GetPointData().GetArray( array_i )
+ new_arr = vtkDataArray.CreateDataArray( arr.GetDataType() )
+ new_arr.SetName( arr.GetName() )
+ new_arr.SetNumberOfComponents( arr.GetNumberOfComponents() )
+ new_arr.SetNumberOfTuples( clean_points.GetNumberOfPoints() )
+ for old_id, new_id in old_to_new.items():
+ new_arr.SetTuple( new_id, arr.GetTuple( old_id ) )
+ rewrite_mesh.GetPointData().AddArray( new_arr )
+
+ nCleanCollocated = main.GetNumberOfPoints() - rewrite_mesh.GetNumberOfPoints()
+ return rewrite_mesh, nCleanCollocated
+
+
+def __paintNodes(
+ main: vtkUnstructuredGrid,
+ frac_polys: list[ vtkUnstructuredGrid ] ) -> tuple[ vtkUnstructuredGrid, list[ vtkUnstructuredGrid ] ]:
+ """Paints the nodes of the main mesh that are close to the fracture polygons, returning the modified main mesh and the list of fracture polygons."""
+ kd = vtkKdTree()
+ kd.BuildLocatorFromPoints( main )
+
+ narray = vtkUnsignedIntArray()
+ narray.SetNumberOfComponents( 1 )
+ narray.SetNumberOfTuples( main.GetNumberOfPoints() )
+
+ for i in range( main.GetNumberOfPoints() ):
+ narray.SetTuple1( i, 0 )
+
+ count = 0
+ setupLogger.info( f"Number of fracpolys: {len(frac_polys)}" )
+ for poly in frac_polys:
+ setupLogger.info(
+ f"Processing fracpoly with {poly.GetNumberOfPoints()} points and {poly.GetNumberOfCells()} cells." )
+ for i in range( poly.GetNumberOfPoints() ):
+ dist = reference( 0.0 )
+ id_source = kd.FindClosestPoint( poly.GetPoint( i ), dist ) # type: ignore[call-overload]
+ if dist > TOLERANCE: # type: ignore[operator]
+ setupLogger.warning(
+ f"[too far point] main point ({id_source}) is too far from frac point ({i}) = ({dist} > {TOLERANCE})"
+ )
+ narray.SetTuple1( id_source, 1 )
+ count += 1
+
+ setupLogger.info( f"Painted {count}/{narray.GetNumberOfTuples()} nodes based on proximity to fracture polygons." )
+ narray.SetName( "faultNodes" )
+ main.GetPointData().AddArray( narray )
+ return main, frac_polys
+
+
+#TODO refactor with other coloring function to avoid code duplication
+def __coloringNodes( main: vtkUnstructuredGrid ) -> tuple[ vtkUnstructuredGrid, int ]:
+ """Colors the nodes of the main mesh based on their point-connectivity, one array per connected component of faultNodes==1 points. Returns the modified mesh and the number of connected components found."""
+ fault_array = main.GetPointData().GetArray( "faultNodes" )
+ n_pts = main.GetNumberOfPoints()
+
+ # Collect only the fault-node ids up front
+ fault_pids = { pid for pid in range( n_pts ) if fault_array.GetTuple1( pid ) == 1 }
+ setupLogger.info( f"Found {len(fault_pids)} fault nodes to color based on connectivity." )
+
+ visited: set[ int ] = set()
+ color = 0
+
+ for seed in fault_pids:
+ if seed in visited:
+ continue
+
+ # One fresh, zero-filled array per connected component
+ color_array = vtkUnsignedIntArray()
+ color_array.SetNumberOfComponents( 1 )
+ color_array.SetNumberOfTuples( n_pts )
+ color_array.Fill( 0 )
+
+ # Iterative DFS — avoids Python recursion-depth limit
+ stack = [ seed ]
+ count = 0
+ while stack:
+ pid = stack.pop()
+ if pid in visited:
+ continue
+ visited.add( pid )
+ color_array.SetTuple1( pid, 1 ) # mark this point as belonging to component
+ count += 1
+
+ cells = vtkIdList()
+ main.GetPointCells( pid, cells )
+ for ci in range( cells.GetNumberOfIds() ):
+ cell = main.GetCell( cells.GetId( ci ) )
+ for vi in range( cell.GetNumberOfPoints() ):
+ nbr = cell.GetPointId( vi )
+ if nbr not in visited and nbr in fault_pids:
+ stack.append( nbr )
+
+ color_array.SetName( f"faultNodes_{color}" )
+ setupLogger.info( f"Connected component {color}: {count} points" )
+
+ main.GetPointData().AddArray( color_array )
+ color += 1
+
+ return main, color
+
+
+def polydata_to_ugrid( poly: vtkUnstructuredGrid ) -> vtkUnstructuredGrid:
+ """Converts a vtkPolyData to a vtkUnstructuredGrid by copying points, cells, and data."""
+ ugrid = vtkUnstructuredGrid()
+ ugrid.SetPoints( poly.GetPoints() )
+ for cid in range( poly.GetNumberOfCells() ):
+ cell = poly.GetCell( cid )
+ ugrid.InsertNextCell( cell.GetCellType(), cell.GetPointIds() )
+ ugrid.GetPointData().ShallowCopy( poly.GetPointData() )
+ ugrid.GetCellData().ShallowCopy( poly.GetCellData() )
+ return ugrid
+
+
+def meshDoctor_to_surfaceGen( hierachical_mesh: vtkMultiBlockDataSet, attrs: tuple[ int, ...],
+ skip_clean_collocated: bool ) -> tuple[ vtkUnstructuredGrid, int, int ]:
+ """Converts a mesh-doctor multi-block dataset to a surface mesh compatible with SurfaceGen by extracting surface cells that match the given attributes, optionally cleaning collocated points, and returning the resulting unstructured grid along with the number of points cleaned.
+
+ Args:
+ hierachical_mesh: The input multi-block dataset containing the mesh.
+ attrs: A tuple of attribute values to filter surface cells. If empty, all surface cells are included.
+ skip_clean_collocated: If True, skips the step of cleaning collocated points. If False, collocated points will be cleaned and the number of points cleaned will be returned.
+
+ Returns:
+ A tuple containing the converted surface mesh as a vtkUnstructuredGrid and the number of points cleaned from collocated points (if skip_clean_collocated is False) or 0 (if skip
+ """
+ append_filter = vtkAppendPolyData()
+ __process_block( hierachical_mesh, append_filter, list( attrs ) )
+
+ main = __extract_first_vol( hierachical_mesh )
+ if main is None:
+ raise ValueError( "No volumetric block found in the multi-block mesh." )
+
+ nCleanCollocated, nColors = 0, 0
+ if not skip_clean_collocated:
+ main, nCleanCollocated = __clean_collocated( main )
+
+ append_filter.Update()
+ clean = vtkCleanPolyData()
+ clean.SetInputConnection( append_filter.GetOutputPort() )
+ clean.Update()
+
+ painted_main, _ = __paintNodes( main, [ clean.GetOutput() ] )
+ colored_main, nColors = __coloringNodes( painted_main )
+ return polydata_to_ugrid( colored_main ), nCleanCollocated, nColors
+
+
+def toSurfaceGen( hierachical_mesh: vtkUnstructuredGrid, attrs: tuple[ int, ...], skip_clean_collocated: bool,
+ skip_filter_volume_cells: bool ) -> tuple[ vtkUnstructuredGrid, int, int, int ]:
+ """Converts a single unstructured grid mesh to a surface mesh compatible with SurfaceGen by optionally filtering out volume cells, extracting surface cells that match the given attributes, optionally cleaning collocated points, and returning the resulting unstructured grid along with the number of points cleaned and cells filtered.
+
+ Args:
+ hierachical_mesh: The input unstructured grid mesh.
+ attrs: A tuple of attribute values to filter surface cells. If empty, all surface cells are included.
+ skip_clean_collocated: If True, skips the step of cleaning collocated points. If False, collocated points will be cleaned and the number of points cleaned will be returned.
+ skip_filter_volume_cells: If True, skips the step of filtering out volume cells and extracting surface cells. If False, volume cells will be filtered out and surface cells matching the attributes will be extracted, and the number of cells filtered will be returned.
+
+ Returns:
+ A tuple containing the converted surface mesh as a vtkUnstructuredGrid, the number of points cleaned from collocated points (if skip_clean_collocated is False) or 0 (if skip clean_collocated is True), and the number of cells filtered out as volume cells (if skip_filter_volume_cells is False) or 0 (if skip_filter_volume_cells is True).
+ """
+ nCleanCollocated, nFilteredVolumeCells, nColors = 0, 0, 0
+ if skip_filter_volume_cells:
+ main = hierachical_mesh
+ surfaces: list[ vtkUnstructuredGrid ] = []
+ else:
+ main, surfs, nFilteredVolumeCells = _filterVolumeCells( hierachical_mesh, list( attrs ) )
+ surfaces = [ surfs ]
+
+ if not skip_clean_collocated:
+ main, nCleanCollocated = __clean_collocated( main )
+
+ painted_main, _ = __paintNodes( main, surfaces )
+ setupLogger.info( f"Has Array faultNodes: {painted_main.GetPointData().HasArray('faultNodes')}" )
+ setupLogger.info(
+ f"Range of faultNodes: {painted_main.GetPointData().GetArray('faultNodes').GetRange() if painted_main.GetPointData().HasArray('faultNodes') else 'N/A'}"
+ )
+ colored_main, nColors = __coloringNodes( painted_main )
+ return polydata_to_ugrid( colored_main ), nCleanCollocated, nFilteredVolumeCells, nColors
+
+
+def meshAction( mesh: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid ], options: Options ) -> Result:
+ """Performs the conversion of the input mesh to a surface mesh compatible with SurfaceGen using the specified options, and returns the result containing the output file path, bounds, number of points and cells, and details about the cleaning and filtering steps.
+
+ Args:
+ mesh: The input mesh to be converted, which can be either a vtkMultiBlockDataSet or a vtkUnstructuredGrid.
+ options: The options for the conversion, including attributes to filter, whether to skip cleaning collocated points, and whether to skip filtering volume cells.
+
+ Returns:
+ A Result object containing the output file path, bounds, number of points and cells in the converted mesh, the attributes used for filtering, whether cleaning collocated points was skipped, whether filtering volume cells was skipped, and the number of points cleaned and cells filtered if those steps were performed.
+
+ """
+ if isinstance( mesh, vtkMultiBlockDataSet ):
+ converted, nCleanCollocated, nColors = meshDoctor_to_surfaceGen( mesh, options.attrs,
+ options.skipCleanCollocated )
+ nFilteredVolumeCells = 0
+ elif isinstance( mesh, vtkUnstructuredGrid ):
+ converted, nCleanCollocated, nFilteredVolumeCells, nColors = toSurfaceGen( mesh, options.attrs,
+ options.skipCleanCollocated,
+ options.skipFilterVolumeCells )
+ else:
+ raise TypeError( f"Unsupported mesh type {type( mesh )}." )
+
+ return Result( outputMesh=converted,
+ bounds=converted.GetBounds(),
+ numPoints=converted.GetNumberOfPoints(),
+ numCells=converted.GetNumberOfCells(),
+ attrs=options.attrs,
+ skipCleanCollocated=options.skipCleanCollocated,
+ skipFilterVolumeCells=options.skipFilterVolumeCells,
+ nCleanCollocated=nCleanCollocated,
+ nFilterVolumeCells=nFilteredVolumeCells,
+ nColors=nColors )
+
+
+def action( vtuInputFile: str, options: Options ) -> Result:
+ """Reads a mesh from the input file, converts it to a surface mesh compatible with SurfaceGen using the specified options, and returns the result containing the output file path, bounds, number of points and cells, and details about the cleaning and filtering steps.
+
+ Args:
+ vtuInputFile: The path to the input VTU file containing the mesh to be converted.
+ options: The options for the conversion, including attributes to filter, whether to skip cleaning collocated points, and whether to skip filtering volume cells.
+
+ Returns:
+ A Result object containing the output file path, bounds, number of points and cells in the converted mesh, the attributes used for filtering, whether cleaning collocated points was skipped, whether filtering volume cells was skipped, and the number of points cleaned and cells filtered if those steps were performed.
+ """
+ if vtuInputFile is None:
+ raise ValueError( "An input file must be provided." )
+
+ mesh = readMesh( vtuInputFile )
+ result = meshAction( mesh, options )
+
+ setupLogger.info( f"Writing converted mesh to {options.meshVtkOutput.output}" )
+ writeMesh( result.outputMesh, options.meshVtkOutput )
+
+ return result
diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/euler.py b/mesh-doctor/src/geos/mesh_doctor/actions/euler.py
index b72504ab9..8da86e29c 100644
--- a/mesh-doctor/src/geos/mesh_doctor/actions/euler.py
+++ b/mesh-doctor/src/geos/mesh_doctor/actions/euler.py
@@ -52,6 +52,7 @@ class SurfaceComponent:
numBoundaryEdges: int
numNonManifoldEdges: int
interpretation: str
+ nonManifoldEdgeEndpoints: tuple[ tuple[ int, int ], ...] = field( default_factory=tuple )
@dataclass( frozen=True )
@@ -225,10 +226,24 @@ def __interpretSurface( chi: int, boundaryEdges: int, nonManifoldEdges: int ) ->
return f"open (chi={chi}, unusual)"
+def _toGlobalEdgeId( mesh: vtk.vtkUnstructuredGrid, edge: tuple[ int, int ] ) -> tuple[ int, int ]:
+ """Return a global edge ID for a pair of point IDs."""
+ p0, p1 = edge
+ ids = mesh.GetPointData().GetGlobalIds()
+ if ids is not None:
+ ids = vtk_to_numpy( ids ).astype( np.int64, copy=False )
+ return ( ids[ p0 ], ids[ p1 ] )
+ else:
+ setupLogger.warning( "No globalIds found. Falling back to local id for non-manifold edge detection.",
+ stacklevel=2 )
+ return edge
+
+
def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[ SurfaceComponent ]:
"""Compute (V, E, F, chi, boundary, non-manifold) per RegionId of a colored 2D mesh."""
rid = vtk_to_numpy( colored.GetCellData().GetArray( "RegionId" ) ).astype( np.int64 )
cells = colored.GetCells()
+ # GetConnectivityArray / GetOffsetsArray require VTK 9+
conn = vtk_to_numpy( cells.GetConnectivityArray() ).astype( np.int64, copy=False )
off = vtk_to_numpy( cells.GetOffsetsArray() ).astype( np.int64, copy=False )
@@ -251,7 +266,8 @@ def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[
E = len( edgeCount )
F = int( len( sel ) )
bE = sum( 1 for c in edgeCount.values() if c == 1 )
- nm = sum( 1 for c in edgeCount.values() if c > 2 )
+ nmEdges = tuple( _toGlobalEdgeId( colored, ek ) for ek, c in edgeCount.items() if c > 2 )
+ nm = len( nmEdges )
chi = V - E + F
components.append(
SurfaceComponent( componentId=int( regionId ),
@@ -262,7 +278,8 @@ def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[
eulerCharacteristic=chi,
numBoundaryEdges=bE,
numNonManifoldEdges=nm,
- interpretation=__interpretSurface( chi, bE, nm ) ) )
+ interpretation=__interpretSurface( chi, bE, nm ),
+ nonManifoldEdgeEndpoints=nmEdges ) )
return components
diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py b/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py
index ecdb80d9c..f25f20ece 100644
--- a/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py
+++ b/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py
@@ -19,6 +19,7 @@
ORPHAN_2D = "orphan2d"
CHECK_INTERNAL_TAGS = "checkInternalTags"
EULER = "euler"
+CONVERT_MD2SG = "convertMD2SG"
@dataclass( frozen=True )
diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/convertMD2SGParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/convertMD2SGParsing.py
new file mode 100644
index 000000000..06447e7a3
--- /dev/null
+++ b/mesh-doctor/src/geos/mesh_doctor/parsing/convertMD2SGParsing.py
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: GitHub Copilot, Jacques Franc
+from __future__ import annotations
+from argparse import _SubParsersAction
+from typing import Any
+
+from geos.mesh_doctor.actions.convertMD2SG import Options, Result
+from geos.mesh_doctor.parsing import CONVERT_MD2SG
+from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage
+from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument
+from geos.mesh.io.vtkIO import VtkOutput
+
+__ATTRS = "attrs"
+__OUTPUT_FILE = "outputFile"
+__SKIP_CLEAN = "skipCleanCollocated"
+__SKIP_FILTER = "skipFilterVolumeCells"
+
+
+def convert( parsedOptions: dict[ str, Any ] ) -> Options:
+ """Convert parsed command-line options to Options object.
+
+ Args:
+ parsedOptions: Dictionary of parsed command-line options.
+
+ Returns:
+ Options: Converted options object.
+ """
+ return Options( attrs=tuple( parsedOptions.get( __ATTRS, [] ) ),
+ skipCleanCollocated=parsedOptions.get( __SKIP_CLEAN, False ),
+ skipFilterVolumeCells=parsedOptions.get( __SKIP_FILTER, False ),
+ meshVtkOutput=VtkOutput( output=parsedOptions.get( __OUTPUT_FILE ), isDataModeBinary=True ) )
+
+
+def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None:
+ """Fill the argument parser for the convertMD2SG action.
+
+ Args:
+ subparsers: Subparsers from the main argument parser.
+ """
+ p = subparsers.add_parser( CONVERT_MD2SG,
+ help="Convert a mesh-doctor dataset to a SurfaceGen-compatible VTU file." )
+ addVtuInputFileArgument( p )
+ p.add_argument( '-z',
+ '--' + __ATTRS,
+ type=int,
+ nargs='+',
+ default=[],
+ help="[int ...]: Attributes to include when filtering surface cells." )
+ p.add_argument( '--' + __OUTPUT_FILE,
+ type=str,
+ default="converted.vtu",
+ help="[string]: Optional output VTU file path." )
+ p.add_argument( '--' + __SKIP_CLEAN, action='store_true', help="Skip the collocated node cleanup step." )
+ p.add_argument( '--' + __SKIP_FILTER,
+ action='store_true',
+ help="Skip the surface/volume extraction step when input is a single VTU file." )
+
+
+def displayResults( options: Options, result: Result ) -> None:
+ """Display the results of the convertMD2SG action.
+
+ Args:
+ options: The options used for the conversion.
+ result: The results of the conversion.
+ """
+ setupLogger.results( getOptionsUsedMessage( options ) )
+ setupLogger.results( "Converted mesh saved to: {0}".format( options.meshVtkOutput.output ) )
+ setupLogger.results( f" Points: {result.numPoints:,}" )
+ setupLogger.results( f" Cells: {result.numCells:,}" )
+ setupLogger.results( f" Bounds: {result.bounds}" )
+ setupLogger.results(
+ f" Skip clean collocated(npoints cleaned): {result.skipCleanCollocated} ({result.nCleanCollocated})" )
+ setupLogger.results(
+ f" Skip filter volume cells(ncells removed): {result.skipFilterVolumeCells} ({result.nFilterVolumeCells})" )
+ setupLogger.results( f" Number of connected components for faultNodes: {result.nColors}" )
diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/eulerParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/eulerParsing.py
index 1ad7dcfae..d6bbbf2e5 100644
--- a/mesh-doctor/src/geos/mesh_doctor/parsing/eulerParsing.py
+++ b/mesh-doctor/src/geos/mesh_doctor/parsing/eulerParsing.py
@@ -131,6 +131,13 @@ def __displaySurfaceGroup( g: SurfaceGroup ) -> None:
setupLogger.results( " " + f"{c.componentId:>5} {c.numCells:>9,} {c.numVertices:>8,} {c.numEdges:>8,} "
f"{c.numFaces:>8,} {c.eulerCharacteristic:>5} {c.numBoundaryEdges:>6,} "
f"{c.numNonManifoldEdges:>4,} {c.interpretation}" )
+ if c.nonManifoldEdgeEndpoints:
+ _MAX_DISPLAY = 10
+ shown = c.nonManifoldEdgeEndpoints[ :_MAX_DISPLAY ]
+ setupLogger.results( " non-manifold edge endpoints (point id pairs): " + ", ".join( f"({a},{b})"
+ for a, b in shown ) +
+ ( f" … (+{len(c.nonManifoldEdgeEndpoints)-_MAX_DISPLAY} more)"
+ if len( c.nonManifoldEdgeEndpoints ) > _MAX_DISPLAY else "" ) )
if len( g.components ) > 1:
setupLogger.results( f" WARNING: {len(g.components)} components — verify isolated cells" )
diff --git a/mesh-doctor/src/geos/mesh_doctor/register.py b/mesh-doctor/src/geos/mesh_doctor/register.py
index 99675376c..41d96a605 100644
--- a/mesh-doctor/src/geos/mesh_doctor/register.py
+++ b/mesh-doctor/src/geos/mesh_doctor/register.py
@@ -59,7 +59,7 @@ def registerParsingActions(
parsing.FIX_ELEMENTS_ORDERINGS, parsing.GENERATE_CUBE, parsing.GENERATE_FRACTURES,
parsing.GENERATE_GLOBAL_IDS, parsing.MAIN_CHECKS, parsing.NON_CONFORMAL,
parsing.SELF_INTERSECTING_ELEMENTS, parsing.SUPPORTED_ELEMENTS, parsing.ORPHAN_2D,
- parsing.CHECK_INTERNAL_TAGS, parsing.EULER ):
+ parsing.CHECK_INTERNAL_TAGS, parsing.EULER, parsing.CONVERT_MD2SG ):
__HELPERS[ actionName ] = actionName
__ACTIONS[ actionName ] = actionName
diff --git a/mesh-doctor/tests/data/base_hexa_shift_2.vtu b/mesh-doctor/tests/data/base_hexa_shift_2.vtu
new file mode 100644
index 000000000..8b1febf67
Binary files /dev/null and b/mesh-doctor/tests/data/base_hexa_shift_2.vtu differ
diff --git a/mesh-doctor/tests/data/base_tetra_shift.vtm b/mesh-doctor/tests/data/base_tetra_shift.vtm
new file mode 100644
index 000000000..72cfda3fa
--- /dev/null
+++ b/mesh-doctor/tests/data/base_tetra_shift.vtm
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/mesh-doctor/tests/data/base_tetra_shift/domain_fracture_2_ids.vtu b/mesh-doctor/tests/data/base_tetra_shift/domain_fracture_2_ids.vtu
new file mode 100644
index 000000000..bd71da8e6
--- /dev/null
+++ b/mesh-doctor/tests/data/base_tetra_shift/domain_fracture_2_ids.vtu
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+ AQAAAACAAABwEgAAMAMAAA==eJxd1Vd0CFYAh/GEBInYM0GqgpAYsXdpFVWqtLQ1g9iJPWMGbemw2mprFNUqNUqncU4nnUaH0dqxZ8QeidFz5MvLd1/+5/e93nvO7RwU8PA84HSRu8rd5O5yDzle7in3knvLCXIfua/cT+4vD5AHyoPkRDlJHiwPkYfKw+Th8gh5pDxKHi2PkcfK4+Rkebw8QZ4oT5Iny1PkFHmqPE2eLr8svyK/Ks+QZ8qvya/Lb8hvyrPk2fIcea48T35Lflt+R54vvyu/J78vL5AXyovkxfIH8hJ5qbxM/lBeLn8kfyyvkD+RV8qr5E/l1fIaea28Tv5MXi9vkD+Xv5C/lL+Sv5a/kTfKm+TN8hb5W/lwSJa/ox/B3+Mf2KP0VPZH+jH8Ez6Ot+ITeBs+iX/Gp/Av+DT+Ff/GnqGfZX+nn8N/4PN4O76Ad+CLeCdOw7vwJfwn/otNp19m/6Zfwf/gq3g3vob34Ot4L76B9+Gb+F/8H3uLfpvdT7+DD+AMfBBn4kP4Lj6M7+Ej+D4+ilOz3ws9IDRrjtED8XGcA5/AOfFJHIRP4WB8GufCZ/BZNjc9D3uOHoLP41B8AefFF3EYTsP58CWcH6fjy2wBekH2Cr0QvooL42u4CL6Oi+IbuBi+iYvjW/g2W4Jekr1DD8cZOAJn4lL4Li6N7+Ey+D6OxA9wQHDWPEIvywbSH8U5cDmcE0fhIFweB+MKOBeuiHPjPGw0vRIbQq+MQ3EMzotjcRiugvPhqjg/roYL4IJsdXocW4heAxfGNXERXAsXxbVxMVwHF8d1cQlckq1Hr8+G0xvgCNwQl8KNcGncGJfBTXBk9j2z2f9DOTlKLi9XkCvK0XIlubIcI8fKVeSqcjW5uhwn15BryrXk2nIdua5cT64vN5Abyo3kxnIT+TG5qdxMflx+Qm4uPym3kFvKreSn5Nby03Ibua38jNxOflZuL3eQn5OflzvKneQX5Bfll+TOche5q9xN7i73kOPlnnIvubecIPeR+8r95P7yAHmgPEhOlJPkwfIQeag8TB4uj5BHyqPk0fIYeaw8Tk6Wx8sT5InyJHmyPEVOkafK0+Tp8v+bFOsf
+
+
+ 1113.0004492
+
+
+ 2930.1435118
+
+
+
+
+ AQAAAACAAAA4CQAA/AEAAA==eJw11dViEAQAQFFiwMZGrmDEyAGC0g0iIAKSEtLSKN0tJaGoSEu3dHdIl9IonUp3t+QDx/tyPuHGivW+2IzDuAxgPMZnAgYyiAkZzBAmYmImYVImY3KGMozhjGAkUzAlo5iKqZmGaRnNdEzPDMzITMzMGGZhVmbjB8zOHPyQHzEnczE38zAv8zE/C7AgC7Ewi7Aoi7E4P2YJfsKSLMXS/JRl+BnLshzL83NWYEVWYmVWYVV+wWqszhqsyS9Zi7VZh3VZj/XZgF+xIRuxMZuwKZuxOVvwa37DlmzF1mzDtmzH9uzAjuzEzuzCruzG7uzBnuzF3vyWfdiX/difA/gdB3IQB3MIv+cPHMof+RN/5jD+wuEcwZEcxdEcw7H8leM4nhM4kZM4mVM4ldM4nTM4k7P4G2dzDudyHudzARdyERdzCZdyGZdzBVdyFVdzDddyHddzA3/nRm7iZm7hVm7jdu7gTu7ibv7BP7mHe7mP+3mAB3mIh/kX/+YRHuUxHucJnuQpnuYZnuU5nuc//JcXeJGXeJlXeJXXeJ03eJO3eJt3eJf3eJ8P+JCP+JhP+JTP+Jwv+B9f8hVf8w3f8v/xx2YcxmUA4zE+EzCQQUzIYIYwERMzCZMyGZMzlGEMZwQjmYIpGcVUTM00TMtopmN6ZmBGZmJmxvAdK1+CjQ==
+
+
+
+
+ AQAAAACAAAAwDAAAHwAAAA==eJztxaENAAAMArCF/4+ewHFDa5qr2LZt2/b8qo4DDQ==
+
+
+ AQAAAACAAAAwDAAAiQIAAA==eJw11dViEAQAQFEYMWB05+jO0bkRG6O7YXR3jhjdHUoqjQFKd2OQJiihdCihNCqNPHC4L+cTbqxY74rNAMZhXMZjfAYyARMyEYOYmEmYlMmYnCmYkqmYmmmYlumYnhmYkZmYmVkYzKzMxuzMwZzMxdzMw7zMx/wswIIsxMIswqIsxhAWZwmWZCmWZhmWZTmWZwVWZCWGMoyVWYVVWY3hjGB1RrIGa7IWa7MO67Ie67MBG7IRG7MJm7IZm7MFW7IVW7MNo9iW7dieHdiRndiZXdiV3didPdiTvdibfdiX/difAziQgziYQxjNoRzG4RzBGI7kKI7mGI7lOI7nBE7kJE7mFE7lNE7nDM7kLM7mHM7lPH7ADzmfC7iQi7iYS/gRP+ZSLuNyruBKruJqruEn/JSf8XOu5Tp+wS+5nhu4kZu4mVu4ldu4nTu4k7u4m3u4l/u4nwd4kIf4Fb/mN/yWh3mER3mMx3mC3/F7/sAf+RN/5kme4i/8lad5hmd5jr/xd57nBV7kJV7mFV7lNV7nDf7BP3mTt3ibd/gX/+Zd3uN9PuBDPuJjPuE//Jf/8Smf8Tlf8CVf8TXf8H++H39sBjAO4zIe4zOQCZiQiRjExEzCpEzG5EzBlEzF1EzDtEzH9MzAjMzEzMzCYGZlNmZnDuZkLuZmHuZlPuZnARZkIRZmERZlMYawOEuwJEuxNMuwLMuxPCuwIisxlGGszCqsymoMZwSrM5I1WJO1WJt1WJf1WJ8N2JCN2JhN2JTN2Jwt2JKt2JptGMW2bMf27MCO7MTO7MKu7Mbu7MGe7MXe7MO+7Mf+HMCBHMTBHMJoDuUwDucIxnAkR3E0x3Asx3E8J3AiJ/Eti52i1g==
+
+
+
+
+ AQAAAACAAACoGwAA2AMAAA==eJx1lj1MFVEQRq2pscUWa22VUlusqbXVFlupsaCBwgaaryExmiiJISIYxABRQZ8oKD+KKIgV1ro7vpiZj7PNhsPL3rtzzyZnYry5NDDR3scHbvdOX9kZHvv393/+uG/kcPDxPePx+ynj+e/6nGnjR4PNf+7D8x8a7+lM3rzU8wjWnTF+fmj02Un/E9jPrPGpv0/vTD6Ffc4Zv9Be87D/BePzJ/1/d/Qc3mvR+LV2oy/gfZeM7ww3C7+EOSwbv9W87s0VmM+q8TPt9Qrm5vxqu6HXME/nzS6HRt/AnNeMt2PrOI/5rxs/17xW31s4F+c3rjfXOzivjvFm1d5p53GO743/bpY9cR73DeOX24U/wLk7b1935CP4sGl8Zbm5nIcnW8bPti/8Cfxx3i479Bm82oa5OQ/fnMc8nYeHOzBn5+Gn85j/LnjrPJZ1Hj7vwXk5D92cxzl+Af+dx/k6j+/iK5y78/henIcP+/AdOQ9PnMf39Q38cR7fnfPw6gC8ch5eOQ+vnIdX38Er5+GV8/Dqh/G7rVfOwyvn4dUheOU8vHIeXh2BV87DK+fh1U/wynl45Ty8OgavnIdXzsOrX+CV8/Cqcv2bwx3gtaMEHSXoKL6f3lGCjhJ0lKCjuvfaUYKOEnSUoKMEHSXoKEFHCTpK0FGCjurea0cJOkrQUYKOEnSUoKMEHSXoKEFHCTpK0FGCjhJ0lKCjBB0l6ChBRwk6StBRgo7q3mtHCTpK0FGCjhJ0lKCjBB0l6ChBRwk6StBRgo4SdJSgowQd1b07P72jBB0l6ChBRwk6StBRgo4SdJSgowQdJegoQUcJOkrQUYKOEnSUoKMEHSXoKEFHCTpK0FGCjhJ0VPfu/PSOEnSUoKMEHSXoKEFHCTpK0FGCjhJ0lKCjBB0l6ChBRwk6StBRgo4SdJSsoy62831gHZX5mPHaUfn3U8brPvJzpo3XjsrPf2i8dlRed8Z47ai8n1njtaPyPueM147K+18wXjsqv9ei8dpR+X2XjNeOynNYNl47Ks9n1XjtqDw357Wj8jyd147Kc14zXjsqz3/deO2ofC7Oa0fl8+oYrx2Vz/G98dpR+Xw3jNeOyufuvHZU9mHTeO2o7MmW8dpR2R/ntaOyV9swN+e1o/I8ndeOynN2Xjsqz38XvHVeOyr7vAfn5bx2VD7HL8ZrR+XzdV47Kp+789pR2Yd947WjsifOa0dlf5zXjspeHYBXzmtHZa+c147KXjmvHZW9+mG8dlT2ynl4dQheOa8dlb06Aq+c147KXv0Er5zXjspeHYNXzmtHZa+8i7od9Qfhzwss
+
+
+ 0.059371710435
+
+
+ 0.99252204006
+
+
+
+
+
+
+ AQAAAACAAACQJAAAAgcAAA==eJxd2VXc3nUdh/F10bAmtrERdhEiIJMUE2UJSzdim4MhYJBTEAUlxQBRZzIEkxIMhh2YlEo4MUhbAQPhgM/7YJ+dXK/t/73ug//vup+9vr9n6YCn/wwKB4ZHhkPCweER9XfeUeHQ8o4Oh9XzZeHwer48HFHPV4Qj6/kbw1H1fGW4UT0/Jty4nh8bblLPV4Wb1vPvhEvCN4TfC72npeH3wyPr+Q/Co+r5D8Oj6/mPwmX1/JZweT3/cbiinr8tHBeODX8Seq+8n4Yr6/nPwmPq+c/DY+v5L8JV9fzW8Lh6fm94Snhy+L/w0vCS0Hno2B+fu1noPJfUnHN8U7h5ec7T5/OPD7cozzn7nvBPCLcsz/n39+3EcKvydOH7xn9zOLo8vfge8t8SjilPR76f/LeGuuHpy/eWf1I4PtSdvnyf+c55Qnm68z3n62NieXr0/eefGm5dnk79XOCfFm5Tnn79vOCfHm5bnq79HOGvDrcrT7d65b89nFSebvXKf0c4uTzd6pV/RjilPN3qlX9muH15utUr/53h1PJ0q1f+WeG08nSrV/67wh3K061e+e8OdyzPz0md888OdypP5/rmnxPuXJ7O9c1/T/iM8nSub/57w2eWp3N9888Nn1WezvXNPy98dnk61zf//PA55a0O9c2/IHxueTrXN//C8Hnl6Vzf/IvC55en88nlvy98QXk61zf/4vCF5elc3/z3hy8qT+f65n8g3KU8neub/8Fw1/J0rm/+h8LdytO5vvn+v929PJ3rm+//6ReXp3N98z8c7lGezvXNvyx8SXk61zf/I+Ge5elc3/yPhnuVp3N98z8W7l2ezvXNXxO+tDyd65v/8XCf8nSub/4nwunl6Vzf/E+GLytP5/rmfyrctzyd65v/6XC/8nSub/5nwv3L07m++ZeHB5Snc33z14YHlqdzffOvCA8qT+f65n82fHl5Otc3/8rw4PJ0rm/+VeErytO5vvl63rPmPhe+sj5Pzzrmfz58VXlrQh3zvxC+ujw965j/xfA15elZx/wvha8tT8/Ty/9yeEh5etYx/+rwdeXpWcf8a8LXl6dnHfOvDQ8tT8865l8XzihPzzrmXx/OLE/POubr9qCa+0o4qz5Pt3rl3xDOLk+3euXfGM4pT5+65H81nFuePnXJ/1p4WHn61CX/6+Hh5elTl/xvhPPK06cu+TeF88vTpy7568IF5elTl/ybw4Xl6VOX/G+Gi8rTpy753woXl6dPXfJ1OKPmdDWz5vQzq+Z0Mrvm9DCn5pz73JpzvofVnHM8vOac17yaWxfOrznvf0HNec8La877XFRz3w4X19x3Q/cL7iVuC+11x9W8OfdZt4f2Op57LZ/PvyO01/Hcd7m/4N8Z2ut47sHcX/B/GdrreO7H3F/wfxXa63juzdxf8H8d2ut47tPcX/DvCu11PPds7i/4d4f2Op57NvcX/HtCex3P/Zv7C/5vQnua/c69nPsL/vrQnsZzX+f+gv/b0J7Gc4/n/oJ/X7i6PPd77i/4vwvtaTzd6pX/+9CextOtXvl/CO1pPN3qlf/H0J7G061e+feH9jSebvXKfyC0p/F0q1f+g6E9jadbvfIfCu1pPN3qlf9waE/j6Vav/EdCexpPt3rl/ym0p/Hc6+qc/+fQnsbTub75fwntabz1ob75ej6t5v4a2t98np51zP9baH/j6Xl1+X8P7W88PeuY/4/Q/sbTs475/wztbzw965j/r9D+xtOzjvmPhvY3np51zH8stL/x9Kxj/uOh/Y2nZx3z/x3a33h61jH/P6H9jadnHfP/G9rfeHrWMf+J0D5mj9Ozjvn/D+1jPD3rmP9kaE/j6VavfL+gsKfxdKtX/sDM29N4utUrf1Dm1wzY0NOtXvmDM29P4+lWr/whmben8XSrV/7QzNvTeLrVK39Y5u1pPN3qlT888/Y0nm71yh+ReXsaT7d65Y/MvD2Np1u98kdl3p7G061e+Rtl3v7G83s0nfM3zrz9jadzffM3ybz9jadzffP1fFnNbZrPsdf5PD3rmL9Z/t1exxtY/fM3D+11vEHVP3+L0F7HG1z987cM7XW8IdU/f6vQXscbWv3zR4f2Ot6w6p8/JrTX8YZX//yxob2ON6L6548L7XU8PeuYPz607/H0rGO+btfW3ITQfujzdKtX/sTQ3sjTrV75W4f2SZ4+dcnfJrRn8vSpS/62of2Tp09d8rcL7aU8feqSPym0r/L0qUv+5NAey9OnLvlTQvstT5+65G8frhuwoadPXfKnhvZhnj51yZ8W2pN5+tQlf4fQ/szTpy75O4b2b54O9cfX2/U1pys9mdOPbszpRB/m9KADc87deZtzvs7VnHN0fuacl3OaUueybsCGc96/9z613rP3O63ep/do7infwyxp
+
+
+ AQAAAACAAAAwDAAAlwIAAA==eJwtxVlgCAQAAFDmmixX5Mo95FjkWLRF7vuKMEeGyNUc0WTO3OfGWDnKrZWQ3EOpue8ULbSaVkpuUpPiY+/9vGxZMuV0bgc5nwu6sIu6hEu5rINdyVUc4hqu5VDXc7gbuJGbuoVbu507urO7urt7OdL9PMCDPNTDPNKjPcYxnuDJnuoZnu15jnO8E7zEy73Cq73Oid7gTd7ibd7pJO/zfif7kI/6hE/7rM85xRed6jSn+4qv+rpv+a7vO8MP/cgBWTPP4UDncV4XcCEXcXGXdBmXd0VXdjVXd03XcV2Hub4buombu5XbuoM7uYsj3NO93df9PdBDHOURHuVoj/V4T/IUT/csz3WsF3qx3/cyf+hVXuuP/Ik3+jNv9Q7v9l5/6a990Ed83Kf8jb/z977gH/2zf/Fv/sPXfNN3/Jf/8b/+31kDMs/uXH7CTzq/n/LTLuZnXNrlXMHPuqqf8/Ou7Rf8ol/yy27sZm7pNm7vV/yqu7mHX3Mfv+43PNhverjf8tt+x+M80e96mmd6jud7gRf5PS/1B17pNV7vj/2pN/tzb/cu7/EX/soHfNjHfNJn/K3P+wdf8k++7F/9u//0Dd/2Pf/tB/7PWbLJOZ3bQc7ngi7soi7hUi7rYFdyFYe4hms51PUc7gZu5KZu4dZu547u7K7u7l6OdD8P8CAP9TCP9GiPcYwneLKneoZne57jHO8EL/Fyr/Bqr3OiN3iTt3ibdzrJ+7zfyT7koz7h0z7rc07xRac6zem+4qu+7lu+6/vO8EM/ckD2zHM40Hmc1wVcyEVc3CVdxuVd0ZVdzdVd03Vc12Gu74Zu4uZu5bbu4E7u4gj3dG/3dX8P9BBHeYRHOdpjPd6TPMXTPctzHeuFfgyLILtF
+
+
+ AQAAAACAAACGAQAADQAAAA==eJxjZx8FAw8AJtEKqw==
+
+
+
+
+
diff --git a/mesh-doctor/tests/data/base_tetra_shift/domain_ids.vtu b/mesh-doctor/tests/data/base_tetra_shift/domain_ids.vtu
new file mode 100644
index 000000000..958a15a50
--- /dev/null
+++ b/mesh-doctor/tests/data/base_tetra_shift/domain_ids.vtu
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ AQAAAACAAADwUQAAnA8AAA==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
+
+
+
+
+ AgAAAACAAADQdQAA5Q8AADAPAAA=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
+
+
+ 0
+
+
+ 1.0099504938
+
+
+
+
+
+
+ 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YZc99vnDAYccccwJp5xxzgWXXHHNDbfccc8DjzzxzAuvvPHOB5988c3Pn4HOv/9LgEGGGGaEUcYY5zcTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz5/OOCQI4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dc/+CXAIEMMM8IoY4zzmwkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnz8ccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw6//7JcAgQwwzwihjjPObCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfPxxwyBHHnHDKGedccMkV19xwyx33PPDIE8+88Mob73zwyRff/PDPQH+AQYYYZoRRxhjnNxNMMsU0M8wyxzwLLLLEMiusssY6G2yyxTY77LLHPn844JAjjjnhlDPOueCSK6654ZY77nngkSeeeeGVN9754JMvvvn5s8jxD38JMMgQw4wwyhjj/GaCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OcPBxxyxDEnnHLGORdccsU1N9xyxz0PPPLEMy+88sY7H3zyxTc//PpHvwQYZIhhRhhljHF+M8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvs84cDDjnimBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/eNfAgwyxDAjjDLGOL+ZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfb5wwGHHHHMCaeccc4Fl1xxzQ233HHPA4888cwLr7zxzgeffPHND/8s8gUYZIhhRhhljHF+M8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvs84cDDjnimBNOOeOcCy654pobbrnjngceeeKZF155450PPvnim58/C5z/5JcAgwwxzAijjDHObyaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPff5wwCFHHHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDr3/6S4BBhhhmhFHGGOc3E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+fzjgkCOOOeGUM8654JIrrrnhljvueeCRJ5554ZU33vngky+++eHXP/slwCBDDDPCKGOM85sJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58/HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwyhvvfPDJF9/88M8Cf4BBhhhmhFHGGOc3E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+fzjgkCOOOeGUM8654JIrrrnhljvueeCRJ5554ZU33vngky+++eHXP/8lwCBDDDPCKGOM85sJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58/HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwyhvvfPDJF9/88Otf/BJgkCGGGWGUMcb5zQSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzx8OOOSIY0445YxzLrjkimtuuOWOex545IlnXnjljXc++OSLb3749S9/CTDIEMOMMMoY4/xmgkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjnDwcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/wT3BNgkCGGGWGUMcb5zQSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzx8OOOSIY0445YxzLrjkimtuuOWOex545IlnXnjljXc++OSLb3749a9+CTDIEMOMMMoY4/xmgkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjnDwcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z6178EGGSIYUYYZYxxfjPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577POHAw454pgTTjnjnAsuueKaG265454HHnnimRdeeeOdDz754psffv2bXwIMMsQwI4wyxji/mWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32+cMBhxxxzAmnnHHOBZdccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ//BPYFGGSIYUYYZYxxfjPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577POHAw454pgTTjnjnAsuueKaG265454HHnnimRdeeeOdDz754psffv3bXwIMMsQwI4wyxji/mWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32+cMBhxxxzAmnnHHOBZdccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ+//t0vAQYZYpgRRhljnN9MMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvv84YBDjjjmhFPOOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh1///pcAgwwxzAijjDHObyaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPff5wwCFHHHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDP0G9AQYZYpgRRhljnN9MMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvv84YBDjjjmhFPOOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh1//4ZcAgwwxzAijjDHObyaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPff5wwCFHHHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDr//4S4BBhhhmhFHGGOc3E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+fzjgkCOOOeGUM8654JIrrrnhljvueeCRJ5554ZU33vngky+++eHXf/olwCBDDDPCKGOM85sJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58/HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwyhvvfPDJF9/88E9Af4BBhhhmhFHGGOc3E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+fzjgkCOOOeGUM8654JIrrrnhljvueeCRJ5554ZU33vngky+++eHX//9LgEGGGGaEUcYY5zcTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz5/OOCQI4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dd//iXAIEMMM8IoY4zzmwkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnz8ccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw67/8EmCQIYYZYZQxxvnNBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPHw445IhjTjjljHMuuOSKa2645Y57HnjkiWdeeOWNdz745ItvfvjnME+AQYYYZoRRxhjnNxNMMsU0M8wyxzwLLLLEMiusssY6G2yyxTY77LLHPn844JAjjjnhlDPOueCSK6654ZY77nngkSeeeeGVN9754JMvvvnh13/9JcAgQwwzwihjjPObCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfPxxwyBHHnHDKGedccMkV19xwyx33PPDIE8+88Mob73zwyRff/PDrv/0SYJAhhhlhlDHG+c0Ek0wxzQyzzDHPAossscwKq6yxzgabbLHNDrvssc8fDjjkiGNOOOWMcy645IprbrjljnseeOSJZ1545Y13Pvjki29++PXffwkwyBDDjDDKGOP8ZoJJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY5w8HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzL7zyxjsffPLFNz/8c5AvwCBDDDPCKGOM85sJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58/HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwyhvvfPDJF9/88Ot//BJgkCGGGWGUMcb5zQSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzx8OOOSIY0445YxzLrjkimtuuOWOex545IlnXnjljXc++OSLb3749T9/CTDIEMOMMMoY4/xmgkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjnDwcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z6X78EGGSIYUYYZYxxfjPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577POHAw454pgTTjnjnAsuueKaG265454HHnnimRdeeeOdDz754psf/jnEG2CQIYYZYZQxxvnNBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPHw445IhjTjjljHMuuOSKa2645Y57HnjkiWdeeOWNdz745Itvfvj1v38JMMgQw4wwyhjj/GaCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OcPBxxyxDEnnHLGORdccsU1N9xyxz0PPPLEMy+88sY7H3zyxTc//Po/vwQYZIhhRhhljHF+M8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvs84cDDjnimBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/d9fAgwyxDAjjDLGOL+ZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfb5wwGHHHHMCaeccc4Fl1xxzQ233HHPA4888cwLr7zxzgeffPHND79yv/w/dLzYyHicLcUhuAEAACBmQRAEQRAEQRAEQRAEd7OdbbbZZnd2ZzvbbLPNNtvczW7uThCEFwRBEARBEARBEARBEF4QBEEQBEEQhBf2ffP/5Q8WAv+/kMOOOOqY40446ZTTzjjrnPMuuOiSy6646prrbrjpltvuuOue+x546JF/PPbEU88898JLr7z2xlvvvPfBR5/867Mvvvrmux9++uW3P/5z4B98CzrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565B+PPfHUM8+98NIrr73x1jvvffDRJ//67Iuvvvnuh59++e2P/xz4h9+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B/6db0GHHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDj/zjsSeeeua5F1565bU33nrnvQ8++uRfn33x1Tff/fDTL7/98Z8DxW9Bhxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4/847EnnnrmuRdeeuW1N956570PPvrkX5998dU33/3w0y+//fGfA//ut6BDDjviqGOOO+GkU04746xzzrvgoksuu+Kqa6674aZbbrvjrnvue+ChR/7x2BNPPfPcCy+98tobb73z3gcfffKvz7746pvvfvjpl9/++M+Bf+9b0CGHHXHUMcedcNIpp51x1jnnXXDRJZddcdU1191w0y233XHXPfc98NAj/3jsiaeeee6Fl1557Y233nnvg48++ddnX3z1zXc//PTLb3/858A/+hZ0yGFHHHXMcSecdMppZ5x1znkXXHTJZVdcdc11N9x0y2133HXPfQ889Mg/HnviqWeee+GlV15746133vvgo0/+9dkXX33z3Q8//fLbH/85UPoWdMhhRxx1zHEnnHTKaWecdc55F1x0yWVXXHXNdTfcdMttd9x1z30PPPTIPx574qlnnnvhpVdee+Otd9774KNP/vXZF199890PP/3y2x//OfDvfws65LAjjjrmuBNOOuW0M84657wLLrrksiuuuua6G2665bY77rrnvgceeuQfjz3x1DPPvfDSK6+98dY7733w0Sf/+uyLr7757oeffvntj/8c+A++BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898o/HnnjqmedeeOmV19546533Pvjok3999sVX33z3w0+//PbHfw78h9+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B8rfgg457IijjjnuhJNOOe2Ms84574KLLrnsiquuue6Gm2657Y677rnvgYce+cdjTzz1zHMvvPTKa2+89c57H3z0yb8+++Krb7774adffvvjPwf+o29Bhxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4/847EnnnrmuRdeeuW1N956570PPvrkX5998dU33/3w0y+//fGfA//xt6BDDjviqGOOO+GkU04746xzzrvgoksuu+Kqa6674aZbbrvjrnvue+ChR/7x2BNPPfPcCy+98tobb73z3gcfffKvz7746pvvfvjpl9/++M+B/+Rb0CGHHXHUMcedcNIpp51x1jnnXXDRJZddcdU1191w0y233XHXPfc98NAj/3jsiaeeee6Fl1557Y233nnvg48++ddnX3z1zXc//PTLb3/850DlW9Ahhx1x1DHHnXDSKaedcdY5511w0SWXXXHVNdfdcNMtt91x1z33PfDQI/947ImnnnnuhZdeee2Nt95574OPPvnXZ1989c13P/z0y29//OfAf/ot6JDDjjjqmONOOOmU084465zzLrjoksuuuOqa62646Zbb7rjrnvseeOiRfzz2xFPPPPfCS6+89sZb77z3wUef/OuzL7765rsffvrltz/+c+A/+xZ0yGFHHHXMcSecdMppZ5x1znkXXHTJZVdcdc11N9x0y2133HXPfQ889Mg/HnviqWeee+GlV15746133vvgo0/+9dkXX33z3Q8//fLbH/858J9/CzrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565B+PPfHUM8+98NIrr73x1jvvffDRJ//67Iuvvvnuh59++e2P/xyofgs65LAjjjrmuBNOOuW0M84657wLLrrksiuuuua6G2665bY77rrnvgceeuQfjz3x1DPPvfDSK6+98dY7733w0Sf/+uyLr7757oeffvntj/8c+C++BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898o/HnnjqmedeeOmV19546533Pvjok3999sVX33z3w0+//PbHfw78429Bhxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4/847EnnnrmuRdeeuW1N956570PPvrkX5998dU33/3w0y+//fGfA//kW9Ahhx1x1DHHnXDSKaedcdY5511w0SWXXXHVNdfdcNMtt91x1z33PfDQI/947ImnnnnuhZdeee2Nt95574OPPvnXZ1989c13P/z0y29//OdA7VvQIYcdcdQxx51w0imnnXHWOeddcNEll11x1TXX3XDTLbfdcdc99z3w0CP/eOyJp5557oWXXnntjbfeee+Djz7512dffPXNdz/89Mtvf/znwH/5LeiQw4446pjjTjjplNPOOOuc8y646JLLrrjqmutuuOmW2+646577HnjokX889sRTzzz3wkuvvPbGW++898FHn/zrsy+++ua7H3765bc//nPgn34LOuSwI4465rgTTjrltDPOOue8Cy665LIrrrrmuhtuuuW2O+66574HHnrkH4898dQzz73w0iuvvfHWO+998NEn//rsi6+++e6Hn3757Y//HPhn34IOOeyIo4457oSTTjntjLPOOe+Ciy657Iqrrrnuhptuue2Ou+6574GHHvnHY0889cxzL7z0ymtvvPXOex989Mm/Pvviq2++++GnX3774z8H6t+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B/6rb0GHHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDj/zjsSeeeua5F1565bU33nrnvQ8++uRfn33x1Tff/fDTL7/98Z8D//W3oEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574KFH/vHYE08989wLL73y2htvvfPeBx998q/Pvvjqm+9++OmX3/74z4F//i3okMOOOOqY40446ZTTzjjrnPMuuOiSy6646prrbrjpltvuuOue+x546JF/PPbEU88898JLr7z2xlvvvPfBR5/867Mvvvrmux9++uW3P/5zoPEt6JDDjjjqmONOOOmU084465zzLrjoksuuuOqa62646Zbb7rjrnvseeOiRfzz2xFPPPPfCS6+89sZb77z3wUef/OuzL7765rsffvrltz/+c+C/+RZ0yGFHHHXMcSecdMppZ5x1znkXXHTJZVdcdc11N9x0y2133HXPfQ889Mg/HnviqWeee+GlV15746133vvgo0/+9dkXX33z3Q8//fLbH/858N9+CzrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565B+PPfHUM8+98NIrr73x1jvvffDRJ//67Iuvvvnuh59++e2P/xz4774FHXLYEUcdc9wJJ51y2hlnnXPeBRddctkVV11z3Q033XLbHXfdc98DDz3yj8eeeOqZ51546ZXX3njrnfc++OiTf332xVfffPfDT7/89sd/DjS/BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898o/HnnjqmedeeOmV19546533Pvjok3999sVX33z3w0+//PbHfw7899+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B/6Hb0GHHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDj/zjsSeeeua5F1565bU33nrnvQ8++uRfn33x1Tff/fDTL7/98Z8D/+O3oEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574KFH/vHYE08989wLL73y2htvvfPeBx998q/Pvvjqm+9++OmX3/74z4HWt6BDDjviqGOOO+GkU04746xzzrvgoksuu+Kqa6674aZbbrvjrnvue+ChR/7x2BNPPfPcCy+98tobb73z3gcfffKvz7746pvvfvjpl9/++M+B/+lb0CGHHXHUMcedcNIpp51x1jnnXXDRJZddcdU1191w0y233XHXPfc98NAj/3jsiaeeee6Fl1557Y233nnvg48++ddnX3z1zXc//PTLb3/858D//C3okMOOOOqY40446ZTTzjjrnPMuuOiSy6646prrbrjpltvuuOue+x546JF/PPbEU88898JLr7z2xlvvvPfBR5/867Mvvvrmux9++uW3P/5z4H/5FnTIYUccdcxxJ5x0ymlnnHXOeRdcdMllV1x1zXU33HTLbXfcdc99Dzz0yD8ee+KpZ5574aVXXnvjrXfe++CjT/712RdfffPdDz/98tsf/znQ/hZ0yGFHHHXMcSecdMppZ5x1znkXXHTJZVdcdc11N9x0y2133HXPfQ889Mg/HnviqWeee+GlV15746133vvgo0/+9dkXX33z3Q8//fLbH/858L9+CzrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565B+PPfHUM8+98NIrr73x1jvvffDRJ//67Iuvvvnuh59++e2P/xz4374FHXLYEUcdc9wJJ51y2hlnnXPeBRddctkVV11z3Q033XLbHXfdc98DDz3yj8eeeOqZ51546ZXX3njrnfc++OiTf332xVfffPfDT7/89sd/Dvzv34IOOeyIo4457oSTTjntjLPOOe+Ciy657Iqrrrnuhptuue2Ou+6574GHHvnHY0889cxzL7z0ymtvvPXOex989Mm/Pvviq2++++GnX3774z8HOt+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B/6Pb0GHHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDj/zjsSeeeua5F1565bU33nrnvQ8++uRfn33x1Tff/fDTL7/98Z8D/+e3oEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574KFH/vHYE08989wLL73y2htvvfPeBx998q/Pvvjqm+9++OmX3/74z4H/61vQIYcdcdQxx51w0imnnXHWOeddcNEll11x1TXX3XDTLbfdcdc99z3w0CP/eOyJp5557oWXXnntjbfeee+Djz7512dffPXNdz/89Mtvf/znQPdb0CGHHXHUMcedcNIpp51x1jnnXXDRJZddcdU1191w0y233XHXPfc98NAj/3jsiaeeee6Fl1557Y233nnvg48++ddnX3z1zXc//PTLb3/858C/+BZ0yGFHHHXMcSecdMppZ5x1znkXXHTJZVdcdc11N9x0y2133HXPfQ889Mg/HnviqWeee+GlV15746133vvgo0/+9dkXX33z3Q8//fLbH/858C+/BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898o/HnnjqmedeeOmV19546533Pvjok3999sVX33z3w0+//PbHfw7839+CDjnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx75x2NPPPXMcy+89Mprb7z1znsffPTJvz774qtvvvvhp19+++M/B3rfgg457IijjjnuhJNOOe2Ms84574KLLrnsiquuue6Gm2657Y677rnvgYce+cdjTzz1zHMvvPTKa2+89c57H3z0yb8+++Krb7774adffvvjPwf+n29Bhxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4/847EnnnrmuRdeeuW1N956570PPvrkX5998dU33/3w0y+//fGfA//qW9Ahhx1x1DHHnXDSKaedcdY5511w0SWXXXHVNdfdcNMtt91x1z33PfDQI/947ImnnnnuhZdeee2Nt95574OPPvnXZ1989c13P/z0y29//OfA//st6JDDjjjqmONOOOmU084465zzLrjoksuuuOqa62646Zbb7rjrnvseeOiRfzz2xFPPPPfCS6+89sZb77z3wUef/OuzL7765rsffvrltz/+c6D/LeiQw4446pjjTjjplNPOOOuc8y646JLLrrjqmutuuOmW2+646577HnjokX889sRTzzz3wkuvvPbGW++898FHn/zrsy+++ua7H3765bc//nPgX38LOuSwI4465rgTTjrltDPOOue8Cy665LIrrrrmuhtuuuW2O+66574HHnrkH4898dQzz73w0iuvvfHWO+998NEn//rsi6+++e6Hn3757Y//HPg334IOOeyIo4457oSTTjntjLPOOe+Ciy657Iqrrrnuhptuue2Ou+6574GHHvnHY0889cxzL7z0ymtvvPXOex989Mm/Pvviq2++++GnX3774z8H/u23oEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574KFH/vHYE08989wLL73y2htvvfPeBx998q/Pvvjqm+9++OmX3/74z4HBt/8PdlHZBHicLcVxnAOKQADgo8niaLI4miyOJoujyeJosjiaLI4mi6PJYjRZHE3mvb1nz5tnGIZLp1YunVq5NLWyNLVy6dTKpVMrl6ZWlk6tXPj92vf98wVqa/8v6HWHHPaGI4560zHHveWEk952ymnvOOOsd51z3nsuuOiSy65431XXXHfDTbfcdscHPnTXRz52zyfue+ChRx771Gee+NwXnvrSM8+98JWXvvba61YFHPS6Qw57wxFHvemY495ywklvO+W0d5xx1rvOOe89F1x0yWVXvO+qa6674aZbbrvjAx+66yMfu+cT9z3w0COPfeozT3zuC0996ZnnXvjKS1977YZVAQe97pDD3nDEUW865ri3nHDS20457R1nnPWuc857zwUXXXLZFe+76prrbrjpltvu+MCH7vrIx+75xH0PPPTIY5/6zBOf+8JTX3rmuRe+8tLXXrtxVcBBrzvksDcccdSbjjnuLSec9LZTTnvHGWe965zz3nPBRZdcdsX7rrrmuhtuuuW2Oz7wobs+8rF7PnHfAw898tinPvPE577w1Jeeee6Fr7z0tdfqqwIOet0hh73hiKPedMxxbznhpLedcto7zjjrXeec954LLrrksived9U1191w0y233fGBD931kY/d84n7Hnjokcc+9ZknPveFp770zHMvfOWlr71206qAg153yGFvOOKoNx1z3FtOOOltp5z2jjPOetc5573ngosuueyK9111zXU33HTLbXd84EN3feRj93zivgceeuSxT33mic994akvPfPcC1956Wuv3bwq4KDXHXLYG4446k3HHPeWE0562ymnveOMs951znnvueCiSy674n1XXXPdDTfdctsdH/jQXR/52D2fuO+Bhx557FOfeeJzX3jqS88898JXXvraa69fFXDQ6w457A1HHPWmY457ywknve2U095xxlnvOue891xw0SWXXfG+q6657oabbrntjg986K6PfOyeT9z3wEOPPPapzzzxuS889aVnnnvhKy997bXGqoCDXnfIYW844qg3HXPcW0446W2nnPaOM8561znnveeCiy657Ir3XXXNdTfcdMttd3zgQ3d95GP3fOK+Bx565LFPfeaJz33hqS8989wLX3npa6/dsirgoNcdctgbjjjqTccc95YTTnrbKae944yz3nXOee+54KJLLrvifVddc90NN91y2x0f+NBdH/nYPZ+474GHHnnsU5954nNfeOpLzzz3wlde+tprb1gVcNDrDjnsDUcc9aZjjnvLCSe97ZTT3nHGWe8657z3XHDRJZdd8b6rrrnuhptuue2OD3zoro987J5P3PfAQ4889qnPPPG5Lzz1pWeee+ErL33ttVtXBRz0ukMOe8MRR73pmOPecsJJbzvltHeccda7zjnvPRdcdMllV7zvqmuuu+GmW2674wMfuusjH7vnE/c98NAjj33qM0987gtPfemZ5174yktfe625KuCg1x1y2BuOOOpNxxz3lhNOetspp73jjLPedc5577ngoksuu+J9V11z3Q033XLbHR/40F0f+dg9n7jvgYceeexTn3nic1946kvPPPfCV1762mtvXBVw0OsOOewNRxz1pmOOe8sJJ73tlNPeccZZ7zrnvPdccNEll13xvquuue6Gm2657Y4PfOiuj3zsnk/c98BDjzz2qc888bkvPPWlZ5574Ssvfe2121YFHPS6Qw57wxFHvemY495ywklvO+W0d5xx1rvOOe89F1x0yWVXvO+qa6674aZbbrvjAx+66yMfu+cT9z3w0COPfeozT3zuC0996ZnnXvjKS1977U2rAg563SGHveGIo950zHFvOeGkt51y2jvOOOtd55z3ngsuuuSyK9531TXX3XDTLbfd8YEP3fWRj93zifseeOiRxz71mSc+94WnvvTMcy985aWvvdZaFXDQ6w457A1HHPWmY457ywknve2U095xxlnvOue891xw0SWXXfG+q6657oabbrntjg986K6PfOyeT9z3wEOPPPapzzzxuS889aVnnnvhKy997bU3rwo46HWHHPaGI4560zHHveWEk952ymnvOOOsd51z3nsuuOiSy65431XXXHfDTbfcdscHPnTXRz52zyfue+ChRx771Gee+NwXnvrSM8+98JWXvvbaW1YFHPS6Qw57wxFHvemY495ywklvO+W0d5xx1rvOOe89F1x0yWVXvO+qa6674aZbbrvjAx+66yMfu+cT9z3w0COPfeozT3zuC0996ZnnXvjKS1977a2rAg563SGHveGIo950zHFvOeGkt51y2jvOOOtd55z3ngsuuuSyK9531TXX3XDTLbfd8YEP3fWRj93zifseeOiRxz71mSc+94WnvvTMcy985aWvvdZeFXDQ6w457A1HHPWmY457ywknve2U095xxlnvOue891xw0SWXXfG+q6657oabbrntjg986K6PfOyeT9z3wEOPPPapzzzxuS889aVnnnvhKy997bW3rQo46HWHHPaGI4560zHHveWEk952ymnvOOOsd51z3nsuuOiSy65431XXXHfDTbfcdscHPnTXRz52zyfue+ChRx771Gee+NwXnvrSM8+98JWXvvZXffu3r76Dg76z7+q7Oex7+t6+j6O+vx/oBznuh/rhfoSTfrQf68c57Sf6yX6Ks366n+lnOe/n+vl+gYt+sV/ql7niV/rVfo1rvtE3+xY3/Sa/xW9zx+/2e/xed/0+v98fcM8f9If8YQ/8EX/UH/PYH/cn/ElP/Cl/2p/x1J/15/x5z/0Ff9Ff8tJf9ld8u3esDviOvpPv4pDv7nv4Xo74vr6fH+CYH+yH+GFO+JF+lB/jlB/vJ/hJzvipfpqf4Zyf7ef4eS74hX6RX+KyX+5X+FWu+rW+wTe54Vt9m9/stt/hd/nnfOhf9C/7V3zsX/dv+rfc9+/69/0HHvmP/af+M5/5L/3X/htf+O/9j/4nz/yv/nf/h6/83/5f/5/XOqu+zl/vb/C6v9Hf5G/2hr/F3+pv86a/3d/h7/SWv8vf7e/xtr/X3+fv945/wD/oH/Kuf9g/4h/1nn/MP+6fcMk/6Z/yT3vfP+Of9etc9+v9Br/RLb/Vb/c7feCf9y/4l3zkX/Wv+Td84t/27/j3PPQf+o/8Jz71n/sv/Fc+99/67/wPvvQ/+1/8b174P/1f/h9f+6u+/TtX38FB39l39d0c9j19b9/HUd/fD/SDHPdD/XA/wkk/2o/145z2E/1kP8VZP93P9LOc93P9fL/ARb/YL/XLXPEr/Wq/xjXf6Jt9i5t+k9/it7njd/s9fq+7fp/f7w+45w/6Q/6wB/6IP+qPeeyP+xP+pCf+lD/tz3jqz/pz/rzn/oK/6C956S/7K77du1YHfEffyXdxyHf3PXwvR3xf388PcMwP9kP8MCf8SD/Kj3HKj/cT/CRn/FQ/zc9wzs/2c/w8F/xCv8gvcdkv9yv8Klf9Wt/gm9zwrb7Nb3bbXwMukFCt
+
+
+ AQAAAACAAAAHJwAAJQAAAA==eJztwaEBAAAMAqC04v8H7weTAUgAAAAAAAAAAAAoHBMecq9+rg==
+
+
+
+
+
diff --git a/mesh-doctor/tests/test_convertMD2SG.py b/mesh-doctor/tests/test_convertMD2SG.py
new file mode 100644
index 000000000..3c3bfe4f7
--- /dev/null
+++ b/mesh-doctor/tests/test_convertMD2SG.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: GitHub Copilot, Jacques Franc
+import argparse
+
+from geos.mesh_doctor.parsing.convertMD2SGParsing import convert, fillSubparser
+from geos.mesh_doctor.actions.convertMD2SG import Result, action
+
+
+def test_convert_md2sg_parser() -> None:
+ """Test the convertMD2SG parser and conversion function."""
+ parser = argparse.ArgumentParser( description='Testing.' )
+ subparsers = parser.add_subparsers()
+ fillSubparser( subparsers )
+
+ args = parser.parse_args( [ 'convertMD2SG', '-i', 'mesh-doctor/tests/data/base_tetra_shift.vtm', '-z', '2' ] )
+
+ options = convert( vars( args ) )
+ assert options.attrs == ( 2, )
+ assert options.skipCleanCollocated is False
+ assert options.skipFilterVolumeCells is False
+ assert options.meshVtkOutput.output == "converted.vtu"
+
+
+def test_convertion() -> None:
+ """Test the convertMD2SG action."""
+ parser = argparse.ArgumentParser( description='Testing.' )
+ subparsers = parser.add_subparsers()
+ fillSubparser( subparsers )
+
+ args = parser.parse_args( [
+ 'convertMD2SG', '-i', 'mesh-doctor/tests/data/base_tetra_shift.vtm', '-z', '2', '--outputFile', 'converted.vtu'
+ ] )
+
+ options = convert( vars( args ) )
+ actionsResult = action( vars( args )[ 'vtuInputFile' ], options )
+ assert isinstance( actionsResult, Result )
+ assert actionsResult.outputMesh is not None
+ assert actionsResult.nCleanCollocated != 0
+ assert actionsResult.outputMesh.GetNumberOfCells() > 0
+ assert actionsResult.outputMesh.GetPointData().HasArray( "faultNodes_0" )
+ assert actionsResult.outputMesh.GetPointData().GetArray( "faultNodes_0" ).GetRange() == ( 0.0, 1.0 )
+
+
+def test_convertion_cc() -> None:
+ """Test the convertMD2SG action from vtu file with multiple connected components."""
+ parser = argparse.ArgumentParser( description='Testing.' )
+ subparsers = parser.add_subparsers()
+ fillSubparser( subparsers )
+
+ args = parser.parse_args( [
+ 'convertMD2SG', '-i', 'mesh-doctor/tests/data/base_hexa_shift_2.vtu', '-z', '2', '3', '--outputFile',
+ 'converted_cc.vtu', '--skipCleanCollocated'
+ ] )
+
+ options = convert( vars( args ) )
+ actionsResult = action( vars( args )[ 'vtuInputFile' ], options )
+ assert isinstance( actionsResult, Result )
+ assert actionsResult.outputMesh is not None
+ assert actionsResult.nCleanCollocated == 0
+ assert actionsResult.outputMesh.GetNumberOfCells() > 0
+ assert actionsResult.outputMesh.GetPointData().HasArray( "faultNodes_0" )
+ assert actionsResult.outputMesh.GetPointData().GetArray( "faultNodes_0" ).GetRange() == ( 0.0, 1.0 )
+ assert actionsResult.outputMesh.GetPointData().HasArray( "faultNodes_1" )
+ assert actionsResult.outputMesh.GetPointData().GetArray( "faultNodes_1" ).GetRange() == ( 0.0, 1.0 )