unifying interface

same same for same functionality, allow user to specify single integer
for convenience
This commit is contained in:
Martin Diehl 2022-02-27 16:16:09 +01:00
parent 53fe11484d
commit f9e04bc4cb
5 changed files with 61 additions and 43 deletions

View File

@ -4,7 +4,7 @@ import warnings
import multiprocessing as mp import multiprocessing as mp
from functools import partial from functools import partial
import typing import typing
from typing import Union, Optional, TextIO, List, Sequence from typing import Union, Optional, TextIO, List, Sequence, Collection
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
@ -17,7 +17,7 @@ from . import util
from . import grid_filters from . import grid_filters
from . import Rotation from . import Rotation
from . import Table from . import Table
from ._typehints import FloatSequence, IntSequence from ._typehints import FloatSequence, IntSequence, IntCollection
class Grid: class Grid:
""" """
@ -907,7 +907,8 @@ class Grid:
def clean(self, def clean(self,
stencil: int = 3, stencil: int = 3,
mutable: IntSequence = None, selection: IntCollection = None,
invert_selection: bool = False,
periodic: bool = True) -> 'Grid': periodic: bool = True) -> 'Grid':
""" """
Smooth grid by selecting most frequent material ID within given stencil at each location. Smooth grid by selecting most frequent material ID within given stencil at each location.
@ -916,8 +917,10 @@ class Grid:
---------- ----------
stencil : int, optional stencil : int, optional
Size of smoothing stencil. Defaults to 3. Size of smoothing stencil. Defaults to 3.
mutable : sequence of int, optional selection : int or collection of int, optional
Material ID that can be altered. Defaults to all. Material IDs to consider.
invert_selection : bool, optional
Consider all material IDs except those in selection. Defaults to False.
periodic : bool, optional periodic : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
@ -927,21 +930,23 @@ class Grid:
Updated grid-based geometry. Updated grid-based geometry.
""" """
def mostFrequent(arr: np.ndarray, mutable = None): def mostFrequent(arr: np.ndarray, selection: List, invert: bool):
me = arr[arr.size//2] me = arr[arr.size//2]
if selection is None or me in mutable: if len(selection) == 0 or np.isin(me,selection,invert=invert):
unique, inverse = np.unique(arr, return_inverse=True) unique, inverse = np.unique(arr,return_inverse=True)
return unique[np.argmax(np.bincount(inverse))] return unique[np.argmax(np.bincount(inverse))]
else: else:
return me return me
return Grid(material = ndimage.filters.generic_filter( extra_keywords = dict(selection=util.tbd(selection),invert=invert_selection)
self.material, material = ndimage.filters.generic_filter(
mostFrequent, self.material,
size=(stencil if mutable is None else stencil//2*2+1,)*3, mostFrequent,
mode=('wrap' if periodic else 'nearest'), size=(stencil if selection is None else stencil//2*2+1,)*3,
extra_keywords=dict(mutable=mutable), mode=('wrap' if periodic else 'nearest'),
).astype(self.material.dtype), extra_keywords=extra_keywords,
).astype(self.material.dtype)
return Grid(material = material,
size = self.size, size = self.size,
origin = self.origin, origin = self.origin,
comments = self.comments+[util.execution_stamp('Grid','clean')], comments = self.comments+[util.execution_stamp('Grid','clean')],
@ -1061,16 +1066,16 @@ class Grid:
def substitute(self, def substitute(self,
from_material: IntSequence, from_material: Union[int,IntSequence],
to_material: IntSequence) -> 'Grid': to_material: Union[int,IntSequence]) -> 'Grid':
""" """
Substitute material indices. Substitute material indices.
Parameters Parameters
---------- ----------
from_material : sequence of int from_material : int or sequence of int
Material indices to be substituted. Material indices to be substituted.
to_material : sequence of int to_material : int or sequence of int
New material indices. New material indices.
Returns Returns
@ -1080,7 +1085,8 @@ class Grid:
""" """
material = self.material.copy() material = self.material.copy()
for f,t in zip(from_material,to_material): # ToDo Python 3.10 has strict mode for zip 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 material[self.material==f] = t
return Grid(material = material, return Grid(material = material,
@ -1115,14 +1121,14 @@ class Grid:
def vicinity_offset(self, def vicinity_offset(self,
vicinity: int = 1, vicinity: int = 1,
offset: int = None, offset: int = None,
trigger: IntSequence = [], selection: IntCollection = None,
invert_selection: bool = False,
periodic: bool = True) -> 'Grid': periodic: bool = True) -> 'Grid':
""" """
Offset material ID of points in the vicinity of xxx. Offset material ID of points in the vicinity of xxx.
Different from themselves (or listed as triggers) within a given (cubic) vicinity, Different from themselves (or listed as triggers) within a given (cubic) vicinity,
i.e. within the region close to a grain/phase boundary. i.e. within the region close to a grain/phase boundary.
ToDo: use include/exclude as in seeds.from_grid
Parameters Parameters
---------- ----------
@ -1132,9 +1138,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 : sequence of int, optional selection : int or collection of int, optional
List of material indices that trigger a change. Material IDs to that triger xxx.
Defaults to [], meaning that any different neighbor triggers a change. invert_selection : bool, optional
Consider all material IDs except those in selection. Defaults to False.
periodic : bool, optional periodic : bool, optional
Assume grid to be periodic. Defaults to True. Assume grid to be periodic. Defaults to True.
@ -1144,17 +1151,19 @@ class Grid:
Updated grid-based geometry. Updated grid-based geometry.
""" """
def tainted_neighborhood(stencil: np.ndarray, trigger): def tainted_neighborhood(stencil: np.ndarray, selection):
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(selection) == 0 else
np.in1d(stencil,np.array(list(set(trigger) - {me})))) np.in1d(stencil,np.array(list(set(selection) - {me}))))
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_ = util.tbd(selection) if not invert_selection else \
list(set(self.material) - set(util.tbd(selection)))
mask = ndimage.filters.generic_filter(self.material, mask = ndimage.filters.generic_filter(self.material,
tainted_neighborhood, tainted_neighborhood,
size=1+2*vicinity, size=1+2*vicinity,
mode='wrap' if periodic else 'nearest', mode='wrap' if periodic else 'nearest',
extra_keywords={'trigger':trigger}) extra_keywords=dict(selection=selection_))
return Grid(material = np.where(mask, self.material + offset_,self.material), return Grid(material = np.where(mask, self.material + offset_,self.material),
size = self.size, size = self.size,

View File

@ -1,6 +1,6 @@
"""Functionality for typehints.""" """Functionality for typehints."""
from typing import Sequence, Union, Literal, TextIO from typing import Sequence, Union, Literal, TextIO, Collection
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
@ -8,6 +8,7 @@ import numpy as np
FloatSequence = Union[np.ndarray,Sequence[float]] FloatSequence = Union[np.ndarray,Sequence[float]]
IntSequence = Union[np.ndarray,Sequence[int]] IntSequence = Union[np.ndarray,Sequence[int]]
IntCollection = Union[np.ndarray,Collection[int]]
FileHandle = Union[TextIO, str, Path] FileHandle = Union[TextIO, str, Path]
CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']] CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']]
CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']] CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']]

View File

@ -6,7 +6,8 @@ from typing import Tuple as _Tuple
from scipy import spatial as _spatial from scipy import spatial as _spatial
import numpy as _np import numpy as _np
from ._typehints import FloatSequence as _FloatSequence, IntSequence as _IntSequence, NumpyRngSeed as _NumpyRngSeed from ._typehints import FloatSequence as _FloatSequence, IntSequence as _IntSequence, \
NumpyRngSeed as _NumpyRngSeed, IntCollection as _IntCollection
from . import util as _util from . import util as _util
from . import grid_filters as _grid_filters from . import grid_filters as _grid_filters
@ -106,7 +107,7 @@ def from_Poisson_disc(size: _FloatSequence,
def from_grid(grid, def from_grid(grid,
selection: _IntSequence = None, selection: _IntCollection = None,
invert_selection: bool = False, invert_selection: bool = False,
average: bool = False, average: bool = False,
periodic: bool = True) -> _Tuple[_np.ndarray, _np.ndarray]: periodic: bool = True) -> _Tuple[_np.ndarray, _np.ndarray]:
@ -117,7 +118,7 @@ def from_grid(grid,
---------- ----------
grid : damask.Grid grid : damask.Grid
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 : int or collection of int, optional
Material IDs to consider. Material IDs to consider.
invert_selection : bool, optional 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.
@ -133,8 +134,9 @@ def from_grid(grid,
""" """
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 \ selection_ = _util.tbd(selection)
_np.isin(material,selection,invert=invert_selection).flatten() mask = _np.full(grid.cells.prod(),True,dtype=bool) if len(selection_) == 0 else \
_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

@ -9,7 +9,7 @@ import re
import fractions import fractions
from collections import abc from collections import abc
from functools import reduce from functools import reduce
from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, Collection
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
@ -720,7 +720,13 @@ def dict_flatten(d: Dict) -> Dict:
return new return new
def tbd(arg) -> List:
if arg is None:
return []
elif isinstance(arg,(np.ndarray,Collection)):
return list(arg)
else:
return [arg]
#################################################################################################### ####################################################################################################
# Classes # Classes

View File

@ -155,7 +155,7 @@ class TestGrid:
@pytest.mark.parametrize('selection',[None,[1],[1,2,3]]) @pytest.mark.parametrize('selection',[None,[1],[1,2,3]])
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
def test_clean(self,default,update,ref_path,stencil,selection,periodic): def test_clean(self,default,update,ref_path,stencil,selection,periodic):
current = default.clean(stencil,selection,periodic) current = default.clean(stencil,selection,periodic=periodic)
reference = ref_path/f'clean_{stencil}_{"+".join(map(str,[None] if selection is None else selection))}_{periodic}.vti' reference = ref_path/f'clean_{stencil}_{"+".join(map(str,[None] if selection is None else selection))}_{periodic}.vti'
if update and stencil > 1: if update and stencil > 1:
current.save(reference) current.save(reference)
@ -296,8 +296,8 @@ class TestGrid:
assert grid_equal(G_1,G_2) assert grid_equal(G_1,G_2)
@pytest.mark.parametrize('trigger',[[1],[]]) @pytest.mark.parametrize('selection',[[1],[]])
def test_vicinity_offset(self,trigger): def test_vicinity_offset(self,selection):
offset = np.random.randint(2,4) offset = np.random.randint(2,4)
vicinity = np.random.randint(2,4) vicinity = np.random.randint(2,4)
@ -309,17 +309,17 @@ class TestGrid:
for i in [0,1,2]: for i in [0,1,2]:
m2[(np.roll(m,+vicinity,i)-m)!=0] += offset m2[(np.roll(m,+vicinity,i)-m)!=0] += offset
m2[(np.roll(m,-vicinity,i)-m)!=0] += offset m2[(np.roll(m,-vicinity,i)-m)!=0] += offset
if len(trigger) > 0: if len(selection) > 0:
m2[m==1] = 1 m2[m==1] = 1
grid = Grid(m,np.random.rand(3)).vicinity_offset(vicinity,offset,trigger=trigger) grid = Grid(m,np.random.rand(3)).vicinity_offset(vicinity,offset,selection=selection)
assert np.all(m2==grid.material) assert np.all(m2==grid.material)
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
def test_vicinity_offset_invariant(self,default,periodic): def test_vicinity_offset_invariant(self,default,periodic):
offset = default.vicinity_offset(trigger=[default.material.max()+1, offset = default.vicinity_offset(selection=[default.material.max()+1,
default.material.min()-1]) default.material.min()-1])
assert np.all(offset.material==default.material) assert np.all(offset.material==default.material)