From 01cc6ffd2c0df6e9ffba8e00c9f3b05353a124bc Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 14 Jan 2022 17:56:58 +0100 Subject: [PATCH 01/11] added minor grid adjustments after initial merge of typehints_grid branch --- python/damask/_grid.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 5006e1b40..ed1b2b57f 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -4,7 +4,7 @@ import warnings import multiprocessing as mp from functools import partial import typing -from typing import Union, Optional, TextIO, List, Sequence +from typing import Union, Optional, TextIO, List, Sequence, Literal, Dict, Callable from pathlib import Path import numpy as np @@ -422,6 +422,7 @@ class Grid: Grid-based geometry from tessellation. """ + weights_p: FloatSequence if periodic: weights_p = np.tile(weights,27) # Laguerre weights (1,2,3,1,2,3,...,1,2,3) seeds_p = np.vstack((seeds -np.array([size[0],0.,0.]),seeds, seeds +np.array([size[0],0.,0.]))) @@ -821,7 +822,7 @@ class Grid: ) - def flip(self, directions: Sequence[str]) -> "Grid": + def flip(self, directions: Union[Literal['x', 'y', 'z'], Sequence[Literal['x', 'y', 'z']]]) -> "Grid": """ Flip grid along given directions. @@ -841,7 +842,8 @@ class Grid: if not set(directions).issubset(valid): 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)) + + mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid]) return Grid(material = mat, size = self.size, @@ -1034,7 +1036,7 @@ class Grid: 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 - canvas = np.full(cells_,fill,dtype) + canvas: np.ndarray = np.full(cells_,fill,dtype) LL = np.clip( offset_, 0,np.minimum(self.cells, cells_+offset_)) UR = np.clip( offset_+cells_, 0,np.minimum(self.cells, cells_+offset_)) @@ -1067,13 +1069,13 @@ class Grid: Updated grid-based geometry. """ - def mp(entry, mapper): + def mp(entry: np.ndarray, mapper:Dict[np.ndarray, np.ndarray]) -> np.ndarray: return mapper[entry] if entry in mapper else entry - mp = np.vectorize(mp) + mp_: Callable = np.vectorize(mp) mapper = dict(zip(from_material,to_material)) - return Grid(material = mp(self.material,mapper).reshape(self.cells), + return Grid(material = mp_(self.material,mapper).reshape(self.cells), size = self.size, origin = self.origin, comments = self.comments+[util.execution_stamp('Grid','substitute')], From b796bc0697edf9978ae461e433eb6fe7b8ea78ef Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 14 Jan 2022 20:44:34 +0100 Subject: [PATCH 02/11] simplified --- python/damask/_grid.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/python/damask/_grid.py b/python/damask/_grid.py index ed1b2b57f..9b24e5600 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -4,7 +4,7 @@ import warnings import multiprocessing as mp from functools import partial import typing -from typing import Union, Optional, TextIO, List, Sequence, Literal, Dict, Callable +from typing import Union, Optional, TextIO, List, Sequence, Literal from pathlib import Path import numpy as np @@ -1069,13 +1069,11 @@ class Grid: Updated grid-based geometry. """ - def mp(entry: np.ndarray, mapper:Dict[np.ndarray, np.ndarray]) -> np.ndarray: - return mapper[entry] if entry in mapper else entry + material = self.material.copy() + for f,t in zip(from_material,to_material): # ToDo Python 3.10 has strict mode for zip + material[self.material==f] = t - mp_: Callable = np.vectorize(mp) - mapper = dict(zip(from_material,to_material)) - - return Grid(material = mp_(self.material,mapper).reshape(self.cells), + return Grid(material = material, size = self.size, origin = self.origin, comments = self.comments+[util.execution_stamp('Grid','substitute')], From adf7abbda62be655b04b69fea18119b8a0a29800 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Mon, 17 Jan 2022 14:58:08 +0100 Subject: [PATCH 03/11] added typehints for util module --- python/damask/util.py | 75 +++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index 0581302db..a28a9e2eb 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -8,6 +8,8 @@ import shlex import re import fractions from functools import reduce +from typing import Union, Tuple, Sequence, Callable, Dict, List, Any, Literal +import pathlib import numpy as np import h5py @@ -50,7 +52,7 @@ _colors = { #################################################################################################### # Functions #################################################################################################### -def srepr(arg,glue = '\n'): +def srepr(arg: Union[np.ndarray, Sequence[Any]], glue: str = '\n') -> str: r""" Join items with glue string. @@ -75,7 +77,7 @@ def srepr(arg,glue = '\n'): return arg if isinstance(arg,str) else repr(arg) -def emph(what): +def emph(what: Any) -> str: """ Format with emphasis. @@ -92,7 +94,7 @@ def emph(what): """ return _colors['bold']+srepr(what)+_colors['end_color'] -def deemph(what): +def deemph(what: Any) -> str: """ Format with deemphasis. @@ -109,7 +111,7 @@ def deemph(what): """ return _colors['dim']+srepr(what)+_colors['end_color'] -def warn(what): +def warn(what: Any) -> str: """ Format for warning. @@ -126,7 +128,7 @@ def warn(what): """ return _colors['warning']+emph(what)+_colors['end_color'] -def strikeout(what): +def strikeout(what: Any) -> str: """ Format as strikeout. @@ -144,7 +146,7 @@ def strikeout(what): return _colors['crossout']+srepr(what)+_colors['end_color'] -def run(cmd,wd='./',env=None,timeout=None): +def run(cmd: str, wd: str = './', env: Dict[str, Any] = None, timeout: int = None) -> Tuple[str, str]: """ Run a command. @@ -185,7 +187,7 @@ def run(cmd,wd='./',env=None,timeout=None): execute = run -def natural_sort(key): +def natural_sort(key: str) -> List[Union[int, str]]: """ Natural sort. @@ -200,7 +202,10 @@ def natural_sort(key): return [ convert(c) for c in re.split('([0-9]+)', key) ] -def show_progress(iterable,N_iter=None,prefix='',bar_length=50): +def show_progress(iterable: Sequence[Any], + N_iter: int = None, + prefix: str = '', + bar_length: int = 50) -> Any: """ Decorate a loop with a progress bar. @@ -229,7 +234,7 @@ def show_progress(iterable,N_iter=None,prefix='',bar_length=50): status.update(i) -def scale_to_coprime(v): +def scale_to_coprime(v: np.ndarray) -> np.ndarray: """ Scale vector to co-prime (relatively prime) integers. @@ -267,13 +272,13 @@ def scale_to_coprime(v): return m -def project_equal_angle(vector,direction='z',normalize=True,keepdims=False): +def project_equal_angle(vector: np.ndarray, direction: str = 'z', normalize: bool = True, keepdims: bool = False) -> np.ndarray: """ Apply equal-angle projection to vector. Parameters ---------- - vector : numpy.ndarray, shape (...,3) + vector : numpy.ndarray of shape (...,3) Vector coordinates to be projected. direction : str Projection direction 'x', 'y', or 'z'. @@ -309,7 +314,10 @@ def project_equal_angle(vector,direction='z',normalize=True,keepdims=False): return np.roll(np.block([v[...,:2]/(1.0+np.abs(v[...,2:3])),np.zeros_like(v[...,2:3])]), -shift if keepdims else 0,axis=-1)[...,:3 if keepdims else 2] -def project_equal_area(vector,direction='z',normalize=True,keepdims=False): +def project_equal_area(vector: np.ndarray, + direction: Literal['x', 'y', 'z'] = 'z', + normalize: bool = True, + keepdims: bool = False) -> np.ndarray: """ Apply equal-area projection to vector. @@ -351,15 +359,14 @@ def project_equal_area(vector,direction='z',normalize=True,keepdims=False): return np.roll(np.block([v[...,:2]/np.sqrt(1.0+np.abs(v[...,2:3])),np.zeros_like(v[...,2:3])]), -shift if keepdims else 0,axis=-1)[...,:3 if keepdims else 2] - -def execution_stamp(class_name,function_name=None): +def execution_stamp(class_name: str, function_name: str = None) -> str: """Timestamp the execution of a (function within a) class.""" now = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z') _function_name = '' if function_name is None else f'.{function_name}' return f'damask.{class_name}{_function_name} v{version} ({now})' -def hybrid_IA(dist,N,rng_seed=None): +def hybrid_IA(dist: np.ndarray, N: int, rng_seed: Union[int, np.ndarray] = None) -> np.ndarray: """ Hybrid integer approximation. @@ -387,7 +394,10 @@ def hybrid_IA(dist,N,rng_seed=None): return np.repeat(np.arange(len(dist)),repeats)[np.random.default_rng(rng_seed).permutation(N_inv_samples)[:N]] -def shapeshifter(fro,to,mode='left',keep_ones=False): +def shapeshifter(fro: Tuple[int, ...], + to: Tuple[int, ...], + mode: Literal['left','right'] = 'left', + keep_ones: bool = False) -> Tuple[int, ...]: """ Return dimensions that reshape 'fro' to become broadcastable to 'to'. @@ -434,21 +444,22 @@ def shapeshifter(fro,to,mode='left',keep_ones=False): fro = (1,) if not len(fro) else fro to = (1,) if not len(to) else to try: - grp = re.match(beg[mode] + match = re.match((beg[mode] +f',{sep[mode]}'.join(map(lambda x: f'{x}' if x>1 or (keep_ones and len(fro)>1) else '\\d+',fro)) - +f',{end[mode]}', - ','.join(map(str,to))+',').groups() - except AttributeError: + +f',{end[mode]}'),','.join(map(str,to))+',') + assert match + except AssertionError: raise ValueError(f'Shapes can not be shifted {fro} --> {to}') - fill = () + grp: Sequence[str] = match.groups() + fill: Tuple[int, ...] = () for g,d in zip(grp,fro+(None,)): fill += (1,)*g.count(',')+(d,) return fill[:-1] -def shapeblender(a,b): +def shapeblender(a: Tuple[int, ...], b: Tuple[int, ...]) -> Tuple[int, ...]: """ Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. @@ -476,7 +487,7 @@ def shapeblender(a,b): return a + b[i:] -def extend_docstring(extra_docstring): +def extend_docstring(extra_docstring: str) -> Callable: """ Decorator: Append to function's docstring. @@ -492,7 +503,7 @@ def extend_docstring(extra_docstring): return _decorator -def extended_docstring(f,extra_docstring): +def extended_docstring(f: Callable, extra_docstring: str) -> Callable: """ Decorator: Combine another function's docstring with a given docstring. @@ -510,7 +521,7 @@ def extended_docstring(f,extra_docstring): return _decorator -def DREAM3D_base_group(fname): +def DREAM3D_base_group(fname: Union[str, pathlib.Path]) -> str: """ Determine the base group of a DREAM.3D file. @@ -536,7 +547,7 @@ def DREAM3D_base_group(fname): return base_group -def DREAM3D_cell_data_group(fname): +def DREAM3D_cell_data_group(fname: Union[str, pathlib.Path]) -> str: """ Determine the cell data group of a DREAM.3D file. @@ -568,7 +579,7 @@ def DREAM3D_cell_data_group(fname): return cell_data_group -def Bravais_to_Miller(*,uvtw=None,hkil=None): +def Bravais_to_Miller(*, uvtw: np.ndarray = None, hkil: np.ndarray = None) -> np.ndarray: """ Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). @@ -595,7 +606,7 @@ def Bravais_to_Miller(*,uvtw=None,hkil=None): return np.einsum('il,...l',basis,axis) -def Miller_to_Bravais(*,uvw=None,hkl=None): +def Miller_to_Bravais(*, uvw: np.ndarray = None, hkl: np.ndarray = None) -> np.ndarray: """ Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). @@ -624,7 +635,7 @@ def Miller_to_Bravais(*,uvw=None,hkl=None): return np.einsum('il,...l',basis,axis) -def dict_prune(d): +def dict_prune(d: Dict[Any, Any]) -> Dict[Any, Any]: """ Recursively remove empty dictionaries. @@ -650,7 +661,7 @@ def dict_prune(d): return new -def dict_flatten(d): +def dict_flatten(d: Dict[Any, Any]) -> Dict[Any, Any]: """ Recursively remove keys of single-entry dictionaries. @@ -685,7 +696,7 @@ class _ProgressBar: Works for 0-based loops, ETA is estimated by linear extrapolation. """ - def __init__(self,total,prefix,bar_length): + def __init__(self, total: int, prefix: str, bar_length: int): """ Set current time as basis for ETA estimation. @@ -708,7 +719,7 @@ class _ProgressBar: sys.stderr.write(f"{self.prefix} {'░'*self.bar_length} 0% ETA n/a") sys.stderr.flush() - def update(self,iteration): + def update(self, iteration: int) -> None: fraction = (iteration+1) / self.total filled_length = int(self.bar_length * fraction) From 7b158ba10899de258a48142b6737faea9596a946 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Mon, 17 Jan 2022 15:00:25 +0100 Subject: [PATCH 04/11] added typehints for vtk module --- python/damask/_vtk.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index dbbfb1b10..561726153 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -2,6 +2,7 @@ import os import warnings import multiprocessing as mp from pathlib import Path +from typing import Union, Optional, Literal, List import numpy as np import vtk @@ -20,7 +21,7 @@ class VTK: High-level interface to VTK. """ - def __init__(self,vtk_data): + def __init__(self, vtk_data: vtk.vtkImageData): """ New spatial visualization. @@ -36,7 +37,8 @@ class VTK: @staticmethod - def from_image_data(cells,size,origin=np.zeros(3)): + #ITERABLES PROPER + def from_image_data(cells: np.ndarray, size: np.ndarray, origin: Optional[np.ndarray] = np.zeros(3)) -> "VTK": """ Create VTK of type vtk.vtkImageData. @@ -66,7 +68,7 @@ class VTK: @staticmethod - def from_rectilinear_grid(grid,size,origin=np.zeros(3)): + def from_rectilinear_grid(grid: np.ndarray, size: np.ndarray, origin: np.ndarray = np.zeros(3)) -> "VTK": """ Create VTK of type vtk.vtkRectilinearGrid. @@ -98,7 +100,7 @@ class VTK: @staticmethod - def from_unstructured_grid(nodes,connectivity,cell_type): + def from_unstructured_grid(nodes: np.ndarray, connectivity: np.ndarray, cell_type: str) -> "VTK": """ Create VTK of type vtk.vtkUnstructuredGrid. @@ -138,7 +140,7 @@ class VTK: @staticmethod - def from_poly_data(points): + def from_poly_data(points: np.ndarray) -> "VTK": """ Create VTK of type vtk.polyData. @@ -172,7 +174,8 @@ class VTK: @staticmethod - def load(fname,dataset_type=None): + def load(fname: Union[str, Path], + dataset_type: Literal['vtkImageData', 'vtkRectilinearGrid', 'vtkUnstructuredGrid', 'vtkPolyData'] = None) -> "VTK": """ Load from VTK file. @@ -189,7 +192,7 @@ class VTK: VTK-based geometry from file. """ - if not os.path.isfile(fname): # vtk has a strange error handling + if not os.path.isfile(fname): # vtk has a strange error handling raise FileNotFoundError(f'No such file: {fname}') ext = Path(fname).suffix if ext == '.vtk' or dataset_type is not None: @@ -234,7 +237,7 @@ class VTK: def _write(writer): """Wrapper for parallel writing.""" writer.Write() - def save(self,fname,parallel=True,compress=True): + def save(self, fname: Union[str, Path], parallel: bool = True, compress: bool = True): """ Save as VTK file. @@ -280,7 +283,7 @@ class VTK: # Check https://blog.kitware.com/ghost-and-blanking-visibility-changes/ for missing data # Needs support for damask.Table - def add(self,data,label=None): + def add(self, data: Union[np.ndarray, np.ma.MaskedArray], label: str = None): """ Add data to either cells or points. @@ -327,7 +330,7 @@ class VTK: raise TypeError - def get(self,label): + def get(self, label: str) -> np.ndarray: """ Get either cell or point data. @@ -369,7 +372,7 @@ class VTK: raise ValueError(f'Array "{label}" not found.') - def get_comments(self): + def get_comments(self) -> List[str]: """Return the comments.""" fielddata = self.vtk_data.GetFieldData() for a in range(fielddata.GetNumberOfArrays()): @@ -379,7 +382,7 @@ class VTK: return [] - def set_comments(self,comments): + def set_comments(self, comments: Union[str, List[str]]): """ Set comments. @@ -396,7 +399,7 @@ class VTK: self.vtk_data.GetFieldData().AddArray(s) - def add_comments(self,comments): + def add_comments(self, comments: Union[str, List[str]]): """ Add comments. @@ -409,7 +412,7 @@ class VTK: self.set_comments(self.get_comments() + ([comments] if isinstance(comments,str) else comments)) - def __repr__(self): + def __repr__(self) -> str: """ASCII representation of the VTK data.""" writer = vtk.vtkDataSetWriter() writer.SetHeader(f'# {util.execution_stamp("VTK")}') From 23743c73d47fe9631c3c35e966a484d469f6ff2a Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 19 Jan 2022 10:37:22 +0100 Subject: [PATCH 05/11] adjusted util.hybrid_IA function argument --- python/damask/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/damask/util.py b/python/damask/util.py index a28a9e2eb..053ca9a2e 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -14,6 +14,7 @@ import pathlib import numpy as np import h5py +from ._typehints import IntSequence from . import version # limit visibility @@ -366,7 +367,7 @@ def execution_stamp(class_name: str, function_name: str = None) -> str: return f'damask.{class_name}{_function_name} v{version} ({now})' -def hybrid_IA(dist: np.ndarray, N: int, rng_seed: Union[int, np.ndarray] = None) -> np.ndarray: +def hybrid_IA(dist: np.ndarray, N: int, rng_seed: Union[int, IntSequence] = None) -> np.ndarray: """ Hybrid integer approximation. From 5d8fff423e40f1d945ca42871397e980d363d619 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 19 Jan 2022 10:40:29 +0100 Subject: [PATCH 06/11] replaced vtk.init argument type with vtkDataSet --- python/damask/_vtk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index 561726153..ab2cc5270 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -21,7 +21,7 @@ class VTK: High-level interface to VTK. """ - def __init__(self, vtk_data: vtk.vtkImageData): + def __init__(self, vtk_data: vtk.vtkDataSet): """ New spatial visualization. From f9f0972e3e0a7d83d661a8cf02700da178931d00 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 19 Jan 2022 10:58:33 +0100 Subject: [PATCH 07/11] added FloatSequence type to vtk.from_rectilinear_gridfunction --- python/damask/_vtk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index ab2cc5270..1ebf0c684 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -10,6 +10,7 @@ from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray from vtk.util.numpy_support import vtk_to_numpy as vtk_to_np +from ._typehints import FloatSequence from . import util from . import Table @@ -68,7 +69,7 @@ class VTK: @staticmethod - def from_rectilinear_grid(grid: np.ndarray, size: np.ndarray, origin: np.ndarray = np.zeros(3)) -> "VTK": + def from_rectilinear_grid(grid: np.ndarray, size: FloatSequence, origin: FloatSequence = np.zeros(3)) -> "VTK": """ Create VTK of type vtk.vtkRectilinearGrid. From 76ccd4aaaa7f83b286779a5eb85f57c22ea23441 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 21 Jan 2022 11:45:14 +0100 Subject: [PATCH 08/11] replaced typehint in shapeshifter function --- python/damask/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index 053ca9a2e..37d10a7ad 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -8,7 +8,7 @@ import shlex import re import fractions from functools import reduce -from typing import Union, Tuple, Sequence, Callable, Dict, List, Any, Literal +from typing import Union, Tuple, Sequence, Callable, Dict, List, Any, Literal, Optional import pathlib import numpy as np @@ -398,7 +398,7 @@ def hybrid_IA(dist: np.ndarray, N: int, rng_seed: Union[int, IntSequence] = None def shapeshifter(fro: Tuple[int, ...], to: Tuple[int, ...], mode: Literal['left','right'] = 'left', - keep_ones: bool = False) -> Tuple[int, ...]: + keep_ones: bool = False) -> Tuple[Optional[int], ...]: """ Return dimensions that reshape 'fro' to become broadcastable to 'to'. @@ -454,7 +454,7 @@ def shapeshifter(fro: Tuple[int, ...], except AssertionError: raise ValueError(f'Shapes can not be shifted {fro} --> {to}') grp: Sequence[str] = match.groups() - fill: Tuple[int, ...] = () + fill: Tuple[Optional[int], ...] = () for g,d in zip(grp,fro+(None,)): fill += (1,)*g.count(',')+(d,) return fill[:-1] From 7e9ce682e7403c3f064da1aeee31a7d381af5fb3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 21 Jan 2022 23:50:16 +0100 Subject: [PATCH 09/11] correcting types Not really sure if srepr and friends take really 'Any'. They take everything that can be casted (piecewise) to a string. So keep it open at the moment and leave out a typehint --- python/damask/util.py | 106 +++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index 37d10a7ad..8ae83014c 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -9,13 +9,13 @@ import re import fractions from functools import reduce from typing import Union, Tuple, Sequence, Callable, Dict, List, Any, Literal, Optional -import pathlib +from pathlib import Path import numpy as np import h5py -from ._typehints import IntSequence from . import version +from ._typehints import FloatSequence, IntSequence # limit visibility __all__=[ @@ -53,16 +53,16 @@ _colors = { #################################################################################################### # Functions #################################################################################################### -def srepr(arg: Union[np.ndarray, Sequence[Any]], glue: str = '\n') -> str: +def srepr(msg, glue: str = '\n') -> str: r""" Join items with glue string. Parameters ---------- - arg : iterable + msg : object with __repr__ or sequence of objects with __repr__ Items to join. glue : str, optional - Glue used for joining operation. Defaults to \n. + Glue used for joining operation. Defaults to '\n'. Returns ------- @@ -70,21 +70,21 @@ def srepr(arg: Union[np.ndarray, Sequence[Any]], glue: str = '\n') -> str: String representation of the joined items. """ - if (not hasattr(arg, 'strip') and - (hasattr(arg, '__getitem__') or - hasattr(arg, '__iter__'))): - return glue.join(str(x) for x in arg) + if (not hasattr(msg, 'strip') and + (hasattr(msg, '__getitem__') or + hasattr(msg, '__iter__'))): + return glue.join(str(x) for x in msg) else: - return arg if isinstance(arg,str) else repr(arg) + return msg if isinstance(msg,str) else repr(msg) -def emph(what: Any) -> str: +def emph(msg) -> str: """ Format with emphasis. Parameters ---------- - what : object with __repr__ or iterable of objects with __repr__. + msg : object with __repr__ or sequence of objects with __repr__ Message to format. Returns @@ -93,15 +93,15 @@ def emph(what: Any) -> str: Formatted string representation of the joined items. """ - return _colors['bold']+srepr(what)+_colors['end_color'] + return _colors['bold']+srepr(msg)+_colors['end_color'] -def deemph(what: Any) -> str: +def deemph(msg) -> str: """ Format with deemphasis. Parameters ---------- - what : object with __repr__ or iterable of objects with __repr__. + msg : object with __repr__ or sequence of objects with __repr__ Message to format. Returns @@ -110,15 +110,15 @@ def deemph(what: Any) -> str: Formatted string representation of the joined items. """ - return _colors['dim']+srepr(what)+_colors['end_color'] + return _colors['dim']+srepr(msg)+_colors['end_color'] -def warn(what: Any) -> str: +def warn(msg) -> str: """ Format for warning. Parameters ---------- - what : object with __repr__ or iterable of objects with __repr__. + msg : object with __repr__ or sequence of objects with __repr__ Message to format. Returns @@ -127,15 +127,15 @@ def warn(what: Any) -> str: Formatted string representation of the joined items. """ - return _colors['warning']+emph(what)+_colors['end_color'] + return _colors['warning']+emph(msg)+_colors['end_color'] -def strikeout(what: Any) -> str: +def strikeout(msg) -> str: """ Format as strikeout. Parameters ---------- - what : object with __repr__ or iterable of objects with __repr__. + msg : object with __repr__ or iterable of objects with __repr__ Message to format. Returns @@ -144,10 +144,10 @@ def strikeout(what: Any) -> str: Formatted string representation of the joined items. """ - return _colors['crossout']+srepr(what)+_colors['end_color'] + return _colors['crossout']+srepr(msg)+_colors['end_color'] -def run(cmd: str, wd: str = './', env: Dict[str, Any] = None, timeout: int = None) -> Tuple[str, str]: +def run(cmd: str, wd: str = './', env: Dict[str, str] = None, timeout: int = None) -> Tuple[str, str]: """ Run a command. @@ -156,7 +156,7 @@ def run(cmd: str, wd: str = './', env: Dict[str, Any] = None, timeout: int = Non cmd : str Command to be executed. wd : str, optional - Working directory of process. Defaults to ./ . + Working directory of process. Defaults to './'. env : dict, optional Environment for execution. timeout : integer, optional @@ -235,18 +235,18 @@ def show_progress(iterable: Sequence[Any], status.update(i) -def scale_to_coprime(v: np.ndarray) -> np.ndarray: +def scale_to_coprime(v: FloatSequence) -> np.ndarray: """ Scale vector to co-prime (relatively prime) integers. Parameters ---------- - v : numpy.ndarray of shape (:) + v : sequence of float, len (:) Vector to scale. Returns ------- - m : numpy.ndarray of shape (:) + m : numpy.ndarray, shape (:) Vector scaled to co-prime numbers. """ @@ -263,27 +263,30 @@ def scale_to_coprime(v: np.ndarray) -> np.ndarray: except AttributeError: return a * b // np.gcd(a, b) - m = (np.array(v) * reduce(lcm, map(lambda x: int(get_square_denominator(x)),v)) ** 0.5).astype(int) + v_ = np.array(v) + m = (v_ * reduce(lcm, map(lambda x: int(get_square_denominator(x)),v_))**0.5).astype(int) m = m//reduce(np.gcd,m) with np.errstate(invalid='ignore'): - if not np.allclose(np.ma.masked_invalid(v/m),v[np.argmax(abs(v))]/m[np.argmax(abs(v))]): - raise ValueError(f'Invalid result {m} for input {v}. Insufficient precision?') + if not np.allclose(np.ma.masked_invalid(v_/m),v_[np.argmax(abs(v_))]/m[np.argmax(abs(v_))]): + raise ValueError(f'Invalid result {m} for input {v_}. Insufficient precision?') return m -def project_equal_angle(vector: np.ndarray, direction: str = 'z', normalize: bool = True, keepdims: bool = False) -> np.ndarray: +def project_equal_angle(vector: np.ndarray, + direction: Literal['x', 'y', 'z'] = 'z', + normalize: bool = True, + keepdims: bool = False) -> np.ndarray: """ Apply equal-angle projection to vector. Parameters ---------- - vector : numpy.ndarray of shape (...,3) + vector : numpy.ndarray, shape (...,3) Vector coordinates to be projected. - direction : str - Projection direction 'x', 'y', or 'z'. - Defaults to 'z'. + direction : {'x', 'y', 'z'} + Projection direction. Defaults to 'z'. normalize : bool Ensure unit length of input vector. Defaults to True. keepdims : bool @@ -326,9 +329,8 @@ def project_equal_area(vector: np.ndarray, ---------- vector : numpy.ndarray, shape (...,3) Vector coordinates to be projected. - direction : str - Projection direction 'x', 'y', or 'z'. - Defaults to 'z'. + direction : {'x', 'y', 'z'} + Projection direction. Defaults to 'z'. normalize : bool Ensure unit length of input vector. Defaults to True. keepdims : bool @@ -367,7 +369,7 @@ def execution_stamp(class_name: str, function_name: str = None) -> str: return f'damask.{class_name}{_function_name} v{version} ({now})' -def hybrid_IA(dist: np.ndarray, N: int, rng_seed: Union[int, IntSequence] = None) -> np.ndarray: +def hybrid_IA(dist: np.ndarray, N: int, rng_seed = None) -> np.ndarray: """ Hybrid integer approximation. @@ -409,9 +411,9 @@ def shapeshifter(fro: Tuple[int, ...], to : tuple Target shape of array after broadcasting. len(to) cannot be less than len(fro). - mode : str, optional + mode : {'left', 'right'}, optional Indicates whether new axes are preferably added to - either 'left' or 'right' of the original shape. + either left or right of the original shape. Defaults to 'left'. keep_ones : bool, optional Treat '1' in fro as literal value instead of dimensional placeholder. @@ -445,15 +447,15 @@ def shapeshifter(fro: Tuple[int, ...], fro = (1,) if not len(fro) else fro to = (1,) if not len(to) else to try: - match = re.match((beg[mode] + match = re.match(beg[mode] +f',{sep[mode]}'.join(map(lambda x: f'{x}' if x>1 or (keep_ones and len(fro)>1) else '\\d+',fro)) - +f',{end[mode]}'),','.join(map(str,to))+',') + +f',{end[mode]}',','.join(map(str,to))+',') assert match + grp = match.groups() except AssertionError: raise ValueError(f'Shapes can not be shifted {fro} --> {to}') - grp: Sequence[str] = match.groups() fill: Tuple[Optional[int], ...] = () for g,d in zip(grp,fro+(None,)): fill += (1,)*g.count(',')+(d,) @@ -522,7 +524,7 @@ def extended_docstring(f: Callable, extra_docstring: str) -> Callable: return _decorator -def DREAM3D_base_group(fname: Union[str, pathlib.Path]) -> str: +def DREAM3D_base_group(fname: Union[str, Path]) -> str: """ Determine the base group of a DREAM.3D file. @@ -548,7 +550,7 @@ def DREAM3D_base_group(fname: Union[str, pathlib.Path]) -> str: return base_group -def DREAM3D_cell_data_group(fname: Union[str, pathlib.Path]) -> str: +def DREAM3D_cell_data_group(fname: Union[str, Path]) -> str: """ Determine the cell data group of a DREAM.3D file. @@ -586,12 +588,12 @@ def Bravais_to_Miller(*, uvtw: np.ndarray = None, hkil: np.ndarray = None) -> np Parameters ---------- - uvtw|hkil : numpy.ndarray of shape (...,4) + uvtw|hkil : numpy.ndarray, shape (...,4) Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). Returns ------- - uvw|hkl : numpy.ndarray of shape (...,3) + uvw|hkl : numpy.ndarray, shape (...,3) Miller indices of [uvw] direction or (hkl) plane normal. """ @@ -613,12 +615,12 @@ def Miller_to_Bravais(*, uvw: np.ndarray = None, hkl: np.ndarray = None) -> np.n Parameters ---------- - uvw|hkl : numpy.ndarray of shape (...,3) + uvw|hkl : numpy.ndarray, shape (...,3) Miller indices of crystallographic direction [uvw] or plane normal (hkl). Returns ------- - uvtw|hkil : numpy.ndarray of shape (...,4) + uvtw|hkil : numpy.ndarray, shape (...,4) Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. """ @@ -636,7 +638,7 @@ def Miller_to_Bravais(*, uvw: np.ndarray = None, hkl: np.ndarray = None) -> np.n return np.einsum('il,...l',basis,axis) -def dict_prune(d: Dict[Any, Any]) -> Dict[Any, Any]: +def dict_prune(d: Dict) -> Dict: """ Recursively remove empty dictionaries. @@ -662,7 +664,7 @@ def dict_prune(d: Dict[Any, Any]) -> Dict[Any, Any]: return new -def dict_flatten(d: Dict[Any, Any]) -> Dict[Any, Any]: +def dict_flatten(d: Dict) -> Dict: """ Recursively remove keys of single-entry dictionaries. From a35a01d41b443f205bc224f589758b736a247dbf Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 22 Jan 2022 07:50:52 +0100 Subject: [PATCH 10/11] iterable can be any Iterable, not just a Sequence need to give N_iter only in cases when iterable is not a Sequence --- python/damask/seeds.py | 2 +- python/damask/util.py | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/python/damask/seeds.py b/python/damask/seeds.py index 6ff150df3..e6d1f9613 100644 --- a/python/damask/seeds.py +++ b/python/damask/seeds.py @@ -79,7 +79,7 @@ def from_Poisson_disc(size: _FloatSequence, N_seeds: int, N_candidates: int, dis s = 1 i = 0 - progress = _util._ProgressBar(N_seeds+1,'',50) + progress = _util.ProgressBar(N_seeds+1,'',50) while s < N_seeds: i += 1 candidates = rng.random((N_candidates,3))*_np.broadcast_to(size,(N_candidates,3)) diff --git a/python/damask/util.py b/python/damask/util.py index 8ae83014c..d7f674a1c 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -7,8 +7,9 @@ import subprocess import shlex import re import fractions +import collections.abc as abc from functools import reduce -from typing import Union, Tuple, Sequence, Callable, Dict, List, Any, Literal, Optional +from typing import Union, Tuple, Iterable, Sequence, Callable, Dict, List, Any, Literal, Optional from pathlib import Path import numpy as np @@ -203,7 +204,7 @@ def natural_sort(key: str) -> List[Union[int, str]]: return [ convert(c) for c in re.split('([0-9]+)', key) ] -def show_progress(iterable: Sequence[Any], +def show_progress(iterable: Iterable, N_iter: int = None, prefix: str = '', bar_length: int = 50) -> Any: @@ -214,22 +215,32 @@ def show_progress(iterable: Sequence[Any], Parameters ---------- - iterable : iterable or function with yield statement - Iterable (or function with yield statement) to be decorated. + iterable : iterable + Iterable to be decorated. N_iter : int, optional - Total number of iterations. Required unless obtainable as len(iterable). + Total number of iterations. Required if iterable is not a sequence. prefix : str, optional Prefix string. bar_length : int, optional Length of progress bar in characters. Defaults to 50. """ - if N_iter in [0,1] or (hasattr(iterable,'__len__') and len(iterable) <= 1): + if isinstance(iterable,abc.Sequence): + if N_iter is None: + N = len(iterable) + else: + raise ValueError('N_iter given for sequence') + else: + if N_iter is None: + raise ValueError('N_iter not given') + else: + N = N_iter + + if N <= 1: for item in iterable: yield item else: - status = _ProgressBar(N_iter if N_iter is not None else len(iterable),prefix,bar_length) - + status = ProgressBar(N,prefix,bar_length) for i,item in enumerate(iterable): yield item status.update(i) @@ -692,7 +703,7 @@ def dict_flatten(d: Dict) -> Dict: #################################################################################################### # Classes #################################################################################################### -class _ProgressBar: +class ProgressBar: """ Report progress of an interation as a status bar. From 2bbc4c4e4609f5dd36dadc152d7214ae6a3164ad Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 22 Jan 2022 08:05:49 +0100 Subject: [PATCH 11/11] polishing --- python/damask/_grid.py | 40 ++++++++++++++++++++-------------------- python/damask/_vtk.py | 26 +++++++++++++------------- python/damask/util.py | 4 ++-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 53b0bd5dc..94f7eefd0 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -70,7 +70,7 @@ class Grid: ]) - def __copy__(self) -> "Grid": + def __copy__(self) -> 'Grid': """Create deep copy.""" return copy.deepcopy(self) @@ -161,7 +161,7 @@ class Grid: @staticmethod - def load(fname: Union[str, Path]) -> "Grid": + def load(fname: Union[str, Path]) -> 'Grid': """ Load from VTK image data file. @@ -190,7 +190,7 @@ class Grid: @typing. no_type_check @staticmethod - def load_ASCII(fname)-> "Grid": + def load_ASCII(fname)-> 'Grid': """ Load from geom file. @@ -264,7 +264,7 @@ class Grid: @staticmethod - def load_Neper(fname: Union[str, Path]) -> "Grid": + def load_Neper(fname: Union[str, Path]) -> 'Grid': """ Load from Neper VTK file. @@ -279,7 +279,7 @@ class Grid: Grid-based geometry from file. """ - v = VTK.load(fname,'vtkImageData') + v = VTK.load(fname,'ImageData') cells = np.array(v.vtk_data.GetDimensions())-1 bbox = np.array(v.vtk_data.GetBounds()).reshape(3,2).T @@ -292,7 +292,7 @@ class Grid: def load_DREAM3D(fname: Union[str, Path], feature_IDs: str = None, cell_data: str = None, phases: str = 'Phases', Euler_angles: str = 'EulerAngles', - base_group: str = None) -> "Grid": + base_group: str = None) -> 'Grid': """ Load DREAM.3D (HDF5) file. @@ -354,7 +354,7 @@ class Grid: @staticmethod def from_table(table: Table, coordinates: str, - labels: Union[str, Sequence[str]]) -> "Grid": + labels: Union[str, Sequence[str]]) -> 'Grid': """ Create grid from ASCII table. @@ -453,7 +453,7 @@ class Grid: size: FloatSequence, seeds: np.ndarray, material: IntSequence = None, - periodic: bool = True) -> "Grid": + periodic: bool = True) -> 'Grid': """ Create grid from Voronoi tessellation. @@ -539,7 +539,7 @@ class Grid: surface: str, threshold: float = 0.0, periods: int = 1, - materials: IntSequence = (0,1)) -> "Grid": + materials: IntSequence = (0,1)) -> 'Grid': """ Create grid from definition of triply periodic minimal surface. @@ -685,7 +685,7 @@ class Grid: fill: int = None, R: Rotation = Rotation(), inverse: bool = False, - periodic: bool = True) -> "Grid": + periodic: bool = True) -> 'Grid': """ Insert a primitive geometric object at a given position. @@ -770,7 +770,7 @@ class Grid: ) - def mirror(self, directions: Sequence[str], reflect: bool = False) -> "Grid": + def mirror(self, directions: Sequence[str], reflect: bool = False) -> 'Grid': """ Mirror grid along given directions. @@ -822,7 +822,7 @@ class Grid: ) - def flip(self, directions: Union[Literal['x', 'y', 'z'], Sequence[Literal['x', 'y', 'z']]]) -> "Grid": + def flip(self, directions: Union[Literal['x', 'y', 'z'], Sequence[Literal['x', 'y', 'z']]]) -> 'Grid': """ Flip grid along given directions. @@ -852,7 +852,7 @@ class Grid: ) - def scale(self, cells: IntSequence, periodic: bool = True) -> "Grid": + def scale(self, cells: IntSequence, periodic: bool = True) -> 'Grid': """ Scale grid to new cells. @@ -899,7 +899,7 @@ class Grid: def clean(self, stencil: int = 3, selection: IntSequence = None, - periodic: bool = True) -> "Grid": + periodic: bool = True) -> 'Grid': """ Smooth grid by selecting most frequent material index within given stencil at each location. @@ -939,7 +939,7 @@ class Grid: ) - def renumber(self) -> "Grid": + def renumber(self) -> 'Grid': """ Renumber sorted material indices as 0,...,N-1. @@ -958,7 +958,7 @@ class Grid: ) - def rotate(self, R: Rotation, fill: int = None) -> "Grid": + def rotate(self, R: Rotation, fill: int = None) -> 'Grid': """ Rotate grid (pad if required). @@ -998,7 +998,7 @@ class Grid: def canvas(self, cells: IntSequence = None, offset: IntSequence = None, - fill: int = None) -> "Grid": + fill: int = None) -> 'Grid': """ Crop or enlarge/pad grid. @@ -1049,7 +1049,7 @@ class Grid: ) - def substitute(self, from_material: IntSequence, to_material: IntSequence) -> "Grid": + def substitute(self, from_material: IntSequence, to_material: IntSequence) -> 'Grid': """ Substitute material indices. @@ -1077,7 +1077,7 @@ class Grid: ) - def sort(self) -> "Grid": + def sort(self) -> 'Grid': """ Sort material indices such that min(material) is located at (0,0,0). @@ -1103,7 +1103,7 @@ class Grid: vicinity: int = 1, offset: int = None, trigger: IntSequence = [], - periodic: bool = True) -> "Grid": + periodic: bool = True) -> 'Grid': """ Offset material index of points in the vicinity of xxx. diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index 787aa3c72..4e3f27e0e 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -2,7 +2,7 @@ import os import warnings import multiprocessing as mp from pathlib import Path -from typing import Union, Optional, Literal, List +from typing import Union, Literal, List import numpy as np import vtk @@ -10,7 +10,7 @@ from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray from vtk.util.numpy_support import vtk_to_numpy as vtk_to_np -from ._typehints import FloatSequence +from ._typehints import FloatSequence, IntSequence from . import util from . import Table @@ -38,8 +38,7 @@ class VTK: @staticmethod - #ITERABLES PROPER - def from_image_data(cells: np.ndarray, size: np.ndarray, origin: Optional[np.ndarray] = np.zeros(3)) -> "VTK": + def from_image_data(cells: IntSequence, size: FloatSequence, origin: FloatSequence = np.zeros(3)) -> 'VTK': """ Create VTK of type vtk.vtkImageData. @@ -63,13 +62,13 @@ class VTK: vtk_data = vtk.vtkImageData() vtk_data.SetDimensions(*(np.array(cells)+1)) vtk_data.SetOrigin(*(np.array(origin))) - vtk_data.SetSpacing(*(size/cells)) + vtk_data.SetSpacing(*(np.array(size)/np.array(cells))) return VTK(vtk_data) @staticmethod - def from_rectilinear_grid(grid: np.ndarray, size: FloatSequence, origin: FloatSequence = np.zeros(3)) -> "VTK": + def from_rectilinear_grid(grid: np.ndarray, size: FloatSequence, origin: FloatSequence = np.zeros(3)) -> 'VTK': """ Create VTK of type vtk.vtkRectilinearGrid. @@ -101,7 +100,7 @@ class VTK: @staticmethod - def from_unstructured_grid(nodes: np.ndarray, connectivity: np.ndarray, cell_type: str) -> "VTK": + def from_unstructured_grid(nodes: np.ndarray, connectivity: np.ndarray, cell_type: str) -> 'VTK': """ Create VTK of type vtk.vtkUnstructuredGrid. @@ -141,7 +140,7 @@ class VTK: @staticmethod - def from_poly_data(points: np.ndarray) -> "VTK": + def from_poly_data(points: np.ndarray) -> 'VTK': """ Create VTK of type vtk.polyData. @@ -176,15 +175,16 @@ class VTK: @staticmethod def load(fname: Union[str, Path], - dataset_type: Literal['vtkImageData', 'vtkRectilinearGrid', 'vtkUnstructuredGrid', 'vtkPolyData'] = None) -> "VTK": + dataset_type: Literal['ImageData', 'UnstructuredGrid', 'PolyData'] = None) -> 'VTK': """ Load from VTK file. Parameters ---------- fname : str or pathlib.Path - Filename for reading. Valid extensions are .vti, .vtr, .vtu, .vtp, and .vtk. - dataset_type : {'vtkImageData', ''vtkRectilinearGrid', 'vtkUnstructuredGrid', 'vtkPolyData'}, optional + Filename for reading. + Valid extensions are .vti, .vtr, .vtu, .vtp, and .vtk. + dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData'}, optional Name of the vtk.vtkDataSet subclass when opening a .vtk file. Returns @@ -193,7 +193,7 @@ class VTK: VTK-based geometry from file. """ - if not os.path.isfile(fname): # vtk has a strange error handling + if not os.path.isfile(fname): # vtk has a strange error handling raise FileNotFoundError(f'No such file: {fname}') ext = Path(fname).suffix if ext == '.vtk' or dataset_type is not None: @@ -423,7 +423,7 @@ class VTK: return writer.GetOutputString() - def show(self) -> None: + def show(self): """ Render. diff --git a/python/damask/util.py b/python/damask/util.py index d7f674a1c..2872762b9 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -9,14 +9,14 @@ import re import fractions import collections.abc as abc from functools import reduce -from typing import Union, Tuple, Iterable, Sequence, Callable, Dict, List, Any, Literal, Optional +from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, Optional from pathlib import Path import numpy as np import h5py from . import version -from ._typehints import FloatSequence, IntSequence +from ._typehints import FloatSequence # limit visibility __all__=[