Merge branch 'development' into CI-with-statistics

This commit is contained in:
Philip Eisenlohr 2022-03-12 15:23:55 -05:00
commit f57a9aa952
11 changed files with 478 additions and 465 deletions

View File

@ -45,7 +45,7 @@ variables:
# ++++++++++++ PETSc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++ PETSc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
PETSC_GNU: "Libraries/PETSc/3.16.4/GNU-10-OpenMPI-4.1.2" PETSC_GNU: "Libraries/PETSc/3.16.4/GNU-10-OpenMPI-4.1.2"
PETSC_INTELLLVM: "Libraries/PETSc/3.16.3/oneAPI-2022.0.1-IntelMPI-2021.5.0" PETSC_INTELLLVM: "Libraries/PETSc/3.16.3/oneAPI-2022.0.1-IntelMPI-2021.5.0"
PETSC_INTEL: "Libraries/PETSc/3.16.4/Intel-2022.0.1-IntelMPI-2021.5.0" PETSC_INTEL: "Libraries/PETSc/3.16.5/Intel-2022.0.1-IntelMPI-2021.5.0"
# ++++++++++++ MSC Marc +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++ MSC Marc +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MSC: "FEM/MSC/2021.3.1" MSC: "FEM/MSC/2021.3.1"
IntelMarc: "Compiler/Intel/19.1.2 Libraries/IMKL/2020" IntelMarc: "Compiler/Intel/19.1.2 Libraries/IMKL/2020"

View File

@ -1 +1 @@
v3.0.0-alpha6-126-g8d9d13c15 v3.0.0-alpha6-140-g527c1c222

View File

@ -373,7 +373,7 @@ class Colormap(mpl.colors.ListedColormap):
""" """
labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3} labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3}
t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}') t = Table(labels,self.colors,f'Creator: {util.execution_stamp("Colormap")}')
t.save(self._get_file_handle(fname,'.txt')) t.save(self._get_file_handle(fname,'.txt'))

View File

