plot3d 1.7.14__tar.gz → 1.8.1__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.
- {plot3d-1.7.14 → plot3d-1.8.1}/PKG-INFO +4 -2
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/__init__.py +2 -2
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/blockfunctions.py +2 -2
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/connectivity.py +156 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/gridpro/import_functions.py +12 -2
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/periodicity.py +195 -18
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/split_block.py +14 -14
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/write.py +1 -1
- {plot3d-1.7.14 → plot3d-1.8.1}/pyproject.toml +1 -1
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/block.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/block_merging_mixed_facepairs.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/differencing.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/face.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/facefunctions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/glennht/__init__.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/glennht/class_definitions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/glennht/export_functions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/glennht/import_functions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/graph.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/gridpro/__init__.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/listfunctions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/point_match.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/pointwise/__init__.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/pointwise/import_functions.py +0 -0
- {plot3d-1.7.14 → plot3d-1.8.1}/plot3d/read.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3d
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
4
4
|
Summary: Plot3D python utilities for reading and writing and also finding connectivity between blocks
|
|
5
5
|
Author: Paht Juangphanich
|
|
6
6
|
Author-email: paht.juangphanich@nasa.gov
|
|
@@ -8,6 +8,8 @@ Requires-Python: >=3.10.12,<4.0.0
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
13
|
Requires-Dist: matplotlib
|
|
12
14
|
Requires-Dist: networkx
|
|
13
15
|
Requires-Dist: numpy
|
|
@@ -5,13 +5,13 @@ import os, warnings
|
|
|
5
5
|
from .block import Block
|
|
6
6
|
from .blockfunctions import rotate_block, get_outer_bounds, block_connection_matrix,split_blocks, plot_blocks, reduce_blocks, find_matching_faces
|
|
7
7
|
from .block_merging_mixed_facepairs import combine_nxnxn_cubes_mixed_pairs
|
|
8
|
-
from .connectivity import find_matching_blocks, get_face_intersection, connectivity_fast, face_matches_to_dict
|
|
8
|
+
from .connectivity import find_matching_blocks, get_face_intersection, connectivity_fast, face_matches_to_dict, verify_connectivity
|
|
9
9
|
from .face import Face
|
|
10
10
|
from .facefunctions import create_face_from_diagonals, get_outer_faces, find_bounding_faces,split_face,find_face_nearest_point,match_faces_dict_to_list,outer_face_dict_to_list,find_closest_block
|
|
11
11
|
from .read import read_plot3D, read_ap_nasa
|
|
12
12
|
from .write import write_plot3D
|
|
13
13
|
from .differencing import find_edges, find_face_edges
|
|
14
|
-
from .periodicity import periodicity, periodicity_fast, create_rotation_matrix, rotated_periodicity, translational_periodicity
|
|
14
|
+
from .periodicity import periodicity, periodicity_fast, create_rotation_matrix, rotated_periodicity, translational_periodicity, verify_periodicity
|
|
15
15
|
from .point_match import point_match
|
|
16
16
|
from .split_block import split_blocks, Direction
|
|
17
17
|
from .listfunctions import unique_pairs
|
|
@@ -216,7 +216,7 @@ def plot_blocks(blocks):
|
|
|
216
216
|
block = blocks[block_indx]
|
|
217
217
|
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
218
218
|
gcd_to_use = min(gcd_array) # You need to use the minimum gcd otherwise 1 block may not exactly match the next block. They all have to be scaled the same way.
|
|
219
|
-
blocks = reduce_blocks(deepcopy(blocks),
|
|
219
|
+
blocks = reduce_blocks(deepcopy(blocks),gcd_to_use)
|
|
220
220
|
|
|
221
221
|
fig = plt.figure(figsize=(10, 8))
|
|
222
222
|
ax = fig.add_subplot(111, projection='3d')
|
|
@@ -327,7 +327,7 @@ def calculate_outward_normals(block:Block):
|
|
|
327
327
|
z = [Z[0,0,0],Z[0,jmax,0],Z[0,0,kmax]]
|
|
328
328
|
u = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
329
329
|
v = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
330
|
-
n_imin = np.cross(
|
|
330
|
+
n_imin = np.cross(u,v)
|
|
331
331
|
|
|
332
332
|
# Normals I direction: IMAX
|
|
333
333
|
x = [X[imax,0,0],X[imax,jmax,0],X[imax,0,kmax]]
|
|
@@ -565,3 +565,159 @@ def face_matches_to_dict(face1:Face, face2:Face,block1:Block,block2:Block):
|
|
|
565
565
|
match['block2']['JMAX'] = int(df.iloc[0]['J'])
|
|
566
566
|
match['block2']['KMAX'] = int(df.iloc[0]['K'])
|
|
567
567
|
return match
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def verify_connectivity(blocks: List[Block], face_matches: list, tol: float = 1E-6):
|
|
571
|
+
"""Verifies that the diagonal corners of face_matches are spatially consistent.
|
|
572
|
+
|
|
573
|
+
For each face_match, checks that block1's lower corner coordinates match
|
|
574
|
+
block2's lower corner coordinates (and similarly for upper corners) within
|
|
575
|
+
the specified tolerance. If the stored diagonal doesn't match, tries all
|
|
576
|
+
permutations of block2's face corners. If a valid permutation is found,
|
|
577
|
+
the face_match is corrected and added to the verified list.
|
|
578
|
+
|
|
579
|
+
Uses GCD reduction (same as connectivity_fast) for efficient coordinate lookups.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
blocks (List[Block]): List of all blocks (original full-resolution)
|
|
583
|
+
face_matches (list): List of face_match dicts from connectivity or periodicity
|
|
584
|
+
tol (float, optional): Euclidean distance tolerance. Defaults to 1E-6.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
(list): verified face_matches whose diagonals are confirmed or corrected
|
|
588
|
+
(list): mismatched face_matches where no corner permutation matched
|
|
589
|
+
"""
|
|
590
|
+
# Compute GCD and reduce blocks (same pattern as connectivity_fast)
|
|
591
|
+
gcd_array = list()
|
|
592
|
+
for block_indx in range(len(blocks)):
|
|
593
|
+
block = blocks[block_indx]
|
|
594
|
+
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
595
|
+
gcd_to_use = min(gcd_array)
|
|
596
|
+
reduced_blocks = reduce_blocks(deepcopy(blocks), gcd_to_use)
|
|
597
|
+
|
|
598
|
+
# Scale down face_matches indices by GCD
|
|
599
|
+
scaled_matches = deepcopy(face_matches)
|
|
600
|
+
for fm in scaled_matches:
|
|
601
|
+
for side in ['block1', 'block2']:
|
|
602
|
+
for key in ['IMIN', 'JMIN', 'KMIN', 'IMAX', 'JMAX', 'KMAX']:
|
|
603
|
+
fm[side][key] = fm[side][key] // gcd_to_use
|
|
604
|
+
|
|
605
|
+
verified = list()
|
|
606
|
+
mismatched = list()
|
|
607
|
+
|
|
608
|
+
for idx in range(len(scaled_matches)):
|
|
609
|
+
fm = scaled_matches[idx]
|
|
610
|
+
b1 = fm['block1']
|
|
611
|
+
b2 = fm['block2']
|
|
612
|
+
b1_idx = b1['block_index']
|
|
613
|
+
b2_idx = b2['block_index']
|
|
614
|
+
block1 = reduced_blocks[b1_idx]
|
|
615
|
+
block2 = reduced_blocks[b2_idx]
|
|
616
|
+
|
|
617
|
+
# Block1 diagonal coordinates
|
|
618
|
+
x1_l = block1.X[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
619
|
+
y1_l = block1.Y[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
620
|
+
z1_l = block1.Z[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
621
|
+
|
|
622
|
+
x1_u = block1.X[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
623
|
+
y1_u = block1.Y[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
624
|
+
z1_u = block1.Z[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
625
|
+
|
|
626
|
+
# Enumerate unique corners of block2's face
|
|
627
|
+
I2 = [b2['IMIN'], b2['IMAX']]
|
|
628
|
+
J2 = [b2['JMIN'], b2['JMAX']]
|
|
629
|
+
K2 = [b2['KMIN'], b2['KMAX']]
|
|
630
|
+
|
|
631
|
+
unique_corners = list()
|
|
632
|
+
seen = set()
|
|
633
|
+
for i in I2:
|
|
634
|
+
for j in J2:
|
|
635
|
+
for k in K2:
|
|
636
|
+
key = (i, j, k)
|
|
637
|
+
if key not in seen:
|
|
638
|
+
seen.add(key)
|
|
639
|
+
unique_corners.append(key)
|
|
640
|
+
|
|
641
|
+
# Check stored diagonal first
|
|
642
|
+
x2_l = block2.X[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
643
|
+
y2_l = block2.Y[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
644
|
+
z2_l = block2.Z[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
645
|
+
|
|
646
|
+
x2_u = block2.X[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
647
|
+
y2_u = block2.Y[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
648
|
+
z2_u = block2.Z[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
649
|
+
|
|
650
|
+
dx = x2_l - x1_l; dy = y2_l - y1_l; dz = z2_l - z1_l
|
|
651
|
+
d_lower = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
652
|
+
dx = x2_u - x1_u; dy = y2_u - y1_u; dz = z2_u - z1_u
|
|
653
|
+
d_upper = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
654
|
+
|
|
655
|
+
if d_lower < tol and d_upper < tol:
|
|
656
|
+
verified.append(face_matches[idx])
|
|
657
|
+
continue
|
|
658
|
+
|
|
659
|
+
# Try all permutations of block2's corners
|
|
660
|
+
found = False
|
|
661
|
+
best_d_lower = d_lower
|
|
662
|
+
best_d_upper = d_upper
|
|
663
|
+
|
|
664
|
+
for corner_lower in unique_corners:
|
|
665
|
+
for corner_upper in unique_corners:
|
|
666
|
+
if corner_lower == corner_upper:
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
il, jl, kl = corner_lower
|
|
670
|
+
iu, ju, ku = corner_upper
|
|
671
|
+
|
|
672
|
+
x2_l = block2.X[il, jl, kl]
|
|
673
|
+
y2_l = block2.Y[il, jl, kl]
|
|
674
|
+
z2_l = block2.Z[il, jl, kl]
|
|
675
|
+
|
|
676
|
+
x2_u = block2.X[iu, ju, ku]
|
|
677
|
+
y2_u = block2.Y[iu, ju, ku]
|
|
678
|
+
z2_u = block2.Z[iu, ju, ku]
|
|
679
|
+
|
|
680
|
+
dx = x2_l - x1_l; dy = y2_l - y1_l; dz = z2_l - z1_l
|
|
681
|
+
dl = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
682
|
+
dx = x2_u - x1_u; dy = y2_u - y1_u; dz = z2_u - z1_u
|
|
683
|
+
du = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
684
|
+
|
|
685
|
+
if dl < best_d_lower:
|
|
686
|
+
best_d_lower = dl
|
|
687
|
+
if du < best_d_upper:
|
|
688
|
+
best_d_upper = du
|
|
689
|
+
|
|
690
|
+
if dl < tol and du < tol:
|
|
691
|
+
corrected = deepcopy(face_matches[idx])
|
|
692
|
+
corrected['block2']['IMIN'] = il * gcd_to_use
|
|
693
|
+
corrected['block2']['JMIN'] = jl * gcd_to_use
|
|
694
|
+
corrected['block2']['KMIN'] = kl * gcd_to_use
|
|
695
|
+
corrected['block2']['IMAX'] = iu * gcd_to_use
|
|
696
|
+
corrected['block2']['JMAX'] = ju * gcd_to_use
|
|
697
|
+
corrected['block2']['KMAX'] = ku * gcd_to_use
|
|
698
|
+
verified.append(corrected)
|
|
699
|
+
if b1_idx == b2_idx:
|
|
700
|
+
print("verify_connectivity: Self-match corrected for block index {0}".format(b1_idx))
|
|
701
|
+
found = True
|
|
702
|
+
break
|
|
703
|
+
if found:
|
|
704
|
+
break
|
|
705
|
+
|
|
706
|
+
if not found:
|
|
707
|
+
orig = face_matches[idx]
|
|
708
|
+
b1_orig = orig['block1']
|
|
709
|
+
b2_orig = orig['block2']
|
|
710
|
+
print(f"verify_connectivity: MISMATCH at face_match index {idx}")
|
|
711
|
+
print(f" block1 (block_index={b1_orig['block_index']}): "
|
|
712
|
+
f"lower=({b1_orig['IMIN']},{b1_orig['JMIN']},{b1_orig['KMIN']}) "
|
|
713
|
+
f"upper=({b1_orig['IMAX']},{b1_orig['JMAX']},{b1_orig['KMAX']})")
|
|
714
|
+
print(f" block2 (block_index={b2_orig['block_index']}): "
|
|
715
|
+
f"lower=({b2_orig['IMIN']},{b2_orig['JMIN']},{b2_orig['KMIN']}) "
|
|
716
|
+
f"upper=({b2_orig['IMAX']},{b2_orig['JMAX']},{b2_orig['KMAX']})")
|
|
717
|
+
print(f" block1 lower xyz = ({x1_l:.6e}, {y1_l:.6e}, {z1_l:.6e})")
|
|
718
|
+
print(f" block1 upper xyz = ({x1_u:.6e}, {y1_u:.6e}, {z1_u:.6e})")
|
|
719
|
+
print(f" Closest block2 corner dist to block1 lower: {best_d_lower:.6e}")
|
|
720
|
+
print(f" Closest block2 corner dist to block1 upper: {best_d_upper:.6e}")
|
|
721
|
+
mismatched.append(face_matches[idx])
|
|
722
|
+
|
|
723
|
+
return verified, mismatched
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from typing import Dict, List, Tuple, Optional, Union, Set
|
|
1
|
+
from typing import Dict, List, Tuple, Optional, Union, Set, Any
|
|
2
2
|
import numpy as np
|
|
3
3
|
import pandas as pd
|
|
4
4
|
from tqdm import tqdm
|
|
5
|
-
from typing import List, Tuple, Optional, Any
|
|
6
5
|
from plot3d import Block
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
|
|
@@ -221,6 +220,8 @@ def read_gridpro_connectivity(
|
|
|
221
220
|
parse_patch(toks)
|
|
222
221
|
|
|
223
222
|
patches_df = pd.DataFrame(patch_rows)
|
|
223
|
+
patches_to_connectivity = patches_df[(patches_df['sb1'] > 0) & (patches_df['sb2'] > 0) & (patches_df['pty'] > 1)]
|
|
224
|
+
|
|
224
225
|
|
|
225
226
|
# ---------------------------- helpers ----------------------------
|
|
226
227
|
def face_dict(sb: int, imin: int, jmin: int, kmin: int, imax: int, jmax: int, kmax: int, pty:int=-1) -> Dict[str, int]:
|
|
@@ -253,6 +254,14 @@ def read_gridpro_connectivity(
|
|
|
253
254
|
)
|
|
254
255
|
)
|
|
255
256
|
|
|
257
|
+
# These are patches that should be in connectivity but aren't
|
|
258
|
+
connectivity_to_check: List[Dict[str, Dict[str, int]]] = []
|
|
259
|
+
for p in patches_to_connectivity.itertuples(index=False):
|
|
260
|
+
connectivity_to_check.append(pair_dict(
|
|
261
|
+
p.sb1, (p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k),
|
|
262
|
+
p.sb2, (p.L2i, p.L2j, p.L2k, p.H2i, p.H2j, p.H2k),
|
|
263
|
+
))
|
|
264
|
+
|
|
256
265
|
# Boundary-condition groups (by pty)
|
|
257
266
|
def bc_faces_for(name: str, pty_vals: List[int], bc_type: str) -> List[Dict[str, int]]:
|
|
258
267
|
targets = set(pty_vals)
|
|
@@ -384,6 +393,7 @@ def read_gridpro_connectivity(
|
|
|
384
393
|
"periodic_faces": periodic_faces,
|
|
385
394
|
"volume_zones": volume_zones,
|
|
386
395
|
"blocksizes": superblock_sizes,
|
|
396
|
+
"connectivity_to_check": connectivity_to_check,
|
|
387
397
|
"patches": patches_df,
|
|
388
398
|
}
|
|
389
399
|
|
|
@@ -6,19 +6,19 @@ from .blockfunctions import rotate_block, reduce_blocks
|
|
|
6
6
|
from .face import Face
|
|
7
7
|
from .facefunctions import outer_face_dict_to_list,match_faces_dict_to_list, create_face_from_diagonals, find_bounding_faces,get_outer_faces
|
|
8
8
|
from .connectivity import get_face_intersection, face_matches_to_dict
|
|
9
|
-
from math import cos, radians, sin, sqrt, acos
|
|
9
|
+
from math import cos, radians, sin, sqrt, acos
|
|
10
10
|
from copy import deepcopy
|
|
11
11
|
from tqdm import trange, tqdm
|
|
12
12
|
import math
|
|
13
13
|
|
|
14
|
-
def periodicity_fast(blocks:List[Block],outer_faces:List[
|
|
14
|
+
def periodicity_fast(blocks:List[Block],outer_faces:List[Dict[str,int]], matched_faces:List[Dict[str,int]], periodic_direction:str='k', rotation_axis:str='x',nblades:int=55):
|
|
15
15
|
"""Finds the connectivity of blocks when they are rotated by an angle defined by the number of blades. Only use this if your mesh is of an annulus.
|
|
16
16
|
This function reduces the size of the blocks by a factor of the minimum gcd. This speeds up finding the connectivity
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
blocks (List[Block]): List of blocks that will be scanned for perodicity
|
|
20
20
|
outer_faces (List[Dict[str,int]]): List of outer faces for each block as a dictionary format. You can get this from connectivity
|
|
21
|
-
matched_faces (
|
|
21
|
+
matched_faces (List[Dict[str,int]]): List of matched faces from connectivity. Matched faces was added so that it's always removed from outer faces
|
|
22
22
|
periodic_direction (str): either i,j,k to look for
|
|
23
23
|
rotation_axis (str): either x,y,z
|
|
24
24
|
nblades (int): Number of blades to consider, this affects the rotation angle.
|
|
@@ -132,8 +132,10 @@ def create_rotation_matrix(rotation_angle:float, rotation_axis:str="x"):
|
|
|
132
132
|
rotation_matrix = np.array([[cos(rotation_angle),-sin(rotation_angle), 0],
|
|
133
133
|
[sin(rotation_angle),cos(rotation_angle), 0],
|
|
134
134
|
[0, 0, 1]])
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError(f"rotation_axis must be 'x', 'y', or 'z', got '{rotation_axis}'")
|
|
135
137
|
|
|
136
|
-
return rotation_matrix
|
|
138
|
+
return rotation_matrix
|
|
137
139
|
|
|
138
140
|
def periodicity(blocks:List[Block],outer_faces:List[Dict[str,int]], matched_faces:List[Dict[str,int]], periodic_direction:str='k', rotation_axis:str='x',nblades:int=55):
|
|
139
141
|
"""This function is used to check for periodicity of the other faces rotated about an axis. Use periodicity_fast instead. Periodicity_fast calls this function after reducing the size of the mesh.
|
|
@@ -142,7 +144,7 @@ def periodicity(blocks:List[Block],outer_faces:List[Dict[str,int]], matched_face
|
|
|
142
144
|
Args:
|
|
143
145
|
blocks (List[Block]): List of blocks that will be scanned for perodicity
|
|
144
146
|
outer_faces (List[Dict[str,int]]): List of outer faces for each block as a dictionary format. You can get this from connectivity
|
|
145
|
-
matched_faces (
|
|
147
|
+
matched_faces (List[Dict[str,int]]): List of matched faces from connectivity. Matched faces was added so that it's always removed from outer faces
|
|
146
148
|
periodic_direction (str): either i,j,k to look for
|
|
147
149
|
rotation_axis (str): either x,y,z
|
|
148
150
|
nblades (int): Number of blades to consider, this affects the rotation angle.
|
|
@@ -171,9 +173,10 @@ def periodicity(blocks:List[Block],outer_faces:List[Dict[str,int]], matched_face
|
|
|
171
173
|
matched_faces_all = match_faces_dict_to_list(blocks,matched_faces)
|
|
172
174
|
|
|
173
175
|
split_faces = list() # List of split but free surfaces, this will be appended to outer_faces_to_remove list
|
|
176
|
+
outer_faces_to_remove = list() # Integer list of which outer surfaces to remove
|
|
174
177
|
while periodic_found:
|
|
175
|
-
periodic_found = False
|
|
176
|
-
outer_faces_to_remove = list()
|
|
178
|
+
periodic_found = False
|
|
179
|
+
outer_faces_to_remove = list()
|
|
177
180
|
outer_face_combos = list(combinations_with_replacement(range(len(outer_faces_all)),2))
|
|
178
181
|
t = trange(len(outer_face_combos))
|
|
179
182
|
for i in t:
|
|
@@ -204,7 +207,7 @@ def periodicity(blocks:List[Block],outer_faces:List[Dict[str,int]], matched_face
|
|
|
204
207
|
outer_faces_to_remove.append(periodic_faces_temp[0]) # Make sure periodic faces are also removed from outer faces during the loop
|
|
205
208
|
outer_faces_to_remove.append(periodic_faces_temp[1])
|
|
206
209
|
periodic_faces.append(periodic_faces_temp)
|
|
207
|
-
periodic_faces_export.append((face1,face2,block1_rotated,block2))
|
|
210
|
+
periodic_faces_export.append(face_matches_to_dict(face1,face2,block1_rotated,block2))
|
|
208
211
|
split_faces.extend(split_faces_temp)
|
|
209
212
|
periodic_found = True
|
|
210
213
|
break
|
|
@@ -339,7 +342,8 @@ def rotated_periodicity(blocks:List[Block], matched_faces:List[Dict[str,int]], o
|
|
|
339
342
|
Rotates the set of blocks by an angle and checks the matching surfaces for R and L.
|
|
340
343
|
|
|
341
344
|
Args:
|
|
342
|
-
blocks (List[Block]): List of blocks for a particular geometry. Do not duplicate the geometry and pass it in!
|
|
345
|
+
blocks (List[Block]): List of blocks for a particular geometry. Do not duplicate the geometry and pass it in!
|
|
346
|
+
matched_faces (List[Dict[str,int]]): List of matched faces from connectivity. Matched faces was added so that it's always removed from outer faces
|
|
343
347
|
outer_faces (List[Dict[str,int]]): List of outer faces in dictionary form
|
|
344
348
|
rotation_angle (float): rotation angle in between geometry in degrees.
|
|
345
349
|
rotation_axis (str, Optional): "x", "y", or "z"
|
|
@@ -364,7 +368,8 @@ def rotated_periodicity(blocks:List[Block], matched_faces:List[Dict[str,int]], o
|
|
|
364
368
|
- **outer_faces_all** (List[Face]): This is a list of outer faces save as a list of Faces
|
|
365
369
|
"""
|
|
366
370
|
gcd_array = list()
|
|
367
|
-
|
|
371
|
+
gcd_to_use = 1
|
|
372
|
+
# Find the gcd of all the blocks
|
|
368
373
|
if ReduceMesh:
|
|
369
374
|
for block_indx in range(len(blocks)):
|
|
370
375
|
block = blocks[block_indx]
|
|
@@ -388,29 +393,29 @@ def rotated_periodicity(blocks:List[Block], matched_faces:List[Dict[str,int]], o
|
|
|
388
393
|
|
|
389
394
|
split_faces = list() # List of split but free surfaces, this will be appended to outer_faces_to_remove list
|
|
390
395
|
non_matching = list()
|
|
396
|
+
outer_faces_to_remove = list() # Integer list of which outer surfaces to remove
|
|
391
397
|
while periodic_found:
|
|
392
|
-
periodic_found = False
|
|
393
|
-
outer_faces_to_remove = list()
|
|
398
|
+
periodic_found = False
|
|
399
|
+
outer_faces_to_remove = list()
|
|
394
400
|
outer_face_combos = list(permutations(range(len(outer_faces_all)),2))
|
|
395
401
|
outer_face_combos = list(set(outer_face_combos) - set(non_matching)) # removes face combinations already checked
|
|
396
402
|
t = trange(len(outer_face_combos))
|
|
397
|
-
for i in t:
|
|
403
|
+
for i in t:
|
|
398
404
|
# Check if surfaces are periodic with each other
|
|
399
405
|
face1_indx = outer_face_combos[i][0]
|
|
400
406
|
face2_indx = outer_face_combos[i][1]
|
|
401
407
|
face1 = outer_faces_all[face1_indx]
|
|
402
408
|
face2 = outer_faces_all[face2_indx]
|
|
403
|
-
t.set_description(f"Checking connections block {face1.blockIndex} with {face2.blockIndex}")
|
|
404
409
|
|
|
405
410
|
if (face1.IMIN == face1.IMAX) and (face2.IMIN == face2.IMAX) or \
|
|
406
411
|
(face1.JMIN == face1.JMAX) and (face2.JMIN == face2.JMAX) or \
|
|
407
412
|
(face1.KMIN == face1.KMAX) and (face2.KMIN == face2.KMAX):
|
|
408
|
-
|
|
413
|
+
|
|
409
414
|
# Rotate Block 1 -> Check periodicity -> if not periodic -> Rotate Block 1 opposite direction -> Check periodicity
|
|
410
415
|
# Rotate Block 1
|
|
411
416
|
block1_rotated = blocks_rotated[face1.blockIndex]
|
|
412
417
|
block2 = blocks[face2.blockIndex]
|
|
413
|
-
|
|
418
|
+
t.set_description(f"Blk {face1.blockIndex} <-> {face2.blockIndex} | found {len(periodic_faces)}")
|
|
414
419
|
# Check periodicity
|
|
415
420
|
df, periodic_faces_temp, split_faces_temp = __periodicity_check__(face1,face2,block1_rotated, block2)
|
|
416
421
|
|
|
@@ -473,8 +478,6 @@ def rotated_periodicity(blocks:List[Block], matched_faces:List[Dict[str,int]], o
|
|
|
473
478
|
periodic_faces_export[i]['block1']['JMAX'] *= gcd_to_use
|
|
474
479
|
periodic_faces_export[i]['block1']['KMAX'] *= gcd_to_use
|
|
475
480
|
|
|
476
|
-
if (periodic_faces_export[i]['block2']['IMIN'] == 168):
|
|
477
|
-
print("check")
|
|
478
481
|
periodic_faces_export[i]['block2']['IMIN'] *= gcd_to_use
|
|
479
482
|
periodic_faces_export[i]['block2']['JMIN'] *= gcd_to_use
|
|
480
483
|
periodic_faces_export[i]['block2']['KMIN'] *= gcd_to_use
|
|
@@ -904,3 +907,177 @@ def __periodicity_check__(face1:Face, face2:Face,block1:Block,block2:Block,tol:f
|
|
|
904
907
|
return df,periodic_faces,split_faces
|
|
905
908
|
|
|
906
909
|
|
|
910
|
+
def verify_periodicity(blocks: List[Block], face_matches: list, theta: float, rotation_axis: str = 'x', tol: float = 1E-6):
|
|
911
|
+
"""Verifies that the diagonal corners of periodic face_matches are spatially
|
|
912
|
+
consistent after rotating block1 by ±theta about the given axis.
|
|
913
|
+
|
|
914
|
+
For each face_match, rotates block1 by +theta (and if needed -theta) and checks
|
|
915
|
+
that the rotated lower/upper corner coordinates match block2's lower/upper
|
|
916
|
+
corners within tolerance. If the stored diagonal doesn't match, tries all
|
|
917
|
+
permutations of block2's face corners. If a valid permutation is found,
|
|
918
|
+
the face_match is corrected and added to the verified list.
|
|
919
|
+
|
|
920
|
+
Uses GCD reduction (same as connectivity_fast / periodicity_fast) for
|
|
921
|
+
efficient coordinate lookups.
|
|
922
|
+
|
|
923
|
+
Args:
|
|
924
|
+
blocks (List[Block]): List of all blocks (original full-resolution)
|
|
925
|
+
face_matches (list): List of face_match dicts from periodicity or rotated_periodicity
|
|
926
|
+
theta (float): Rotation angle in degrees
|
|
927
|
+
rotation_axis (str, optional): Axis of rotation 'x', 'y', or 'z'. Defaults to 'x'.
|
|
928
|
+
tol (float, optional): Euclidean distance tolerance. Defaults to 1E-6.
|
|
929
|
+
|
|
930
|
+
Returns:
|
|
931
|
+
(list): verified face_matches whose diagonals are confirmed or corrected
|
|
932
|
+
(list): mismatched face_matches where no corner permutation matched
|
|
933
|
+
"""
|
|
934
|
+
# Compute GCD and reduce blocks (same pattern as connectivity_fast)
|
|
935
|
+
gcd_array = list()
|
|
936
|
+
for block_indx in range(len(blocks)):
|
|
937
|
+
block = blocks[block_indx]
|
|
938
|
+
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
939
|
+
gcd_to_use = min(gcd_array)
|
|
940
|
+
reduced_blocks = reduce_blocks(deepcopy(blocks), gcd_to_use)
|
|
941
|
+
|
|
942
|
+
# Build rotation matrices for +theta and -theta
|
|
943
|
+
rotation_matrix_pos = create_rotation_matrix(radians(theta), rotation_axis)
|
|
944
|
+
rotation_matrix_neg = create_rotation_matrix(radians(-theta), rotation_axis)
|
|
945
|
+
|
|
946
|
+
# Pre-rotate all reduced blocks in both directions
|
|
947
|
+
rotated_blocks_pos = [rotate_block(b, rotation_matrix_pos) for b in reduced_blocks]
|
|
948
|
+
rotated_blocks_neg = [rotate_block(b, rotation_matrix_neg) for b in reduced_blocks]
|
|
949
|
+
|
|
950
|
+
# Scale down face_matches indices by GCD
|
|
951
|
+
scaled_matches = deepcopy(face_matches)
|
|
952
|
+
for fm in scaled_matches:
|
|
953
|
+
for side in ['block1', 'block2']:
|
|
954
|
+
for key in ['IMIN', 'JMIN', 'KMIN', 'IMAX', 'JMAX', 'KMAX']:
|
|
955
|
+
fm[side][key] = fm[side][key] // gcd_to_use
|
|
956
|
+
|
|
957
|
+
verified = list()
|
|
958
|
+
mismatched = list()
|
|
959
|
+
|
|
960
|
+
for idx in range(len(scaled_matches)):
|
|
961
|
+
fm = scaled_matches[idx]
|
|
962
|
+
b1 = fm['block1']
|
|
963
|
+
b2 = fm['block2']
|
|
964
|
+
b1_idx = b1['block_index']
|
|
965
|
+
b2_idx = b2['block_index']
|
|
966
|
+
block2 = reduced_blocks[b2_idx]
|
|
967
|
+
|
|
968
|
+
# Enumerate unique corners of block2's face
|
|
969
|
+
I2 = [b2['IMIN'], b2['IMAX']]
|
|
970
|
+
J2 = [b2['JMIN'], b2['JMAX']]
|
|
971
|
+
K2 = [b2['KMIN'], b2['KMAX']]
|
|
972
|
+
|
|
973
|
+
unique_corners = list()
|
|
974
|
+
seen = set()
|
|
975
|
+
for i in I2:
|
|
976
|
+
for j in J2:
|
|
977
|
+
for k in K2:
|
|
978
|
+
key = (i, j, k)
|
|
979
|
+
if key not in seen:
|
|
980
|
+
seen.add(key)
|
|
981
|
+
unique_corners.append(key)
|
|
982
|
+
|
|
983
|
+
found = False
|
|
984
|
+
best_d_lower = float('inf')
|
|
985
|
+
best_d_upper = float('inf')
|
|
986
|
+
|
|
987
|
+
# Try +theta rotation first, then -theta
|
|
988
|
+
for rotated_blocks in [rotated_blocks_pos, rotated_blocks_neg]:
|
|
989
|
+
if found:
|
|
990
|
+
break
|
|
991
|
+
|
|
992
|
+
block1_rotated = rotated_blocks[b1_idx]
|
|
993
|
+
|
|
994
|
+
# Block1 rotated diagonal coordinates
|
|
995
|
+
x1_l = block1_rotated.X[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
996
|
+
y1_l = block1_rotated.Y[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
997
|
+
z1_l = block1_rotated.Z[b1['IMIN'], b1['JMIN'], b1['KMIN']]
|
|
998
|
+
|
|
999
|
+
x1_u = block1_rotated.X[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
1000
|
+
y1_u = block1_rotated.Y[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
1001
|
+
z1_u = block1_rotated.Z[b1['IMAX'], b1['JMAX'], b1['KMAX']]
|
|
1002
|
+
|
|
1003
|
+
# Check stored diagonal first
|
|
1004
|
+
x2_l = block2.X[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
1005
|
+
y2_l = block2.Y[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
1006
|
+
z2_l = block2.Z[b2['IMIN'], b2['JMIN'], b2['KMIN']]
|
|
1007
|
+
|
|
1008
|
+
x2_u = block2.X[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
1009
|
+
y2_u = block2.Y[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
1010
|
+
z2_u = block2.Z[b2['IMAX'], b2['JMAX'], b2['KMAX']]
|
|
1011
|
+
|
|
1012
|
+
dx = x2_l - x1_l; dy = y2_l - y1_l; dz = z2_l - z1_l
|
|
1013
|
+
d_lower = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
1014
|
+
dx = x2_u - x1_u; dy = y2_u - y1_u; dz = z2_u - z1_u
|
|
1015
|
+
d_upper = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
1016
|
+
|
|
1017
|
+
if d_lower < best_d_lower:
|
|
1018
|
+
best_d_lower = d_lower
|
|
1019
|
+
if d_upper < best_d_upper:
|
|
1020
|
+
best_d_upper = d_upper
|
|
1021
|
+
|
|
1022
|
+
if d_lower < tol and d_upper < tol:
|
|
1023
|
+
verified.append(face_matches[idx])
|
|
1024
|
+
found = True
|
|
1025
|
+
break
|
|
1026
|
+
|
|
1027
|
+
# Try all permutations of block2's corners
|
|
1028
|
+
for corner_lower in unique_corners:
|
|
1029
|
+
for corner_upper in unique_corners:
|
|
1030
|
+
if corner_lower == corner_upper:
|
|
1031
|
+
continue
|
|
1032
|
+
|
|
1033
|
+
il, jl, kl = corner_lower
|
|
1034
|
+
iu, ju, ku = corner_upper
|
|
1035
|
+
|
|
1036
|
+
x2_l = block2.X[il, jl, kl]
|
|
1037
|
+
y2_l = block2.Y[il, jl, kl]
|
|
1038
|
+
z2_l = block2.Z[il, jl, kl]
|
|
1039
|
+
|
|
1040
|
+
x2_u = block2.X[iu, ju, ku]
|
|
1041
|
+
y2_u = block2.Y[iu, ju, ku]
|
|
1042
|
+
z2_u = block2.Z[iu, ju, ku]
|
|
1043
|
+
|
|
1044
|
+
dx = x2_l - x1_l; dy = y2_l - y1_l; dz = z2_l - z1_l
|
|
1045
|
+
dl = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
1046
|
+
dx = x2_u - x1_u; dy = y2_u - y1_u; dz = z2_u - z1_u
|
|
1047
|
+
du = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
1048
|
+
|
|
1049
|
+
if dl < best_d_lower:
|
|
1050
|
+
best_d_lower = dl
|
|
1051
|
+
if du < best_d_upper:
|
|
1052
|
+
best_d_upper = du
|
|
1053
|
+
|
|
1054
|
+
if dl < tol and du < tol:
|
|
1055
|
+
corrected = deepcopy(face_matches[idx])
|
|
1056
|
+
corrected['block2']['IMIN'] = il * gcd_to_use
|
|
1057
|
+
corrected['block2']['JMIN'] = jl * gcd_to_use
|
|
1058
|
+
corrected['block2']['KMIN'] = kl * gcd_to_use
|
|
1059
|
+
corrected['block2']['IMAX'] = iu * gcd_to_use
|
|
1060
|
+
corrected['block2']['JMAX'] = ju * gcd_to_use
|
|
1061
|
+
corrected['block2']['KMAX'] = ku * gcd_to_use
|
|
1062
|
+
verified.append(corrected)
|
|
1063
|
+
found = True
|
|
1064
|
+
break
|
|
1065
|
+
if found:
|
|
1066
|
+
break
|
|
1067
|
+
|
|
1068
|
+
if not found:
|
|
1069
|
+
orig = face_matches[idx]
|
|
1070
|
+
b1_orig = orig['block1']
|
|
1071
|
+
b2_orig = orig['block2']
|
|
1072
|
+
print(f"verify_periodicity: MISMATCH at face_match index {idx}")
|
|
1073
|
+
print(f" block1 (block_index={b1_orig['block_index']}): "
|
|
1074
|
+
f"lower=({b1_orig['IMIN']},{b1_orig['JMIN']},{b1_orig['KMIN']}) "
|
|
1075
|
+
f"upper=({b1_orig['IMAX']},{b1_orig['JMAX']},{b1_orig['KMAX']})")
|
|
1076
|
+
print(f" block2 (block_index={b2_orig['block_index']}): "
|
|
1077
|
+
f"lower=({b2_orig['IMIN']},{b2_orig['JMIN']},{b2_orig['KMIN']}) "
|
|
1078
|
+
f"upper=({b2_orig['IMAX']},{b2_orig['JMAX']},{b2_orig['KMAX']})")
|
|
1079
|
+
print(f" Closest rotated block1 corner dist to block2 lower: {best_d_lower:.6e}")
|
|
1080
|
+
print(f" Closest rotated block1 corner dist to block2 upper: {best_d_upper:.6e}")
|
|
1081
|
+
mismatched.append(face_matches[idx])
|
|
1082
|
+
|
|
1083
|
+
return verified, mismatched
|
|
@@ -50,17 +50,17 @@ def max_aspect_ratio(X:np.ndarray,Y:np.ndarray,Z:np.ndarray,ix:int,jx:int,kx:int
|
|
|
50
50
|
|
|
51
51
|
ds = list()
|
|
52
52
|
for n in range(len(i2)):
|
|
53
|
-
ds.append(sqrt((X
|
|
54
|
-
(Y
|
|
55
|
-
(Z
|
|
53
|
+
ds.append(sqrt((X[i2[n],j1[n],k1[n]]-X[i1[n],j1[n],k1[n]])**2 +
|
|
54
|
+
(Y[i2[n],j1[n],k1[n]]-Y[i1[n],j1[n],k1[n]])**2 +
|
|
55
|
+
(Z[i2[n],j1[n],k1[n]]-Z[i1[n],j1[n],k1[n]])**2)
|
|
56
56
|
)
|
|
57
|
-
ds.append(sqrt((X
|
|
58
|
-
(Y
|
|
59
|
-
(Z
|
|
57
|
+
ds.append(sqrt((X[i1[n],j2[n],k1[n]]-X[i1[n],j1[n],k1[n]])**2 +
|
|
58
|
+
(Y[i1[n],j2[n],k1[n]]-Y[i1[n],j1[n],k1[n]])**2 +
|
|
59
|
+
(Z[i1[n],j2[n],k1[n]]-Z[i1[n],j1[n],k1[n]])**2)
|
|
60
60
|
)
|
|
61
|
-
ds.append(sqrt((X
|
|
62
|
-
(Y
|
|
63
|
-
(Z
|
|
61
|
+
ds.append(sqrt((X[i1[n],j1[n],k2[n]]-X[i1[n],j1[n],k1[n]])**2 +
|
|
62
|
+
(Y[i1[n],j1[n],k2[n]]-Y[i1[n],j1[n],k1[n]])**2 +
|
|
63
|
+
(Z[i1[n],j1[n],k2[n]]-Z[i1[n],j1[n],k1[n]])**2)
|
|
64
64
|
)
|
|
65
65
|
aspect = [0,0,0]
|
|
66
66
|
if ds[0]>0:
|
|
@@ -134,7 +134,7 @@ def split_blocks(blocks:List[Block], ncells_per_block:int,direction:Direction=No
|
|
|
134
134
|
total_cells = block.IMAX*block.JMAX*block.KMAX
|
|
135
135
|
|
|
136
136
|
if direction==None:
|
|
137
|
-
indx = np.
|
|
137
|
+
indx = np.argmax(np.array([block.IMAX,block.JMAX,block.KMAX]))
|
|
138
138
|
if indx == 0:
|
|
139
139
|
direction_to_use=Direction.i
|
|
140
140
|
elif indx == 1:
|
|
@@ -153,7 +153,7 @@ def split_blocks(blocks:List[Block], ncells_per_block:int,direction:Direction=No
|
|
|
153
153
|
if step_size==-1:
|
|
154
154
|
step_size = __step_search(total_cells,greatest_common_divisor,ncells_per_block,denominator,direction='forward')
|
|
155
155
|
if step_size==-1:
|
|
156
|
-
|
|
156
|
+
raise ValueError('no valid step size found, do you have multi-block? gcd > 1')
|
|
157
157
|
# step_size-1 is the IMAX of the sub_blocks e.g. 0 to 92 this shows IMAX=93, (93-1) % 4 = 0 (good)
|
|
158
158
|
|
|
159
159
|
iprev = 0
|
|
@@ -181,10 +181,10 @@ def split_blocks(blocks:List[Block], ncells_per_block:int,direction:Direction=No
|
|
|
181
181
|
if step_size==-1:
|
|
182
182
|
step_size = __step_search(total_cells,greatest_common_divisor,ncells_per_block,denominator,direction='forward')
|
|
183
183
|
if step_size==-1:
|
|
184
|
-
|
|
184
|
+
raise ValueError('no valid step size found, do you have multi-block? gcd > 1')
|
|
185
185
|
jprev = 0
|
|
186
186
|
for j in range(step_size,block.JMAX,step_size):
|
|
187
|
-
if (j+1) > block.
|
|
187
|
+
if (j+1) > block.JMAX:
|
|
188
188
|
break
|
|
189
189
|
X = block.X[:,jprev:j,:] # New X, Y, Z splits
|
|
190
190
|
Y = block.Y[:,jprev:j,:]
|
|
@@ -205,7 +205,7 @@ def split_blocks(blocks:List[Block], ncells_per_block:int,direction:Direction=No
|
|
|
205
205
|
if step_size==-1:
|
|
206
206
|
step_size = __step_search(total_cells,greatest_common_divisor,ncells_per_block,denominator,direction='forward')
|
|
207
207
|
if step_size==-1:
|
|
208
|
-
|
|
208
|
+
raise ValueError('no valid step size found, do you have multi-block? gcd > 1')
|
|
209
209
|
kprev = 0
|
|
210
210
|
for k in range(step_size,block.KMAX,step_size):
|
|
211
211
|
if (k+1) > block.KMAX:
|
|
@@ -51,7 +51,7 @@ def __write_plot3D_block_ASCII(f, B:Block, double_precision:bool=True, columns:i
|
|
|
51
51
|
batch_size (int, optional): number of lines to buffer before writing. Defaults to 100.
|
|
52
52
|
"""
|
|
53
53
|
# Scientific notation format: width.precision E
|
|
54
|
-
fmt = '{0:23.
|
|
54
|
+
fmt = '{0:23.15f}' if double_precision else '{0:15.8f}'
|
|
55
55
|
|
|
56
56
|
def write_var(V:np.ndarray):
|
|
57
57
|
line_entries = []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|