Merge branch 'typehints_grid' into 'development'

added fist typehints for _grid module

See merge request damask/DAMASK!474
This commit is contained in:
Daniel Otto de Mentock 2022-01-13 16:21:19 +00:00
commit a000e477cf
9 changed files with 247 additions and 187 deletions

View File

@ -205,7 +205,7 @@ class Colormap(mpl.colors.ListedColormap):
Returns Returns
------- -------
color : np.ndarray, shape(...,4) color : numpy.ndarray, shape(...,4)
RGBA values of interpolated color(s). RGBA values of interpolated color(s).
Examples Examples

View File

@ -3,6 +3,9 @@ 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 pathlib import Path
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -13,7 +16,8 @@ from . import VTK
from . import util from . import util
from . import grid_filters from . import grid_filters
from . import Rotation from . import Rotation
from . import Table
from ._typehints import FloatSequence, IntSequence
class Grid: class Grid:
""" """
@ -25,30 +29,34 @@ class Grid:
the physical size. the physical size.
""" """
def __init__(self,material,size,origin=[0.0,0.0,0.0],comments=[]): def __init__(self,
material: np.ndarray,
size: FloatSequence,
origin: FloatSequence = np.zeros(3),
comments: Union[str, Sequence[str]] = []):
""" """
New geometry definition for grid solvers. New geometry definition for grid solvers.
Parameters Parameters
---------- ----------
material : numpy.ndarray of shape (:,:,:) material : numpy.ndarray, 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): def __repr__(self) -> str:
"""Basic information on grid definition.""" """Basic information on grid definition."""
mat_min = np.nanmin(self.material) mat_min = np.nanmin(self.material)
mat_max = np.nanmax(self.material) mat_max = np.nanmax(self.material)
@ -62,14 +70,14 @@ class Grid:
]) ])
def __copy__(self): def __copy__(self) -> "Grid":
"""Create deep copy.""" """Create deep copy."""
return copy.deepcopy(self) return copy.deepcopy(self)
copy = __copy__ copy = __copy__
def __eq__(self,other): def __eq__(self, other: object) -> bool:
""" """
Test equality of other. Test equality of other.
@ -79,22 +87,24 @@ class Grid:
Grid to compare self against. Grid to compare self against.
""" """
return (np.allclose(other.size,self.size) if not isinstance(other, Grid):
return NotImplemented
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))
@property @property
def material(self): def material(self) -> np.ndarray:
"""Material indices.""" """Material indices."""
return self._material return self._material
@material.setter @material.setter
def material(self,material): def material(self, material: np.ndarray):
if len(material.shape) != 3: if len(material.shape) != 3:
raise ValueError(f'invalid material shape {material.shape}') raise ValueError(f'invalid material shape {material.shape}')
elif material.dtype not in np.sctypes['float'] + np.sctypes['int']: elif material.dtype not in np.sctypes['float'] and material.dtype not in np.sctypes['int']:
raise TypeError(f'invalid material data type {material.dtype}') raise TypeError(f'invalid material data type {material.dtype}')
else: else:
self._material = np.copy(material) self._material = np.copy(material)
@ -105,59 +115,59 @@ class Grid:
@property @property
def size(self): def size(self) -> np.ndarray:
"""Physical size of grid in meter.""" """Physical size of grid in meter."""
return self._size return self._size
@size.setter @size.setter
def size(self,size): 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): 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): 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:
self._origin = np.array(origin) self._origin = np.array(origin)
@property @property
def comments(self): def comments(self) -> List[str]:
"""Comments, e.g. history of operations.""" """Comments, e.g. history of operations."""
return self._comments return self._comments
@comments.setter @comments.setter
def comments(self,comments): def comments(self, comments: Union[str, Sequence[str]]):
self._comments = [str(c) for c in comments] if isinstance(comments,list) else [str(comments)] self._comments = [str(c) for c in comments] if isinstance(comments,list) else [str(comments)]
@property @property
def cells(self): def cells(self) -> np.ndarray:
"""Number of cells in x,y,z direction.""" """Number of cells in x,y,z direction."""
return np.asarray(self.material.shape) return np.asarray(self.material.shape)
@property @property
def N_materials(self): def N_materials(self) -> int:
"""Number of (unique) material indices within grid.""" """Number of (unique) material indices within grid."""
return np.unique(self.material).size return np.unique(self.material).size
@staticmethod @staticmethod
def load(fname): def load(fname: Union[str, Path]) -> "Grid":
""" """
Load from VTK image data file. Load from VTK image data file.
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.
@ -178,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.
@ -198,15 +209,17 @@ class Grid:
""" """
warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.0.0', DeprecationWarning,2) warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.0.0', DeprecationWarning,2)
try: if isinstance(fname, (str, Path)):
f = open(fname) f = open(fname)
except TypeError: elif isinstance(fname, TextIO):
f = fname f = fname
else:
raise TypeError
f.seek(0) f.seek(0)
try: try:
header_length,keyword = f.readline().split()[:2] header_length_,keyword = f.readline().split()[:2]
header_length = int(header_length) header_length = int(header_length_)
except ValueError: except ValueError:
header_length,keyword = (-1, 'invalid') header_length,keyword = (-1, 'invalid')
if not keyword.startswith('head') or header_length < 3: if not keyword.startswith('head') or header_length < 3:
@ -226,19 +239,19 @@ class Grid:
else: else:
comments.append(line.strip()) comments.append(line.strip())
material = np.empty(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()
if len(items) == 3: if len(items) == 3:
if items[1].lower() == 'of': if items[1].lower() == 'of':
items = np.ones(int(items[0]))*float(items[2]) material_entry = np.ones(int(items[0]))*float(items[2])
elif items[1].lower() == 'to': elif items[1].lower() == 'to':
items = np.linspace(int(items[0]),int(items[2]), material_entry = np.linspace(int(items[0]),int(items[2]),
abs(int(items[2])-int(items[0]))+1,dtype=float) abs(int(items[2])-int(items[0]))+1,dtype=float)
else: items = list(map(float,items)) else: material_entry = list(map(float, items))
else: items = list(map(float,items)) else: material_entry = list(map(float, items))
material[i:i+len(items)] = items material[i:i+len(material_entry)] = material_entry
i += len(items) i += len(items)
if i != cells.prod(): if i != cells.prod():
@ -251,13 +264,13 @@ class Grid:
@staticmethod @staticmethod
def load_Neper(fname): def load_Neper(fname: Union[str, Path]) -> "Grid":
""" """
Load from Neper VTK file. Load from Neper VTK file.
Parameters Parameters
---------- ----------
fname : str, pathlib.Path, or file handle fname : str or pathlib.Path
Geometry file to read. Geometry file to read.
Returns Returns
@ -276,10 +289,10 @@ class Grid:
@staticmethod @staticmethod
def load_DREAM3D(fname, def load_DREAM3D(fname: Union[str, Path],
feature_IDs=None,cell_data=None, feature_IDs: str = None, cell_data: str = None,
phases='Phases',Euler_angles='EulerAngles', phases: str = 'Phases', Euler_angles: str = 'EulerAngles',
base_group=None): base_group: str = None) -> "Grid":
""" """
Load DREAM.3D (HDF5) file. Load DREAM.3D (HDF5) file.
@ -290,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.
@ -339,7 +352,9 @@ class Grid:
@staticmethod @staticmethod
def from_table(table,coordinates,labels): def from_table(table: Table,
coordinates: str,
labels: Union[str, Sequence[str]]) -> "Grid":
""" """
Create grid from ASCII table. Create grid from ASCII table.
@ -350,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.
@ -372,28 +387,33 @@ class Grid:
@staticmethod @staticmethod
def _find_closest_seed(seeds, weights, point): def _find_closest_seed(seeds: np.ndarray, weights: np.ndarray, point: np.ndarray) -> np.integer:
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,size,seeds,weights,material=None,periodic=True): def from_Laguerre_tessellation(cells: IntSequence,
size: FloatSequence,
seeds: np.ndarray,
weights: FloatSequence,
material: IntSequence = None,
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, 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 : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -421,29 +441,33 @@ 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,size,seeds,material=None,periodic=True): def from_Voronoi_tessellation(cells: IntSequence,
size: FloatSequence,
seeds: np.ndarray,
material: IntSequence = None,
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, 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 : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -460,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'),
) )
@ -509,15 +533,20 @@ class Grid:
@staticmethod @staticmethod
def from_minimal_surface(cells,size,surface,threshold=0.0,periods=1,materials=(0,1)): def from_minimal_surface(cells: IntSequence,
size: FloatSequence,
surface: str,
threshold: float = 0.0,
periods: int = 1,
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.
@ -525,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
@ -566,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)
""" """
@ -595,7 +623,7 @@ class Grid:
) )
def save(self,fname,compress=True): def save(self, fname: Union[str, Path], compress: bool = True):
""" """
Save as VTK image data file. Save as VTK image data file.
@ -611,10 +639,10 @@ 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): def save_ASCII(self, fname: Union[str, TextIO]):
""" """
Save as geom file. Save as geom file.
@ -644,26 +672,33 @@ 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,dimension,center,exponent, def add_primitive(self,
fill=None,R=Rotation(),inverse=False,periodic=True): dimension: Union[FloatSequence, IntSequence],
center: Union[FloatSequence, IntSequence],
exponent: Union[FloatSequence, float],
fill: int = None,
R: Rotation = Rotation(),
inverse: bool = False,
periodic: bool = True) -> "Grid":
""" """
Insert a primitive geometric object at a given position. Insert a primitive geometric object at a given position.
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)
@ -671,10 +706,10 @@ class Grid:
Fill value for primitive. Defaults to material.max()+1. Fill value for primitive. Defaults to material.max()+1.
R : damask.Rotation, optional R : damask.Rotation, optional
Rotation of primitive. Defaults to no rotation. Rotation of primitive. Defaults to no rotation.
inverse : Boolean, optional inverse : bool, optional
Retain original materials within primitive and fill outside. Retain original materials within primitive and fill outside.
Defaults to False. Defaults to False.
periodic : Boolean, optional periodic : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -690,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.
@ -701,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
""" """
@ -734,13 +769,13 @@ class Grid:
) )
def mirror(self,directions,reflect=False): def mirror(self, directions: Sequence[str], reflect: bool = False) -> "Grid":
""" """
Mirror grid along given directions. Mirror grid along given directions.
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
@ -759,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
""" """
@ -769,7 +804,7 @@ class Grid:
if not set(directions).issubset(valid): if not set(directions).issubset(valid):
raise ValueError(f'invalid direction {set(directions).difference(valid)} specified') raise ValueError(f'invalid direction {set(directions).difference(valid)} specified')
limits = [None,None] if reflect else [-2,0] limits: Sequence[Optional[int]] = [None,None] if reflect else [-2,0]
mat = self.material.copy() mat = self.material.copy()
if 'x' in directions: if 'x' in directions:
@ -786,13 +821,13 @@ class Grid:
) )
def flip(self,directions): def flip(self, directions: Sequence[str]) -> "Grid":
""" """
Flip grid along given directions. Flip grid along given directions.
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'.
@ -815,15 +850,15 @@ class Grid:
) )
def scale(self,cells,periodic=True): 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 : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -839,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
""" """
@ -859,7 +894,10 @@ class Grid:
) )
def clean(self,stencil=3,selection=None,periodic=True): 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.
@ -867,9 +905,9 @@ 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 : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -878,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)
@ -899,7 +937,7 @@ class Grid:
) )
def renumber(self): def renumber(self) -> "Grid":
""" """
Renumber sorted material indices as 0,...,N-1. Renumber sorted material indices as 0,...,N-1.
@ -918,7 +956,7 @@ class Grid:
) )
def rotate(self,R,fill=None): def rotate(self, R: Rotation, fill: int = None) -> "Grid":
""" """
Rotate grid (pad if required). Rotate grid (pad if required).
@ -926,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
@ -956,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
@ -981,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,to_material): 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
@ -1038,7 +1080,7 @@ class Grid:
) )
def sort(self): def sort(self) -> "Grid":
""" """
Sort material indices such that min(material) is located at (0,0,0). Sort material indices such that min(material) is located at (0,0,0).
@ -1060,7 +1102,11 @@ class Grid:
) )
def vicinity_offset(self,vicinity=1,offset=None,trigger=[],periodic=True): def vicinity_offset(self,
vicinity: int = 1,
offset: int = None,
trigger: IntSequence = [],
periodic: bool = True) -> "Grid":
""" """
Offset material index of points in the vicinity of xxx. Offset material index of points in the vicinity of xxx.
@ -1076,10 +1122,10 @@ 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 : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
Returns Returns
@ -1088,8 +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}))))
@ -1108,15 +1153,15 @@ 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.
Parameters Parameters
---------- ----------
periodic : Boolean, optional periodic : bool, 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

