from .block import Block
from .blockfunctions import reduce_blocks
from .face import Face
from .facefunctions import create_face_from_diagonals, split_face, get_outer_faces
import math
from itertools import product, combinations
from tqdm import trange
import numpy as np
import pandas as pd
from typing import List
import math
from .point_match import point_match
from copy import deepcopy
[docs]
def find_matching_blocks(block1:Block,block2:Block,block1_outer:List[Face], block2_outer:List[Face],tol:float=1E-6):
"""Takes two blocks and finds all matching pairs
Args:
block1 (Block): Any plot3d Block that is not the same as block2
block2 (Block): Any plot3d Block that is not the same as block1
block1_outer (List[Face]): outer faces for block 1.
block2_outer (List[Face]): Outer faces for block 2
tol (float, Optional): tolerance to use. Defaults to 1E-6
Note:
This function was changed to be given an input of outer faces for block 1 and block 2. Outer faces can change and we should use the updated value
Returns:
(tuple): containing
- **df** (pandas.DataFrame): corners of matching pair as block1_corners,block2_corners ([imin,jmin,kmin],[imax,jmax,kmax]), ([imin,jmin,kmin],[imax,jmax,kmax])
- **block1_outer** (List[Face]):
- **block2_outer** (List[Face]):
"""
# Check to see if outer face of block 1 matches any of the outer faces of block 2
block_match_indices = list()
block1_split_faces = list()
block2_split_faces = list()
# Create a dataframe for block1 and block 2 inner matches, add to df later
# df,split_faces1,split_faces2 = get_face_intersection(block1_outer[3],block2_outer[4],block1,block2,tol=1E-6)
# Checks the nodes of the outer faces to see if any of them match
match = True
while match:
match = False
for p in range(len(block1_outer)):
block1_face = block1_outer[p]
for q in range(len(block2_outer)):
block2_face = block2_outer[q]
df, split_faces1, split_faces2 = get_face_intersection(block1_face,block2_face,block1,block2,tol)
if len(df)>0: # the number of intersection points has to be more than 4
# if not block1_face in block1MatchingFace and not block2_face in block2MatchingFace:
block_match_indices.append(df)
block1_split_faces.extend(split_faces1)
block2_split_faces.extend(split_faces2)
match = True
break
if match:
break
if match:
block1_outer.pop(p)
block2_outer.pop(q)
block1_outer.extend(block1_split_faces)
block2_outer.extend(block2_split_faces)
block1_split_faces.clear()
block2_split_faces.clear()
return block_match_indices, block1_outer, block2_outer # Remove duplicates using set and list
[docs]
def select_multi_dimensional(T:np.ndarray,dim1:tuple,dim2:tuple, dim3:tuple):
"""Takes a block (T) and selects X,Y,Z from the block given a face's dimensions
theres really no good way to do this in python
Args:
T (np.ndarray): arbitrary array so say a full matrix containing X
dim1 (tuple): 20,50 this selects X in the i direction from i=20 to 50
dim2 (tuple): 40,60 this selects X in the j direction from j=40 to 60
dim3 (tuple): 10,20 this selects X in the k direction from k=10 to 20
Returns:
np.ndarray: returns X or Y or Z given some range of I,J,K
"""
if dim1[0] == dim1[1]:
return T[ dim1[0], dim2[0]:dim2[1]+1, dim3[0]:dim3[1]+1 ]
if dim2[0] == dim2[1]:
return T[ dim1[0]:dim1[1]+1, dim2[0], dim3[0]:dim3[1]+1 ]
if dim3[0] == dim3[1]:
return T[ dim1[0]:dim1[1]+1, dim2[0]:dim2[1]+1, dim3[0] ]
return T[dim1[0]:dim1[1], dim2[0]:dim2[1], dim3[0]:dim3[1]]
[docs]
def get_face_intersection(face1:Face,face2:Face,block1:Block,block2:Block,tol:float=1E-6):
"""Get the index of the intersection between two faces located on two different blocks
Face1 needs to be the smaller face.
Args:
face1 (Face): An exterior face
face2 (Face): An exterior face from a different block
block1 (Block): block containing face1
block2 (Block): block containing face2
tol (float): matching tolerance
Returns:
(Tuple): containing
- (pandas.DataFrame): dataframe with matches. Columns = I1, J1, K1, I2, J2, K2
- (List[Face]): any split faces from block 1
- (List[Face]): any split faces from block 2
"""
match_location = list()
df =pd.DataFrame(columns=['i1','j1','k1','i2','j2','k2'])
split_faces1 = list()
split_faces2 = list()
I1 = [face1.IMIN,face1.IMAX]
J1 = [face1.JMIN,face1.JMAX]
K1 = [face1.KMIN,face1.KMAX]
I2 = [face2.IMIN,face2.IMAX]
J2 = [face2.JMIN,face2.JMAX]
K2 = [face2.KMIN,face2.KMAX]
# Grab the points of Face 1 and Face 2
X1 = select_multi_dimensional(block1.X, (I1[0],I1[1]),(J1[0],J1[1]),(K1[0],K1[1]))
Y1 = select_multi_dimensional(block1.Y, (I1[0],I1[1]),(J1[0],J1[1]),(K1[0],K1[1]))
Z1 = select_multi_dimensional(block1.Z, (I1[0],I1[1]),(J1[0],J1[1]),(K1[0],K1[1]))
X2 = select_multi_dimensional(block2.X, (I2[0],I2[1]),(J2[0],J2[1]),(K2[0],K2[1]))
Y2 = select_multi_dimensional(block2.Y, (I2[0],I2[1]),(J2[0],J2[1]),(K2[0],K2[1]))
Z2 = select_multi_dimensional(block2.Z, (I2[0],I2[1]),(J2[0],J2[1]),(K2[0],K2[1]))
# General Search
if I1[0] == I1[1]: # I is constant in Face 1
combo = product(range(X1.shape[0]), range(X1.shape[1]))
for c in combo:
p, q = c
x = X1[p,q]
y = Y1[p,q]
z = Z1[p,q]
block2_match_location = point_match(x, y, z, X2, Y2, Z2,tol)
if sum(block2_match_location)!=-2:
p2 = int(block2_match_location[0])
q2 = int(block2_match_location[1])
# if __edge_match2(df1_edges,df2_edges, p, q, p2, q2):
if I2[0]==I2[1]:
match_location.append({"i1":I1[0],"j1":p+J1[0],"k1":q+K1[0],'i2':I2[0],'j2':p2+J2[0],'k2':q2+K2[0]})
if J2[0]==J2[1]:
match_location.append({"i1":I1[0],"j1":p+J1[0],"k1":q+K1[0],'i2':p2+I2[0],'j2':J2[0],'k2':q2+K2[0]})
if K2[0]==K2[1]:
match_location.append({"i1":I1[0],"j1":p+J1[0],"k1":q+K1[0],'i2':p2+I2[0],'j2':q2+J2[0],'k2':K2[0]})
df = pd.concat([df, pd.DataFrame(match_location)], ignore_index=True)
elif J1[0] == J1[1]: # J is constant in face 1
combo = product(range(0,X1.shape[0]), range(0,X1.shape[1]))
for c in combo:
p, q = c
x = X1[p,q]
y = Y1[p,q]
z = Z1[p,q]
block2_match_location = point_match(x, y, z, X2, Y2, Z2,tol)
if sum(block2_match_location)!=-2:
p2 = int(block2_match_location[0])
q2 = int(block2_match_location[1])
# if __edge_match2(df1_edges,df2_edges, p, q, p2, q2):
if I2[0]==I2[1]:
match_location.append({"i1":p+I1[0],"j1":J1[0],"k1":q+K1[0],'i2':I2[0],'j2':p2+J2[0],'k2':q2+K2[0]}) # Added an offset because some faces don't start at I=0 or J=0 or K=0
if J2[0]==J2[1]:
match_location.append({"i1":p+I1[0],"j1":J1[0],"k1":q+K1[0],'i2':p2+I2[0],'j2':J2[0],'k2':q2+K2[0]})
if K2[0]==K2[1]:
match_location.append({"i1":p+I1[0],"j1":J1[0],"k1":q+K1[0],'i2':p2+I2[0],'j2':q2+J2[0],'k2':K2[0]})
df = pd.concat([df, pd.DataFrame(match_location)], ignore_index=True)
elif K1[0] == K1[1]: # K is constant in face 1
combo = product(range(X1.shape[0]), range(X1.shape[1]))
for c in combo:
p, q = c
x = X1[p,q]
y = Y1[p,q]
z = Z1[p,q]
block2_match_location = point_match(x, y, z, X2, Y2, Z2,tol) # pm,qm are the p and q indicies where match occurs
if sum(block2_match_location)!=-2:
p2 = int(block2_match_location[0])
q2 = int(block2_match_location[1])
# if __edge_match2(df1_edges,df2_edges, p, q, p2, q2):
if I2[0]==I2[1]:
match_location.append({"i1":p+I1[0],"j1":q+J1[0],"k1":K1[0],'i2':I2[0],'j2':p2+J2[0],'k2':q2+K2[0]})
if J2[0]==J2[1]:
match_location.append({"i1":p+I1[0],"j1":q+J1[0],"k1":K1[0],'i2':p2+I2[0],'j2':J2[0],'k2':q2+K2[0]})
if K2[0]==K2[1]:
match_location.append({"i1":p+I1[0],"j1":q+J1[0],"k1":K1[0],'i2':p2+I2[0],'j2':q2+J2[0],'k2':K2[0]})
df = pd.concat([df, pd.DataFrame(match_location)], ignore_index=True)
# Checking for split faces
if len(df)>=4:
if (__check_edge(df)):
df = pd.DataFrame() # If it's an edge
else: # not edge
# Filter match increasing - This keeps uniqueness
if I1[0]==I1[1]:
df = __filter_block_increasing(df,'j1')
df = __filter_block_increasing(df,'k1')
elif J1[0]==J1[1]:
df = __filter_block_increasing(df,'i1')
df = __filter_block_increasing(df,'k1')
elif K1[0]==K1[1]:
df = __filter_block_increasing(df,'i1')
df = __filter_block_increasing(df,'j1')
if I2[0]==I2[1]:
df = __filter_block_increasing(df,'j2')
df = __filter_block_increasing(df,'k2')
elif J2[0]==J2[1]:
df = __filter_block_increasing(df,'i2')
df = __filter_block_increasing(df,'k2')
elif K2[0]==K2[1]:
df = __filter_block_increasing(df,'i2')
df = __filter_block_increasing(df,'j2')
# Do a final check after doing all these checks
if len(df)>=4: # Greater than 4 because match can occur with simply 4 corners but the interior doesn't match.
# Check for Split faces
## Block 1
main_face = create_face_from_diagonals(block1,imin=I1[0],imax=I1[1], jmin=J1[0],jmax=J1[1],kmin=K1[0],kmax=K1[1])
imin, jmin, kmin = df['i1'].min(), df['j1'].min(), df['k1'].min()
imax, jmax, kmax = df['i1'].max(), df['j1'].max(), df['k1'].max()
if int(imin==imax) + int(jmin==jmax) + int(kmin==kmax)==1:
split_faces1 = split_face(main_face,block1,imin=imin,imax=imax,jmin=jmin,jmax=jmax,kmin=kmin,kmax=kmax)
[s.set_block_index(face1.blockIndex) for s in split_faces1]
[s.set_face_id(face1.id) for s in split_faces1]
## Block 2
main_face = create_face_from_diagonals(block2,imin=I2[0],imax=I2[1], jmin=J2[0],jmax=J2[1],kmin=K2[0],kmax=K2[1])
imin, jmin, kmin = df['i2'].min(), df['j2'].min(), df['k2'].min()
imax, jmax, kmax = df['i2'].max(), df['j2'].max(), df['k2'].max()
if int(imin==imax) + int(jmin==jmax) + int(kmin==kmax)==1:
split_faces2 = split_face(main_face,block2,imin=imin,imax=imax,jmin=jmin,jmax=jmax,kmin=kmin,kmax=kmax)
[s.set_block_index(face2.blockIndex) for s in split_faces2]
[s.set_face_id(face2.id) for s in split_faces2]
else:
df = pd.DataFrame() # set df to empty dataframe
return df, split_faces1, split_faces2
def __filter_block_increasing(df:pd.DataFrame,key1:str):
"""Filters dataframe results of get_face_intersection to make sure both key1 is increasing.
When searching through a plot3D we check based on the planes e.g. const i, j, or k
values will be removed if they are not
Args:
df (pd.DataFrame): DataFrame containing matching points
key1 (str): column that you want to be in increasing order
Returns:
pd.DataFrame: sorted dataframe
"""
'''
Sometimes there's a match on 2 edges and we do not want to keep that
| face1 | face2 | face1 |
Above shows face 2 touching face 1 at 2 edges. this is not a match.
'''
if len(df)==0:
return df
key1_vals = list(df[key1].unique()) # get the unique values
key1_vals.sort()
key1_vals_to_use = list()
if len(key1_vals)<=1:
return pd.DataFrame() # Returning an empty dataframe. This solves the condition where you have edge matching
for i in range(len(key1_vals)-1):
if (key1_vals[i+1] - key1_vals[i])==1: # Remove
key1_vals_to_use.append(key1_vals[i])
# Look backwards
if (key1_vals[-1] - key1_vals[-2])==1: # Remove
key1_vals_to_use.append(key1_vals[-1])
df = df[df[key1].isin(key1_vals_to_use)]
return df
def __check_edge(df:pd.DataFrame):
""" Check if the results of get_face_intersection is an edge instead of a face.
if it's an edge then both intersecting blocks are connected by an edge on both blocks
Args:
df (pd.DataFrame): dataframe containing columns i1, j1, k1, i2, j2, k2
Returns:
boolean: True = It is an edge, False = not edge
"""
face1_diagonal = [(df['i1'].min(),df['j1'].min(),df['k1'].min()),(df['i1'].max(),df['j1'].max(),df['k1'].max()) ]
face2_diagonal = [(df['i2'].min(),df['j2'].min(),df['k2'].min()), (df['i2'].max(),df['j2'].max(),df['k2'].max())]
edge1 = face1_diagonal[0]
edge2 = face1_diagonal[1]
edge_matches = 0
for i in range(3):
if edge1[i]==edge2[i]:
edge_matches+=1
if edge_matches<2:
return False
else:
return True
[docs]
def combinations_of_nearest_blocks(blocks:List[Block],nearest_nblocks:int=4):
"""Returns the indices of the nearest 6 blocks based on their centroid
Args:
block (Block): block you are interested in
blocks (List[Block]): list of all your blocks
Returns:
List[Tuple[int,int]]: combinations of nearest blocks
"""
# Pick a block get centroid of all outer faces
centroids = np.array([(b.cx,b.cy,b.cz) for b in blocks])
distance_matrix = np.zeros((centroids.shape[0],centroids.shape[0]))+10000
# Build a matrix
for i in range(centroids.shape[0]):
for j in range(centroids.shape[0]):
if i!=j:
dx = centroids[i,0]-centroids[j,0]
dy = centroids[i,1]-centroids[j,1]
dz = centroids[i,2]-centroids[j,2]
distance_matrix[i,j] = np.sqrt(dx*dx+dy*dy+dz*dz)
# Now that we have this matrix, we sort the distances by rows and pick the closest 8 blocks, can use 4 but 8 might be safer
new_combos = list()
for i in range(len(blocks)): # For block i
indices = np.argsort(distance_matrix[i,:])
for j in indices[:nearest_nblocks]:
if distance_matrix[i,j] < 10000:
new_combos.append((i,j))
return new_combos
[docs]
def connectivity_fast(blocks:List[Block]):
"""Reduces the size of the blocks by a factor of the minimum gcd. This speeds up finding the connectivity
Args:
blocks (List[Block]): Lists of blocks you want to find the connectivity for
Returns:
(List[Dict]): All matching faces formatted as a list of { 'block1': {'block_index', 'IMIN', 'JMIN','KMIN', 'IMAX','JMAX','KMAX'} }
(List[Dict]): All exterior surfaces formatted as a list of { 'block_index', 'surfaces': [{'IMIN', 'JMIN','KMIN', 'IMAX','JMAX','KMAX', 'ID'}] }
"""
gcd_array = list()
# Find the gcd of all the blocks
for block_indx in range(len(blocks)):
block = blocks[block_indx]
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
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.
print(f"gcd to use {gcd_to_use}")
new_blocks = reduce_blocks(deepcopy(blocks),gcd_to_use)
# Find Connectivity
face_matches, outer_faces_formatted = connectivity(new_blocks)
# scale it up
for i in range(len(face_matches)):
face_matches[i]['block1']['IMIN'] *= gcd_to_use
face_matches[i]['block1']['JMIN'] *= gcd_to_use
face_matches[i]['block1']['KMIN'] *= gcd_to_use
face_matches[i]['block1']['IMAX'] *= gcd_to_use
face_matches[i]['block1']['JMAX'] *= gcd_to_use
face_matches[i]['block1']['KMAX'] *= gcd_to_use
face_matches[i]['block2']['IMIN'] *= gcd_to_use
face_matches[i]['block2']['JMIN'] *= gcd_to_use
face_matches[i]['block2']['KMIN'] *= gcd_to_use
face_matches[i]['block2']['IMAX'] *= gcd_to_use
face_matches[i]['block2']['JMAX'] *= gcd_to_use
face_matches[i]['block2']['KMAX'] *= gcd_to_use
for j in range(len(outer_faces_formatted)):
outer_faces_formatted[j]['IMIN'] *= gcd_to_use
outer_faces_formatted[j]['JMIN'] *= gcd_to_use
outer_faces_formatted[j]['KMIN'] *= gcd_to_use
outer_faces_formatted[j]['IMAX'] *= gcd_to_use
outer_faces_formatted[j]['JMAX'] *= gcd_to_use
outer_faces_formatted[j]['KMAX'] *= gcd_to_use
return face_matches, outer_faces_formatted
[docs]
def connectivity(blocks:List[Block]):
"""Returns a dictionary outlining the connectivity of the blocks along with any exterior surfaces
Args:
blocks (List[Block]): List of all blocks in multi-block plot3d mesh
Returns:
(List[Dict]): All matching faces formatted as a list of { 'block1': {'block_index', 'IMIN', 'JMIN','KMIN', 'IMAX','JMAX','KMAX'} }
(List[Dict]): All exterior surfaces formatted as a list of { 'block_index', 'surfaces': [{'IMIN', 'JMIN','KMIN', 'IMAX','JMAX','KMAX', 'ID'}] }
"""
outer_faces = list()
face_matches = list()
matches_to_remove = list()
# df_matches, blocki_outerfaces, blockj_outerfaces = find_matching_blocks(blocks[4],blocks[7],1E-12) # This function finds partial matches between blocks
temp = [get_outer_faces(b) for b in blocks]
block_outer_faces = [t[0] for t in temp]
combos = combinations_of_nearest_blocks(blocks,6) # Find the 6 nearest Blocks and search through all that.
t = trange(len(combos))
for indx in t: # block i
i,j = combos[indx]
t.set_description(f"Checking connections block {i} with {j}")
# Takes 2 blocks, gets the matching faces exterior faces of both blocks
df_matches, blocki_outerfaces, blockj_outerfaces = find_matching_blocks(blocks[i],blocks[j],block_outer_faces[i],block_outer_faces[j]) # This function finds partial matches between blocks
[o.set_block_index(i) for o in blocki_outerfaces]
[o.set_block_index(j) for o in blockj_outerfaces]
block_outer_faces[i] = blocki_outerfaces
block_outer_faces[j] = blockj_outerfaces
# Update connectivity for blocks with matching faces
if (len(df_matches)>0):
for df in df_matches:
matches_to_remove.append(create_face_from_diagonals(block=blocks[i],imin=df['i1'].min(),jmin=df['j1'].min(),kmin=df['k1'].min(),
imax=df['i1'].max(),jmax=df['j1'].max(),kmax=df['k1'].max()))
matches_to_remove[-1].set_block_index(i)
matches_to_remove.append(create_face_from_diagonals(block=blocks[j],imin=df['i2'].min(),jmin=df['j2'].min(),kmin=df['k2'].min(),
imax=df['i2'].max(),jmax=df['j2'].max(),kmax=df['k2'].max()))
matches_to_remove[-1].set_block_index(j)
face1 = matches_to_remove[-2]
face2 = matches_to_remove[-1]
temp = face_matches_to_dict(face1,face2,blocks[i],blocks[j])
temp['match'] = df
face_matches.append(temp)
# Update Outer Faces
[outer_faces.extend(o) for o in block_outer_faces] # all the outer faces
outer_faces = list(set(outer_faces)) # Get most unique
outer_faces = [o for o in outer_faces if o not in matches_to_remove]
# Remove any outer faces that may have been found by mistake
# Check I,J,K if J and K are the same with another outer face, select the face with shorter I
outer_faces_to_remove = list()
for i in range(len(blocks)):
block_outerfaces = [o for o in outer_faces if o.BlockIndex == i]
for o in block_outerfaces:
IJK = np.array([o.IMIN,o.JMIN,o.KMIN,o.IMAX,o.JMAX,o.KMAX])
for o2 in block_outerfaces:
IJK2 = np.array([o2.IMIN,o2.JMIN,o2.KMIN,o2.IMAX,o2.JMAX,o2.KMAX])
if sum((IJK-IJK2)==0) == 5: # [0,0,0,40,100,0] (outer) [0,0,0,56,100,0] (outer) -> remove the longer face
if (o2.diagonal_length>o.diagonal_length):
outer_faces_to_remove.append(o2)
else:
outer_faces_to_remove.append(o)
outer_faces = [o for o in outer_faces if o not in outer_faces_to_remove]
# Find self-matches: Do any faces of, for example, block1 match another face in block 1
for i in range(len(blocks)):
_,self_matches = get_outer_faces(blocks[i])
for match in self_matches: # Append to face matches
face_matches.append({'block1':{
'block_index':i,'IMIN':match[0].I.min(),'JMIN':match[0].J.min(),'KMIN':match[0].K.min(),
'IMAX':match[0].I.max(),'JMAX':match[0].J.max(),'KMAX':match[0].K.max()
},
'block2':{
'block_index':i,'IMIN':match[1].I.min(),'JMIN':match[1].J.min(),'KMIN':match[1].K.min(),
'IMAX':match[1].I.max(),'JMAX':match[1].J.max(),'KMAX':match[1].K.max()
},
'match':pd.DataFrame([{
'block_index':i,'IMIN':match[0].I.min(),'JMIN':match[0].J.min(),'KMIN':match[0].K.min(),
'IMAX':match[0].I.max(),'JMAX':match[0].J.max(),'KMAX':match[0].K.max()
},{
'block_index':i,'IMIN':match[1].I.min(),'JMIN':match[1].J.min(),'KMIN':match[1].K.min(),
'IMAX':match[1].I.max(),'JMAX':match[1].J.max(),'KMAX':match[1].K.max()
}])
})
# Update the outer faces
outer_faces_formatted = list() # This will contain
id = 1
for face in outer_faces:
outer_faces_formatted.append({ 'IMIN':min(face.I), 'JMIN':min(face.J), 'KMIN':min(face.K),
'IMAX':max(face.I), 'JMAX':max(face.J), 'KMAX':max(face.K),
'id':id, 'block_index':face.BlockIndex })
id += 1
return face_matches, outer_faces_formatted
[docs]
def face_matches_to_dict(face1:Face, face2:Face,block1:Block,block2:Block):
"""Makes sure the diagonal of face 1 match the diagonal of face 2
Args:
face1 (Face): Face 1 with block index
face2 (Face): Face 2 with block index
block1 (Block): Block 1
block2 (Block): Block 2
Returns:
(dict): dictionary describing the corner matches
"""
match = {
'block1':{
'block_index':face1.BlockIndex,
'IMIN':-1,'JMIN':-1,'KMIN':-1, # Lower Corner
'IMAX':-1,'JMAX':-1,'KMAX':-1, # Upper Corner
'id':face1.id
},
'block2':{
'block_index':face2.BlockIndex,
'IMIN':-1,'JMIN':-1,'KMIN':-1, # Lower Corner
'IMAX':-1,'JMAX':-1,'KMAX':-1, # Upper Corner
'id':face2.id
}
}
I1 = [face1.IMIN,face1.IMAX]
J1 = [face1.JMIN,face1.JMAX]
K1 = [face1.KMIN,face1.KMAX]
I2 = [face2.IMIN,face2.IMAX]
J2 = [face2.JMIN,face2.JMAX]
K2 = [face2.KMIN,face2.KMAX]
# Search for corners
x1_l = block1.X[I1[0],J1[0],K1[0]] # lower corner of block 1
y1_l = block1.Y[I1[0],J1[0],K1[0]]
z1_l = block1.Z[I1[0],J1[0],K1[0]]
# Matches which corner in block 2
search_results = list()
for p in I2:
for q in J2:
for r in K2:
x2 = block2.X[p,q,r]
y2 = block2.Y[p,q,r]
z2 = block2.Z[p,q,r]
dx = x2-x1_l; dy = y2-y1_l; dz = z2 -z1_l
search_results.append({'I':p,'J':q,'K':r,'d':math.sqrt(dx*dx + dy*dy + dz*dz)})
df = pd.DataFrame(search_results)
df = df.sort_values(by=['d'])
match['block1']['IMIN'] = face1.IMIN
match['block1']['JMIN'] = face1.JMIN
match['block1']['KMIN'] = face1.KMIN
match['block2']['IMIN'] = int(df.iloc[0]['I'])
match['block2']['JMIN'] = int(df.iloc[0]['J'])
match['block2']['KMIN'] = int(df.iloc[0]['K'])
# Search for corners
x1_u = block1.X[I1[1],J1[1],K1[1]] # lower corner of block 1
y1_u = block1.Y[I1[1],J1[1],K1[1]]
z1_u = block1.Z[I1[1],J1[1],K1[1]]
# Matches which corner in block 2
search_results = list()
for p in I2:
for q in J2:
for r in K2:
x2 = block2.X[p,q,r]
y2 = block2.Y[p,q,r]
z2 = block2.Z[p,q,r]
dx = x2-x1_u; dy = y2-y1_u; dz = z2 -z1_u
search_results.append({'I':p,'J':q,'K':r,'d':math.sqrt(dx*dx + dy*dy + dz*dz)})
df = pd.DataFrame(search_results)
df = df.sort_values(by=['d'])
match['block1']['IMAX'] = face1.IMAX
match['block1']['JMAX'] = face1.JMAX
match['block1']['KMAX'] = face1.KMAX
match['block2']['IMAX'] = int(df.iloc[0]['I'])
match['block2']['JMAX'] = int(df.iloc[0]['J'])
match['block2']['KMAX'] = int(df.iloc[0]['K'])
return match