diff --git a/PRIVATE b/PRIVATE index 95f7faea9..5e104e75b 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 95f7faea920dd6956884e4a55f72e5d5b1ffcdc8 +Subproject commit 5e104e75bbfb9d0d1969658aaf10999590956e3e diff --git a/python/damask/__init__.py b/python/damask/__init__.py index ad46d454f..060c5e295 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -14,7 +14,6 @@ from . import tensor # noqa from . import mechanics # noqa from . import solver # noqa from . import grid_filters # noqa -from . import lattice # noqa #Modules that contain only one class (of the same name), are prefixed by a '_'. #For example, '_colormap' containsa class called 'Colormap' which is imported as 'damask.Colormap'. from ._rotation import Rotation # noqa diff --git a/python/damask/_colormap.py b/python/damask/_colormap.py index 02e2b922c..7e82853b4 100644 --- a/python/damask/_colormap.py +++ b/python/damask/_colormap.py @@ -110,13 +110,13 @@ class Colormap(mpl.colors.ListedColormap): low_,high_ = map(Colormap._rgb2msh,low_high) elif model.lower() == 'hsv': - if np.any(low_high<0) or np.any(low_high[:,1:3]>1) or np.any(low_high[:,0]>360): + if np.any(low_high<0) or np.any(low_high>[360,1,1]): raise ValueError(f'HSV color {low} | {high} are out of range.') low_,high_ = map(Colormap._hsv2msh,low_high) elif model.lower() == 'hsl': - if np.any(low_high<0) or np.any(low_high[:,1:3]>1) or np.any(low_high[:,0]>360): + if np.any(low_high<0) or np.any(low_high>[360,1,1]): raise ValueError(f'HSL color {low} | {high} are out of range.') low_,high_ = map(Colormap._hsl2msh,low_high) diff --git a/python/damask/lattice.py b/python/damask/_lattice.py similarity index 100% rename from python/damask/lattice.py rename to python/damask/_lattice.py diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 00e74d36c..919d9e518 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -5,6 +5,35 @@ import numpy as np from . import Rotation from . import util from . import tensor +from . import _lattice + +_crystal_families = ['triclinic', + 'monoclinic', + 'orthorhombic', + 'tetragonal', + 'hexagonal', + 'cubic'] + +_lattice_symmetries = { + 'aP': 'triclinic', + + 'mP': 'monoclinic', + 'mS': 'monoclinic', + + 'oP': 'orthorhombic', + 'oS': 'orthorhombic', + 'oI': 'orthorhombic', + 'oF': 'orthorhombic', + + 'tP': 'tetragonal', + 'tI': 'tetragonal', + + 'hP': 'hexagonal', + + 'cP': 'cubic', + 'cI': 'cubic', + 'cF': 'cubic', + } _parameter_doc = \ """lattice : str @@ -33,7 +62,7 @@ class Orientation(Rotation): """ Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. - The crystal family is one of Orientation.crystal_families: + The crystal family is one of: - triclinic - monoclinic @@ -45,7 +74,7 @@ class Orientation(Rotation): and enables symmetry-related operations such as "equivalent", "reduced", "disorientation", "IPF_color", or "to_SST". - The Bravais lattice is one of Orientation.lattice_symmetries: + The Bravais lattice is given in the Pearson notation: - triclinic - aP : primitive @@ -85,35 +114,6 @@ class Orientation(Rotation): """ - crystal_families = ['triclinic', - 'monoclinic', - 'orthorhombic', - 'tetragonal', - 'hexagonal', - 'cubic'] - - lattice_symmetries = { - 'aP': 'triclinic', - - 'mP': 'monoclinic', - 'mS': 'monoclinic', - - 'oP': 'orthorhombic', - 'oS': 'orthorhombic', - 'oI': 'orthorhombic', - 'oF': 'orthorhombic', - - 'tP': 'tetragonal', - 'tI': 'tetragonal', - - 'hP': 'hexagonal', - - 'cP': 'cubic', - 'cI': 'cubic', - 'cF': 'cubic', - } - - @util.extend_docstring(_parameter_doc) def __init__(self, rotation = None, @@ -132,34 +132,17 @@ class Orientation(Rotation): Defaults to no rotation. """ - from damask.lattice import kinematics - Rotation.__init__(self) if rotation is None else Rotation.__init__(self,rotation=rotation) - if ( lattice not in self.lattice_symmetries - and lattice not in self.crystal_families): - raise KeyError(f'Lattice "{lattice}" is unknown') - - self.family = None - self.lattice = None - self.a = None - self.b = None - self.c = None - self.alpha = None - self.beta = None - self.gamma = None self.kinematics = None - if lattice in self.lattice_symmetries: - self.family = self.lattice_symmetries[lattice] + if lattice in _lattice_symmetries: + self.family = _lattice_symmetries[lattice] self.lattice = lattice + self.a = 1 if a is None else a self.b = b self.c = c - self.alpha = (np.radians(alpha) if degrees else alpha) if alpha is not None else None - self.beta = (np.radians(beta) if degrees else beta) if beta is not None else None - self.gamma = (np.radians(gamma) if degrees else gamma) if gamma is not None else None - self.a = float(self.a) if self.a is not None else \ (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) @@ -171,9 +154,13 @@ class Orientation(Rotation): (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else self.b / self.ratio['b'] * self.ratio['c'] if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.alpha = self.alpha if self.alpha is not None else self.immutable['alpha'] if 'alpha' in self.immutable else None - self.beta = self.beta if self.beta is not None else self.immutable['beta'] if 'beta' in self.immutable else None - self.gamma = self.gamma if self.gamma is not None else self.immutable['gamma'] if 'gamma' in self.immutable else None + + self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha + self.beta = np.radians(beta) if degrees and beta is not None else beta + self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma + if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] + if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] + if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] if \ (self.a is None) \ @@ -190,16 +177,22 @@ class Orientation(Rotation): > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): raise ValueError ('Each lattice angle must be less than sum of others') - if self.lattice in kinematics: - master = kinematics[self.lattice] + if self.lattice in _lattice.kinematics: + master = _lattice.kinematics[self.lattice] self.kinematics = {} for m in master: self.kinematics[m] = {'direction':master[m][:,0:3],'plane':master[m][:,3:6]} \ if master[m].shape[-1] == 6 else \ {'direction':self.Bravais_to_Miller(uvtw=master[m][:,0:4]), 'plane': self.Bravais_to_Miller(hkil=master[m][:,4:8])} - elif lattice in self.crystal_families: - self.family = lattice + elif lattice in _crystal_families: + self.family = lattice + self.lattice = None + + self.a = self.b = self.c = None + self.alpha = self.beta = self.gamma = None + else: + raise KeyError(f'Lattice "{lattice}" is unknown') def __repr__(self): @@ -676,11 +669,9 @@ class Orientation(Rotation): https://doi.org/10.1016/j.actamat.2004.11.021 """ - from damask.lattice import relations - - if model not in relations: + if model not in _lattice.relations: raise KeyError(f'Orientation relationship "{model}" is unknown') - r = relations[model] + r = _lattice.relations[model] if self.lattice not in r: raise KeyError(f'Relationship "{model}" not supported for lattice "{self.lattice}"') diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index 029abab19..a40a5990f 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -150,9 +150,8 @@ class VTK: ---------- fname : str or pathlib.Path Filename for reading. Valid extensions are .vtr, .vtu, .vtp, and .vtk. - dataset_type : str, optional + dataset_type : {'vtkRectilinearGrid', 'vtkUnstructuredGrid', 'vtkPolyData'}, optional Name of the vtk.vtkDataSet subclass when opening a .vtk file. - Valid types are vtkRectilinearGrid, vtkUnstructuredGrid, and vtkPolyData. Returns ------- diff --git a/python/damask/mechanics.py b/python/damask/mechanics.py index 6ba785df1..c8519abad 100644 --- a/python/damask/mechanics.py +++ b/python/damask/mechanics.py @@ -124,9 +124,6 @@ def strain(F,t,m): """ Calculate strain tensor (Seth–Hill family). - For details refer to https://en.wikipedia.org/wiki/Finite_strain_theory and - https://de.wikipedia.org/wiki/Verzerrungstensor - Parameters ---------- F : numpy.ndarray of shape (...,3,3) @@ -142,6 +139,11 @@ def strain(F,t,m): epsilon : numpy.ndarray of shape (...,3,3) Strain of F. + References + ---------- + https://en.wikipedia.org/wiki/Finite_strain_theory + https://de.wikipedia.org/wiki/Verzerrungstensor + """ if t == 'V': w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F)) @@ -150,7 +152,6 @@ def strain(F,t,m): if m > 0.0: eps = 1.0/(2.0*abs(m)) * (+ _np.einsum('...j,...kj,...lj',w**m,n,n) - _np.eye(3)) - elif m < 0.0: eps = 1.0/(2.0*abs(m)) * (- _np.einsum('...j,...kj,...lj',w**m,n,n) + _np.eye(3)) else: diff --git a/python/damask/util.py b/python/damask/util.py index fcf41c251..529b63d20 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -1,3 +1,5 @@ +"""Miscellaneous helper functionality.""" + import sys import datetime import os @@ -177,26 +179,36 @@ def execute(cmd,wd='./',env=None): def natural_sort(key): + """ + Natural sort. + + For use in python's 'sorted'. + + References + ---------- + https://en.wikipedia.org/wiki/Natural_sort_order + + """ convert = lambda text: int(text) if text.isdigit() else text return [ convert(c) for c in re.split('([0-9]+)', key) ] def show_progress(iterable,N_iter=None,prefix='',bar_length=50): """ - Decorate a loop with a status bar. + Decorate a loop with a progress bar. Use similar like enumerate. Parameters ---------- - iterable : iterable/function with yield statement + iterable : iterable or function with yield statement Iterable (or function with yield statement) to be decorated. - N_iter : int - Total # of iterations. Needed if number of iterations can not be obtained as len(iterable). - prefix : str, optional. + N_iter : int, optional + Total number of iterations. Required unless obtainable as len(iterable). + prefix : str, optional Prefix string. bar_length : int, optional - Character length of bar. Defaults to 50. + Length of progress bar in characters. Defaults to 50. """ if N_iter in [0,1] or (hasattr(iterable,'__len__') and len(iterable) <= 1): @@ -509,6 +521,7 @@ def dict_prune(d): v = dict_prune(v) if not isinstance(v,dict) or v != {}: new[k] = v + return new diff --git a/python/setup.py b/python/setup.py index e590a1197..5d66d3c88 100644 --- a/python/setup.py +++ b/python/setup.py @@ -12,7 +12,7 @@ setuptools.setup( author='The DAMASK team', author_email='damask@mpie.de', description='DAMASK library', - long_description='Python library for pre and post processing of DAMASK simulations', + long_description='Python library for managing DAMASK simulations', url='https://damask.mpie.de', packages=setuptools.find_packages(), include_package_data=True, diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index dce0cf40f..3c4e5bbff 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -5,9 +5,10 @@ from itertools import permutations from damask import Rotation from damask import Orientation from damask import Table -from damask import lattice from damask import util from damask import grid_filters +from damask import _lattice as lattice +from damask._orientation import _crystal_families as crystal_families @pytest.fixture @@ -22,7 +23,7 @@ def set_of_rodrigues(set_of_quaternions): class TestOrientation: - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) def test_equal(self,lattice,shape): R = Rotation.from_random(shape) @@ -30,14 +31,14 @@ class TestOrientation: (Orientation(R,lattice) == Orientation(R,lattice)).all() - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) def test_unequal(self,lattice,shape): R = Rotation.from_random(shape) assert not ( Orientation(R,lattice) != Orientation(R,lattice) if shape is None else \ (Orientation(R,lattice) != Orientation(R,lattice)).any()) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) def test_close(self,lattice,shape): R = Orientation.from_random(lattice=lattice,shape=shape) @@ -182,14 +183,14 @@ class TestOrientation: with pytest.raises(ValueError): Orientation(lattice='aP',a=1,b=2,c=3,alpha=45,beta=45,gamma=90.0001,degrees=True) # noqa - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('angle',[10,20,30,40]) def test_average(self,angle,lattice): o = Orientation.from_axis_angle(lattice=lattice,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) avg_angle = o.average().as_axis_angle(degrees=True,pair=True)[1] assert np.isclose(avg_angle,10+(angle-10)/2.) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_reduced_equivalent(self,lattice): i = Orientation(lattice=lattice) o = Orientation.from_random(lattice=lattice) @@ -197,7 +198,7 @@ class TestOrientation: FZ = np.argmin(abs(eq.misorientation(i.broadcast_to(len(eq))).as_axis_angle(pair=True)[1])) assert o.reduced == eq[FZ] - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('N',[1,8,32]) def test_disorientation(self,lattice,N): o = Orientation.from_random(lattice=lattice,shape=N) @@ -215,7 +216,7 @@ class TestOrientation: .misorientation(p[n].equivalent[ops[n][1]]) .as_quaternion()) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), @@ -230,20 +231,20 @@ class TestOrientation: assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ .isclose(o.disorientation(p)[tuple(loc)]) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_disorientation360(self,lattice): o_1 = Orientation(Rotation(),lattice) o_2 = Orientation.from_Euler_angles(lattice=lattice,phi=[360,0,0],degrees=True) assert np.allclose((o_1.disorientation(o_2)).as_matrix(),np.eye(3)) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) def test_reduced_vectorization(self,lattice,shape): o = Orientation.from_random(lattice=lattice,shape=shape) for r, theO in zip(o.reduced.flatten(),o.flatten()): assert r == theO.reduced - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_reduced_corner_cases(self,lattice): # test whether there is always a sym-eq rotation that falls into the FZ N = np.random.randint(10,40) @@ -253,7 +254,7 @@ class TestOrientation: assert evenly_distributed.shape == evenly_distributed.reduced.shape - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) @@ -262,7 +263,7 @@ class TestOrientation: for r, theO in zip(o.to_SST(vector=vector,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.to_SST(vector=vector,proper=proper)) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) @@ -272,7 +273,7 @@ class TestOrientation: for r, theO in zip(o.IPF_color(vector,in_SST=in_SST,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.IPF_color(vector,in_SST=in_SST,proper=proper)) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), @@ -300,7 +301,7 @@ class TestOrientation: assert np.allclose(np.array(color['RGB']), cube.IPF_color(vector=np.array(direction),proper=proper)) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('proper',[True,False]) def test_IPF_equivalent(self,set_of_quaternions,lattice,proper): direction = np.random.random(3)*2.0-1.0 @@ -308,13 +309,13 @@ class TestOrientation: color = o.IPF_color(vector=direction,proper=proper) assert np.allclose(np.broadcast_to(color[0,...],color.shape),color) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_in_FZ_vectorization(self,set_of_rodrigues,lattice): result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),lattice=lattice).in_FZ.reshape(-1) for r,rho in zip(result,set_of_rodrigues[:len(result)]): assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_FZ - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,lattice): result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)), lattice=lattice).in_disorientation_FZ.reshape(-1) @@ -322,7 +323,7 @@ class TestOrientation: assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_disorientation_FZ @pytest.mark.parametrize('proper',[True,False]) - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) def test_in_SST_vectorization(self,lattice,proper): vecs = np.random.rand(20,4,3) result = Orientation(lattice=lattice).in_SST(vecs,proper).flatten() @@ -393,7 +394,7 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma).related(relation) # noqa - @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('lattice',crystal_families) @pytest.mark.parametrize('proper',[True,False]) def test_in_SST(self,lattice,proper): assert Orientation(lattice=lattice).in_SST(np.zeros(3),proper)