using best practices from grid_filters

This commit is contained in:
Martin Diehl 2022-01-12 20:34:29 +01:00
parent 9a8e7c8445
commit 2c1231a806
3 changed files with 153 additions and 125 deletions

View File

@ -3,6 +3,7 @@ import copy
import warnings import warnings
import multiprocessing as mp import multiprocessing as mp
from functools import partial from functools import partial
import typing
from typing import Union, Optional, TextIO, List, Sequence from typing import Union, Optional, TextIO, List, Sequence
from pathlib import Path from pathlib import Path
@ -16,6 +17,7 @@ from . import util
from . import grid_filters from . import grid_filters
from . import Rotation from . import Rotation
from . import Table from . import Table
from ._typehints import FloatSequence, IntSequence
class Grid: class Grid:
""" """
@ -29,9 +31,9 @@ class Grid:
def __init__(self, def __init__(self,
material: np.ndarray, material: np.ndarray,
size, size: FloatSequence,
origin = [0.0,0.0,0.0], origin: FloatSequence = np.zeros(3),
comments = []): comments: Union[str, Sequence[str]] = []):
""" """
New geometry definition for grid solvers. New geometry definition for grid solvers.
@ -40,18 +42,18 @@ class Grid:
material : numpy.ndarray of shape (:,:,:) material : numpy.ndarray of shape (:,:,:)
Material indices. The shape of the material array defines Material indices. The shape of the material array defines
the number of cells. the number of cells.
size : list or numpy.ndarray of shape (3) size : sequence of float, len (3)
Physical size of grid in meter. Physical size of grid in meter.
origin : list or numpy.ndarray of shape (3), optional origin : sequence of float, len (3), optional
Coordinates of grid origin in meter. Coordinates of grid origin in meter. Defaults to [0.0,0.0,0.0].
comments : list of str, optional comments : (list of) str, optional
Comments, e.g. history of operations. Comments, e.g. history of operations.
""" """
self.material = material self.material = material
self.size = size self.size = size # type: ignore
self.origin = origin self.origin = origin # type: ignore
self.comments = comments self.comments = comments # type: ignore
def __repr__(self) -> str: def __repr__(self) -> str:
@ -75,7 +77,7 @@ class Grid:
copy = __copy__ copy = __copy__
def __eq__(self, other): def __eq__(self, other: object) -> bool:
""" """
Test equality of other. Test equality of other.
@ -86,8 +88,8 @@ class Grid:
""" """
if not isinstance(other, Grid): if not isinstance(other, Grid):
raise TypeError return NotImplemented
return (np.allclose(other.size,self.size) return bool(np.allclose(other.size,self.size)
and np.allclose(other.origin,self.origin) and np.allclose(other.origin,self.origin)
and np.all(other.cells == self.cells) and np.all(other.cells == self.cells)
and np.all(other.material == self.material)) and np.all(other.material == self.material))
@ -118,19 +120,19 @@ class Grid:
return self._size return self._size
@size.setter @size.setter
def size(self, size: Union[Sequence[float], np.ndarray]): def size(self, size: FloatSequence):
if len(size) != 3 or any(np.array(size) < 0): if len(size) != 3 or any(np.array(size) < 0):
raise ValueError(f'invalid size {size}') raise ValueError(f'invalid size {size}')
else: else:
self._size = np.array(size) self._size = np.array(size)
@property @property
def origin(self) -> Union[Sequence[float], np.ndarray]: def origin(self) -> np.ndarray:
"""Coordinates of grid origin in meter.""" """Coordinates of grid origin in meter."""
return self._origin return self._origin
@origin.setter @origin.setter
def origin(self, origin: np.ndarray): def origin(self, origin: FloatSequence):
if len(origin) != 3: if len(origin) != 3:
raise ValueError(f'invalid origin {origin}') raise ValueError(f'invalid origin {origin}')
else: else:
@ -165,7 +167,7 @@ class Grid:
Parameters Parameters
---------- ----------
fname : str or or pathlib.Path fname : str or pathlib.Path
Grid file to read. Valid extension is .vti, which will be appended Grid file to read. Valid extension is .vti, which will be appended
if not given. if not given.
@ -186,8 +188,9 @@ class Grid:
comments=comments) comments=comments)
@typing. no_type_check
@staticmethod @staticmethod
def load_ASCII(fname): def load_ASCII(fname)-> "Grid":
""" """
Load from geom file. Load from geom file.
@ -225,10 +228,10 @@ class Grid:
comments = [] comments = []
content = f.readlines() content = f.readlines()
for i,line in enumerate(content[:header_length]): for i,line in enumerate(content[:header_length]):
items: List[str] = line.split('#')[0].lower().strip().split() items = line.split('#')[0].lower().strip().split()
key = items[0] if items else '' key = items[0] if items else ''
if key == 'grid': if key == 'grid':
cells = np.array([int(dict(zip(items[1::2],items[2::2]))[i]) for i in ['a','b','c']]) cells = np.array([ int(dict(zip(items[1::2],items[2::2]))[i]) for i in ['a','b','c']])
elif key == 'size': elif key == 'size':
size = np.array([float(dict(zip(items[1::2],items[2::2]))[i]) for i in ['x','y','z']]) size = np.array([float(dict(zip(items[1::2],items[2::2]))[i]) for i in ['x','y','z']])
elif key == 'origin': elif key == 'origin':
@ -236,7 +239,7 @@ class Grid:
else: else:
comments.append(line.strip()) comments.append(line.strip())
material = np.empty(int(cells.prod())) # initialize as flat array material = np.empty(int(cells.prod())) # initialize as flat array
i = 0 i = 0
for line in content[header_length:]: for line in content[header_length:]:
items = line.split('#')[0].split() items = line.split('#')[0].split()
@ -267,7 +270,7 @@ class Grid:
Parameters Parameters
---------- ----------
fname : str, pathlib.Path, or file handle fname : str or pathlib.Path
Geometry file to read. Geometry file to read.
Returns Returns
@ -286,7 +289,7 @@ class Grid:
@staticmethod @staticmethod
def load_DREAM3D(fname: str, def load_DREAM3D(fname: Union[str, Path],
feature_IDs: str = None, cell_data: str = None, feature_IDs: str = None, cell_data: str = None,
phases: str = 'Phases', Euler_angles: str = 'EulerAngles', phases: str = 'Phases', Euler_angles: str = 'EulerAngles',
base_group: str = None) -> "Grid": base_group: str = None) -> "Grid":
@ -300,24 +303,24 @@ class Grid:
Parameters Parameters
---------- ----------
fname : str fname : str or or pathlib.Path
Filename of the DREAM.3D (HDF5) file. Filename of the DREAM.3D (HDF5) file.
feature_IDs : str feature_IDs : str, optional
Name of the dataset containing the mapping between cells and Name of the dataset containing the mapping between cells and
grain-wise data. Defaults to 'None', in which case cell-wise grain-wise data. Defaults to 'None', in which case cell-wise
data is used. data is used.
cell_data : str cell_data : str, optional
Name of the group (folder) containing cell-wise data. Defaults to Name of the group (folder) containing cell-wise data. Defaults to
None in wich case it is automatically detected. None in wich case it is automatically detected.
phases : str phases : str, optional
Name of the dataset containing the phase ID. It is not used for Name of the dataset containing the phase ID. It is not used for
grain-wise data, i.e. when feature_IDs is not None. grain-wise data, i.e. when feature_IDs is not None.
Defaults to 'Phases'. Defaults to 'Phases'.
Euler_angles : str Euler_angles : str, optional
Name of the dataset containing the crystallographic orientation as Name of the dataset containing the crystallographic orientation as
Euler angles in radians It is not used for grain-wise data, i.e. Euler angles in radians It is not used for grain-wise data, i.e.
when feature_IDs is not None. Defaults to 'EulerAngles'. when feature_IDs is not None. Defaults to 'EulerAngles'.
base_group : str base_group : str, optional
Path to the group (folder) that contains geometry (_SIMPL_GEOMETRY), Path to the group (folder) that contains geometry (_SIMPL_GEOMETRY),
and grain- or cell-wise data. Defaults to None, in which case and grain- or cell-wise data. Defaults to None, in which case
it is set as the path that contains _SIMPL_GEOMETRY/SPACING. it is set as the path that contains _SIMPL_GEOMETRY/SPACING.
@ -349,7 +352,9 @@ class Grid:
@staticmethod @staticmethod
def from_table(table: Table, coordinates: str, labels: Union[str, Sequence[str]]) -> "Grid": def from_table(table: Table,
coordinates: str,
labels: Union[str, Sequence[str]]) -> "Grid":
""" """
Create grid from ASCII table. Create grid from ASCII table.
@ -360,7 +365,7 @@ class Grid:
coordinates : str coordinates : str
Label of the vector column containing the spatial coordinates. Label of the vector column containing the spatial coordinates.
Need to be ordered (1./x fast, 3./z slow). Need to be ordered (1./x fast, 3./z slow).
labels : str or list of str labels : (list of) str
Label(s) of the columns containing the material definition. Label(s) of the columns containing the material definition.
Each unique combination of values results in one material ID. Each unique combination of values results in one material ID.
@ -386,26 +391,26 @@ class Grid:
return np.argmin(np.sum((np.broadcast_to(point,(len(seeds),3))-seeds)**2,axis=1) - weights) return np.argmin(np.sum((np.broadcast_to(point,(len(seeds),3))-seeds)**2,axis=1) - weights)
@staticmethod @staticmethod
def from_Laguerre_tessellation(cells, def from_Laguerre_tessellation(cells: IntSequence,
size, size: FloatSequence,
seeds, seeds: np.ndarray,
weights, weights: FloatSequence,
material = None, material: IntSequence = None,
periodic = True): periodic: bool = True):
""" """
Create grid from Laguerre tessellation. Create grid from Laguerre tessellation.
Parameters Parameters
---------- ----------
cells : int numpy.ndarray of shape (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Number of cells in x,y,z direction.
size : list or numpy.ndarray of shape (3) size : sequence of float, len (3)
Physical size of the grid in meter. Physical size of the grid in meter.
seeds : numpy.ndarray of shape (:,3) seeds : numpy.ndarray of shape (:,3)
Position of the seed points in meter. All points need to lay within the box. Position of the seed points in meter. All points need to lay within the box.
weights : numpy.ndarray of shape (seeds.shape[0]) weights : sequence of float, len (seeds.shape[0])
Weights of the seeds. Setting all weights to 1.0 gives a standard Voronoi tessellation. Weights of the seeds. Setting all weights to 1.0 gives a standard Voronoi tessellation.
material : numpy.ndarray of shape (seeds.shape[0]), optional material : sequence of int, len (seeds.shape[0]), optional
Material ID of the seeds. Material ID of the seeds.
Defaults to None, in which case materials are consecutively numbered. Defaults to None, in which case materials are consecutively numbered.
periodic : Boolean, optional periodic : Boolean, optional
@ -427,6 +432,7 @@ class Grid:
seeds_p = seeds seeds_p = seeds
coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3) coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3)
pool = mp.Pool(int(os.environ.get('OMP_NUM_THREADS',4))) pool = mp.Pool(int(os.environ.get('OMP_NUM_THREADS',4)))
result = pool.map_async(partial(Grid._find_closest_seed,seeds_p,weights_p), coords) result = pool.map_async(partial(Grid._find_closest_seed,seeds_p,weights_p), coords)
pool.close() pool.close()
@ -435,30 +441,30 @@ class Grid:
if periodic: material_ %= len(weights) if periodic: material_ %= len(weights)
return Grid(material = material_ if material is None else material[material_], return Grid(material = material_ if material is None else np.array(material)[material_],
size = size, size = size,
comments = util.execution_stamp('Grid','from_Laguerre_tessellation'), comments = util.execution_stamp('Grid','from_Laguerre_tessellation'),
) )
@staticmethod @staticmethod
def from_Voronoi_tessellation(cells: np.ndarray, def from_Voronoi_tessellation(cells: IntSequence,
size: Union[Sequence[float], np.ndarray], size: FloatSequence,
seeds: np.ndarray, seeds: np.ndarray,
material: np.ndarray = None, material: IntSequence = None,
periodic: bool = True) -> "Grid": periodic: bool = True) -> "Grid":
""" """
Create grid from Voronoi tessellation. Create grid from Voronoi tessellation.
Parameters Parameters
---------- ----------
cells : int numpy.ndarray of shape (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Number of cells in x,y,z direction.
size : list or numpy.ndarray of shape (3) size : sequence of float, len (3)
Physical size of the grid in meter. Physical size of the grid in meter.
seeds : numpy.ndarray of shape (:,3) seeds : numpy.ndarray of shape (:,3)
Position of the seed points in meter. All points need to lay within the box. Position of the seed points in meter. All points need to lay within the box.
material : numpy.ndarray of shape (seeds.shape[0]), optional material : sequence of int, len (seeds.shape[0]), optional
Material ID of the seeds. Material ID of the seeds.
Defaults to None, in which case materials are consecutively numbered. Defaults to None, in which case materials are consecutively numbered.
periodic : Boolean, optional periodic : Boolean, optional
@ -478,7 +484,7 @@ class Grid:
except TypeError: except TypeError:
material_ = tree.query(coords, n_jobs = int(os.environ.get('OMP_NUM_THREADS',4)))[1] # scipy <1.6 material_ = tree.query(coords, n_jobs = int(os.environ.get('OMP_NUM_THREADS',4)))[1] # scipy <1.6
return Grid(material = (material_ if material is None else material[material_]).reshape(cells), return Grid(material = (material_ if material is None else np.array(material)[material_]).reshape(cells),
size = size, size = size,
comments = util.execution_stamp('Grid','from_Voronoi_tessellation'), comments = util.execution_stamp('Grid','from_Voronoi_tessellation'),
) )
@ -527,20 +533,20 @@ class Grid:
@staticmethod @staticmethod
def from_minimal_surface(cells: np.ndarray, def from_minimal_surface(cells: IntSequence,
size: Union[Sequence[float], np.ndarray], size: FloatSequence,
surface: str, surface: str,
threshold: float = 0.0, threshold: float = 0.0,
periods: int = 1, periods: int = 1,
materials: tuple = (0,1)) -> "Grid": materials: IntSequence = (0,1)) -> "Grid":
""" """
Create grid from definition of triply periodic minimal surface. Create grid from definition of triply periodic minimal surface.
Parameters Parameters
---------- ----------
cells : int numpy.ndarray of shape (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Number of cells in x,y,z direction.
size : list or numpy.ndarray of shape (3) size : sequence of float, len (3)
Physical size of the grid in meter. Physical size of the grid in meter.
surface : str surface : str
Type of the minimal surface. See notes for details. Type of the minimal surface. See notes for details.
@ -548,7 +554,7 @@ class Grid:
Threshold of the minimal surface. Defaults to 0.0. Threshold of the minimal surface. Defaults to 0.0.
periods : integer, optional. periods : integer, optional.
Number of periods per unit cell. Defaults to 1. Number of periods per unit cell. Defaults to 1.
materials : (int, int), optional materials : sequence of int, len (2)
Material IDs. Defaults to (0,1). Material IDs. Defaults to (0,1).
Returns Returns
@ -589,22 +595,21 @@ class Grid:
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> damask.Grid.from_minimal_surface(np.array([64]*3,int),np.ones(3), >>> damask.Grid.from_minimal_surface([64]*3,np.ones(3)*1.e-4,'Gyroid')
... 'Gyroid') cells : 64 x 64 x 64
cells a b c: 64 x 64 x 64 size : 0.0001 x 0.0001 x 0.0001 /
size x y z: 1.0 x 1.0 x 1.0 origin: 0.0 0.0 0.0 / m
origin x y z: 0.0 0.0 0.0
# materials: 2 # materials: 2
Minimal surface of 'Neovius' type. non-default material IDs. Minimal surface of 'Neovius' type. non-default material IDs.
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> damask.Grid.from_minimal_surface(np.array([80]*3,int),np.ones(3), >>> damask.Grid.from_minimal_surface([80]*3,np.ones(3)*5.e-4,
... 'Neovius',materials=(1,5)) ... 'Neovius',materials=(1,5))
cells a b c: 80 x 80 x 80 cells : 80 x 80 x 80
size x y z: 1.0 x 1.0 x 1.0 size : 0.0005 x 0.0005 x 0.0005 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 2 (min: 1, max: 5) # materials: 2 (min: 1, max: 5)
""" """
@ -634,7 +639,7 @@ class Grid:
v.add(self.material.flatten(order='F'),'material') v.add(self.material.flatten(order='F'),'material')
v.add_comments(self.comments) v.add_comments(self.comments)
v.save(fname if str(fname).endswith('.vti') else str(fname)+'.vti',parallel=False,compress=compress) v.save(fname,parallel=False,compress=compress)
def save_ASCII(self, fname: Union[str, TextIO]): def save_ASCII(self, fname: Union[str, TextIO]):
@ -667,15 +672,15 @@ class Grid:
header='\n'.join(header), fmt=format_string, comments='') header='\n'.join(header), fmt=format_string, comments='')
def show(self): def show(self) -> None:
"""Show on screen.""" """Show on screen."""
VTK.from_rectilinear_grid(self.cells,self.size,self.origin).show() VTK.from_rectilinear_grid(self.cells,self.size,self.origin).show()
def add_primitive(self, def add_primitive(self,
dimension: np.ndarray, dimension: Union[FloatSequence, IntSequence],
center: np.ndarray, center: Union[FloatSequence, IntSequence],
exponent: Union[np.ndarray, float], exponent: Union[FloatSequence, float],
fill: int = None, fill: int = None,
R: Rotation = Rotation(), R: Rotation = Rotation(),
inverse: bool = False, inverse: bool = False,
@ -685,14 +690,15 @@ class Grid:
Parameters Parameters
---------- ----------
dimension : int or float numpy.ndarray of shape (3) dimension : sequence of int or float, len (3)
Dimension (diameter/side length) of the primitive. If given as Dimension (diameter/side length) of the primitive.
integers, cell centers are addressed. If given as integers, cell centers are addressed.
If given as floats, coordinates are addressed. If given as floats, physical coordinates are addressed.
center : int or float numpy.ndarray of shape (3) center : sequence of int or float, len (3)
Center of the primitive. If given as integers, cell centers are addressed. Center of the primitive.
If given as floats, coordinates in space are addressed. If given as integers, cell centers are addressed.
exponent : numpy.ndarray of shape (3) or float If given as floats, physical coordinates are addressed.
exponent : float or sequence of float, len (3)
Exponents for the three axes. Exponents for the three axes.
0 gives octahedron (ǀxǀ^(2^0) + ǀyǀ^(2^0) + ǀzǀ^(2^0) < 1) 0 gives octahedron (ǀxǀ^(2^0) + ǀyǀ^(2^0) + ǀzǀ^(2^0) < 1)
1 gives sphere (ǀxǀ^(2^1) + ǀyǀ^(2^1) + ǀzǀ^(2^1) < 1) 1 gives sphere (ǀxǀ^(2^1) + ǀyǀ^(2^1) + ǀzǀ^(2^1) < 1)
@ -719,9 +725,9 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
>>> g.add_primitive(np.ones(3)*5e-5,np.ones(3)*5e-5,1) >>> g.add_primitive(np.ones(3)*5e-5,np.ones(3)*5e-5,1)
cells a b c: 64 x 64 x 64 cells : 64 x 64 x 64
size x y z: 0.0001 x 0.0001 x 0.0001 size : 0.0001 x 0.0001 x 0.0001 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 2 # materials: 2
Add a cube at the origin. Add a cube at the origin.
@ -730,9 +736,9 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
>>> g.add_primitive(np.ones(3,int)*32,np.zeros(3),np.inf) >>> g.add_primitive(np.ones(3,int)*32,np.zeros(3),np.inf)
cells a b c: 64 x 64 x 64 cells : 64 x 64 x 64
size x y z: 0.0001 x 0.0001 x 0.0001 size : 0.0001 x 0.0001 x 0.0001 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 2 # materials: 2
""" """
@ -769,7 +775,7 @@ class Grid:
Parameters Parameters
---------- ----------
directions : iterable containing str directions : (sequence of) str
Direction(s) along which the grid is mirrored. Direction(s) along which the grid is mirrored.
Valid entries are 'x', 'y', 'z'. Valid entries are 'x', 'y', 'z'.
reflect : bool, optional reflect : bool, optional
@ -788,9 +794,9 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int), np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([32]*3,int), np.ones(3)*1e-4)
>>> g.mirror('xy',True) >>> g.mirror('xy',True)
cells a b c: 64 x 64 x 32 cells : 64 x 64 x 32
size x y z: 0.0002 x 0.0002 x 0.0001 size : 0.0002 x 0.0002 x 0.0001 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 1 # materials: 1
""" """
@ -821,7 +827,7 @@ class Grid:
Parameters Parameters
---------- ----------
directions : iterable containing str directions : (sequence of) str
Direction(s) along which the grid is flipped. Direction(s) along which the grid is flipped.
Valid entries are 'x', 'y', 'z'. Valid entries are 'x', 'y', 'z'.
@ -844,13 +850,13 @@ class Grid:
) )
def scale(self, cells: np.ndarray, periodic: bool = True) -> "Grid": def scale(self, cells: IntSequence, periodic: bool = True) -> "Grid":
""" """
Scale grid to new cells. Scale grid to new cells.
Parameters Parameters
---------- ----------
cells : numpy.ndarray of shape (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Number of cells in x,y,z direction.
periodic : Boolean, optional periodic : Boolean, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
@ -868,9 +874,9 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.scale(g.cells*2) >>> g.scale(g.cells*2)
cells a b c: 64 x 64 x 64 cells : 64 x 64 x 64
size x y z: 0.0001 x 0.0001 x 0.0001 size : 0.0001 x 0.0001 x 0.0001 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 1 # materials: 1
""" """
@ -888,7 +894,10 @@ class Grid:
) )
def clean(self, stencil: int = 3, selection: Sequence[float] = None, periodic: bool = True) -> "Grid": def clean(self,
stencil: int = 3,
selection: IntSequence = None,
periodic: bool = True) -> "Grid":
""" """
Smooth grid by selecting most frequent material index within given stencil at each location. Smooth grid by selecting most frequent material index within given stencil at each location.
@ -896,7 +905,7 @@ class Grid:
---------- ----------
stencil : int, optional stencil : int, optional
Size of smoothing stencil. Size of smoothing stencil.
selection : list, optional selection : sequence of int, optional
Field values that can be altered. Defaults to all. Field values that can be altered. Defaults to all.
periodic : Boolean, optional periodic : Boolean, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
@ -907,7 +916,7 @@ class Grid:
Updated grid-based geometry. Updated grid-based geometry.
""" """
def mostFrequent(arr, selection = None): def mostFrequent(arr: np.ndarray, selection = None):
me = arr[arr.size//2] me = arr[arr.size//2]
if selection is None or me in selection: if selection is None or me in selection:
unique, inverse = np.unique(arr, return_inverse=True) unique, inverse = np.unique(arr, return_inverse=True)
@ -947,7 +956,7 @@ class Grid:
) )
def rotate(self, R: Rotation, fill: Union[int, float] = None) -> "Grid": def rotate(self, R: Rotation, fill: int = None) -> "Grid":
""" """
Rotate grid (pad if required). Rotate grid (pad if required).
@ -955,7 +964,7 @@ class Grid:
---------- ----------
R : damask.Rotation R : damask.Rotation
Rotation to apply to the grid. Rotation to apply to the grid.
fill : int or float, optional fill : int, optional
Material index to fill the corners. Defaults to material.max() + 1. Material index to fill the corners. Defaults to material.max() + 1.
Returns Returns
@ -985,17 +994,20 @@ class Grid:
) )
def canvas(self, cells = None, offset = None, fill = None): def canvas(self,
cells: IntSequence = None,
offset: IntSequence = None,
fill: int = None) -> "Grid":
""" """
Crop or enlarge/pad grid. Crop or enlarge/pad grid.
Parameters Parameters
---------- ----------
cells : numpy.ndarray of shape (3) cells : sequence of int, len (3), optional
Number of cells x,y,z direction. Number of cells x,y,z direction.
offset : numpy.ndarray of shape (3) offset : sequence of int, len (3), optional
Offset (measured in cells) from old to new grid [0,0,0]. Offset (measured in cells) from old to new grid [0,0,0].
fill : int or float, optional fill : int, optional
Material index to fill the background. Defaults to material.max() + 1. Material index to fill the background. Defaults to material.max() + 1.
Returns Returns
@ -1010,42 +1022,43 @@ class Grid:
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.canvas(np.array([32,32,16],int)) >>> g.canvas([32,32,16])
cells a b c: 33 x 32 x 16 cells : 33 x 32 x 16
size x y z: 0.0001 x 0.0001 x 5e-05 size : 0.0001 x 0.0001 x 5e-05 /
origin x y z: 0.0 0.0 0.0 origin: 0.0 0.0 0.0 / m
# materials: 1 # materials: 1
""" """
if offset is None: offset = 0 offset_ = np.array(offset,int) if offset is not None else np.zeros(3,int)
cells_ = np.array(cells,int) if cells is not None else self.cells
if fill is None: fill = np.nanmax(self.material) + 1 if fill is None: fill = np.nanmax(self.material) + 1
dtype = float if int(fill) != fill or self.material.dtype in np.sctypes['float'] else int dtype = float if int(fill) != fill or self.material.dtype in np.sctypes['float'] else int
canvas = np.full(self.cells if cells is None else cells,fill,dtype) canvas = np.full(cells_,fill,dtype)
LL = np.clip( offset, 0,np.minimum(self.cells, cells+offset)) LL = np.clip( offset_, 0,np.minimum(self.cells, cells_+offset_))
UR = np.clip( offset+cells, 0,np.minimum(self.cells, cells+offset)) UR = np.clip( offset_+cells_, 0,np.minimum(self.cells, cells_+offset_))
ll = np.clip(-offset, 0,np.minimum( cells,self.cells-offset)) ll = np.clip(-offset_, 0,np.minimum( cells_,self.cells-offset_))
ur = np.clip(-offset+self.cells,0,np.minimum( cells,self.cells-offset)) ur = np.clip(-offset_+self.cells,0,np.minimum( cells_,self.cells-offset_))
canvas[ll[0]:ur[0],ll[1]:ur[1],ll[2]:ur[2]] = self.material[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.material[LL[0]:UR[0],LL[1]:UR[1],LL[2]:UR[2]]
return Grid(material = canvas, return Grid(material = canvas,
size = self.size/self.cells*np.asarray(canvas.shape), size = self.size/self.cells*np.asarray(canvas.shape),
origin = self.origin+offset*self.size/self.cells, origin = self.origin+offset_*self.size/self.cells,
comments = self.comments+[util.execution_stamp('Grid','canvas')], comments = self.comments+[util.execution_stamp('Grid','canvas')],
) )
def substitute(self, from_material: np.ndarray, to_material: np.ndarray) -> "Grid": def substitute(self, from_material: IntSequence, to_material: IntSequence) -> "Grid":
""" """
Substitute material indices. Substitute material indices.
Parameters Parameters
---------- ----------
from_material : iterable of ints from_material : sequence of int
Material indices to be substituted. Material indices to be substituted.
to_material : iterable of ints to_material : sequence of int
New material indices. New material indices.
Returns Returns
@ -1092,7 +1105,7 @@ class Grid:
def vicinity_offset(self, def vicinity_offset(self,
vicinity: int = 1, vicinity: int = 1,
offset: int = None, offset: int = None,
trigger: Sequence[int] = [], trigger: IntSequence = [],
periodic: bool = True) -> "Grid": periodic: bool = True) -> "Grid":
""" """
Offset material index of points in the vicinity of xxx. Offset material index of points in the vicinity of xxx.
@ -1109,7 +1122,7 @@ class Grid:
offset : int, optional offset : int, optional
Offset (positive or negative) to tag material indices, Offset (positive or negative) to tag material indices,
defaults to material.max()+1. defaults to material.max()+1.
trigger : list of ints, optional trigger : sequence of int, optional
List of material indices that trigger a change. List of material indices that trigger a change.
Defaults to [], meaning that any different neighbor triggers a change. Defaults to [], meaning that any different neighbor triggers a change.
periodic : Boolean, optional periodic : Boolean, optional
@ -1121,7 +1134,7 @@ class Grid:
Updated grid-based geometry. Updated grid-based geometry.
""" """
def tainted_neighborhood(stencil, trigger): def tainted_neighborhood(stencil: np.ndarray, trigger):
me = stencil[stencil.shape[0]//2] me = stencil[stencil.shape[0]//2]
return np.any(stencil != me if len(trigger) == 0 else return np.any(stencil != me if len(trigger) == 0 else
np.in1d(stencil,np.array(list(set(trigger) - {me})))) np.in1d(stencil,np.array(list(set(trigger) - {me}))))
@ -1140,7 +1153,7 @@ class Grid:
) )
def get_grain_boundaries(self, periodic = True, directions = 'xyz'): def get_grain_boundaries(self, periodic: bool = True, directions: Sequence[str] = 'xyz'):
""" """
Create VTK unstructured grid containing grain boundaries. Create VTK unstructured grid containing grain boundaries.
@ -1148,7 +1161,7 @@ class Grid:
---------- ----------
periodic : Boolean, optional periodic : Boolean, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
directions : iterable containing str, optional directions : (sequence of) string, optional
Direction(s) along which the boundaries are determined. Direction(s) along which the boundaries are determined.
Valid entries are 'x', 'y', 'z'. Defaults to 'xyz'. Valid entries are 'x', 'y', 'z'. Defaults to 'xyz'.

