removed set_X(), duplicate(), and .homogenization; renamed ".microstructure" to ".materials"

This commit is contained in:
Philip Eisenlohr 2020-09-22 14:47:08 -04:00
parent e683cbef69
commit 05835bacd3
2 changed files with 246 additions and 390 deletions

View File

@ -17,29 +17,44 @@ from . import grid_filters
class Geom:
"""Geometry definition for grid solvers."""
def __init__(self,microstructure,size,origin=[0.0,0.0,0.0],homogenization=1,comments=[]):
def __init__(self,materials,size,origin=[0.0,0.0,0.0],comments=[]):
"""
New geometry definition from array of microstructures and size.
New geometry definition from array of materials, size, and origin.
Parameters
----------
microstructure : numpy.ndarray
Microstructure array (3D)
materials : numpy.ndarray
Material index array (3D).
size : list or numpy.ndarray
Physical size of the microstructure in meter.
Physical size of the geometry in meter.
origin : list or numpy.ndarray, optional
Physical origin of the microstructure in meter.
homogenization : int, optional
Homogenization index.
Physical origin of the geometry in meter.
comments : list of str, optional
Comment lines.
"""
self.set_microstructure(microstructure,inplace=True)
self.set_size(size,inplace=True)
self.set_origin(origin,inplace=True)
self.set_homogenization(homogenization,inplace=True)
self.set_comments(comments,inplace=True)
if len(materials.shape) != 3:
raise ValueError(f'Invalid materials shape {materials.shape}.')
elif materials.dtype not in np.sctypes['float'] + np.sctypes['int']:
raise TypeError(f'Invalid materials data type {materials.dtype}.')
else:
self.materials = np.copy(materials)
if self.materials.dtype in np.sctypes['float'] and \
np.all(self.materials == self.materials.astype(int).astype(float)):
self.materials = self.materials.astype(int)
if len(size) != 3 or any(np.array(size) <= 0):
raise ValueError(f'Invalid size {size}.')
else:
self.size = np.array(size)
if len(origin) != 3:
raise ValueError(f'Invalid origin {origin}.')
else:
self.origin = np.array(origin)
self.comments = [str(c) for c in comments] if isinstance(comments,list) else [str(comments)]
def __repr__(self):
@ -48,8 +63,8 @@ class Geom:
f'grid a b c: {util.srepr(self.grid, " x ")}',
f'size x y z: {util.srepr(self.size, " x ")}',
f'origin x y z: {util.srepr(self.origin," ")}',
f'# materialpoints: {self.N_microstructure}',
f'max materialpoint: {np.nanmax(self.microstructure)}',
f'# materialpoints: {self.N_materials}',
f'max materialpoint: {np.nanmax(self.materials)}',
])
@ -63,42 +78,6 @@ class Geom:
return self.__copy__()
def duplicate(self,microstructure=None,size=None,origin=None,comments=None,autosize=False):
"""
Create a duplicate having updated microstructure, size, and origin.
Parameters
----------
microstructure : numpy.ndarray, optional
Microstructure array (3D).
size : list or numpy.ndarray, optional
Physical size of the microstructure in meter.
origin : list or numpy.ndarray, optional
Physical origin of the microstructure in meter.
comments : list of str, optional
Comment lines.
autosize : bool, optional
Ignore size parameter and rescale according to change of grid points.
"""
if size is not None and autosize:
raise ValueError('Auto-sizing conflicts with explicit size parameter.')
grid_old = self.grid
dup = self.set_microstructure(microstructure)\
.set_origin(origin)
if comments is not None:
dup.set_comments(comments,inplace=True)
if size is not None:
dup.set_size(size,inplace=True)
elif autosize:
dup.set_size(dup.grid/grid_old*self.size,inplace=True)
return dup
def diff(self,other):
"""
Report property differences of self relative to other.
@ -114,154 +93,33 @@ class Geom:
message.append(util.delete(f'grid a b c: {util.srepr(other.grid," x ")}'))
message.append(util.emph( f'grid a b c: {util.srepr( self.grid," x ")}'))
if np.any(other.size != self.size):
if not np.allclose(other.size,self.size):
message.append(util.delete(f'size x y z: {util.srepr(other.size," x ")}'))
message.append(util.emph( f'size x y z: {util.srepr( self.size," x ")}'))
if np.any(other.origin != self.origin):
if not np.allclose(other.origin,self.origin):
message.append(util.delete(f'origin x y z: {util.srepr(other.origin," ")}'))
message.append(util.emph( f'origin x y z: {util.srepr( self.origin," ")}'))
if other.N_microstructure != self.N_microstructure:
message.append(util.delete(f'# materialpoints: {other.N_microstructure}'))
message.append(util.emph( f'# materialpoints: { self.N_microstructure}'))
if other.N_materials != self.N_materials:
message.append(util.delete(f'# materialpoints: {other.N_materials}'))
message.append(util.emph( f'# materialpoints: { self.N_materials}'))
if np.nanmax(other.microstructure) != np.nanmax(self.microstructure):
message.append(util.delete(f'max materialpoint: {np.nanmax(other.microstructure)}'))
message.append(util.emph( f'max materialpoint: {np.nanmax( self.microstructure)}'))
if np.nanmax(other.materials) != np.nanmax(self.materials):
message.append(util.delete(f'max materialpoint: {np.nanmax(other.materials)}'))
message.append(util.emph( f'max materialpoint: {np.nanmax( self.materials)}'))
return util.return_message(message)
def set_comments(self,comments,inplace=False):
"""
Replace all existing comments.
Parameters
----------
comments : list of str
All comments.
"""
target = self if inplace else self.copy()
target.comments = []
target.add_comments(comments,inplace=True)
if not inplace: return target
def add_comments(self,comments,inplace=False):
"""
Append comments to existing comments.
Parameters
----------
comments : list of str
New comments.
"""
target = self if inplace else self.copy()
target.comments += [str(c) for c in comments] if isinstance(comments,list) else [str(comments)]
if not inplace: return target
def set_microstructure(self,microstructure,inplace=False):
"""
Replace the existing microstructure representation.
The complete microstructure is replaced (indcluding grid definition),
unless a masked array is provided in which case the grid dimensions
need to match and masked entries are not replaced.
Parameters
----------
microstructure : numpy.ndarray or numpy.ma.core.MaskedArray of shape (:,:,:)
Microstructure indices.
"""
target = self if inplace else self.copy()
if microstructure is not None:
if isinstance(microstructure,np.ma.core.MaskedArray):
target.microstructure = np.where(microstructure.mask,
target.microstructure,microstructure.data)
else:
target.microstructure = np.copy(microstructure)
if target.microstructure.dtype in np.sctypes['float'] and \
np.all(target.microstructure == target.microstructure.astype(int).astype(float)):
target.microstructure = target.microstructure.astype(int)
if len(target.microstructure.shape) != 3:
raise ValueError(f'Invalid microstructure shape {microstructure.shape}')
elif target.microstructure.dtype not in np.sctypes['float'] + np.sctypes['int']:
raise TypeError(f'Invalid microstructure data type {microstructure.dtype}')
if not inplace: return target
def set_size(self,size,inplace=False):
"""
Replace the existing size information.
Parameters
----------
size : list or numpy.ndarray
Physical size of the microstructure in meter.
"""
target = self if inplace else self.copy()
if size is not None:
if len(size) != 3 or any(np.array(size) <= 0):
raise ValueError(f'Invalid size {size}')
else:
target.size = np.array(size)
if not inplace: return target
def set_origin(self,origin,inplace=False):
"""
Replace the existing origin information.
Parameters
----------
origin : list or numpy.ndarray
Physical origin of the microstructure in meter.
"""
target = self if inplace else self.copy()
if origin is not None:
if len(origin) != 3:
raise ValueError(f'Invalid origin {origin}')
else:
target.origin = np.array(origin)
if not inplace: return target
def set_homogenization(self,homogenization,inplace=False):
"""
Replace the existing homogenization index.
Parameters
----------
homogenization : int
Homogenization index.
"""
target = self if inplace else self.copy()
if homogenization is not None:
if not isinstance(homogenization,int) or homogenization < 1:
raise TypeError(f'Invalid homogenization {homogenization}.')
else:
target.homogenization = homogenization
if not inplace: return target
@property
def grid(self):
return np.asarray(self.microstructure.shape)
return np.asarray(self.materials.shape)
@property
def N_microstructure(self):
return np.unique(self.microstructure).size
def N_materials(self):
return np.unique(self.materials).size
@staticmethod
@ -301,12 +159,10 @@ class Geom:
size = np.array([float(dict(zip(items[1::2],items[2::2]))[i]) for i in ['x','y','z']])
elif key == 'origin':
origin = np.array([float(dict(zip(items[1::2],items[2::2]))[i]) for i in ['x','y','z']])
elif key == 'homogenization':
homogenization = int(items[1])
else:
comments.append(line.strip())
microstructure = np.empty(grid.prod()) # initialize as flat array
materials = np.empty(grid.prod()) # initialize as flat array
i = 0
for line in content[header_length:]:
items = line.split('#')[0].split()
@ -318,16 +174,16 @@ class Geom:
abs(int(items[2])-int(items[0]))+1,dtype=float)
else: items = list(map(float,items))
else: items = list(map(float,items))
microstructure[i:i+len(items)] = items
materials[i:i+len(items)] = items
i += len(items)
if i != grid.prod():
raise TypeError(f'Invalid file: expected {grid.prod()} entries, found {i}')
if not np.any(np.mod(microstructure,1) != 0.0): # no float present
microstructure = microstructure.astype('int')
if not np.any(np.mod(materials,1) != 0.0): # no float present
materials = materials.astype('int')
return Geom(microstructure.reshape(grid,order='F'),size,origin,homogenization,comments)
return Geom(materials.reshape(grid,order='F'),size,origin,comments)
@staticmethod
@ -365,7 +221,7 @@ class Geom:
grid : int numpy.ndarray of shape (3)
Number of grid points in x,y,z direction.
size : list or numpy.ndarray of shape (3)
Physical size of the microstructure in meter.
Physical size of the geometry in meter.
seeds : numpy.ndarray of shape (:,3)
Position of the seed points in meter. All points need to lay within the box.
weights : numpy.ndarray of shape (seeds.shape[0])
@ -389,16 +245,17 @@ class Geom:
result = pool.map_async(partial(Geom._find_closest_seed,seeds_p,weights_p), [coord for coord in coords])
pool.close()
pool.join()
microstructure = np.array(result.get())
materials = np.array(result.get())
if periodic:
microstructure = microstructure.reshape(grid*3)
microstructure = microstructure[grid[0]:grid[0]*2,grid[1]:grid[1]*2,grid[2]:grid[2]*2]%seeds.shape[0]
materials = materials.reshape(grid*3)
materials = materials[grid[0]:grid[0]*2,grid[1]:grid[1]*2,grid[2]:grid[2]*2]%seeds.shape[0]
else:
microstructure = microstructure.reshape(grid)
materials = materials.reshape(grid)
return Geom(microstructure+1,size,homogenization=1,
comments=util.execution_stamp('Geom','from_Laguerre_tessellation'),
return Geom(materials = materials+1,
size = size,
comments = util.execution_stamp('Geom','from_Laguerre_tessellation'),
)
@ -412,7 +269,7 @@ class Geom:
grid : int numpy.ndarray of shape (3)
Number of grid points in x,y,z direction.
size : list or numpy.ndarray of shape (3)
Physical size of the microstructure in meter.
Physical size of the geometry in meter.
seeds : numpy.ndarray of shape (:,3)
Position of the seed points in meter. All points need to lay within the box.
periodic : Boolean, optional
@ -421,10 +278,11 @@ class Geom:
"""
coords = grid_filters.cell_coord0(grid,size).reshape(-1,3)
KDTree = spatial.cKDTree(seeds,boxsize=size) if periodic else spatial.cKDTree(seeds)
devNull,microstructure = KDTree.query(coords)
devNull,materials = KDTree.query(coords)
return Geom(microstructure.reshape(grid)+1,size,homogenization=1,
comments=util.execution_stamp('Geom','from_Voronoi_tessellation'),
return Geom(materials = materials.reshape(grid)+1,
size = size,
comments = util.execution_stamp('Geom','from_Voronoi_tessellation'),
)
@ -462,21 +320,21 @@ class Geom:
+[ 'grid a {} b {} c {}'.format(*geom.grid),
'size x {} y {} z {}'.format(*geom.size),
'origin x {} y {} z {}'.format(*geom.origin),
f'homogenization {geom.homogenization}',
'homogenization 1',
]
grid = geom.grid
if pack is None:
plain = grid.prod()/geom.N_microstructure < 250
plain = grid.prod()/geom.N_materials < 250
else:
plain = not pack
if plain:
format_string = '%g' if geom.microstructure.dtype in np.sctypes['float'] else \
'%{}i'.format(1+int(np.floor(np.log10(np.nanmax(geom.microstructure)))))
format_string = '%g' if geom.materials.dtype in np.sctypes['float'] else \
'%{}i'.format(1+int(np.floor(np.log10(np.nanmax(geom.materials)))))
np.savetxt(fname,
geom.microstructure.reshape([grid[0],np.prod(grid[1:])],order='F').T,
geom.materials.reshape([grid[0],np.prod(grid[1:])],order='F').T,
header='\n'.join(header), fmt=format_string, comments='')
else:
try:
@ -487,7 +345,7 @@ class Geom:
compressType = None
former = start = -1
reps = 0
for current in geom.microstructure.flatten('F'):
for current in geom.materials.flatten('F'):
if abs(current - former) == 1 and (start - current) == reps*(former - current):
compressType = 'to'
reps += 1
@ -532,7 +390,7 @@ class Geom:
"""
v = VTK.from_rectilinearGrid(geom.grid,geom.size,geom.origin)
v.add(geom.microstructure.flatten(order='F'),'materialpoint')
v.add(geom.materials.flatten(order='F'),'materialpoint')
v.add_comments(geom.comments)
if fname:
@ -575,11 +433,11 @@ class Geom:
0 gives octahedron (|x|^(2^0) + |y|^(2^0) + |z|^(2^0) < 1)
1 gives a sphere (|x|^(2^1) + |y|^(2^1) + |z|^(2^1) < 1)
fill : int, optional
Fill value for primitive. Defaults to microstructure.max() + 1.
Fill value for primitive. Defaults to materials.max() + 1.
R : damask.Rotation, optional
Rotation of primitive. Defaults to no rotation.
inverse : Boolean, optional
Retain original microstructure within primitive and fill outside.
Retain original materials within primitive and fill outside.
Defaults to False.
periodic : Boolean, optional
Repeat primitive over boundaries. Defaults to True.
@ -601,22 +459,23 @@ class Geom:
if periodic: # translate back to center
mask = np.roll(mask,((c-np.ones(3)*.5)*self.grid).astype(int),(0,1,2))
fill_ = np.full_like(self.microstructure,np.nanmax(self.microstructure)+1 if fill is None else fill)
ms = np.ma.MaskedArray(fill_,np.logical_not(mask) if inverse else mask)
fill_ = np.full_like(self.materials,np.nanmax(self.materials)+1 if fill is None else fill)
return self.duplicate(ms,
comments=self.comments+[util.execution_stamp('Geom','add_primitive')],
return Geom(materials = np.where(np.logical_not(mask) if inverse else mask, self.materials,fill_),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','add_primitive')],
)
def mirror(self,directions,reflect=False):
"""
Mirror microstructure along given directions.
Mirror geometry along given directions.
Parameters
----------
directions : iterable containing str
Direction(s) along which the microstructure is mirrored.
Direction(s) along which the geometry is mirrored.
Valid entries are 'x', 'y', 'z'.
reflect : bool, optional
Reflect (include) outermost layers. Defaults to False.
@ -627,28 +486,30 @@ class Geom:
raise ValueError(f'Invalid direction {set(directions).difference(valid)} specified.')
limits = [None,None] if reflect else [-2,0]
ms = self.microstructure.copy()
ms = self.materials.copy()
if 'z' in directions:
ms = np.concatenate([ms,ms[:,:,limits[0]:limits[1]:-1]],2)
if 'y' in directions:
ms = np.concatenate([ms,ms[:,limits[0]:limits[1]:-1,:]],1)
if 'x' in directions:
ms = np.concatenate([ms,ms[limits[0]:limits[1]:-1,:,:]],0)
if 'y' in directions:
ms = np.concatenate([ms,ms[:,limits[0]:limits[1]:-1,:]],1)
if 'z' in directions:
ms = np.concatenate([ms,ms[:,:,limits[0]:limits[1]:-1]],2)
return self.duplicate(ms,
comments=self.comments+[util.execution_stamp('Geom','mirror')],
autosize=True)
return Geom(materials = ms,
size = self.size/self.grid*np.asarray(ms.shape),
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','mirror')],
)
def flip(self,directions):
"""
Flip microstructure along given directions.
Flip geometry along given directions.
Parameters
----------
directions : iterable containing str
Direction(s) along which the microstructure is flipped.
Direction(s) along which the geometry is flipped.
Valid entries are 'x', 'y', 'z'.
"""
@ -656,16 +517,18 @@ class Geom:
if not set(directions).issubset(valid):
raise ValueError(f'Invalid direction {set(directions).difference(valid)} specified.')
ms = np.flip(self.microstructure, (valid.index(d) for d in directions if d in valid))
ms = np.flip(self.materials, (valid.index(d) for d in directions if d in valid))
return self.duplicate(ms,
comments=self.comments+[util.execution_stamp('Geom','flip')],
return Geom(materials = ms,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','flip')],
)
def scale(self,grid,periodic=True):
"""
Scale microstructure to new grid.
Scale geometry to new grid.
Parameters
----------
@ -675,21 +538,23 @@ class Geom:
Assume geometry to be periodic. Defaults to True.
"""
return self.duplicate(ndimage.interpolation.zoom(
self.microstructure,
return Geom(materials = ndimage.interpolation.zoom(
self.materials,
grid/self.grid,
output=self.microstructure.dtype,
output=self.materials.dtype,
order=0,
mode=('wrap' if periodic else 'nearest'),
prefilter=False
),
comments=self.comments+[util.execution_stamp('Geom','scale')],
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','scale')],
)
def clean(self,stencil=3,selection=None,periodic=True):
"""
Smooth microstructure by selecting most frequent index within given stencil at each location.
Smooth geometry by selecting most frequent material index within given stencil at each location.
Parameters
----------
@ -709,83 +574,87 @@ class Geom:
else:
return me
return self.duplicate(ndimage.filters.generic_filter(
self.microstructure,
return Geom(materials = ndimage.filters.generic_filter(
self.materials,
mostFrequent,
size=(stencil if selection is None else stencil//2*2+1,)*3,
mode=('wrap' if periodic else 'nearest'),
extra_keywords=dict(selection=selection),
).astype(self.microstructure.dtype),
comments=self.comments+[util.execution_stamp('Geom','clean')],
).astype(self.materials.dtype),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','clean')],
)
def renumber(self):
"""Renumber sorted microstructure indices to 1,...,N."""
renumbered = np.empty(self.grid,dtype=self.microstructure.dtype)
for i, oldID in enumerate(np.unique(self.microstructure)):
renumbered = np.where(self.microstructure == oldID, i+1, renumbered)
"""Renumber sorted material indices to 1,...,N."""
renumbered = np.empty(self.grid,dtype=self.materials.dtype)
for i, oldID in enumerate(np.unique(self.materials)):
renumbered = np.where(self.materials == oldID, i+1, renumbered)
return self.duplicate(renumbered,
comments=self.comments+[util.execution_stamp('Geom','renumber')],
return Geom(materials = renumbered,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','renumber')],
)
def rotate(self,R,fill=None):
"""
Rotate microstructure (pad if required).
Rotate geometry (pad if required).
Parameters
----------
R : damask.Rotation
Rotation to apply to the microstructure.
Rotation to apply to the geometry.
fill : int or float, optional
Microstructure index to fill the corners. Defaults to microstructure.max() + 1.
Material index to fill the corners. Defaults to materials.max() + 1.
"""
if fill is None: fill = np.nanmax(self.microstructure) + 1
dtype = float if np.isnan(fill) or int(fill) != fill or self.microstructure.dtype==np.float else int
if fill is None: fill = np.nanmax(self.materials) + 1
dtype = float if np.isnan(fill) or int(fill) != fill or self.materials.dtype==np.float else int
Eulers = R.as_Eulers(degrees=True)
microstructure_in = self.microstructure.copy()
materials_in = self.materials.copy()
# These rotations are always applied in the reference coordinate system, i.e. (z,x,z) not (z,x',z'')
# see https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
for angle,axes in zip(Eulers[::-1], [(0,1),(1,2),(0,1)]):
microstructure_out = ndimage.rotate(microstructure_in,angle,axes,order=0,
materials_out = ndimage.rotate(materials_in,angle,axes,order=0,
prefilter=False,output=dtype,cval=fill)
if np.prod(microstructure_in.shape) == np.prod(microstructure_out.shape):
if np.prod(materials_in.shape) == np.prod(materials_out.shape):
# avoid scipy interpolation errors for rotations close to multiples of 90°
microstructure_in = np.rot90(microstructure_in,k=np.rint(angle/90.).astype(int),axes=axes)
materials_in = np.rot90(materials_in,k=np.rint(angle/90.).astype(int),axes=axes)
else:
microstructure_in = microstructure_out
materials_in = materials_out
origin = self.origin-(np.asarray(microstructure_in.shape)-self.grid)*.5 * self.size/self.grid
origin = self.origin-(np.asarray(materials_in.shape)-self.grid)*.5 * self.size/self.grid
return self.duplicate(microstructure_in,
origin=origin,
comments=self.comments+[util.execution_stamp('Geom','rotate')],
autosize=True,
return Geom(materials = materials_in,
size = self.size/self.grid*np.asarray(materials_in.shape),
origin = origin,
comments = self.comments+[util.execution_stamp('Geom','rotate')],
)
def canvas(self,grid=None,offset=None,fill=None):
"""
Crop or enlarge/pad microstructure.
Crop or enlarge/pad geometry.
Parameters
----------
grid : numpy.ndarray of shape (3)
Number of grid points in x,y,z direction.
offset : numpy.ndarray of shape (3)
Offset (measured in grid points) from old to new microstructure[0,0,0].
Offset (measured in grid points) from old to new geometry [0,0,0].
fill : int or float, optional
Microstructure index to fill the background. Defaults to microstructure.max() + 1.
Material index to fill the background. Defaults to materials.max() + 1.
"""
if offset is None: offset = 0
if fill is None: fill = np.nanmax(self.microstructure) + 1
dtype = float if int(fill) != fill or self.microstructure.dtype in np.sctypes['float'] else int
if fill is None: fill = np.nanmax(self.materials) + 1
dtype = float if int(fill) != fill or self.materials.dtype in np.sctypes['float'] else int
canvas = np.full(self.grid if grid is None else grid,fill,dtype)
@ -794,39 +663,41 @@ class Geom:
ll = np.clip(-offset, 0,np.minimum( grid,self.grid-offset))
ur = np.clip(-offset+self.grid,0,np.minimum( grid,self.grid-offset))
canvas[ll[0]:ur[0],ll[1]:ur[1],ll[2]:ur[2]] = self.microstructure[LL[0]:UR[0],LL[1]:UR[1],LL[2]:UR[2]]
canvas[ll[0]:ur[0],ll[1]:ur[1],ll[2]:ur[2]] = self.materials[LL[0]:UR[0],LL[1]:UR[1],LL[2]:UR[2]]
return self.duplicate(canvas,
origin=self.origin+offset*self.size/self.grid,
comments=self.comments+[util.execution_stamp('Geom','canvas')],
autosize=True,
return Geom(materials = canvas,
size = self.size/self.grid*np.asarray(canvas.shape),
origin = self.origin+offset*self.size/self.grid,
comments = self.comments+[util.execution_stamp('Geom','canvas')],
)
def substitute(self,from_microstructure,to_microstructure):
def substitute(self,from_materials,to_materials):
"""
Substitute microstructure indices.
Substitute material indices.
Parameters
----------
from_microstructure : iterable of ints
Microstructure indices to be substituted.
to_microstructure : iterable of ints
New microstructure indices.
from_materials : iterable of ints
Material indices to be substituted.
to_materials : iterable of ints
New material indices.
"""
substituted = self.microstructure.copy()
for from_ms,to_ms in zip(from_microstructure,to_microstructure):
substituted[self.microstructure==from_ms] = to_ms
substituted = self.materials.copy()
for from_ms,to_ms in zip(from_materials,to_materials):
substituted[self.materials==from_ms] = to_ms
return self.duplicate(substituted,
comments=self.comments+[util.execution_stamp('Geom','substitute')],
return Geom(materials = substituted,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','substitute')],
)
def vicinity_offset(self,vicinity=1,offset=None,trigger=[],periodic=True):
"""
Offset microstructure index of points in the vicinity of xxx.
Offset material index of points in the vicinity of xxx.
Different from themselves (or listed as triggers) within a given (cubic) vicinity,
i.e. within the region close to a grain/phase boundary.
@ -835,14 +706,14 @@ class Geom:
Parameters
----------
vicinity : int, optional
Voxel distance checked for presence of other microstructure.
Voxel distance checked for presence of other materials.
Defaults to 1.
offset : int, optional
Offset (positive or negative) to tag microstructure indices,
defaults to microstructure.max() + 1.
Offset (positive or negative) to tag material indices,
defaults to materials.max() + 1.
trigger : list of ints, optional
List of microstructure indices triggering a change.
Defaults to [], meaning that different neigboors trigger a change.
List of material indices that trigger a change.
Defaults to [], meaning that any different neighbor triggers a change.
periodic : Boolean, optional
Assume geometry to be periodic. Defaults to True.
@ -858,14 +729,15 @@ class Geom:
trigger = list(trigger)
return np.any(np.in1d(stencil,np.array(trigger)))
offset_ = np.nanmax(self.microstructure) if offset is None else offset
mask = ndimage.filters.generic_filter(self.microstructure,
offset_ = np.nanmax(self.materials) if offset is None else offset
mask = ndimage.filters.generic_filter(self.materials,
tainted_neighborhood,
size=1+2*vicinity,
mode='wrap' if periodic else 'nearest',
extra_keywords={'trigger':trigger})
microstructure = np.ma.MaskedArray(self.microstructure + offset_, np.logical_not(mask))
return self.duplicate(microstructure,
comments=self.comments+[util.execution_stamp('Geom','vicinity_offset')],
return Geom(materials = np.where(mask, self.materials + offset_,self.materials),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Geom','vicinity_offset')],
)

View File

@ -11,7 +11,7 @@ from damask import util
def geom_equal(a,b):
return np.all(a.microstructure == b.microstructure) and \
return np.all(a.materials == b.materials) and \
np.all(a.grid == b.grid) and \
np.allclose(a.size, b.size) and \
str(a.diff(b)) == str(b.diff(a))
@ -33,64 +33,21 @@ def reference_dir(reference_dir_base):
class TestGeom:
@pytest.mark.parametrize('flavor',['plain','explicit'])
def test_duplicate(self,default,flavor):
if flavor == 'plain':
modified = default.duplicate()
elif flavor == 'explicit':
modified = default.duplicate(
default.microstructure,
default.size,
default.origin
)
print(modified)
assert geom_equal(default,modified)
def test_diff_equal(self,default):
assert str(default.diff(default)) == ''
def test_diff_not_equal(self,default):
new = Geom(default.microstructure[1:,1:,1:]+1,default.size*.9,np.ones(3)-default.origin,comments=['modified'])
new = Geom(default.materials[1:,1:,1:]+1,default.size*.9,np.ones(3)-default.origin,comments=['modified'])
assert str(default.diff(new)) != ''
def test_set_inplace_outofplace_homogenization(self,default):
default.set_homogenization(123,inplace=True)
outofplace = default.set_homogenization(321,inplace=False)
assert default.homogenization == 123 and outofplace.homogenization == 321
def test_set_inplace_outofplace_microstructure(self,default):
default.set_microstructure(np.arange(72).reshape((2,4,9)),inplace=True)
outofplace = default.set_microstructure(np.arange(72).reshape((8,3,3)),inplace=False)
assert np.array_equal(default.grid,[2,4,9]) and np.array_equal(outofplace.grid,[8,3,3])
def test_set_inplace_outofplace_size(self,default):
default.set_size(np.array([1,2,3]),inplace=True)
outofplace = default.set_size(np.array([3,2,1]),inplace=False)
assert np.array_equal(default.size,[1,2,3]) and np.array_equal(outofplace.size,[3,2,1])
def test_set_inplace_outofplace_comments(self,default):
default.set_comments(['a','and','b'],inplace=True)
outofplace = default.set_comments(['b','or','a'],inplace=False)
assert default.comments == ['a','and','b'] and outofplace.comments == ['b','or','a']
@pytest.mark.parametrize('masked',[True,False])
def test_set_microstructure(self,default,masked):
old = default.microstructure
new = np.random.randint(200,size=default.grid)
default.set_microstructure(np.ma.MaskedArray(new,np.full_like(new,masked)),inplace=True)
assert np.all(default.microstructure==(old if masked else new))
def test_write_read_str(self,default,tmpdir):
default.to_file(str(tmpdir/'default.geom'),format='ASCII')
new = Geom.from_file(str(tmpdir/'default.geom'))
assert geom_equal(default,new)
def test_write_read_file(self,default,tmpdir):
with open(tmpdir/'default.geom','w') as f:
default.to_file(f,format='ASCII',pack=True)
@ -98,6 +55,7 @@ class TestGeom:
new = Geom.from_file(f)
assert geom_equal(default,new)
def test_write_as_ASCII(self,default,tmpdir):
with open(tmpdir/'str.geom','w') as f:
f.write(default.as_ASCII())
@ -105,6 +63,7 @@ class TestGeom:
new = Geom.from_file(f)
assert geom_equal(default,new)
def test_read_write_vtr(self,default,tmpdir):
default.to_file(tmpdir/'default',format='vtr')
for _ in range(10):
@ -114,6 +73,7 @@ class TestGeom:
new = Geom.from_vtr(tmpdir/'default.vtr')
assert geom_equal(new,default)
def test_invalid_geom(self,tmpdir):
with open('invalid_file','w') as f:
f.write('this is not a valid header')
@ -121,6 +81,7 @@ class TestGeom:
with pytest.raises(TypeError):
Geom.from_file(f)
def test_invalid_vtr(self,tmpdir):
v = VTK.from_rectilinearGrid(np.random.randint(5,10,3)*2,np.random.random(3) + 1.0)
v.to_file(tmpdir/'no_materialpoint.vtr')
@ -137,36 +98,38 @@ class TestGeom:
new = Geom.from_file(tmpdir/'default.geom')
assert geom_equal(new,default)
def test_invalid_combination(self,default):
with pytest.raises(ValueError):
default.duplicate(default.microstructure[1:,1:,1:],size=np.ones(3), autosize=True)
def test_invalid_size(self,default):
with pytest.raises(ValueError):
default.duplicate(default.microstructure[1:,1:,1:],size=np.ones(2))
Geom(default.materials[1:,1:,1:],
size=np.ones(2))
def test_invalid_origin(self,default):
with pytest.raises(ValueError):
default.duplicate(default.microstructure[1:,1:,1:],origin=np.ones(4))
Geom(default.materials[1:,1:,1:],
size=np.ones(3),
origin=np.ones(4))
def test_invalid_microstructure_size(self,default):
microstructure = np.ones((3,3))
def test_invalid_materials_shape(self,default):
materials = np.ones((3,3))
with pytest.raises(ValueError):
default.duplicate(microstructure)
Geom(materials,
size=np.ones(3))
def test_invalid_microstructure_type(self,default):
microstructure = np.random.randint(1,300,(3,4,5))==1
with pytest.raises(TypeError):
default.duplicate(microstructure)
def test_invalid_homogenization(self,default):
def test_invalid_materials_type(self,default):
materials = np.random.randint(1,300,(3,4,5))==1
with pytest.raises(TypeError):
default.set_homogenization(homogenization=0)
Geom(materials)
def test_invalid_write_format(self,default):
with pytest.raises(TypeError):
default.to_file(format='invalid')
@pytest.mark.parametrize('directions,reflect',[
(['x'], False),
(['x','y','z'],True),
@ -182,6 +145,7 @@ class TestGeom:
assert geom_equal(Geom.from_file(reference),
modified)
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_mirror_invalid(self,default,directions):
with pytest.raises(ValueError):
@ -203,13 +167,16 @@ class TestGeom:
assert geom_equal(Geom.from_file(reference),
modified)
def test_flip_invariant(self,default):
assert geom_equal(default,default.flip([]))
@pytest.mark.parametrize('direction',[['x'],['x','y']])
def test_flip_double(self,default,direction):
assert geom_equal(default,default.flip(direction).flip(direction))
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_flip_invalid(self,default,directions):
with pytest.raises(ValueError):
@ -231,6 +198,7 @@ class TestGeom:
current
)
@pytest.mark.parametrize('grid',[
(10,11,10),
[10,13,10],
@ -248,22 +216,29 @@ class TestGeom:
assert geom_equal(Geom.from_file(reference),
modified)
def test_renumber(self,default):
microstructure = default.microstructure.copy()
for m in np.unique(microstructure):
microstructure[microstructure==m] = microstructure.max() + np.random.randint(1,30)
modified = default.duplicate(microstructure)
materials = default.materials.copy()
for m in np.unique(materials):
materials[materials==m] = materials.max() + np.random.randint(1,30)
modified = Geom(materials,
default.size,
default.origin)
assert not geom_equal(modified,default)
assert geom_equal(default,
modified.renumber())
def test_substitute(self,default):
offset = np.random.randint(1,500)
modified = default.duplicate(default.microstructure + offset)
modified = Geom(default.materials + offset,
default.size,
default.origin)
assert not geom_equal(modified,default)
assert geom_equal(default,
modified.substitute(np.arange(default.microstructure.max())+1+offset,
np.arange(default.microstructure.max())+1))
modified.substitute(np.arange(default.materials.max())+1+offset,
np.arange(default.materials.max())+1))
@pytest.mark.parametrize('axis_angle',[np.array([1,0,0,86.7]), np.array([0,1,0,90.4]), np.array([0,0,1,90]),
np.array([1,0,0,175]),np.array([0,-1,0,178]),np.array([0,0,1,180])])
@ -273,6 +248,7 @@ class TestGeom:
modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True))
assert geom_equal(default,modified)
@pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0],
[0.0,32.0,240.0]])
def test_rotate(self,default,update,reference_dir,Eulers):
@ -283,11 +259,13 @@ class TestGeom:
assert geom_equal(Geom.from_file(reference),
modified)
def test_canvas(self,default):
grid = default.grid
grid_add = np.random.randint(0,30,(3))
modified = default.canvas(grid + grid_add)
assert np.all(modified.microstructure[:grid[0],:grid[1],:grid[2]] == default.microstructure)
assert np.all(modified.materials[:grid[0],:grid[1],:grid[2]] == default.materials)
@pytest.mark.parametrize('center1,center2',[(np.random.random(3)*.5,np.random.random()*8),
(np.random.randint(4,8,(3)),np.random.randint(9,12,(3)))])
@ -300,13 +278,14 @@ class TestGeom:
np.random.rand()*4,
np.random.randint(20)])
def test_add_primitive_shift(self,center1,center2,diameter,exponent):
"""Same volume fraction for periodic microstructures and different center."""
"""Same volume fraction for periodic geometries and different center."""
o = np.random.random(3)-.5
g = np.random.randint(8,32,(3))
s = np.random.random(3)+.5
G_1 = Geom(np.ones(g,'i'),s,o).add_primitive(diameter,center1,exponent)
G_2 = Geom(np.ones(g,'i'),s,o).add_primitive(diameter,center2,exponent)
assert np.count_nonzero(G_1.microstructure!=2) == np.count_nonzero(G_2.microstructure!=2)
assert np.count_nonzero(G_1.materials!=2) == np.count_nonzero(G_2.materials!=2)
@pytest.mark.parametrize('center',[np.random.randint(4,10,(3)),
np.random.randint(2,10),
@ -323,6 +302,7 @@ class TestGeom:
G_2 = Geom(np.ones(g,'i'),[1.,1.,1.]).add_primitive(.3,center,1,fill,Rotation.from_Eulers(eu),inverse,periodic=periodic)
assert geom_equal(G_1,G_2)
@pytest.mark.parametrize('trigger',[[1],[]])
def test_vicinity_offset(self,trigger):
offset = np.random.randint(2,4)
@ -341,13 +321,15 @@ class TestGeom:
geom = Geom(m,np.random.rand(3)).vicinity_offset(vicinity,offset,trigger=trigger)
assert np.all(m2==geom.microstructure)
assert np.all(m2==geom.materials)
@pytest.mark.parametrize('periodic',[True,False])
def test_vicinity_offset_invariant(self,default,periodic):
offset = default.vicinity_offset(trigger=[default.microstructure.max()+1,
default.microstructure.min()-1])
assert np.all(offset.microstructure==default.microstructure)
offset = default.vicinity_offset(trigger=[default.materials.max()+1,
default.materials.min()-1])
assert np.all(offset.materials==default.materials)
@pytest.mark.parametrize('periodic',[True,False])
def test_tessellation_approaches(self,periodic):
@ -359,6 +341,7 @@ class TestGeom:
Laguerre = Geom.from_Laguerre_tessellation(grid,size,seeds,np.ones(N_seeds),periodic)
assert geom_equal(Laguerre,Voronoi)
def test_Laguerre_weights(self):
grid = np.random.randint(10,20,3)
size = np.random.random(3) + 1.0
@ -368,17 +351,18 @@ class TestGeom:
ms = np.random.randint(1, N_seeds+1)
weights[ms-1] = np.random.random()
Laguerre = Geom.from_Laguerre_tessellation(grid,size,seeds,weights,np.random.random()>0.5)
assert np.all(Laguerre.microstructure == ms)
assert np.all(Laguerre.materials == ms)
@pytest.mark.parametrize('approach',['Laguerre','Voronoi'])
def test_tessellate_bicrystal(self,approach):
grid = np.random.randint(5,10,3)*2
size = grid.astype(np.float)
seeds = np.vstack((size*np.array([0.5,0.25,0.5]),size*np.array([0.5,0.75,0.5])))
microstructure = np.ones(grid)
microstructure[:,grid[1]//2:,:] = 2
materials = np.ones(grid)
materials[:,grid[1]//2:,:] = 2
if approach == 'Laguerre':
geom = Geom.from_Laguerre_tessellation(grid,size,seeds,np.ones(2),np.random.random()>0.5)
elif approach == 'Voronoi':
geom = Geom.from_Voronoi_tessellation(grid,size,seeds, np.random.random()>0.5)
assert np.all(geom.microstructure == microstructure)
assert np.all(geom.materials == materials)