PyHOPE 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyhope-1.0.0 → pyhope-1.1.0}/PKG-INFO +13 -8
- {pyhope-1.0.0 → pyhope-1.1.0}/README.md +10 -6
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/basis/basis_basis.py +77 -45
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/basis/basis_connect.py +15 -9
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/basis/basis_jacobian.py +28 -2
- pyhope-1.1.0/pyhope/basis/basis_orient.py +195 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/basis/basis_watertight.py +19 -13
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/check/check_health.py +2 -2
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_vars.py +37 -2
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/gmsh/gmsh_install.py +90 -15
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/io.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/io_debug.py +241 -142
- pyhope-1.1.0/pyhope/io/io_gmsh.py +69 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/connect/connect.py +13 -6
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/extrude/mesh_extrude.py +21 -6
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_sides.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/reader/reader_gmsh.py +3 -2
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/mesh_transform.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/cylinder.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/sphere.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/sphere_box.py +1 -1
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/meshio/meshio_convert.py +2 -2
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/script/pyhope_cli.py +6 -3
- {pyhope-1.0.0 → pyhope-1.1.0}/pyproject.toml +7 -4
- pyhope-1.0.0/pyhope/io/io_gmsh.py +0 -68
- pyhope-1.0.0/pyhope/mesh/mesh_orient.py +0 -196
- {pyhope-1.0.0 → pyhope-1.1.0}/AUTHORS.md +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/LICENSE.md +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/__init__.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/__main__.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/check/check.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/check/check_install.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/check/check_unittest.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_git.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_numba.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_parallel.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_progress.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_template.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_tools.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/common/common_unique.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/config/config.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/gmsh/gmsh.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/gmsh/gmsh_override.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/formats/cgns.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/formats/gmsh.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/formats/meshio.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/formats/vtk.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/io_vars.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/io/io_xdmf.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/connect/connect_mortar.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/connect/connect_rbtree.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/elements/elements_ordering.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/elements/elements_shapefunctions.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/extrude/templates/linear.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/fem/fem.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_builtin.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_common.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_duplicates.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_external.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_mortar.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_sort.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/mesh_vars.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/reader/reader_gambit.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/reader/reader_hopr.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/sort/sort_hilbert.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/topology/mesh_serendipity.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/topology/mesh_splittohex.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/topology/mesh_topology.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/convtest.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/default.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/phill.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/mesh/transform/templates/sine.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/meshio/meshio_nodes.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/meshio/meshio_ordering.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/output/output.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/readintools/commandline.py +0 -0
- {pyhope-1.0.0 → pyhope-1.1.0}/pyhope/readintools/readintools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyHOPE
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Python High-Order Preprocessing Environment
|
|
5
5
|
License-Expression: GPL-3.0-only
|
|
6
6
|
License-File: AUTHORS.md
|
|
@@ -28,7 +28,8 @@ Requires-Dist: meshio
|
|
|
28
28
|
Requires-Dist: numba ; extra == "numba"
|
|
29
29
|
Requires-Dist: numpy (>=2.0.0)
|
|
30
30
|
Requires-Dist: packaging
|
|
31
|
-
Requires-Dist: plotext
|
|
31
|
+
Requires-Dist: plotext (>=6.0.0)
|
|
32
|
+
Requires-Dist: requests
|
|
32
33
|
Requires-Dist: scipy
|
|
33
34
|
Requires-Dist: typing-extensions
|
|
34
35
|
Project-URL: Changelog, https://github.com/hopr-framework/PyHOPE/blob/main/CHANGELOG.md
|
|
@@ -121,6 +122,7 @@ $ pyhope tutorials/1-01-cartbox/parameter.ini
|
|
|
121
122
|
│ INIT OUTPUT...
|
|
122
123
|
│ ProjectName │ 1-01-cartbox │ *CUSTOM │
|
|
123
124
|
│ OutputFormat │ 0 [HDF5] │ *CUSTOM │
|
|
125
|
+
│ OutputBytes │ 0 [int32] │ DEFAULT │
|
|
124
126
|
│ DebugMesh │ T │ *CUSTOM │
|
|
125
127
|
│ DebugVisu │ F │ *CUSTOM │
|
|
126
128
|
├─────────────────────────────────────────────
|
|
@@ -162,23 +164,26 @@ $ pyhope tutorials/1-01-cartbox/parameter.ini
|
|
|
162
164
|
├── BUILD DATA STRUCTURE...
|
|
163
165
|
├────
|
|
164
166
|
├── Removing duplicate points
|
|
165
|
-
├── Ensuring normals point outward
|
|
166
|
-
├────
|
|
167
|
-
│ CheckSurfaceNormals │ True │ DEFAULT │
|
|
168
|
-
│ Processing Elements |█████████████████████████████████| 512/512 [100%] in 0.0s (24000.00/s)
|
|
169
167
|
├────
|
|
170
168
|
├── Generating sides
|
|
171
169
|
├─────────────────────────────────────────────
|
|
172
170
|
│ SORT MESH...
|
|
173
171
|
├────
|
|
174
172
|
│ MeshSorting │ 1 [SFC] │ DEFAULT │
|
|
173
|
+
│ MeshSortingSFC │ 0 [default] │ DEFAULT │
|
|
175
174
|
├────
|
|
176
175
|
├── Sorting elements along space-filling curve
|
|
177
176
|
├─────────────────────────────────────────────
|
|
177
|
+
│ CHECK NORMALS POINTING OUTWARDS...
|
|
178
|
+
├────
|
|
179
|
+
│ CheckSurfaceNormals │ True │ DEFAULT │
|
|
180
|
+
│ Processing Elements |█████████████████████████████████| 512/512 [100%] in 0.0s (24000.00/s)
|
|
181
|
+
├─────────────────────────────────────────────
|
|
178
182
|
│ CONNECT MESH...
|
|
179
183
|
├────
|
|
180
184
|
│ doPeriodicCorrect │ False │ DEFAULT │
|
|
181
185
|
│ doMortars │ True │ DEFAULT │
|
|
186
|
+
│ doMortarRebuild │ 1 [auto] │ DEFAULT │
|
|
182
187
|
│ Processing Sides |█████████████████████████████████| 3072/3072 [100%] in 0.0s (24000.00/s)
|
|
183
188
|
├────
|
|
184
189
|
│ Number of sides : 3072
|
|
@@ -246,8 +251,8 @@ This is a scientific project. If you use PyHOPE for publications or presentation
|
|
|
246
251
|
author = {Kopper, Patrick and Blind, Marcel P. and Schwarz, Anna and Kurz, Marius and Rodach, Felix and Copplestone, Stephen M. and Beck, Andrea D.},
|
|
247
252
|
journal = {Journal of Open Source Software},
|
|
248
253
|
year = {2025},
|
|
249
|
-
volume = {10},
|
|
250
|
-
number = {115},
|
|
254
|
+
volume = {10},
|
|
255
|
+
number = {115},
|
|
251
256
|
pages = {8769},
|
|
252
257
|
publisher = {The Open Journal},
|
|
253
258
|
doi = {10.21105/joss.08769}
|
|
@@ -81,6 +81,7 @@ $ pyhope tutorials/1-01-cartbox/parameter.ini
|
|
|
81
81
|
│ INIT OUTPUT...
|
|
82
82
|
│ ProjectName │ 1-01-cartbox │ *CUSTOM │
|
|
83
83
|
│ OutputFormat │ 0 [HDF5] │ *CUSTOM │
|
|
84
|
+
│ OutputBytes │ 0 [int32] │ DEFAULT │
|
|
84
85
|
│ DebugMesh │ T │ *CUSTOM │
|
|
85
86
|
│ DebugVisu │ F │ *CUSTOM │
|
|
86
87
|
├─────────────────────────────────────────────
|
|
@@ -122,23 +123,26 @@ $ pyhope tutorials/1-01-cartbox/parameter.ini
|
|
|
122
123
|
├── BUILD DATA STRUCTURE...
|
|
123
124
|
├────
|
|
124
125
|
├── Removing duplicate points
|
|
125
|
-
├── Ensuring normals point outward
|
|
126
|
-
├────
|
|
127
|
-
│ CheckSurfaceNormals │ True │ DEFAULT │
|
|
128
|
-
│ Processing Elements |█████████████████████████████████| 512/512 [100%] in 0.0s (24000.00/s)
|
|
129
126
|
├────
|
|
130
127
|
├── Generating sides
|
|
131
128
|
├─────────────────────────────────────────────
|
|
132
129
|
│ SORT MESH...
|
|
133
130
|
├────
|
|
134
131
|
│ MeshSorting │ 1 [SFC] │ DEFAULT │
|
|
132
|
+
│ MeshSortingSFC │ 0 [default] │ DEFAULT │
|
|
135
133
|
├────
|
|
136
134
|
├── Sorting elements along space-filling curve
|
|
137
135
|
├─────────────────────────────────────────────
|
|
136
|
+
│ CHECK NORMALS POINTING OUTWARDS...
|
|
137
|
+
├────
|
|
138
|
+
│ CheckSurfaceNormals │ True │ DEFAULT │
|
|
139
|
+
│ Processing Elements |█████████████████████████████████| 512/512 [100%] in 0.0s (24000.00/s)
|
|
140
|
+
├─────────────────────────────────────────────
|
|
138
141
|
│ CONNECT MESH...
|
|
139
142
|
├────
|
|
140
143
|
│ doPeriodicCorrect │ False │ DEFAULT │
|
|
141
144
|
│ doMortars │ True │ DEFAULT │
|
|
145
|
+
│ doMortarRebuild │ 1 [auto] │ DEFAULT │
|
|
142
146
|
│ Processing Sides |█████████████████████████████████| 3072/3072 [100%] in 0.0s (24000.00/s)
|
|
143
147
|
├────
|
|
144
148
|
│ Number of sides : 3072
|
|
@@ -206,8 +210,8 @@ This is a scientific project. If you use PyHOPE for publications or presentation
|
|
|
206
210
|
author = {Kopper, Patrick and Blind, Marcel P. and Schwarz, Anna and Kurz, Marius and Rodach, Felix and Copplestone, Stephen M. and Beck, Andrea D.},
|
|
207
211
|
journal = {Journal of Open Source Software},
|
|
208
212
|
year = {2025},
|
|
209
|
-
volume = {10},
|
|
210
|
-
number = {115},
|
|
213
|
+
volume = {10},
|
|
214
|
+
number = {115},
|
|
211
215
|
pages = {8769},
|
|
212
216
|
publisher = {The Open Journal},
|
|
213
217
|
doi = {10.21105/joss.08769}
|
|
@@ -661,55 +661,87 @@ def change_basis_1D(Vdm: npt.NDArray[np.float64], x1D_In: npt.NDArray[np.float64
|
|
|
661
661
|
return Vdm @ x1D_In
|
|
662
662
|
|
|
663
663
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
664
|
+
if not NUMBA_AVAILABLE:
|
|
665
|
+
def evaluate_jacobian(xGeo_In: npt.NDArray[np.float64],
|
|
666
|
+
VdmGLtoAP: npt.NDArray[np.float64],
|
|
667
|
+
D_EqToGL: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
668
|
+
""" Calculate the Jacobian of the mapping for a given element
|
|
669
|
+
"""
|
|
670
|
+
dim1 = xGeo_In.shape[0]
|
|
671
|
+
n_In = xGeo_In.shape[1]
|
|
672
|
+
n_Out = D_EqToGL.shape[0]
|
|
673
|
+
|
|
674
|
+
# Perform tensor contraction for the first derivative (Xi direction)
|
|
675
|
+
# dXdXiGL = np.tensordot(D_EqToGL, xGeo_In, axes=(1, 1))
|
|
676
|
+
# dXdXiGL = np.moveaxis(dXdXiGL , 1, 0) # Correct the shape to (3, nGeoRef, nGeoRef, nGeoRef)
|
|
677
|
+
_X1 = xGeo_In.reshape(dim1, n_In, n_In * n_In)
|
|
678
|
+
dXdXiGL = (D_EqToGL @ _X1).reshape(dim1, n_Out, n_In, n_In)
|
|
679
|
+
|
|
680
|
+
# Perform tensor contraction for the second derivative (Eta direction)
|
|
681
|
+
dXdEtaGL = np.tensordot(D_EqToGL, xGeo_In, axes=(1, 2))
|
|
682
|
+
dXdEtaGL = np.transpose(dXdEtaGL, (1, 2, 0, 3))
|
|
683
|
+
# PERF: This is actually slower than the individual contractions
|
|
684
|
+
# dXdEtaGL = np.einsum('qj,dijk->dqik', D_EqToGL, xGeo_In, optimize=True)
|
|
685
|
+
|
|
686
|
+
# Perform tensor contraction for the third derivative (Zeta direction)
|
|
687
|
+
# dXdZetaGL = np.tensordot(D_EqToGL, xGeo_In, axes=(1, 3))
|
|
688
|
+
# dXdZetaGL = np.moveaxis(dXdZetaGL, 1, 0) # Correct the shape to (3, nGeoRef, nGeoRef, nGeoRef)
|
|
689
|
+
_X3 = xGeo_In.reshape(dim1, n_In * n_In, n_In)
|
|
690
|
+
dXdZetaGL = (_X3 @ D_EqToGL.T).reshape(dim1, n_In, n_In, n_Out)
|
|
691
|
+
dXdZetaGL = np.transpose(dXdZetaGL, (0, 3, 1, 2))
|
|
692
|
+
# PERF: This is actually slower than the individual contractions
|
|
693
|
+
# dXdZetaGL = np.einsum('rk,dijk->drij', D_EqToGL, xGeo_In, optimize=True)
|
|
694
|
+
|
|
695
|
+
# Change basis for each direction
|
|
696
|
+
dXdXiAP = change_basis_3D(VdmGLtoAP, dXdXiGL )
|
|
697
|
+
dXdEtaAP = change_basis_3D(VdmGLtoAP, dXdEtaGL )
|
|
698
|
+
dXdZetaAP = change_basis_3D(VdmGLtoAP, dXdZetaGL)
|
|
699
|
+
|
|
700
|
+
# Precompute cross products between dXdEtaAP and dXdZetaAP for all points
|
|
701
|
+
# cross_eta_zeta = np.cross(dXdEtaAP, dXdZetaAP, axis=0) # Shape: (3, nGeoRef, nGeoRef, nGeoRef)
|
|
702
|
+
# > Manually compute cross product
|
|
703
|
+
cross_eta_zeta = np.empty_like(dXdEtaAP)
|
|
704
|
+
cross_eta_zeta[0] = dXdEtaAP[1] * dXdZetaAP[2] - dXdEtaAP[2] * dXdZetaAP[1]
|
|
705
|
+
cross_eta_zeta[1] = dXdEtaAP[2] * dXdZetaAP[0] - dXdEtaAP[0] * dXdZetaAP[2]
|
|
706
|
+
cross_eta_zeta[2] = dXdEtaAP[0] * dXdZetaAP[1] - dXdEtaAP[1] * dXdZetaAP[0]
|
|
707
|
+
|
|
708
|
+
# Fill output Jacobian array
|
|
709
|
+
# jacOut = np.einsum('ijkl,ijkl->jkl', dXdXiAP, cross_eta_zeta)
|
|
710
|
+
# PERF: This is actually slower than the individual contractions
|
|
711
|
+
# jacOut = np.sum(dXdXiAP * cross_eta_zeta, axis=0)
|
|
693
712
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
713
|
+
return np.einsum('ijkl,ijkl->jkl', dXdXiAP, cross_eta_zeta)
|
|
714
|
+
else:
|
|
715
|
+
@jit(nopython=True, cache=True, nogil=True)
|
|
716
|
+
def evaluate_jacobian(xGeo_In: npt.NDArray[np.float64],
|
|
717
|
+
VdmGLtoAP: npt.NDArray[np.float64],
|
|
718
|
+
D_EqToGL: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
719
|
+
""" Calculate the Jacobian of the mapping for a given element
|
|
720
|
+
"""
|
|
721
|
+
dim1 = xGeo_In.shape[0]
|
|
722
|
+
n_In = xGeo_In.shape[1]
|
|
723
|
+
n_Out = D_EqToGL.shape[0]
|
|
698
724
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
cross_eta_zeta[0] = dXdEtaAP[1] * dXdZetaAP[2] - dXdEtaAP[2] * dXdZetaAP[1]
|
|
704
|
-
cross_eta_zeta[1] = dXdEtaAP[2] * dXdZetaAP[0] - dXdEtaAP[0] * dXdZetaAP[2]
|
|
705
|
-
cross_eta_zeta[2] = dXdEtaAP[0] * dXdZetaAP[1] - dXdEtaAP[1] * dXdZetaAP[0]
|
|
725
|
+
dXdXiGL = np.zeros((dim1, n_Out, n_In, n_In), dtype=np.float64)
|
|
726
|
+
for d in range(dim1):
|
|
727
|
+
_X1 = np.ascontiguousarray(xGeo_In[d]).reshape(n_In, n_In * n_In)
|
|
728
|
+
dXdXiGL[d] = (D_EqToGL @ _X1).reshape(n_Out, n_In, n_In)
|
|
706
729
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
730
|
+
_X2 = np.ascontiguousarray(xGeo_In.transpose(0, 1, 3, 2)).reshape(-1, n_In)
|
|
731
|
+
dXdEtaGL = (_X2 @ D_EqToGL.T).reshape(dim1, n_In, n_In, n_Out).transpose(0, 1, 3, 2)
|
|
732
|
+
|
|
733
|
+
_X3 = np.ascontiguousarray(xGeo_In).reshape(-1, n_In)
|
|
734
|
+
dXdZetaGL = (_X3 @ D_EqToGL.T).reshape(dim1, n_In, n_In, n_Out).transpose(0, 3, 1, 2)
|
|
735
|
+
|
|
736
|
+
dXdXiAP = change_basis_3D(VdmGLtoAP, dXdXiGL )
|
|
737
|
+
dXdEtaAP = change_basis_3D(VdmGLtoAP, dXdEtaGL )
|
|
738
|
+
dXdZetaAP = change_basis_3D(VdmGLtoAP, dXdZetaGL)
|
|
739
|
+
|
|
740
|
+
cross_0 = dXdEtaAP[1] * dXdZetaAP[2] - dXdEtaAP[2] * dXdZetaAP[1]
|
|
741
|
+
cross_1 = dXdEtaAP[2] * dXdZetaAP[0] - dXdEtaAP[0] * dXdZetaAP[2]
|
|
742
|
+
cross_2 = dXdEtaAP[0] * dXdZetaAP[1] - dXdEtaAP[1] * dXdZetaAP[0]
|
|
711
743
|
|
|
712
|
-
|
|
744
|
+
return dXdXiAP[0] * cross_0 + dXdXiAP[1] * cross_1 + dXdXiAP[2] * cross_2
|
|
713
745
|
|
|
714
746
|
|
|
715
747
|
def evaluate_jacobian_simplex(xGeo_In: npt.NDArray[np.float64],
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
import re
|
|
30
|
-
import sys
|
|
31
30
|
from typing import Final, Optional, cast
|
|
32
31
|
from collections.abc import Iterable
|
|
32
|
+
from operator import itemgetter
|
|
33
33
|
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
34
34
|
# Third-party libraries
|
|
35
35
|
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -142,10 +142,12 @@ def CheckConnect() -> None:
|
|
|
142
142
|
""" Check if the mesh is correctly connected
|
|
143
143
|
"""
|
|
144
144
|
# Local imports ----------------------------------------
|
|
145
|
+
import pyhope.io.io_vars as io_vars
|
|
145
146
|
import pyhope.output.output as hopout
|
|
146
147
|
import pyhope.mesh.mesh_vars as mesh_vars
|
|
147
148
|
from pyhope.common.common_parallel import run_in_parallel
|
|
148
149
|
from pyhope.common.common_vars import np_mtp
|
|
150
|
+
from pyhope.io.io_debug import DebugIO
|
|
149
151
|
from pyhope.readintools.readintools import GetLogical
|
|
150
152
|
# ------------------------------------------------------
|
|
151
153
|
|
|
@@ -178,13 +180,13 @@ def CheckConnect() -> None:
|
|
|
178
180
|
ordering = False, # noqa: E251
|
|
179
181
|
)
|
|
180
182
|
else:
|
|
181
|
-
res = [elem for elem in elems
|
|
183
|
+
res = [check_sides(elem, failed_only=True) for elem in elems]
|
|
182
184
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
for result in elem_results) # noqa: E272
|
|
185
|
+
# Flatten per-element results (skip None placeholders)
|
|
186
|
+
results = tuple(result for elem_results in res if isinstance(elem_results, Iterable) and elem_results is not None
|
|
187
|
+
for result in elem_results) # noqa: E272
|
|
187
188
|
|
|
189
|
+
if len(results): # pragma: no cover
|
|
188
190
|
nGeo: Final[int] = mesh_vars.nGeo
|
|
189
191
|
sides: Final[list] = mesh_vars.sides
|
|
190
192
|
points: Final[np.ndarray] = mesh_vars.mesh.points
|
|
@@ -216,7 +218,7 @@ def CheckConnect() -> None:
|
|
|
216
218
|
nodes = elem.nodes[face_to_nodes( side.face, elem.type, nGeo)]
|
|
217
219
|
nbnodes = nbelem.nodes[face_to_nodes(nbside.face, nbelem.type, nGeo)]
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
hopout.info('')
|
|
220
222
|
# Check if side is oriented inwards
|
|
221
223
|
errStr = 'Side connectivity does not match the calculated neighbour side'
|
|
222
224
|
print(hopout.warn(errStr, length=len(errStr)+16))
|
|
@@ -235,5 +237,9 @@ def CheckConnect() -> None:
|
|
|
235
237
|
print(hopout.warn('- Coordinates : [' + ' '.join(f'{s:12.3f}' for s in points[nbnodes[-1, 0]]) + ']')) # noqa: E271
|
|
236
238
|
print(hopout.warn('- Coordinates : [' + ' '.join(f'{s:12.3f}' for s in points[nbnodes[-1, -1]]) + ']')) # noqa: E271
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
# Write a low-order debug mesh if requested
|
|
241
|
+
if io_vars.debugmesh:
|
|
242
|
+
hopout.info('')
|
|
243
|
+
DebugIO(errSides=list(map(itemgetter(1), results)))
|
|
244
|
+
|
|
245
|
+
hopout.error(f'Connectivity check failed for {len(results)} / {nconn} connections!')
|
|
@@ -94,8 +94,34 @@ def plot_histogram(data: npt.NDArray[np.float64]) -> None:
|
|
|
94
94
|
hopout.sep()
|
|
95
95
|
hopout.info('Scaled Jacobians')
|
|
96
96
|
hopout.separator(18)
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
|
|
98
|
+
# Horizontal bars are stacked from the bottom up, thus reverse the data to keep the first bin on top
|
|
99
|
+
labels = ticks[::-1]
|
|
100
|
+
counts = count[::-1]
|
|
101
|
+
|
|
102
|
+
# Reserve space at the end of the longest bar for its count label
|
|
103
|
+
barMax = max(int(count.max()), 1)
|
|
104
|
+
barWidth = STD_LENGTH - len(ticks[0])
|
|
105
|
+
labelWidth = len(str(barMax)) + 1
|
|
106
|
+
|
|
107
|
+
# Assemble the frame-less bar plot from the regular [bar], see https://plotext.readthedocs.io/en/latest/bar.html#simple-bar-style
|
|
108
|
+
fig = plt.figure
|
|
109
|
+
fig.clear()
|
|
110
|
+
fig.theme('simple')
|
|
111
|
+
fig.plot_size(STD_LENGTH, len(labels))
|
|
112
|
+
fig.draw(fig.bar(labels, counts, orientation='horizontal', marker='brick', width=0, lines=True))
|
|
113
|
+
|
|
114
|
+
# Print the number of elements at the end of each bar
|
|
115
|
+
for row, c in enumerate(counts, start=1):
|
|
116
|
+
fig.draw(fig.text(c, row, plt.colorize(f' {c}', pixel=('gray+', None, 'bold')), alignment='left'))
|
|
117
|
+
|
|
118
|
+
# Remove the frame and the axis ticks, keeping only the bin labels
|
|
119
|
+
fig.axes(False)
|
|
120
|
+
fig.ruler('x').lim(0, barMax * barWidth / max(barWidth - labelWidth, 1))
|
|
121
|
+
fig.ruler('x').ticks([])
|
|
122
|
+
fig.ruler('y').alignment(tick='left')
|
|
123
|
+
fig.show(flush=True)
|
|
124
|
+
|
|
99
125
|
hopout.separator(18)
|
|
100
126
|
|
|
101
127
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
# This file is part of PyHOPE
|
|
7
|
+
#
|
|
8
|
+
# Copyright (c) 2024 Numerics Research Group, University of Stuttgart, Prof. Andrea Beck
|
|
9
|
+
#
|
|
10
|
+
# PyHOPE is free software: you can redistribute it and/or modify it under the
|
|
11
|
+
# terms of the GNU General Public License as published by the Free Software
|
|
12
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
13
|
+
# version.
|
|
14
|
+
#
|
|
15
|
+
# PyHOPE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
16
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
17
|
+
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# PyHOPE. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
|
|
22
|
+
# ==================================================================================================================================
|
|
23
|
+
# Mesh generation library
|
|
24
|
+
# ==================================================================================================================================
|
|
25
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
26
|
+
# Standard libraries
|
|
27
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
import gc
|
|
30
|
+
import re
|
|
31
|
+
from typing import Final, Optional, cast
|
|
32
|
+
from collections.abc import Iterable
|
|
33
|
+
from operator import itemgetter
|
|
34
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
35
|
+
# Third-party libraries
|
|
36
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
37
|
+
import numpy as np
|
|
38
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
39
|
+
# Typing libraries
|
|
40
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
41
|
+
import typing
|
|
42
|
+
from pyhope.common.common_numba import NUMBA_AVAILABLE
|
|
43
|
+
if typing.TYPE_CHECKING or NUMBA_AVAILABLE:
|
|
44
|
+
import numpy.typing as npt
|
|
45
|
+
from pyhope.mesh.mesh_vars import ELEM
|
|
46
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
47
|
+
# Local imports
|
|
48
|
+
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
49
|
+
import pyhope.mesh.mesh_vars as mesh_vars
|
|
50
|
+
from pyhope.basis.basis_watertight import eval_nsurf
|
|
51
|
+
from pyhope.mesh.mesh_common import face_to_nodes, faces
|
|
52
|
+
# ==================================================================================================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_orientation(elem : ELEM,
|
|
56
|
+
VdmEqToGP : npt.NDArray[np.float64],
|
|
57
|
+
DGP : npt.NDArray[np.float64],
|
|
58
|
+
weights : npt.NDArray[np.float64],
|
|
59
|
+
failed_only: bool = False,
|
|
60
|
+
) -> Optional[list[tuple]]:
|
|
61
|
+
""" Check the orientation of the surface normals
|
|
62
|
+
"""
|
|
63
|
+
points = mesh_vars.mesh.points
|
|
64
|
+
nGeo = mesh_vars.nGeo
|
|
65
|
+
elemType = elem.type
|
|
66
|
+
|
|
67
|
+
if elemType % 10 != 8:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
# Center of element
|
|
71
|
+
cElem = points[elem.nodes].reshape(-1, 3).mean(axis=0)
|
|
72
|
+
results = None
|
|
73
|
+
|
|
74
|
+
# Fall back to geometry topology faces if sides array is not initialized
|
|
75
|
+
sides = getattr(mesh_vars, 'sides', None)
|
|
76
|
+
sideIter = ((sides[s].face, s) for s in elem.sides) if (elem.sides is not None and sides is not None) else \
|
|
77
|
+
((f, None) for f in faces(elemType))
|
|
78
|
+
|
|
79
|
+
for face, SideID in sideIter:
|
|
80
|
+
idx = cast(np.ndarray, elem.nodes)[face_to_nodes(face, elemType, nGeo)]
|
|
81
|
+
fpoints = points[idx]
|
|
82
|
+
|
|
83
|
+
# Calculate high-order surface normal vector via Gauss integration
|
|
84
|
+
nSurf = eval_nsurf(fpoints.transpose(2, 0, 1), VdmEqToGP, DGP, weights)
|
|
85
|
+
|
|
86
|
+
# Vector pointing from face center to element center
|
|
87
|
+
fCenter = fpoints.reshape(-1, 3).mean(axis=0)
|
|
88
|
+
vCenter = cElem - fCenter
|
|
89
|
+
|
|
90
|
+
success = np.dot(nSurf, vCenter) <= 0.
|
|
91
|
+
|
|
92
|
+
# If requested, only return errors
|
|
93
|
+
if failed_only and success:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# Lazily initialize results on first failure
|
|
97
|
+
if results is None:
|
|
98
|
+
results = []
|
|
99
|
+
results.append((success, elem.elemID, face, SideID))
|
|
100
|
+
|
|
101
|
+
# Avoid creating empty lists on elem_results
|
|
102
|
+
if results is None:
|
|
103
|
+
return None if failed_only else []
|
|
104
|
+
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def process_chunk(chunk: tuple) -> list:
|
|
109
|
+
"""Process a chunk of elements by checking surface normal orientation
|
|
110
|
+
"""
|
|
111
|
+
# Only keep failures to reduce memory and avoid building large arrays of successes
|
|
112
|
+
chunk_results = []
|
|
113
|
+
for elem in chunk:
|
|
114
|
+
elem_result = check_orientation(elem,
|
|
115
|
+
process_chunk.VdmEqToGP, # ty: ignore[unresolved-attribute]
|
|
116
|
+
process_chunk.DGP, # ty: ignore[unresolved-attribute]
|
|
117
|
+
process_chunk.weights, # ty: ignore[unresolved-attribute]
|
|
118
|
+
failed_only=True)
|
|
119
|
+
chunk_results.append(elem_result)
|
|
120
|
+
return chunk_results
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def CheckOrient() -> None:
|
|
124
|
+
""" Check if element surface normals point outwards
|
|
125
|
+
"""
|
|
126
|
+
# Local imports ----------------------------------------
|
|
127
|
+
import pyhope.io.io_vars as io_vars
|
|
128
|
+
import pyhope.output.output as hopout
|
|
129
|
+
import pyhope.mesh.mesh_vars as mesh_vars
|
|
130
|
+
from pyhope.basis.basis_basis import barycentric_weights, legendre_gauss_nodes
|
|
131
|
+
from pyhope.basis.basis_basis import calc_vandermonde, polynomial_derivative_matrix
|
|
132
|
+
from pyhope.basis.basis_watertight import init_worker
|
|
133
|
+
from pyhope.common.common_parallel import run_in_parallel
|
|
134
|
+
from pyhope.common.common_vars import np_mtp
|
|
135
|
+
from pyhope.io.io_debug import DebugIO
|
|
136
|
+
from pyhope.readintools.readintools import GetLogical
|
|
137
|
+
# ------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
hopout.separator()
|
|
140
|
+
hopout.info('CHECK NORMALS POINTING OUTWARDS...')
|
|
141
|
+
hopout.sep()
|
|
142
|
+
|
|
143
|
+
checkSurfaceNormals = GetLogical('CheckSurfaceNormals')
|
|
144
|
+
if not checkSurfaceNormals:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
nGeo: Final[int] = mesh_vars.nGeo
|
|
148
|
+
elems: Final[list] = mesh_vars.elems
|
|
149
|
+
|
|
150
|
+
# Setup mathematical structures identical to CheckWatertight
|
|
151
|
+
xEq: Final[npt.NDArray[np.float64]] = np.linspace(-1., 1., nGeo+1)
|
|
152
|
+
wBaryEq: Final[npt.NDArray[np.float64]] = barycentric_weights(nGeo+1, xEq)
|
|
153
|
+
|
|
154
|
+
xGP, wGP = legendre_gauss_nodes(nGeo+1)
|
|
155
|
+
DGP: Final[npt.NDArray[np.float64]] = polynomial_derivative_matrix(nGeo+1, xGP)
|
|
156
|
+
VdmEqToGP: Final[npt.NDArray[np.float64]] = calc_vandermonde(nGeo+1, nGeo+1, wBaryEq, xEq, xGP)
|
|
157
|
+
weights: Final[npt.NDArray[np.float64]] = np.outer(wGP, wGP)
|
|
158
|
+
|
|
159
|
+
# Prepare elements for parallel processing
|
|
160
|
+
if np_mtp > 0:
|
|
161
|
+
# Run in parallel with a chunk size
|
|
162
|
+
# > Dispatch the tasks to the workers, minimum 10 tasks per worker, maximum 1000 tasks per worker
|
|
163
|
+
res = run_in_parallel(process_chunk,
|
|
164
|
+
elems,
|
|
165
|
+
chunk_size = max(1, min(1000, max(10, int(len(elems)/(40.*np_mtp))))), # noqa: E251
|
|
166
|
+
initializer = init_worker, # noqa: E251
|
|
167
|
+
init_args = (process_chunk, VdmEqToGP, DGP, weights), # noqa: E251
|
|
168
|
+
ordering = False, # noqa: E251
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
res = [check_orientation(elem, VdmEqToGP, DGP, weights, failed_only=True) for elem in elems]
|
|
172
|
+
|
|
173
|
+
# Flatten results while filtering empty items
|
|
174
|
+
results = tuple(result for elem_results in res if isinstance(elem_results, Iterable) and elem_results is not None
|
|
175
|
+
for result in elem_results) # noqa: E272
|
|
176
|
+
|
|
177
|
+
if len(results):
|
|
178
|
+
for result in cast(tuple[tuple], results):
|
|
179
|
+
_, elemID, face, sideID = result
|
|
180
|
+
hopout.info('')
|
|
181
|
+
print(hopout.warn(f'Side is oriented inwards! Element {elemID + 1}, Face {face}, Side {sideID + 1}'))
|
|
182
|
+
|
|
183
|
+
if io_vars.debugmesh:
|
|
184
|
+
hopout.info('')
|
|
185
|
+
DebugIO(errSides=list(map(itemgetter(3), results)))
|
|
186
|
+
|
|
187
|
+
hopout.error(f'Surface normals check failed for {len(results)} / {len(elems)} elements!')
|
|
188
|
+
|
|
189
|
+
# Warn if we passed any element types
|
|
190
|
+
if any(e.type % 10 != 8 for e in elems):
|
|
191
|
+
elemTypes = list({e.type for e in elems if e.type % 10 != 8})
|
|
192
|
+
print(hopout.warn('Ignored element type: {}'.format([re.sub(r"\d+$", "", mesh_vars.ELEMTYPE.inam[e][0]) for e in elemTypes]))) # ruff: ignore[line-too-long]
|
|
193
|
+
|
|
194
|
+
# Run garbage collector to release memory
|
|
195
|
+
gc.collect()
|
|
@@ -30,6 +30,7 @@ import gc
|
|
|
30
30
|
import re
|
|
31
31
|
from typing import Final, Optional, cast
|
|
32
32
|
from collections.abc import Callable, Iterable
|
|
33
|
+
from operator import itemgetter
|
|
33
34
|
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
34
35
|
# Third-party libraries
|
|
35
36
|
# ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -96,7 +97,7 @@ def eval_nsurf(XGeo: npt.NDArray[np.float64],
|
|
|
96
97
|
# # dXdetaGP = np.moveaxis(dXdetaGP, 0, 1).reshape(3, -1) # Flatten for cross computation (slower)
|
|
97
98
|
# dXdetaGP = dXdetaGP.reshape(3, -1) # Flatten for cross computation
|
|
98
99
|
|
|
99
|
-
# Compute derivatives at all Gauss points
|
|
100
|
+
# Compute derivatives at all Gauss points
|
|
100
101
|
dXdxiGP = np.empty_like(xGP)
|
|
101
102
|
dXdetaGP = np.empty_like(xGP)
|
|
102
103
|
DT = DGP.T
|
|
@@ -120,7 +121,7 @@ def eval_nsurf(XGeo: npt.NDArray[np.float64],
|
|
|
120
121
|
# Integrate over the Gauss points
|
|
121
122
|
# return -np.sum(nVecW, axis=(1, 2)) # Sum over the last two axes
|
|
122
123
|
|
|
123
|
-
# Compute the weighted cross product integral
|
|
124
|
+
# Compute the weighted cross product integral
|
|
124
125
|
NSurf0, NSurf1, NSurf2 = eval_dotprod(dXdetaGP, dXdxiGP, weights)
|
|
125
126
|
|
|
126
127
|
return np.array((NSurf0, NSurf1, NSurf2), dtype=xGP.dtype)
|
|
@@ -243,9 +244,9 @@ def process_chunk(chunk: tuple) -> list:
|
|
|
243
244
|
chunk_results = []
|
|
244
245
|
for elem in chunk:
|
|
245
246
|
elem_result = check_sides(elem,
|
|
246
|
-
process_chunk.VdmEqToGP, #
|
|
247
|
-
process_chunk.DGP, #
|
|
248
|
-
process_chunk.weights, #
|
|
247
|
+
process_chunk.VdmEqToGP, # ty: ignore[unresolved-attribute]
|
|
248
|
+
process_chunk.DGP, # ty: ignore[unresolved-attribute]
|
|
249
|
+
process_chunk.weights, # ty: ignore[unresolved-attribute]
|
|
249
250
|
failed_only=True)
|
|
250
251
|
# Append a lightweight sentinel (None) for successes, actual failure list otherwise
|
|
251
252
|
chunk_results.append(elem_result)
|
|
@@ -256,12 +257,14 @@ def CheckWatertight() -> None:
|
|
|
256
257
|
""" Check if the mesh is watertight
|
|
257
258
|
"""
|
|
258
259
|
# Local imports ----------------------------------------
|
|
260
|
+
import pyhope.io.io_vars as io_vars
|
|
259
261
|
import pyhope.output.output as hopout
|
|
260
262
|
import pyhope.mesh.mesh_vars as mesh_vars
|
|
261
263
|
from pyhope.basis.basis_basis import barycentric_weights, legendre_gauss_nodes
|
|
262
264
|
from pyhope.basis.basis_basis import calc_vandermonde, polynomial_derivative_matrix
|
|
263
265
|
from pyhope.common.common_parallel import run_in_parallel
|
|
264
266
|
from pyhope.common.common_vars import np_mtp
|
|
267
|
+
from pyhope.io.io_debug import DebugIO
|
|
265
268
|
from pyhope.readintools.readintools import GetLogical
|
|
266
269
|
# ------------------------------------------------------
|
|
267
270
|
|
|
@@ -314,15 +317,13 @@ def CheckWatertight() -> None:
|
|
|
314
317
|
ordering = False, # noqa: E251
|
|
315
318
|
)
|
|
316
319
|
else:
|
|
317
|
-
res = [elem for elem in elems
|
|
318
|
-
VdmEqToGP, DGP, weights,
|
|
319
|
-
failed_only=True)]
|
|
320
|
+
res = [check_sides(elem, VdmEqToGP, DGP, weights, failed_only=True) for elem in elems]
|
|
320
321
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
for result in elem_results) # noqa: E272
|
|
322
|
+
# Flatten per-element results (skip None placeholders)
|
|
323
|
+
results = tuple(result for elem_results in res if isinstance(elem_results, Iterable) and elem_results is not None
|
|
324
|
+
for result in elem_results) # noqa: E272
|
|
325
325
|
|
|
326
|
+
if len(results): # pragma: no cover
|
|
326
327
|
# Compute total number of checked connections without materializing all results
|
|
327
328
|
nconn = 0
|
|
328
329
|
for SideID, side in enumerate(sides):
|
|
@@ -352,7 +353,7 @@ def CheckWatertight() -> None:
|
|
|
352
353
|
nodes = elem.nodes[face_to_nodes( side.face, elem.type, nGeo)]
|
|
353
354
|
nbnodes = nbelem.nodes[face_to_nodes(nbside.face, nbelem.type, nGeo)]
|
|
354
355
|
|
|
355
|
-
|
|
356
|
+
hopout.info('')
|
|
356
357
|
# Check if side is oriented inwards
|
|
357
358
|
errStr = 'Side is oriented inwards!' if nSurfErr < 0 \
|
|
358
359
|
else f'Surface normals are not within tolerance {nSurfErr:9.6e} > {tol:9.6e}'
|
|
@@ -374,6 +375,11 @@ def CheckWatertight() -> None:
|
|
|
374
375
|
print(hopout.warn('- Coordinates : [' + ' '.join(f'{s:12.3f}' for s in points[nbnodes[-1, 0]]) + ']')) # noqa: E271
|
|
375
376
|
print(hopout.warn('- Coordinates : [' + ' '.join(f'{s:12.3f}' for s in points[nbnodes[-1, -1]]) + ']')) # noqa: E271
|
|
376
377
|
|
|
378
|
+
# Write a low-order debug mesh if requested
|
|
379
|
+
if io_vars.debugmesh:
|
|
380
|
+
hopout.info('')
|
|
381
|
+
DebugIO(errSides=list(map(itemgetter(1), results)))
|
|
382
|
+
|
|
377
383
|
hopout.error(f'Watertightness check failed for {len(results)} / {nconn} connections!')
|
|
378
384
|
|
|
379
385
|
# Run garbage collector to release memory
|