@ -393,8 +393,8 @@ class Orientation(Rotation,Crystal):
Returns Returns
------- -------
in : numpy.ndarray of quaternion.shape in : numpy.ndarray of bool, quaternion.shape
Boolean array indicating whether Rodrigues-Frank vector falls into fundamental zone. Whether Rodrigues-Frank vector falls into fundamental zone.
Notes Notes
----- -----
@ -437,8 +437,8 @@ class Orientation(Rotation,Crystal):
Returns Returns
------- -------
in : numpy.ndarray of quaternion.shape in : numpy.ndarray of bool, quaternion.shape
Boolean array indicating whether Rodrigues-Frank vector falls into disorientation FZ. Whether Rodrigues-Frank vector falls into disorientation FZ.
References References
---------- ----------
@ -651,8 +651,8 @@ class Orientation(Rotation,Crystal):
Returns Returns
------- -------
in : numpy.ndarray of shape (...) in : numpy.ndarray, shape (...)
Boolean array indicating whether vector falls into SST. Whether vector falls into SST.
""" """
if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3:

View File

@ -1817,7 +1817,7 @@ class Result:
output : (list of) str, optional output : (list of) str, optional
Names of the datasets to export to the file. Names of the datasets to export to the file.
Defaults to '*', in which case all datasets are exported. Defaults to '*', in which case all datasets are exported.
overwrite : boolean, optional overwrite : bool, optional
Overwrite existing configuration files. Overwrite existing configuration files.
Defaults to False. Defaults to False.