@ -662,9 +662,9 @@ class Grid:
""" """
v = VTK.from_image_data(self.cells,self.size,self.origin)\ v = VTK.from_image_data(self.cells,self.size,self.origin)\
.add(self.material.flatten(order='F'),'material') .add('material',self.material.flatten(order='F'))
for label,data in self.ic.items(): for label,data in self.ic.items():
v = v.add(data.flatten(order='F'),label) v = v.add(label,data.flatten(order='F'))
v.comments = self.comments v.comments = self.comments
v.save(fname,parallel=False,compress=compress) v.save(fname,parallel=False,compress=compress)
@ -713,10 +713,372 @@ class Grid:
""" """
VTK.from_image_data(self.cells,self.size,self.origin) \ VTK.from_image_data(self.cells,self.size,self.origin) \
.add(self.material.flatten('F'),'material') \ .add('material',self.material.flatten('F'),) \
.show('material',colormap) .show('material',colormap)
def canvas(self,
cells: IntSequence = None,
offset: IntSequence = None,
fill: int = None) -> 'Grid':
"""
Crop or enlarge/pad grid.
Parameters
----------
cells : sequence of int, len (3), optional
Number of cells x,y,z direction.
offset : sequence of int, len (3), optional
Offset (measured in cells) from old to new grid.
Defaults to [0,0,0].
fill : int, optional
Material ID to fill the background.
Defaults to material.max() + 1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Remove lower 1/2 of the microstructure in z-direction.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.canvas([32,32,16],[0,0,16])
cells : 33 x 32 x 16
size : 0.0001 x 0.0001 x 5e-05
origin: 0.0 0.0 5e-05 m
# materials: 1
"""
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
canvas = np.full(cells_,np.nanmax(self.material) + 1 if fill is None else fill,self.material.dtype)
LL = np.clip( offset_, 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_))
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]]
return Grid(material = canvas,
size = self.size/self.cells*np.asarray(canvas.shape),
origin = self.origin+offset_*self.size/self.cells,
comments = self.comments+[util.execution_stamp('Grid','canvas')],
)
def mirror(self,
directions: Sequence[str],
reflect: bool = False) -> 'Grid':
"""
Mirror grid along given directions.
Parameters
----------
directions : (sequence of) {'x', 'y', 'z'}
Direction(s) along which the grid is mirrored.
reflect : bool, optional
Reflect (include) outermost layers. Defaults to False.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Mirror along x- and y-direction.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int), np.ones(3)*1e-4)
>>> g.mirror('xy',True)
cells : 64 x 64 x 32
size : 0.0002 x 0.0002 x 0.0001
origin: 0.0 0.0 0.0 m
# materials: 1
"""
if not set(directions).issubset(valid := ['x', 'y', 'z']):
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
limits: Sequence[Optional[int]] = [None,None] if reflect else [-2,0]
mat = self.material.copy()
if 'x' in directions:
mat = np.concatenate([mat,mat[limits[0]:limits[1]:-1,:,:]],0)
if 'y' in directions:
mat = np.concatenate([mat,mat[:,limits[0]:limits[1]:-1,:]],1)
if 'z' in directions:
mat = np.concatenate([mat,mat[:,:,limits[0]:limits[1]:-1]],2)
return Grid(material = mat,
size = self.size/self.cells*np.asarray(mat.shape),
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','mirror')],
)
def flip(self,
directions: Sequence[str]) -> 'Grid':
"""
Flip grid along given directions.
Parameters
----------
directions : (sequence of) {'x', 'y', 'z'}
Direction(s) along which the grid is flipped.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
if not set(directions).issubset(valid := ['x', 'y', 'z']):
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid])
return Grid(material = mat,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','flip')],
)
def rotate(self,
R: Rotation,
fill: int = None) -> 'Grid':
"""
Rotate grid (and pad if required).
Parameters
----------
R : damask.Rotation
Rotation to apply to the grid.
fill : int, optional
Material ID to fill enlarged bounding box.
Defaults to material.max() + 1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
material = self.material
# 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(R.as_Euler_angles(degrees=True)[::-1], [(0,1),(1,2),(0,1)]):
material_temp = ndimage.rotate(material,angle,axes,order=0,prefilter=False,
output=self.material.dtype,
cval=np.nanmax(self.material) + 1 if fill is None else fill)
# avoid scipy interpolation errors for rotations close to multiples of 90°
material = material_temp if np.prod(material_temp.shape) != np.prod(material.shape) else \
np.rot90(material,k=np.rint(angle/90.).astype(int),axes=axes)
origin = self.origin-(np.asarray(material.shape)-self.cells)*.5 * self.size/self.cells
return Grid(material = material,
size = self.size/self.cells*np.asarray(material.shape),
origin = origin,
comments = self.comments+[util.execution_stamp('Grid','rotate')],
)
def scale(self,
cells: IntSequence,
periodic: bool = True) -> 'Grid':
"""
Scale grid to new cells.
Parameters
----------
cells : sequence of int, len (3)
Number of cells in x,y,z direction.
periodic : bool, optional
Assume grid to be periodic. Defaults to True.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Double resolution.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.scale(g.cells*2)
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
origin: 0.0 0.0 0.0 m
# materials: 1
"""
return Grid(material = ndimage.interpolation.zoom(
self.material,
cells/self.cells,
output=self.material.dtype,
order=0,
mode='wrap' if periodic else 'nearest',
prefilter=False
),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','scale')],
)
def renumber(self) -> 'Grid':
"""
Renumber sorted material indices as 0,...,N-1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
_,renumbered = np.unique(self.material,return_inverse=True)
return Grid(material = renumbered.reshape(self.cells),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','renumber')],
)
def substitute(self,
from_material: Union[int,IntSequence],
to_material: Union[int,IntSequence]) -> 'Grid':
"""
Substitute material indices.
Parameters
----------
from_material : int or sequence of int
Material indices to be substituted.
to_material : int or sequence of int
New material indices.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
material = self.material.copy()
for f,t in zip(from_material if isinstance(from_material,(Sequence,np.ndarray)) else [from_material],
to_material if isinstance(to_material,(Sequence,np.ndarray)) else [to_material]): # ToDo Python 3.10 has strict mode for zip
material[self.material==f] = t
return Grid(material = material,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','substitute')],
)
def sort(self) -> 'Grid':
"""
Sort material indices such that min(material) is located at (0,0,0).
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
a = self.material.flatten(order='F')
from_ma = pd.unique(a)
sort_idx = np.argsort(from_ma)
ma = np.unique(a)[sort_idx][np.searchsorted(from_ma,a,sorter = sort_idx)]
return Grid(material = ma.reshape(self.cells,order='F'),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','sort')],
)
def clean(self,
distance: float = np.sqrt(3),
selection: IntCollection = None,
invert_selection: bool = False,
periodic: bool = True,
rng_seed: NumpyRngSeed = None) -> 'Grid':
"""
Smooth grid by selecting most frequent material ID within given stencil at each location.
Parameters
----------
distance : float, optional
Voxel distance checked for presence of other materials.
Defaults to sqrt(3).
selection : int or collection of int, optional
Material IDs to consider. Defaults to all.
invert_selection : bool, optional
Consider all material IDs except those in selection. Defaults to False.
periodic : bool, optional
Assume grid to be periodic. Defaults to True.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Notes
-----
If multiple material IDs are most frequent within a stencil, a random choice is taken.
"""
def most_frequent(stencil: np.ndarray,
selection: Union[None,set],
rng):
me = stencil[stencil.size//2]
if selection is None or me in selection:
unique, counts = np.unique(stencil,return_counts=True)
return rng.choice(unique[counts==np.max(counts)])
else:
return me
rng = np.random.default_rng(rng_seed)
d = np.floor(distance).astype(int)
ext = np.linspace(-d,d,1+2*d,dtype=float),
xx,yy,zz = np.meshgrid(ext,ext,ext)
footprint = xx**2+yy**2+zz**2 <= distance**2+distance*1e-8
selection_ = None if selection is None else \
set(self.material.flatten()) - set(util.aslist(selection)) if invert_selection else \
set(self.material.flatten()) & set(util.aslist(selection))
material = ndimage.filters.generic_filter(
self.material,
most_frequent,
footprint=footprint,
mode='wrap' if periodic else 'nearest',
extra_keywords=dict(selection=selection_,rng=rng),
).astype(self.material.dtype)
return Grid(material = material,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','clean')],
)
def add_primitive(self, def add_primitive(self,
dimension: Union[FloatSequence, IntSequence], dimension: Union[FloatSequence, IntSequence],
center: Union[FloatSequence, IntSequence], center: Union[FloatSequence, IntSequence],
@ -809,366 +1171,6 @@ class Grid:
) )
def mirror(self,
directions: Sequence[str],
reflect: bool = False) -> 'Grid':
"""
Mirror grid along given directions.
Parameters
----------
directions : (sequence of) {'x', 'y', 'z'}
Direction(s) along which the grid is mirrored.
reflect : bool, optional
Reflect (include) outermost layers. Defaults to False.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Mirror along x- and y-direction.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int), np.ones(3)*1e-4)
>>> g.mirror('xy',True)
cells : 64 x 64 x 32
size : 0.0002 x 0.0002 x 0.0001
origin: 0.0 0.0 0.0 m
# materials: 1
"""
if not set(directions).issubset(valid := ['x', 'y', 'z']):
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
limits: Sequence[Optional[int]] = [None,None] if reflect else [-2,0]
mat = self.material.copy()
if 'x' in directions:
mat = np.concatenate([mat,mat[limits[0]:limits[1]:-1,:,:]],0)
if 'y' in directions:
mat = np.concatenate([mat,mat[:,limits[0]:limits[1]:-1,:]],1)
if 'z' in directions:
mat = np.concatenate([mat,mat[:,:,limits[0]:limits[1]:-1]],2)
return Grid(material = mat,
size = self.size/self.cells*np.asarray(mat.shape),
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','mirror')],
)
def flip(self,
directions: Sequence[str]) -> 'Grid':
"""
Flip grid along given directions.
Parameters
----------
directions : (sequence of) {'x', 'y', 'z'}
Direction(s) along which the grid is flipped.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
if not set(directions).issubset(valid := ['x', 'y', 'z']):
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid])
return Grid(material = mat,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','flip')],
)
def scale(self,
cells: IntSequence,
periodic: bool = True) -> 'Grid':
"""
Scale grid to new cells.
Parameters
----------
cells : sequence of int, len (3)
Number of cells in x,y,z direction.
periodic : bool, optional
Assume grid to be periodic. Defaults to True.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Double resolution.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.scale(g.cells*2)
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
origin: 0.0 0.0 0.0 m
# materials: 1
"""
return Grid(material = ndimage.interpolation.zoom(
self.material,
cells/self.cells,
output=self.material.dtype,
order=0,
mode='wrap' if periodic else 'nearest',
prefilter=False
),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','scale')],
)
def clean(self,
distance: float = np.sqrt(3),
selection: IntCollection = None,
invert_selection: bool = False,
periodic: bool = True,
rng_seed: NumpyRngSeed = None) -> 'Grid':
"""
Smooth grid by selecting most frequent material ID within given stencil at each location.
Parameters
----------
distance : float, optional
Voxel distance checked for presence of other materials.
Defaults to sqrt(3).
selection : int or collection of int, optional
Material IDs to consider.
invert_selection : bool, optional
Consider all material IDs except those in selection. Defaults to False.
periodic : bool, optional
Assume grid to be periodic. Defaults to True.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Notes
-----
If multiple material IDs are most frequent within a stencil, a random choice is taken.
"""
def most_frequent(stencil: np.ndarray,
selection: set,
rng):
me = stencil[stencil.size//2]
if not selection or me in selection:
unique, counts = np.unique(stencil,return_counts=True)
return rng.choice(unique[counts==np.max(counts)])
else:
return me
rng = np.random.default_rng(rng_seed)
d = np.floor(distance).astype(int)
ext = np.linspace(-d,d,1+2*d,dtype=float),
xx,yy,zz = np.meshgrid(ext,ext,ext)
footprint = xx**2+yy**2+zz**2 <= distance**2+distance*1e-8
selection_ = set(self.material.flatten()) - set(util.aslist(selection)) if invert_selection else \
set(util.aslist(selection))
material = ndimage.filters.generic_filter(
self.material,
most_frequent,
footprint=footprint,
mode='wrap' if periodic else 'nearest',
extra_keywords=dict(selection=selection_,rng=rng),
).astype(self.material.dtype)
return Grid(material = material,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','clean')],
)
def renumber(self) -> 'Grid':
"""
Renumber sorted material indices as 0,...,N-1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
_,renumbered = np.unique(self.material,return_inverse=True)
return Grid(material = renumbered.reshape(self.cells),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','renumber')],
)
def rotate(self,
R: Rotation,
fill: int = None) -> 'Grid':
"""
Rotate grid (and pad if required).
Parameters
----------
R : damask.Rotation
Rotation to apply to the grid.
fill : int, optional
Material ID to fill enlarged bounding box.
Defaults to material.max() + 1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
material = self.material
# 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(R.as_Euler_angles(degrees=True)[::-1], [(0,1),(1,2),(0,1)]):
material_temp = ndimage.rotate(material,angle,axes,order=0,prefilter=False,
output=self.material.dtype,
cval=np.nanmax(self.material) + 1 if fill is None else fill)
# avoid scipy interpolation errors for rotations close to multiples of 90°
material = material_temp if np.prod(material_temp.shape) != np.prod(material.shape) else \
np.rot90(material,k=np.rint(angle/90.).astype(int),axes=axes)
origin = self.origin-(np.asarray(material.shape)-self.cells)*.5 * self.size/self.cells
return Grid(material = material,
size = self.size/self.cells*np.asarray(material.shape),
origin = origin,
comments = self.comments+[util.execution_stamp('Grid','rotate')],
)
def canvas(self,
cells: IntSequence = None,
offset: IntSequence = None,
fill: int = None) -> 'Grid':
"""
Crop or enlarge/pad grid.
Parameters
----------
cells : sequence of int, len (3), optional
Number of cells x,y,z direction.
offset : sequence of int, len (3), optional
Offset (measured in cells) from old to new grid.
Defaults to [0,0,0].
fill : int, optional
Material ID to fill the background.
Defaults to material.max() + 1.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
Examples
--------
Remove lower 1/2 of the microstructure in z-direction.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.canvas([32,32,16],[0,0,16])
cells : 33 x 32 x 16
size : 0.0001 x 0.0001 x 5e-05
origin: 0.0 0.0 5e-05 m
# materials: 1
"""
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
canvas = np.full(cells_,np.nanmax(self.material) + 1 if fill is None else fill,self.material.dtype)
LL = np.clip( offset_, 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_))
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]]
return Grid(material = canvas,
size = self.size/self.cells*np.asarray(canvas.shape),
origin = self.origin+offset_*self.size/self.cells,
comments = self.comments+[util.execution_stamp('Grid','canvas')],
)
def substitute(self,
from_material: Union[int,IntSequence],
to_material: Union[int,IntSequence]) -> 'Grid':
"""
Substitute material indices.
Parameters
----------
from_material : int or sequence of int
Material indices to be substituted.
to_material : int or sequence of int
New material indices.
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
material = self.material.copy()
for f,t in zip(from_material if isinstance(from_material,(Sequence,np.ndarray)) else [from_material],
to_material if isinstance(to_material,(Sequence,np.ndarray)) else [to_material]): # ToDo Python 3.10 has strict mode for zip
material[self.material==f] = t
return Grid(material = material,
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','substitute')],
)
def sort(self) -> 'Grid':
"""
Sort material indices such that min(material) is located at (0,0,0).
Returns
-------
updated : damask.Grid
Updated grid-based geometry.
"""
a = self.material.flatten(order='F')
from_ma = pd.unique(a)
sort_idx = np.argsort(from_ma)
ma = np.unique(a)[sort_idx][np.searchsorted(from_ma,a,sorter = sort_idx)]
return Grid(material = ma.reshape(self.cells,order='F'),
size = self.size,
origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','sort')],
)
def vicinity_offset(self, def vicinity_offset(self,
distance: float = np.sqrt(3), distance: float = np.sqrt(3),
offset: int = None, offset: int = None,
@ -1204,9 +1206,9 @@ class Grid:
Updated grid-based geometry. Updated grid-based geometry.
""" """
def tainted_neighborhood(stencil: np.ndarray, selection: set): def tainted_neighborhood(stencil: np.ndarray, selection: Union[None,set]):
me = stencil[stencil.size//2] me = stencil[stencil.size//2]
return np.any(stencil != me if not selection else return np.any(stencil != me if selection is None else
np.in1d(stencil,np.array(list(selection - {me})))) np.in1d(stencil,np.array(list(selection - {me}))))
d = np.floor(distance).astype(int) d = np.floor(distance).astype(int)
@ -1214,8 +1216,9 @@ class Grid:
xx,yy,zz = np.meshgrid(ext,ext,ext) xx,yy,zz = np.meshgrid(ext,ext,ext)
footprint = xx**2+yy**2+zz**2 <= distance**2+distance*1e-8 footprint = xx**2+yy**2+zz**2 <= distance**2+distance*1e-8
offset_ = np.nanmax(self.material)+1 if offset is None else offset offset_ = np.nanmax(self.material)+1 if offset is None else offset
selection_ = set(self.material.flatten()) - set(util.aslist(selection)) if invert_selection else \ selection_ = None if selection is None else \
set(util.aslist(selection)) set(self.material.flatten()) - set(util.aslist(selection)) if invert_selection else \
set(self.material.flatten()) & set(util.aslist(selection))
mask = ndimage.filters.generic_filter(self.material, mask = ndimage.filters.generic_filter(self.material,
tainted_neighborhood, tainted_neighborhood,
footprint=footprint, footprint=footprint,

View File

@ -1621,7 +1621,7 @@ class Result:
for inc in util.show_progress(self.visible['increments']): for inc in util.show_progress(self.visible['increments']):
u = _read(f['/'.join([inc,'geometry','u_n' if mode.lower() == 'cell' else 'u_p'])]) u = _read(f['/'.join([inc,'geometry','u_n' if mode.lower() == 'cell' else 'u_p'])])
v = v.add(u,'u') v = v.add('u',u)
for ty in ['phase','homogenization']: for ty in ['phase','homogenization']:
for field in self.visible['fields']: for field in self.visible['fields']:
@ -1648,7 +1648,7 @@ class Result:
outs[out][at_cell_ho[label]] = data[in_data_ho[label]] outs[out][at_cell_ho[label]] = data[in_data_ho[label]]
for label,dataset in outs.items(): for label,dataset in outs.items():
v = v.add(dataset,' / '.join(['/'.join([ty,field,label]),dataset.dtype.metadata['unit']])) v = v.add(' / '.join(['/'.join([ty,field,label]),dataset.dtype.metadata['unit']]),dataset)
v.save(f'{self.fname.stem}_inc{inc[10:].zfill(N_digits)}',parallel=parallel) v.save(f'{self.fname.stem}_inc{inc[10:].zfill(N_digits)}',parallel=parallel)

View File

@ -13,26 +13,27 @@ class Table:
"""Manipulate multi-dimensional spreadsheet-like data.""" """Manipulate multi-dimensional spreadsheet-like data."""
def __init__(self, def __init__(self,
data: np.ndarray,
shapes: dict, shapes: dict,
data: np.ndarray,
comments: Union[str, list] = None): comments: Union[str, list] = None):
""" """
New spreadsheet. New spreadsheet.
Parameters Parameters
---------- ----------
data : numpy.ndarray or pandas.DataFrame
Data. Column labels from a pandas.DataFrame will be replaced.
shapes : dict with str:tuple pairs shapes : dict with str:tuple pairs
Shapes of the columns. Example 'F':(3,3) for a deformation gradient. Shapes of the data columns.
For instance, 'F':(3,3) for a deformation gradient, or 'r':(1,) for a scalar.
data : numpy.ndarray or pandas.DataFrame
Data. Existing column labels of a pandas.DataFrame will be replaced.
comments : str or iterable of str, optional comments : str or iterable of str, optional
Additional, human-readable information. Additional, human-readable information.
""" """
comments_ = [comments] if isinstance(comments,str) else comments comments_ = [comments] if isinstance(comments,str) else comments
self.comments = [] if comments_ is None else [c for c in comments_] self.comments = [] if comments_ is None else [c for c in comments_]
self.data = pd.DataFrame(data=data)
self.shapes = { k:(v,) if isinstance(v,(np.int64,np.int32,int)) else v for k,v in shapes.items() } self.shapes = { k:(v,) if isinstance(v,(np.int64,np.int32,int)) else v for k,v in shapes.items() }
self.data = pd.DataFrame(data=data)
self._relabel('uniform') self._relabel('uniform')
@ -70,8 +71,8 @@ class Table:
-------- --------
>>> import damask >>> import damask
>>> import numpy as np >>> import numpy as np
>>> tbl = damask.Table(data=np.arange(12).reshape((4,3)), >>> tbl = damask.Table(shapes=dict(colA=(1,),colB=(1,),colC=(1,)),
... shapes=dict(colA=(1,),colB=(1,),colC=(1,))) ... data=np.arange(12).reshape((4,3)))
>>> tbl['colA','colB'] >>> tbl['colA','colB']
colA colB colA colB
0 0 1 0 0 1
@ -282,7 +283,7 @@ class Table:
data = pd.read_csv(f,names=list(range(len(labels))),sep=r'\s+') data = pd.read_csv(f,names=list(range(len(labels))),sep=r'\s+')
return Table(data,shapes,comments) return Table(shapes,data,comments)
@staticmethod @staticmethod
@ -329,7 +330,7 @@ class Table:
if (remainder := data.shape[1]-sum(shapes.values())) > 0: if (remainder := data.shape[1]-sum(shapes.values())) > 0:
shapes['unknown'] = remainder shapes['unknown'] = remainder
return Table(data,shapes,comments) return Table(shapes,data,comments)
@property @property
@ -535,7 +536,7 @@ class Table:
raise KeyError('mismatch of shapes or labels or their order') raise KeyError('mismatch of shapes or labels or their order')
dup = self.copy() dup = self.copy()
dup.data = dup.data.append(other.data,ignore_index=True) dup.data = pd.concat([dup.data,other.data],ignore_index=True)
return dup return dup

View File

@ -402,24 +402,32 @@ class VTK:
# Check https://blog.kitware.com/ghost-and-blanking-visibility-changes/ for missing data # Check https://blog.kitware.com/ghost-and-blanking-visibility-changes/ for missing data
def add(self, def add(self,
data: Union[np.ndarray, np.ma.MaskedArray, 'Table'], label: str = None,
label: str = None): data: Union[np.ndarray, np.ma.MaskedArray] = None,
*,
table: 'Table' = None):
""" """
Add data to either cells or points. Add data to either cells or points.
Data can either be a numpy.array, which requires a corresponding label,
or a damask.Table.
Parameters Parameters
---------- ----------
data : numpy.ndarray, numpy.ma.MaskedArray, or damask.Table label : str, optional
Label of data array.
data : numpy.ndarray or numpy.ma.MaskedArray, optional
Data to add. First dimension needs to match either Data to add. First dimension needs to match either
number of cells or number of points. number of cells or number of points.
label : str, optional if data is damask.Table table: damask.Table, optional
Data label. Data to add. Number of rows needs to match either
number of cells or number of points.
""" """
def _add_array(vtk_data, def _add_array(vtk_data,
data: np.ndarray, label: str,
label: str): data: np.ndarray):
N_data = data.shape[0] N_data = data.shape[0]
data_ = data.reshape(N_data,-1) \ data_ = data.reshape(N_data,-1) \
@ -441,17 +449,22 @@ class VTK:
else: else:
raise ValueError(f'data count mismatch ({N_data}{self.N_points} & {self.N_cells})') raise ValueError(f'data count mismatch ({N_data}{self.N_points} & {self.N_cells})')
if data is None and table is None:
raise KeyError('no data given')
if data is not None and table is not None:
raise KeyError('cannot use both, data and table')
dup = self.copy() dup = self.copy()
if isinstance(data,np.ndarray): if isinstance(data,np.ndarray):
if label is not None: if label is not None:
_add_array(dup.vtk_data, _add_array(dup.vtk_data,
np.where(data.mask,data.fill_value,data) if isinstance(data,np.ma.MaskedArray) else data, label,
label) np.where(data.mask,data.fill_value,data) if isinstance(data,np.ma.MaskedArray) else data)
else: else:
raise ValueError('no label defined for numpy.ndarray') raise ValueError('no label defined for data')
elif isinstance(data,Table): elif isinstance(table,Table):
for l in data.labels: for l in table.labels:
_add_array(dup.vtk_data,data.get(l),l) _add_array(dup.vtk_data,l,table.get(l))
else: else:
raise TypeError raise TypeError

View File

@ -90,7 +90,7 @@ class TestConfigMaterial:
np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2), np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2),
np.ones(N*2), np.ones(N*2),
)).T )).T
t = Table(a,{'varying':1,'constant':4,'ones':1}) t = Table({'varying':1,'constant':4,'ones':1},a)
c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':'ones'}) c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':'ones'})
assert len(c['material']) == N assert len(c['material']) == N
for i,m in enumerate(c['material']): for i,m in enumerate(c['material']):
@ -102,7 +102,7 @@ class TestConfigMaterial:
np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2), np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2),
np.ones(N*2), np.ones(N*2),
)).T )).T
t = Table(a,{'varying':1,'constant':4,'ones':1}) t = Table({'varying':1,'constant':4,'ones':1},a)
c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':1}) c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':1})
assert len(c['material']) == N assert len(c['material']) == N
for i,m in enumerate(c['material']): for i,m in enumerate(c['material']):

View File

@ -12,11 +12,6 @@ from damask import seeds
from damask import grid_filters from damask import grid_filters
def grid_equal(a,b):
return np.all(a.material == b.material) and \
np.all(a.cells == b.cells) and \
np.allclose(a.size, b.size)
@pytest.fixture @pytest.fixture
def default(): def default():
"""Simple geometry.""" """Simple geometry."""
@ -63,10 +58,10 @@ class TestGrid:
def test_repr(self,default): def test_repr(self,default):
print(default) print(default)
def test_read_write_vtr(self,default,tmp_path): def test_read_write_vti(self,default,tmp_path):
default.save(tmp_path/'default') default.save(tmp_path/'default')
new = Grid.load(tmp_path/'default.vti') new = Grid.load(tmp_path/'default.vti')
assert grid_equal(new,default) assert new == default
def test_invalid_no_material(self,tmp_path): def test_invalid_no_material(self,tmp_path):
v = VTK.from_image_data(np.random.randint(5,10,3)*2,np.random.random(3) + 1.0) v = VTK.from_image_data(np.random.randint(5,10,3)*2,np.random.random(3) + 1.0)
@ -90,7 +85,7 @@ class TestGrid:
def test_save_load_ASCII(self,default,tmp_path): def test_save_load_ASCII(self,default,tmp_path):
default.save_ASCII(tmp_path/'ASCII') default.save_ASCII(tmp_path/'ASCII')
default.material -= 1 default.material -= 1
assert grid_equal(Grid.load_ASCII(tmp_path/'ASCII'),default) assert Grid.load_ASCII(tmp_path/'ASCII') == default
def test_invalid_origin(self,default): def test_invalid_origin(self,default):
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -124,8 +119,7 @@ class TestGrid:
tag = f'directions_{"-".join(directions)}+reflect_{reflect}' tag = f'directions_{"-".join(directions)}+reflect_{reflect}'
reference = ref_path/f'mirror_{tag}.vti' reference = ref_path/f'mirror_{tag}.vti'
if update: modified.save(reference) if update: modified.save(reference)
assert grid_equal(Grid.load(reference), assert Grid.load(reference) == modified
modified)
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]]) @pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
@ -146,17 +140,16 @@ class TestGrid:
tag = f'directions_{"-".join(directions)}' tag = f'directions_{"-".join(directions)}'
reference = ref_path/f'flip_{tag}.vti' reference = ref_path/f'flip_{tag}.vti'
if update: modified.save(reference) if update: modified.save(reference)
assert grid_equal(Grid.load(reference), assert Grid.load(reference) == modified
modified)
def test_flip_invariant(self,default): def test_flip_invariant(self,default):
assert grid_equal(default,default.flip([])) assert default == default.flip([])
@pytest.mark.parametrize('direction',[['x'],['x','y']]) @pytest.mark.parametrize('direction',[['x'],['x','y']])
def test_flip_double(self,default,direction): def test_flip_double(self,default,direction):
assert grid_equal(default,default.flip(direction).flip(direction)) assert default == default.flip(direction).flip(direction)
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]]) @pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
@ -173,7 +166,7 @@ class TestGrid:
reference = ref_path/f'clean_{distance}_{"+".join(map(str,util.aslist(selection)))}_{periodic}.vti' reference = ref_path/f'clean_{distance}_{"+".join(map(str,util.aslist(selection)))}_{periodic}.vti'
if update: if update:
current.save(reference) current.save(reference)
assert grid_equal(Grid.load(reference),current) assert Grid.load(reference) == current
@pytest.mark.parametrize('selection',[list(np.random.randint(1,20,6)),set(np.random.randint(1,20,6)),np.random.randint(1,20,6)]) @pytest.mark.parametrize('selection',[list(np.random.randint(1,20,6)),set(np.random.randint(1,20,6)),np.random.randint(1,20,6)])
@pytest.mark.parametrize('invert',[True,False]) @pytest.mark.parametrize('invert',[True,False])
@ -201,8 +194,7 @@ class TestGrid:
tag = f'grid_{util.srepr(cells,"-")}' tag = f'grid_{util.srepr(cells,"-")}'
reference = ref_path/f'scale_{tag}.vti' reference = ref_path/f'scale_{tag}.vti'
if update: modified.save(reference) if update: modified.save(reference)
assert grid_equal(Grid.load(reference), assert Grid.load(reference) == modified
modified)
def test_renumber(self,default): def test_renumber(self,default):
@ -213,9 +205,8 @@ class TestGrid:
modified = Grid(material, modified = Grid(material,
default.size, default.size,
default.origin) default.origin)
assert not grid_equal(modified,default) assert not default == modified
assert grid_equal(default, assert default == modified.renumber()
modified.renumber())
def test_substitute(self,default): def test_substitute(self,default):
@ -223,10 +214,9 @@ class TestGrid:
modified = Grid(default.material + offset, modified = Grid(default.material + offset,
default.size, default.size,
default.origin) default.origin)
assert not grid_equal(modified,default) assert not default == modified
assert grid_equal(default, assert default == modified.substitute(np.arange(default.material.max())+1+offset,
modified.substitute(np.arange(default.material.max())+1+offset, np.arange(default.material.max())+1)
np.arange(default.material.max())+1))
def test_substitute_integer_list(self,random): def test_substitute_integer_list(self,random):
f = np.random.randint(30) f = np.random.randint(30)
@ -237,8 +227,8 @@ class TestGrid:
f = np.unique(default.material.flatten())[:np.random.randint(1,default.material.max())] f = np.unique(default.material.flatten())[:np.random.randint(1,default.material.max())]
t = np.random.permutation(f) t = np.random.permutation(f)
modified = default.substitute(f,t) modified = default.substitute(f,t)
assert np.array_equiv(t,f) or (not grid_equal(modified,default)) assert np.array_equiv(t,f) or modified != default
assert grid_equal(default, modified.substitute(t,f)) assert default == modified.substitute(t,f)
def test_sort(self): def test_sort(self):
cells = np.random.randint(5,20,3) cells = np.random.randint(5,20,3)
@ -252,7 +242,7 @@ class TestGrid:
modified = default.copy() modified = default.copy()
for i in range(np.rint(360/axis_angle[3]).astype(int)): for i in range(np.rint(360/axis_angle[3]).astype(int)):
modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True)) modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True))
assert grid_equal(default,modified) assert default == modified
@pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0], @pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0],
@ -262,8 +252,7 @@ class TestGrid:
tag = f'Eulers_{util.srepr(Eulers,"-")}' tag = f'Eulers_{util.srepr(Eulers,"-")}'
reference = ref_path/f'rotate_{tag}.vti' reference = ref_path/f'rotate_{tag}.vti'
if update: modified.save(reference) if update: modified.save(reference)
assert grid_equal(Grid.load(reference), assert Grid.load(reference) == modified
modified)
def test_canvas_extend(self,default): def test_canvas_extend(self,default):
@ -321,7 +310,7 @@ class TestGrid:
fill = np.random.randint(10)+2 fill = np.random.randint(10)+2
G_1 = Grid(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,inverse=inverse,periodic=periodic) G_1 = Grid(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,inverse=inverse,periodic=periodic)
G_2 = Grid(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,Rotation.from_random(),inverse,periodic=periodic) G_2 = Grid(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,Rotation.from_random(),inverse,periodic=periodic)
assert grid_equal(G_1,G_2) assert G_1 == G_2
@pytest.mark.parametrize('selection',[1,None]) @pytest.mark.parametrize('selection',[1,None])
@ -346,11 +335,10 @@ class TestGrid:
@pytest.mark.parametrize('selection',[list(np.random.randint(1,20,6)),set(np.random.randint(1,20,6)),np.random.randint(1,20,6)]) @pytest.mark.parametrize('selection',[list(np.random.randint(1,20,6)),set(np.random.randint(1,20,6)),np.random.randint(1,20,6)])
@pytest.mark.parametrize('invert',[True,False]) @pytest.mark.parametrize('invert',[True,False])
def test_vicinit_offset_invert(self,random,selection,invert): def test_vicinity_offset_invert(self,random,selection,invert):
selection_inverse = set(random.material.flatten()) - set(selection) selection_inverse = set(random.material.flatten()) - set(selection)
assert selection_inverse == set() or \ assert random.vicinity_offset(selection=selection ,invert_selection=not invert) == \
(random.vicinity_offset(selection=selection,invert_selection=invert) == random.vicinity_offset(selection=selection_inverse,invert_selection= invert)
random.vicinity_offset(selection=selection_inverse,invert_selection=not invert))
def test_vicinity_offset_selection_empty(self,random): def test_vicinity_offset_selection_empty(self,random):
assert random.vicinity_offset(selection=None,invert_selection=False) == random.vicinity_offset() and \ assert random.vicinity_offset(selection=None,invert_selection=False) == random.vicinity_offset() and \
@ -372,7 +360,7 @@ class TestGrid:
seeds = np.random.rand(N_seeds,3) * np.broadcast_to(size,(N_seeds,3)) seeds = np.random.rand(N_seeds,3) * np.broadcast_to(size,(N_seeds,3))
Voronoi = Grid.from_Voronoi_tessellation( cells,size,seeds, np.arange(N_seeds)+5,periodic) Voronoi = Grid.from_Voronoi_tessellation( cells,size,seeds, np.arange(N_seeds)+5,periodic)
Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,np.ones(N_seeds),np.arange(N_seeds)+5,periodic) Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,np.ones(N_seeds),np.arange(N_seeds)+5,periodic)
assert grid_equal(Laguerre,Voronoi) assert Laguerre == Voronoi
def test_Laguerre_weights(self): def test_Laguerre_weights(self):
@ -449,7 +437,7 @@ class TestGrid:
coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3,order='F') coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3,order='F')
z = np.ones(cells.prod()) z = np.ones(cells.prod())
z[cells[:2].prod()*int(cells[2]/2):] = 0 z[cells[:2].prod()*int(cells[2]/2):] = 0
t = Table(np.column_stack((coords,z)),{'coords':3,'z':1}) t = Table({'coords':3,'z':1},np.column_stack((coords,z)))
t = t.add('indicator',t.get('coords')[:,0]) t = t.add('indicator',t.get('coords')[:,0])
g = Grid.from_table(t,'coords',['indicator','z']) g = Grid.from_table(t,'coords',['indicator','z'])
assert g.N_materials == g.cells[0]*2 and (g.material[:,:,-1]-g.material[:,:,0] == cells[0]).all() assert g.N_materials == g.cells[0]*2 and (g.material[:,:,-1]-g.material[:,:,0] == cells[0]).all()
@ -461,8 +449,8 @@ class TestGrid:
s = seeds.from_random(size,np.random.randint(60,100)) s = seeds.from_random(size,np.random.randint(60,100))
grid = Grid.from_Voronoi_tessellation(cells,size,s) grid = Grid.from_Voronoi_tessellation(cells,size,s)
coords = grid_filters.coordinates0_point(cells,size) coords = grid_filters.coordinates0_point(cells,size)
t = Table(np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F'))),{'c':3,'m':1}) t = Table({'c':3,'m':1},np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F'))))
assert grid_equal(grid.sort().renumber(),Grid.from_table(t,'c',['m'])) assert grid.sort().renumber() == Grid.from_table(t,'c',['m'])
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
@ -496,7 +484,7 @@ class TestGrid:
if update: if update:
current.save(ref_path/'measured.vti') current.save(ref_path/'measured.vti')
assert grid_equal(current,reference) assert current == reference
def test_load_Neper_reference(self,ref_path,update): def test_load_Neper_reference(self,ref_path,update):
current = Grid.load_Neper(ref_path/'n10-id1_scaled.vtk') current = Grid.load_Neper(ref_path/'n10-id1_scaled.vtk')
@ -504,4 +492,4 @@ class TestGrid:
if update: if update:
current.save(ref_path/'n10-id1_scaled.vti') current.save(ref_path/'n10-id1_scaled.vti')
assert grid_equal(current,reference) assert current == reference

View File

@ -8,7 +8,9 @@ from damask import Table
def default(): def default():
"""Simple Table.""" """Simple Table."""
x = np.ones((5,13),dtype=float) x = np.ones((5,13),dtype=float)
return Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['test data','contains five rows of only ones']) return Table({'F':(3,3),'v':(3,),'s':(1,)},
x,
['test data','contains five rows of only ones'])
@pytest.fixture @pytest.fixture
def ref_path(ref_path_base): def ref_path(ref_path_base):
@ -22,7 +24,7 @@ class TestTable:
@pytest.mark.parametrize('N',[10,40]) @pytest.mark.parametrize('N',[10,40])
def test_len(self,N): def test_len(self,N):
assert len(Table(np.random.rand(N,3),{'X':3})) == N assert len(Table({'X':3},np.random.rand(N,3))) == N
def test_get_scalar(self,default): def test_get_scalar(self,default):
d = default.get('s') d = default.get('s')
@ -110,7 +112,7 @@ class TestTable:
def test_rename_equivalent(self): def test_rename_equivalent(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
t = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) t = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
s = t.get('s') s = t.get('s')
u = t.rename('s','u').get('u') u = t.rename('s','u').get('u')
assert np.all(s == u) assert np.all(s == u)
@ -129,35 +131,35 @@ class TestTable:
def test_join(self): def test_join(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
a = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) a = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
y = np.random.random((5,3)) y = np.random.random((5,3))
b = Table(y,{'u':(3,)},['random test data']) b = Table({'u':(3,)},y,['random test data'])
c = a.join(b) c = a.join(b)
assert np.array_equal(c.get('u'), b.get('u')) assert np.array_equal(c.get('u'), b.get('u'))
def test_join_invalid(self): def test_join_invalid(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
a = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) a = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
with pytest.raises(KeyError): with pytest.raises(KeyError):
a.join(a) a.join(a)
def test_append(self): def test_append(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
a = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) a = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
b = a.append(a) b = a.append(a)
assert np.array_equal(b.data[:5].to_numpy(),b.data[5:].to_numpy()) assert np.array_equal(b.data[:5].to_numpy(),b.data[5:].to_numpy())
def test_append_invalid(self): def test_append_invalid(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
a = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) a = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
b = Table(x,{'F':(3,3),'u':(3,),'s':(1,)},['random test data']) b = Table({'F':(3,3),'u':(3,),'s':(1,)},x,['random test data'])
with pytest.raises(KeyError): with pytest.raises(KeyError):
a.append(b) a.append(b)
def test_invalid_initialization(self): def test_invalid_initialization(self):
x = np.random.random((5,10)) x = np.random.random((5,10))
with pytest.raises(ValueError): with pytest.raises(ValueError):
Table(x,{'F':(3,3)}) Table({'F':(3,3)},x)
def test_invalid_set(self,default): def test_invalid_set(self,default):
x = default.get('v') x = default.get('v')
@ -170,27 +172,27 @@ class TestTable:
def test_sort_scalar(self): def test_sort_scalar(self):
x = np.random.random((5,13)) x = np.random.random((5,13))
t = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) t = Table({'F':(3,3),'v':(3,),'s':(1,)},x,['random test data'])
unsort = t.get('s') unsort = t.get('s')
sort = t.sort_by('s').get('s') sort = t.sort_by('s').get('s')
assert np.all(np.sort(unsort,0)==sort) assert np.all(np.sort(unsort,0)==sort)
def test_sort_component(self): def test_sort_component(self):
x = np.random.random((5,12)) x = np.random.random((5,12))
t = Table(x,{'F':(3,3),'v':(3,)},['random test data']) t = Table({'F':(3,3),'v':(3,)},x,['random test data'])
unsort = t.get('F')[:,1,0] unsort = t.get('F')[:,1,0]
sort = t.sort_by('F[1,0]').get('F')[:,1,0] sort = t.sort_by('F[1,0]').get('F')[:,1,0]
assert np.all(np.sort(unsort,0)==sort) assert np.all(np.sort(unsort,0)==sort)
def test_sort_revert(self): def test_sort_revert(self):
x = np.random.random((5,12)) x = np.random.random((5,12))
t = Table(x,{'F':(3,3),'v':(3,)},['random test data']) t = Table({'F':(3,3),'v':(3,)},x,['random test data'])
sort = t.sort_by('F[1,0]',ascending=False).get('F')[:,1,0] sort = t.sort_by('F[1,0]',ascending=False).get('F')[:,1,0]
assert np.all(np.sort(sort,0)==sort[::-1]) assert np.all(np.sort(sort,0)==sort[::-1])
def test_sort(self): def test_sort(self):
t = Table(np.array([[0,1,],[2,1,]]), t = Table({'v':(2,)},
{'v':(2,)}, np.array([[0,1,],[2,1,]]),
['test data'])\ ['test data'])\
.add('s',np.array(['b','a']))\ .add('s',np.array(['b','a']))\
.sort_by('s') .sort_by('s')

View File

@ -147,16 +147,22 @@ class TestVTK:
def test_invalid_add_shape(self,default): def test_invalid_add_shape(self,default):
with pytest.raises(ValueError): with pytest.raises(ValueError):
default.add(np.ones(3),'valid') default.add('valid',np.ones(3))
def test_invalid_add_missing_label(self,default): def test_invalid_add_missing_label(self,default):
data = np.random.randint(9,size=np.prod(np.array(default.vtk_data.GetDimensions())-1)) data = np.random.randint(9,size=np.prod(np.array(default.vtk_data.GetDimensions())-1))
with pytest.raises(ValueError): with pytest.raises(ValueError):
default.add(data) default.add(data=data)
def test_invalid_add_type(self,default): def test_invalid_add_type(self,default):
with pytest.raises(TypeError): with pytest.raises(TypeError):
default.add('invalid_type','valid') default.add(label='valid',data='invalid_type')
with pytest.raises(TypeError):
default.add(label='valid',table='invalid_type')
def test_invalid_add_dual(self,default):
with pytest.raises(KeyError):
default.add(label='valid',data=0,table=0)
@pytest.mark.parametrize('data_type,shape',[(float,(3,)), @pytest.mark.parametrize('data_type,shape',[(float,(3,)),
(float,(3,3)), (float,(3,3)),
@ -166,7 +172,7 @@ class TestVTK:
@pytest.mark.parametrize('N_values',[5*6*7,6*7*8]) @pytest.mark.parametrize('N_values',[5*6*7,6*7*8])
def test_add_get(self,default,data_type,shape,N_values): def test_add_get(self,default,data_type,shape,N_values):
data = np.squeeze(np.random.randint(0,100,(N_values,)+shape)).astype(data_type) data = np.squeeze(np.random.randint(0,100,(N_values,)+shape)).astype(data_type)
new = default.add(data,'data') new = default.add('data',data)
assert (np.squeeze(data.reshape(N_values,-1)) == new.get('data')).all() assert (np.squeeze(data.reshape(N_values,-1)) == new.get('data')).all()
@ -179,7 +185,7 @@ class TestVTK:
for k,s in shapes.items(): for k,s in shapes.items():
d[k] = dict(shape = s, d[k] = dict(shape = s,
data = np.random.random(N*np.prod(s)).reshape((N,-1))) data = np.random.random(N*np.prod(s)).reshape((N,-1)))
new = default.add(Table(np.column_stack([d[k]['data'] for k in shapes.keys()]),shapes)) new = default.add(table=Table(shapes,np.column_stack([d[k]['data'] for k in shapes.keys()])))
for k,s in shapes.items(): for k,s in shapes.items():
assert np.allclose(np.squeeze(d[k]['data']),new.get(k),rtol=1e-7) assert np.allclose(np.squeeze(d[k]['data']),new.get(k),rtol=1e-7)
@ -187,8 +193,8 @@ class TestVTK:
def test_add_masked(self,default): def test_add_masked(self,default):
data = np.random.rand(5*6*7,3) data = np.random.rand(5*6*7,3)
masked = ma.MaskedArray(data,mask=data<.4,fill_value=42.) masked = ma.MaskedArray(data,mask=data<.4,fill_value=42.)
mask_auto = default.add(masked,'D') mask_auto = default.add('D',masked)
mask_manual = default.add(np.where(masked.mask,masked.fill_value,masked),'D') mask_manual = default.add('D',np.where(masked.mask,masked.fill_value,masked))
assert mask_manual == mask_auto assert mask_manual == mask_auto
@ -202,7 +208,7 @@ class TestVTK:
data = np.squeeze(np.random.randint(0,100,(N_values,)+shape)).astype(data_type) data = np.squeeze(np.random.randint(0,100,(N_values,)+shape)).astype(data_type)
ALPHABET = np.array(list(string.ascii_lowercase + ' ')) ALPHABET = np.array(list(string.ascii_lowercase + ' '))
label = ''.join(np.random.choice(ALPHABET, size=10)) label = ''.join(np.random.choice(ALPHABET, size=10))
new = default.add(data,label) new = default.add(label,data)
if N_values == default.N_points: assert label in new.labels['Point Data'] if N_values == default.N_points: assert label in new.labels['Point Data']
if N_values == default.N_cells: assert label in new.labels['Cell Data'] if N_values == default.N_cells: assert label in new.labels['Cell Data']
@ -217,7 +223,7 @@ class TestVTK:
@pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<8, reason='missing METADATA') @pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<8, reason='missing METADATA')
def test_compare_reference_polyData(self,update,ref_path,tmp_path): def test_compare_reference_polyData(self,update,ref_path,tmp_path):
points=np.dstack((np.linspace(0.,1.,10),np.linspace(0.,2.,10),np.linspace(-1.,1.,10))).squeeze() points=np.dstack((np.linspace(0.,1.,10),np.linspace(0.,2.,10),np.linspace(-1.,1.,10))).squeeze()
polyData = VTK.from_poly_data(points).add(points,'coordinates') polyData = VTK.from_poly_data(points).add('coordinates',points)
if update: if update:
polyData.save(ref_path/'polyData') polyData.save(ref_path/'polyData')
else: else:
@ -234,8 +240,8 @@ class TestVTK:
c = coords[:-1,:-1,:-1,:].reshape(-1,3,order='F') c = coords[:-1,:-1,:-1,:].reshape(-1,3,order='F')
n = coords[:,:,:,:].reshape(-1,3,order='F') n = coords[:,:,:,:].reshape(-1,3,order='F')
rectilinearGrid = VTK.from_rectilinear_grid(grid) \ rectilinearGrid = VTK.from_rectilinear_grid(grid) \
.add(np.ascontiguousarray(c),'cell') \ .add('cell',np.ascontiguousarray(c)) \
.add(np.ascontiguousarray(n),'node') .add('node',np.ascontiguousarray(n))
if update: if update:
rectilinearGrid.save(ref_path/'rectilinearGrid') rectilinearGrid.save(ref_path/'rectilinearGrid')
else: else: