From 41e5f0c06ce4ee33836c47b74d82d663ff44f0b1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 6 Jun 2021 19:49:29 +0200 Subject: [PATCH] re-introduced multiple inheritance --- python/damask/_lattice.py | 77 +++++++++++++++++--------------- python/damask/_orientation.py | 72 +++++++++++++---------------- python/tests/test_Lattice.py | 4 +- python/tests/test_Orientation.py | 4 +- 4 files changed, 76 insertions(+), 81 deletions(-) diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py index 1aa3d6e65..51e29c282 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_lattice.py @@ -29,7 +29,8 @@ lattice_symmetries = { class Lattice(LatticeFamily): """Lattice.""" - def __init__(self, + def __init__(self,*, + family = None, lattice = None, a = None,b = None,c = None, alpha = None,beta = None,gamma = None, @@ -57,46 +58,50 @@ class Lattice(LatticeFamily): Angles are given in degrees. Defaults to False. """ - super().__init__(lattice_symmetries[lattice]) + super().__init__(family = lattice_symmetries[lattice] if family is None else family) + self.lattice = lattice + if self.lattice is not None: + self.a = 1 if a is None else a + self.b = b + self.c = c + 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) + self.b = float(self.b) if self.b is not None else \ + (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] * self.ratio['b'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.c = float(self.c) if self.c is not None else \ + (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.a = 1 if a is None else a - self.b = b - self.c = c - 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) - self.b = float(self.b) if self.b is not None else \ - (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] * self.ratio['b'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.c = float(self.c) if self.c is not None else \ - (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 = 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'] - 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) \ + or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ + or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ + or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ + or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): + raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') - if \ - (self.a is None) \ - or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ - or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ - or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ - or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ - or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): - raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') - - if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): - raise ValueError ('Lattice angles must be positive') - if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] - > 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 np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): + raise ValueError ('Lattice angles must be positive') + if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] + > 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') + else: + self.a = self.b = self.c = None + self.alpha = self.beta = self.gamma = None def __eq__(self,other): diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index ff419117c..c26c0b5f9 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -4,7 +4,6 @@ import copy import numpy as np from . import Rotation -from . import LatticeFamily from . import Lattice from . import util from . import tensor @@ -56,7 +55,7 @@ _parameter_doc = \ """ -class Orientation(Rotation): +class Orientation(Rotation,Lattice): """ Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. @@ -131,19 +130,9 @@ class Orientation(Rotation): Defaults to no rotation. """ - super().__init__(rotation) - - if family in set(lattice_symmetries.values()) and lattice is None: - self.family = family - self.lattice = None - self.structure = LatticeFamily(self.family) - self.related = self.Schmid = self.to_pole = None - elif lattice in lattice_symmetries: - self.family = lattice_symmetries[lattice] - self.lattice = lattice - self.structure = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) - else: - raise KeyError(f'no valid family or lattice') + Rotation.__init__(self,rotation) + Lattice.__init__(self,family=family, lattice=lattice, + a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma, degrees=degrees) def __repr__(self): @@ -173,8 +162,9 @@ class Orientation(Rotation): Orientation to check for equality. """ - matching_type = self.structure == other.structure if hasattr(other,'structure') else \ - False + matching_type = self.family == other.family and \ + self.lattice == other.lattice and \ + self.parameters == other.parameters return np.logical_and(matching_type,super(self.__class__,self.reduced).__eq__(other.reduced)) def __ne__(self,other): @@ -211,8 +201,9 @@ class Orientation(Rotation): Mask indicating where corresponding orientations are close. """ - matching_type = self.structure == other.structure if hasattr(other,'structure') else \ - False + matching_type = self.family == other.family and \ + self.lattice == other.lattice and \ + self.parameters == other.parameters return np.logical_and(matching_type,super(self.__class__,self.reduced).isclose(other.reduced)) @@ -385,8 +376,8 @@ class Orientation(Rotation): """ o = cls(**kwargs) - x = o.structure.to_frame(uvw=uvw) - z = o.structure.to_frame(hkl=hkl) + x = o.to_frame(uvw=uvw) + z = o.to_frame(hkl=hkl) om = np.stack([x,np.cross(z,x),z],axis=-2) return o.copy(rotation=Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) @@ -400,7 +391,7 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - sym_ops = self.structure.symmetry_operations + sym_ops = self.symmetry_operations o = sym_ops.broadcast_to(sym_ops.shape+self.shape,mode='right') return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) @@ -677,21 +668,21 @@ class Orientation(Rotation): if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: raise ValueError('input is not a field of three-dimensional vectors') - if self.structure.basis is None: # direct exit for no symmetry + if self.basis is None: # direct exit for no symmetry return np.ones_like(vector[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['proper'], vector.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector.shape+(3,)), vector), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector.shape+(3,)), vector), 12) return np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector.shape+(3,)), np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) return np.all(components >= 0.0,axis=-1) @@ -732,15 +723,15 @@ class Orientation(Rotation): vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) - if self.structure.basis is None: # direct exit for no symmetry + if self.basis is None: # direct exit for no symmetry return np.zeros_like(vector_) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['proper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), vector_), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), vector_), 12) in_SST = np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) @@ -748,7 +739,7 @@ class Orientation(Rotation): components_proper,components_improper) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self .basis['improper'], vector_.shape+(3,)), np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) in_SST = np.all(components >= 0.0,axis=-1) @@ -781,9 +772,9 @@ class Orientation(Rotation): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ - sym_ops = self.structure.symmetry_operations + sym_ops = self.symmetry_operations # ToDo: simplify 'with_symmetry' - v = self.structure.to_frame(uvw=uvw,hkl=hkl) + v = self.to_frame(uvw=uvw,hkl=hkl) if with_symmetry: v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ @ np.broadcast_to(v,sym_ops.shape+v.shape) @@ -820,8 +811,8 @@ class Orientation(Rotation): """ try: - d = self.structure.to_frame(uvw=self.structure.kinematics(mode)['direction']) - p = self.structure.to_frame(hkl=self.structure.kinematics(mode)['plane']) + d = self.to_frame(uvw=self.kinematics(mode)['direction']) + p = self.to_frame(hkl=self.kinematics(mode)['plane']) except KeyError: raise (f'"{mode}" not defined for lattice "{self.lattice}"') P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), @@ -839,15 +830,14 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - lattice,o = self.structure.relation_operations(model) + lattice,o = self.relation_operations(model) target = Lattice(lattice=lattice) - struct = self.structure o = o.broadcast_to(o.shape+self.shape,mode='right') return Orientation(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), lattice=lattice, - b = struct.b if target.ratio['b'] is None else struct.a*target.ratio['b'], - c = struct.c if target.ratio['c'] is None else struct.a*target.ratio['c'], - alpha = None if 'alpha' in target.immutable else struct.alpha, - beta = None if 'beta' in target.immutable else struct.beta, - gamma = None if 'gamma' in target.immutable else struct.gamma, + b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], + c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else self.alpha, + beta = None if 'beta' in target.immutable else self.beta, + gamma = None if 'gamma' in target.immutable else self.gamma, ) diff --git a/python/tests/test_Lattice.py b/python/tests/test_Lattice.py index c9593301c..0e820e1ce 100644 --- a/python/tests/test_Lattice.py +++ b/python/tests/test_Lattice.py @@ -6,12 +6,12 @@ from damask import Lattice class TestLattice: def test_double_to_lattice(self): - L = Lattice('cF') + L = Lattice(lattice='cF') with pytest.raises(KeyError): L.to_lattice(direction=np.ones(3),plane=np.ones(3)) def test_double_to_frame(self): - L = Lattice('cF') + L = Lattice(lattice='cF') with pytest.raises(KeyError): L.to_frame(uvw=np.ones(3),hkl=np.ones(3)) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 5317ada19..38559c662 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -409,7 +409,7 @@ class TestOrientation: **dict(zip(['a','b','c'],lengths)), **dict(zip(['alpha','beta','gamma'],np.arccos(cosines))), ) - assert np.allclose(o.structure.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' + assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', @@ -436,7 +436,7 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma) assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ - == o.shape + (o.structure.symmetry_operations.shape if with_symmetry else ()) + vector.shape + == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape @pytest.mark.parametrize('lattice',['hP','cI','cF']) def test_Schmid(self,update,ref_path,lattice):