View File

@ -671,7 +671,7 @@ class Rotation:
---------- ----------
q : numpy.ndarray of shape (...,4) q : numpy.ndarray of shape (...,4)
Unit quaternion (q_0, q_1, q_2, q_3) in positive real hemisphere, i.e. ǀqǀ = 1, q_0 0. Unit quaternion (q_0, q_1, q_2, q_3) in positive real hemisphere, i.e. ǀqǀ = 1, q_0 0.
accept_homomorph : boolean, optional accept_homomorph : bool, optional
Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere). Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere).
Defaults to False. Defaults to False.
P : int {-1,1}, optional P : int {-1,1}, optional
@ -706,7 +706,7 @@ class Rotation:
phi : numpy.ndarray of shape (...,3) phi : numpy.ndarray of shape (...,3)
Euler angles (φ_1 [0,2π], ϕ [0,π], φ_2 [0,2π]) Euler angles (φ_1 [0,2π], ϕ [0,π], φ_2 [0,2π])
or (φ_1 [0,360], ϕ [0,180], φ_2 [0,360]) if degrees == True. or (φ_1 [0,360], ϕ [0,180], φ_2 [0,360]) if degrees == True.
degrees : boolean, optional degrees : bool, optional
Euler angles are given in degrees. Defaults to False. Euler angles are given in degrees. Defaults to False.
Notes Notes
@ -737,9 +737,9 @@ class Rotation:
axis_angle : numpy.ndarray of shape (...,4) axis_angle : numpy.ndarray of shape (...,4)
Axis and angle (n_1, n_2, n_3, ω) with ǀnǀ = 1 and ω [0,π] Axis and angle (n_1, n_2, n_3, ω) with ǀnǀ = 1 and ω [0,π]
or ω [0,180] if degrees == True. or ω [0,180] if degrees == True.
degrees : boolean, optional degrees : bool, optional
Angle ω is given in degrees. Defaults to False. Angle ω is given in degrees. Defaults to False.
normalize: boolean, optional normalize: bool, optional
Allow ǀnǀ 1. Defaults to False. Allow ǀnǀ 1. Defaults to False.
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
@ -773,9 +773,9 @@ class Rotation:
---------- ----------
basis : numpy.ndarray of shape (...,3,3) basis : numpy.ndarray of shape (...,3,3)
Three three-dimensional lattice basis vectors. Three three-dimensional lattice basis vectors.
orthonormal : boolean, optional orthonormal : bool, optional
Basis is strictly orthonormal, i.e. is free of stretch components. Defaults to True. Basis is strictly orthonormal, i.e. is free of stretch components. Defaults to True.
reciprocal : boolean, optional reciprocal : bool, optional
Basis vectors are given in reciprocal (instead of real) space. Defaults to False. Basis vectors are given in reciprocal (instead of real) space. Defaults to False.
""" """
@ -851,7 +851,7 @@ class Rotation:
---------- ----------
rho : numpy.ndarray of shape (...,4) rho : numpy.ndarray of shape (...,4)
RodriguesFrank vector (n_1, n_2, n_3, tan(ω/2)) with ǀnǀ = 1 and ω [0,π]. RodriguesFrank vector (n_1, n_2, n_3, tan(ω/2)) with ǀnǀ = 1 and ω [0,π].
normalize : boolean, optional normalize : bool, optional
Allow ǀnǀ 1. Defaults to False. Allow ǀnǀ 1. Defaults to False.
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
@ -977,9 +977,9 @@ class Rotation:
N : integer, optional N : integer, optional
Number of discrete orientations to be sampled from the given ODF. Number of discrete orientations to be sampled from the given ODF.
Defaults to 500. Defaults to 500.
degrees : boolean, optional degrees : bool, optional
Euler space grid coordinates are in degrees. Defaults to True. Euler space grid coordinates are in degrees. Defaults to True.
fractions : boolean, optional fractions : bool, optional
ODF values correspond to volume fractions, not probability densities. ODF values correspond to volume fractions, not probability densities.
Defaults to True. Defaults to True.
rng_seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
@ -1033,7 +1033,7 @@ class Rotation:
Standard deviation of (Gaussian) misorientation distribution. Standard deviation of (Gaussian) misorientation distribution.
N : int, optional N : int, optional
Number of samples. Defaults to 500. Number of samples. Defaults to 500.
degrees : boolean, optional degrees : bool, optional
sigma is given in degrees. Defaults to True. sigma is given in degrees. Defaults to True.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. A seed to initialize the BitGenerator.
@ -1072,7 +1072,7 @@ class Rotation:
Defaults to 0. Defaults to 0.
N : int, optional N : int, optional
Number of samples. Defaults to 500. Number of samples. Defaults to 500.
degrees : boolean, optional degrees : bool, optional
sigma, alpha, and beta are given in degrees. sigma, alpha, and beta are given in degrees.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. A seed to initialize the BitGenerator.

View File

@ -28,8 +28,8 @@ class VTK:
---------- ----------
vtk_data : subclass of vtk.vtkDataSet vtk_data : subclass of vtk.vtkDataSet
Description of geometry and topology, optionally with attached data. Description of geometry and topology, optionally with attached data.
Valid types are vtk.vtkRectilinearGrid, vtk.vtkUnstructuredGrid, Valid types are vtk.vtkImageData, vtk.vtkUnstructuredGrid,
or vtk.vtkPolyData. vtk.vtkPolyData, and vtk.vtkRectilinearGrid.
""" """
self.vtk_data = vtk_data self.vtk_data = vtk_data
@ -242,7 +242,7 @@ class VTK:
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename for writing. Filename for writing.
parallel : boolean, optional parallel : bool, optional
Write data in parallel background process. Defaults to True. Write data in parallel background process. Defaults to True.
compress : bool, optional compress : bool, optional
Compress with zlib algorithm. Defaults to True. Compress with zlib algorithm. Defaults to True.
@ -419,7 +419,7 @@ class VTK:
return writer.GetOutputString() return writer.GetOutputString()
def show(self): def show(self) -> None:
""" """
Render. Render.

View File

@ -61,7 +61,7 @@ def from_Poisson_disc(size: _FloatSequence, N_seeds: int, N_candidates: int, dis
Number of candidates to consider for finding best candidate. Number of candidates to consider for finding best candidate.
distance : float distance : float
Minimum acceptable distance to other seeds. Minimum acceptable distance to other seeds.
periodic : boolean, optional periodic : bool, optional
Calculate minimum distance for periodically repeated grid. Calculate minimum distance for periodically repeated grid.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None. A seed to initialize the BitGenerator. Defaults to None.
@ -99,8 +99,8 @@ def from_Poisson_disc(size: _FloatSequence, N_seeds: int, N_candidates: int, dis
return coords return coords
def from_grid(grid, selection: _IntSequence = None, def from_grid(grid, selection: _IntSequence = None, invert_selection: bool = False,
invert: bool = False, average: bool = False, periodic: bool = True) -> _Tuple[_np.ndarray, _np.ndarray]: average: bool = False, periodic: bool = True) -> _Tuple[_np.ndarray, _np.ndarray]:
""" """
Create seeds from grid description. Create seeds from grid description.
@ -110,11 +110,11 @@ def from_grid(grid, selection: _IntSequence = None,
Grid from which the material IDs are used as seeds. Grid from which the material IDs are used as seeds.
selection : sequence of int, optional selection : sequence of int, optional
Material IDs to consider. Material IDs to consider.
invert : boolean, false invert_selection : bool, optional
Consider all material IDs except those in selection. Defaults to False. Consider all material IDs except those in selection. Defaults to False.
average : boolean, optional average : bool, optional
Seed corresponds to center of gravity of material ID cloud. Seed corresponds to center of gravity of material ID cloud.
periodic : boolean, optional periodic : bool, optional
Center of gravity accounts for periodic boundaries. Center of gravity accounts for periodic boundaries.
Returns Returns
@ -125,7 +125,7 @@ def from_grid(grid, selection: _IntSequence = None,
""" """
material = grid.material.reshape((-1,1),order='F') material = grid.material.reshape((-1,1),order='F')
mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \ mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \
_np.isin(material,selection,invert=invert).flatten() _np.isin(material,selection,invert=invert_selection).flatten()
coords = _grid_filters.coordinates0_point(grid.cells,grid.size).reshape(-1,3,order='F') coords = _grid_filters.coordinates0_point(grid.cells,grid.size).reshape(-1,3,order='F')
if not average: if not average:

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)))])

View File

@ -67,5 +67,5 @@ class TestSeeds:
coords = seeds.from_random(size,N_seeds,cells) coords = seeds.from_random(size,N_seeds,cells)
grid = Grid.from_Voronoi_tessellation(cells,size,coords) grid = Grid.from_Voronoi_tessellation(cells,size,coords)
selection=np.random.randint(N_seeds)+1 selection=np.random.randint(N_seeds)+1
coords,material = seeds.from_grid(grid,average=average,periodic=periodic,invert=invert,selection=[selection]) coords,material = seeds.from_grid(grid,average=average,periodic=periodic,invert_selection=invert,selection=[selection])
assert selection not in material if invert else (selection==material).all() assert selection not in material if invert else (selection==material).all()