View File

@ -419,7 +419,7 @@ class VTK:
return writer.GetOutputString() return writer.GetOutputString()
def show(self): def show(self) -> None:
""" """
Render. Render.

View File

@ -237,12 +237,27 @@ class TestGrid:
modified) modified)
def test_canvas(self,default): def test_canvas_extend(self,default):
cells = default.cells cells = default.cells
grid_add = np.random.randint(0,30,(3)) cells_add = np.random.randint(0,30,(3))
modified = default.canvas(cells + grid_add) modified = default.canvas(cells + cells_add)
assert np.all(modified.material[:cells[0],:cells[1],:cells[2]] == default.material) assert np.all(modified.material[:cells[0],:cells[1],:cells[2]] == default.material)
@pytest.mark.parametrize('sign',[+1,-1])
@pytest.mark.parametrize('extra_offset',[0,-1])
def test_canvas_move_out(self,sign,extra_offset):
g = Grid(np.zeros(np.random.randint(3,30,(3)),int),np.ones(3))
o = sign*np.ones(3)*g.cells.min() +extra_offset*sign
if extra_offset == 0:
assert np.all(g.canvas(offset=o).material == 1)
else:
assert np.all(np.unique(g.canvas(offset=o).material) == (0,1))
def test_canvas_cells(self,default):
g = Grid(np.zeros(np.random.randint(3,30,(3)),int),np.ones(3))
cells = np.random.randint(1,30,(3))
offset = np.random.randint(-30,30,(3))
assert np.all(g.canvas(cells,offset).cells == cells)
@pytest.mark.parametrize('center1,center2',[(np.random.random(3)*.5,np.random.random()*8), @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)))]) (np.random.randint(4,8,(3)),np.random.randint(9,12,(3)))])