Merge branch 'development' into Dislotwin-climb2

This commit is contained in:
Martin Diehl 2019-09-11 12:06:29 -07:00
commit 15e796d599
17 changed files with 676 additions and 306 deletions

@ -1 +1 @@
Subproject commit 18a976753be06aca6e15f580998e713daa08bb41
Subproject commit 5c5adbd8ccc0210fd6507431db8ec82ecec75352

View File

@ -1 +1 @@
v2.0.3-614-g4d6a047b
v2.0.3-625-gbace77db

View File

@ -1,8 +1,9 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 no BOM -*-
# Makes postprocessing routines acessible from everywhere.
import os,sys
# Makes postprocessing routines accessible from everywhere.
import os
import sys
import damask
damaskEnv = damask.Environment()

View File

@ -27,6 +27,7 @@ Additional (globally fixed) rotations of the lab frame and/or crystal frame can
representations = {
'quaternion': ['qu',4],
'rodrigues': ['ro',4],
'Rodrigues': ['Ro',3],
'eulers': ['eu',3],
'matrix': ['om',9],
'angleaxis': ['ax',4],
@ -80,15 +81,20 @@ parser.add_option('-z',
dest = 'z',
metavar = 'string',
help = 'label of lab z vector (expressed in crystal coords)')
parser.add_option('--lattice',
dest = 'lattice',
metavar = 'string',
help = 'lattice structure to reduce rotation into fundamental zone')
parser.set_defaults(output = [],
labrotation = (1.,1.,1.,0.), # no rotation about (1,1,1)
crystalrotation = (1.,1.,1.,0.), # no rotation about (1,1,1)
lattice = None,
)
(options, filenames) = parser.parse_args()
options.output = list(map(lambda x: x.lower(), options.output))
#options.output = list(map(lambda x: x.lower(), options.output))
if options.output == [] or (not set(options.output).issubset(set(representations))):
parser.error('output must be chosen from {}.'.format(', '.join(representations)))
@ -121,7 +127,7 @@ if filenames == []: filenames = [None]
for name in filenames:
try: table = damask.ASCIItable(name = name,
buffered = False)
except: continue
except Exception: continue
damask.util.report(scriptName,name)
# ------------------------------------------ read header ------------------------------------------
@ -156,16 +162,16 @@ for name in filenames:
outputAlive = True
while outputAlive and table.data_read(): # read next data line of ASCII table
if inputtype == 'eulers':
l = representations['eulers'][1]
o = damask.Rotation.fromEulers(list(map(float,table.data[column:column+l])),options.degrees)
d = representations['eulers'][1]
o = damask.Rotation.fromEulers(list(map(float,table.data[column:column+d])),options.degrees)
elif inputtype == 'rodrigues':
l = representations['rodrigues'][1]
o = damask.Rotation.fromRodrigues(list(map(float,table.data[column:column+l])))
d = representations['rodrigues'][1]
o = damask.Rotation.fromRodrigues(list(map(float,table.data[column:column+d])))
elif inputtype == 'matrix':
l = representations['matrix'][1]
o = damask.Rotation.fromMatrix(list(map(float,table.data[column:column+l])))
d = representations['matrix'][1]
o = damask.Rotation.fromMatrix(list(map(float,table.data[column:column+d])))
elif inputtype == 'frame':
M = np.array(list(map(float,table.data[column[0]:column[0]+3] + \
@ -174,14 +180,18 @@ for name in filenames:
o = damask.Rotation.fromMatrix(M/np.linalg.norm(M,axis=0))
elif inputtype == 'quaternion':
l = representations['quaternion'][1]
o = damask.Rotation.fromQuaternion(list(map(float,table.data[column:column+l])))
d = representations['quaternion'][1]
o = damask.Rotation.fromQuaternion(list(map(float,table.data[column:column+d])))
o= r*o*R # apply additional lab and crystal frame rotations
o = r*o*R # apply additional lab and crystal frame rotations
if options.lattice is not None:
o = damask.Orientation(rotation = o,lattice = options.lattice).reduced().rotation
for output in options.output:
if output == 'quaternion': table.data_append(o.asQuaternion())
elif output == 'rodrigues': table.data_append(o.asRodrigues())
elif output == 'Rodrigues': table.data_append(o.asRodrigues(vector=True))
elif output == 'eulers': table.data_append(o.asEulers(degrees=options.degrees))
elif output == 'matrix': table.data_append(o.asMatrix())
elif output == 'angleaxis': table.data_append(o.asAxisAngle(degrees=options.degrees))

View File

@ -1,5 +1,6 @@
####################################################################################################
# Code below available according to the followin conditions on https://github.com/MarDiehl/3Drotations
# Code below available according to the following conditions on
# https://github.com/MarDiehl/3Drotations
####################################################################################################
# Copyright (c) 2017-2019, Martin Diehl/Max-Planck-Institut für Eisenforschung GmbH
# Copyright (c) 2013-2014, Marc De Graef/Carnegie Mellon University
@ -36,7 +37,20 @@ beta = np.pi**(5./6.)/6.**(1./6.)/2.
R1 = (3.*np.pi/4.)**(1./3.)
def CubeToBall(cube):
"""
Map a point in a uniform refinable cubical grid to a point on a uniform refinable grid on a ball.
Parameters
----------
cube : numpy.ndarray
coordinates of a point in a uniform refinable cubical grid.
References
----------
D. Roşca et al., Modelling and Simulation in Materials Science and Engineering 22:075013, 2014
https://doi.org/10.1088/0965-0393/22/7/075013
"""
if np.abs(np.max(cube))>np.pi**(2./3.) * 0.5:
raise ValueError
@ -45,7 +59,7 @@ def CubeToBall(cube):
ball = np.zeros(3)
else:
# get pyramide and scale by grid parameter ratio
p = GetPyramidOrder(cube)
p = get_order(cube)
XYZ = cube[p] * sc
# intercept all the points along the z-axis
@ -74,7 +88,20 @@ def CubeToBall(cube):
def BallToCube(ball):
"""
Map a point on a uniform refinable grid on a ball to a point in a uniform refinable cubical grid.
Parameters
----------
ball : numpy.ndarray
coordinates of a point on a uniform refinable grid on a ball.
References
----------
D. Roşca et al., Modelling and Simulation in Materials Science and Engineering 22:075013, 2014
https://doi.org/10.1088/0965-0393/22/7/075013
"""
rs = np.linalg.norm(ball)
if rs > R1:
raise ValueError
@ -82,7 +109,7 @@ def BallToCube(ball):
if np.allclose(ball,0.0,rtol=0.0,atol=1.0e-300):
cube = np.zeros(3)
else:
p = GetPyramidOrder(ball)
p = get_order(ball)
xyz3 = ball[p]
# inverse M_3
@ -110,8 +137,24 @@ def BallToCube(ball):
return cube
def GetPyramidOrder(xyz):
def get_order(xyz):
"""
Get order of the coordinates.
Depending on the pyramid in which the point is located, the order need to be adjusted.
Parameters
----------
xyz : numpy.ndarray
coordinates of a point on a uniform refinable grid on a ball or
in a uniform refinable cubical grid.
References
----------
D. Roşca et al., Modelling and Simulation in Materials Science and Engineering 22:075013, 2014
https://doi.org/10.1088/0965-0393/22/7/075013
"""
if (abs(xyz[0])<= xyz[2]) and (abs(xyz[1])<= xyz[2]) or \
(abs(xyz[0])<=-xyz[2]) and (abs(xyz[1])<=-xyz[2]):
return [0,1,2]

View File

@ -3,13 +3,7 @@ import math
import numpy as np
class Color():
"""
Conversion of colors between different color-spaces.
Colors should be given in the form Color('model',[vector]).
To convert or copy color from one space to other, use the methods
convertTo('model') or expressAs('model'), respectively.
"""
"""Color representation in and conversion between different color-spaces."""
__slots__ = [
'model',
@ -22,7 +16,17 @@ class Color():
def __init__(self,
model = 'RGB',
color = np.zeros(3,'d')):
"""
Create a Color object.
Parameters
----------
model : string
color model
color : numpy.ndarray
vector representing the color according to the selected model
"""
self.__transforms__ = \
{'HSV': {'index': 0, 'next': self._HSV2HSL},
'HSL': {'index': 1, 'next': self._HSL2RGB, 'prev': self._HSL2HSV},
@ -49,18 +53,27 @@ class Color():
# ------------------------------------------------------------------
def __repr__(self):
"""Color model and values"""
"""Color model and values."""
return 'Model: %s Color: %s'%(self.model,str(self.color))
# ------------------------------------------------------------------
def __str__(self):
"""Color model and values"""
"""Color model and values."""
return self.__repr__()
# ------------------------------------------------------------------
def convertTo(self,toModel = 'RGB'):
"""
Change the color model permanently.
Parameters
----------
toModel : string
color model
"""
toModel = toModel.upper()
if toModel not in list(self.__transforms__.keys()): return
@ -79,16 +92,25 @@ class Color():
# ------------------------------------------------------------------
def expressAs(self,asModel = 'RGB'):
"""
Return the color in a different model.
Parameters
----------
asModel : string
color model
"""
return self.__class__(self.model,self.color).convertTo(asModel)
def _HSV2HSL(self):
"""
Convert H(ue) S(aturation) V(alue or brightness) to H(ue) S(aturation) L(uminance)
Convert H(ue) S(aturation) V(alue or brightness) to H(ue) S(aturation) L(uminance).
with all values in the range of 0 to 1
http://codeitdown.com/hsl-hsb-hsv-color/
All values are in the range [0,1]
http://codeitdown.com/hsl-hsb-hsv-color
"""
if self.model != 'HSV': return
@ -105,10 +127,10 @@ class Color():
def _HSL2HSV(self):
"""
Convert H(ue) S(aturation) L(uminance) to H(ue) S(aturation) V(alue or brightness)
Convert H(ue) S(aturation) L(uminance) to H(ue) S(aturation) V(alue or brightness).
with all values in the range of 0 to 1
http://codeitdown.com/hsl-hsb-hsv-color/
All values are in the range [0,1]
http://codeitdown.com/hsl-hsb-hsv-color
"""
if self.model != 'HSL': return
@ -124,9 +146,9 @@ class Color():
def _HSL2RGB(self):
"""
Convert H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue)
Convert H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).
with all values in the range of 0 to 1
All values are in the range [0,1]
from http://en.wikipedia.org/wiki/HSL_and_HSV
"""
if self.model != 'HSL': return
@ -150,9 +172,9 @@ class Color():
def _RGB2HSL(self):
"""
Convert R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance)
Convert R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).
with all values in the range of 0 to 1
All values are in the range [0,1]
from http://130.113.54.154/~monger/hsl-rgb.html
"""
if self.model != 'RGB': return
@ -190,9 +212,9 @@ class Color():
def _RGB2XYZ(self):
"""
Convert R(ed) G(reen) B(lue) to CIE XYZ
Convert R(ed) G(reen) B(lue) to CIE XYZ.
with all values in the range of 0 to 1
All values are in the range [0,1]
from http://www.cs.rit.edu/~ncs/color/t_convert.html
"""
if self.model != 'RGB': return
@ -219,9 +241,9 @@ class Color():
def _XYZ2RGB(self):
"""
Convert CIE XYZ to R(ed) G(reen) B(lue)
Convert CIE XYZ to R(ed) G(reen) B(lue).
with all values in the range of 0 to 1
All values are in the range [0,1]
from http://www.cs.rit.edu/~ncs/color/t_convert.html
"""
if self.model != 'XYZ':
@ -251,9 +273,9 @@ class Color():
def _CIELAB2XYZ(self):
"""
Convert CIE Lab to CIE XYZ
Convert CIE Lab to CIE XYZ.
with XYZ in the range of 0 to 1
All values are in the range [0,1]
from http://www.easyrgb.com/index.php?X=MATH&H=07#text7
"""
if self.model != 'CIELAB': return
@ -275,11 +297,11 @@ class Color():
def _XYZ2CIELAB(self):
"""
Convert CIE XYZ to CIE Lab
Convert CIE XYZ to CIE Lab.
with XYZ in the range of 0 to 1
All values are in the range [0,1]
from http://en.wikipedia.org/wiki/Lab_color_space,
http://www.cs.rit.edu/~ncs/color/t_convert.html
http://www.cs.rit.edu/~ncs/color/t_convert.html
"""
if self.model != 'XYZ': return
@ -299,7 +321,7 @@ class Color():
def _CIELAB2MSH(self):
"""
Convert CIE Lab to Msh colorspace
Convert CIE Lab to Msh colorspace.
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
"""
@ -319,7 +341,7 @@ class Color():
def _MSH2CIELAB(self):
"""
Convert Msh colorspace to CIE Lab
Convert Msh colorspace to CIE Lab.
with s,h in radians
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
@ -337,7 +359,7 @@ class Color():
class Colormap():
"""Perceptually uniform diverging or sequential colormaps."""
"""Perceptually uniform diverging or sequential colormap."""
__slots__ = [
'left',
@ -394,7 +416,21 @@ class Colormap():
interpolate = 'perceptualuniform',
predefined = None
):
"""
Create a Colormap object.
Parameters
----------
left : Color
left color (minimum value)
right : Color
right color (maximum value)
interpolate : str
interpolation scheme (either 'perceptualuniform' or 'linear')
predefined : bool
ignore other arguments and use predefined definition
"""
if predefined is not None:
left = self.__predefined__[predefined.lower()]['left']
right= self.__predefined__[predefined.lower()]['right']
@ -412,16 +448,22 @@ class Colormap():
# ------------------------------------------------------------------
def __repr__(self):
"""Left and right value of colormap"""
"""Left and right value of colormap."""
return 'Left: %s Right: %s'%(self.left,self.right)
# ------------------------------------------------------------------
def invert(self):
"""Switch left/minimum with right/maximum."""
(self.left, self.right) = (self.right, self.left)
return self
# ------------------------------------------------------------------
def show_predefined(self):
"""Show the labels of the predefined colormaps."""
print('\n'.join(self.__predefined__.keys()))
# ------------------------------------------------------------------
def color(self,fraction = 0.5):
@ -490,21 +532,22 @@ class Colormap():
frac = 0.5*(np.array(crop) + 1.0) # rescale crop range to fractions
colors = [self.color(float(i)/(steps-1)*(frac[1]-frac[0])+frac[0]).expressAs(model).color for i in range(steps)]
if format == 'paraview':
colormap = ['[\n {{\n "ColorSpace" : "RGB", "Name" : "{}",\n "RGBPoints" : ['.format(name)] \
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f},'.format(i,color[0],color[1],color[2],)
for i,color in enumerate(colors[:-1])]\
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f} '.format(len(colors),colors[-1][0],colors[-1][1],colors[-1][2],)]\
colormap = ['[\n {{\n "ColorSpace": "RGB", "Name": "{}", "DefaultMap": true,\n "RGBPoints" : ['.format(name)] \
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f},'.format(i,color[0],color[1],color[2],) \
for i,color in enumerate(colors[:-1])] \
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f} '.format(len(colors),colors[-1][0],colors[-1][1],colors[-1][2],)] \
+ [' ]\n }\n]']
elif format == 'gmsh':
colormap = ['View.ColorTable = {'] \
+ [',\n'.join(['{%s}'%(','.join([str(x*255.0) for x in color])) for color in colors])] \
+ ['}']
elif format == 'gom':
colormap = ['1 1 ' + str(name) \
+ ' 9 ' + str(name) \
+ ' 0 1 0 3 0 0 -1 9 \ 0 0 0 255 255 255 0 0 255 ' \
+ '30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 ' + str(len(colors)) \
colormap = ['1 1 ' + str(name)
+ ' 9 ' + str(name)
+ ' 0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 '
+ '30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 ' + str(len(colors))
+ ' '.join([' 0 %s 255 1'%(' '.join([str(int(x*255.0)) for x in color])) for color in reversed(colors)])]
elif format == 'raw':

View File

@ -10,10 +10,27 @@ from . import version
class Geom():
"""Geometry definition for grid solvers"""
"""Geometry definition for grid solvers."""
def __init__(self,microstructure,size,origin=[0.0,0.0,0.0],homogenization=1,comments=[]):
"""New geometry definition from array of microstructures and size"""
"""
New geometry definition from array of microstructures and size.
Parameters
----------
microstructure : numpy.ndarray
microstructure array (3D)
size : list or numpy.ndarray
physical size of the microstructure in meter.
origin : list or numpy.ndarray, optional
physical origin of the microstructure in meter.
homogenization : integer, optional
homogenization index.
comments : list of str, optional
comments lines.
"""
self.__transforms__ = \
self.set_microstructure(microstructure)
self.set_size(size)
self.set_origin(origin)
@ -22,7 +39,7 @@ class Geom():
def __repr__(self):
"""Basic information on geometry definition"""
"""Basic information on geometry definition."""
return util.srepr([
'grid a b c: {}'.format(' x '.join(map(str,self.get_grid ()))),
'size x y z: {}'.format(' x '.join(map(str,self.get_size ()))),
@ -33,7 +50,21 @@ class Geom():
])
def update(self,microstructure=None,size=None,origin=None,rescale=False):
"""Updates microstructure and size"""
"""
Updates microstructure and size.
Parameters
----------
microstructure : numpy.ndarray, optional
microstructure array (3D).
size : list or numpy.ndarray, optional
physical size of the microstructure in meter.
origin : list or numpy.ndarray, optional
physical origin of the microstructure in meter.
rescale : bool, optional
ignore size parameter and rescale according to change of grid points.
"""
grid_old = self.get_grid()
size_old = self.get_size()
origin_old = self.get_origin()
@ -44,46 +75,77 @@ class Geom():
raise ValueError('Either set size explicitly or rescale automatically')
self.set_microstructure(microstructure)
self.set_size(self.get_grid()/grid_old*self.size if rescale else size)
self.set_origin(origin)
if size is not None:
self.set_size(size)
elif rescale:
self.set_size(self.get_grid()/grid_old*self.size)
message = ['grid a b c: {}'.format(' x '.join(map(str,grid_old)))]
if np.any(grid_old != self.get_grid()):
message[-1] = util.delete(message[-1])
message.append('grid a b c: {}'.format(' x '.join(map(str,self.get_grid()))))
message.append(util.emph('grid a b c: {}'.format(' x '.join(map(str,self.get_grid())))))
message.append('size x y z: {}'.format(' x '.join(map(str,size_old))))
if np.any(size_old != self.get_size()):
message[-1] = util.delete(message[-1])
message.append('size x y z: {}'.format(' x '.join(map(str,self.get_size()))))
message.append(util.emph('size x y z: {}'.format(' x '.join(map(str,self.get_size())))))
message.append('origin x y z: {}'.format(' '.join(map(str,origin_old))))
if np.any(origin_old != self.get_origin()):
message[-1] = util.delete(message[-1])
message.append('origin x y z: {}'.format(' '.join(map(str,self.get_origin()))))
message.append(util.emph('origin x y z: {}'.format(' '.join(map(str,self.get_origin())))))
message.append('homogenization: {}'.format(self.get_homogenization()))
message.append('# microstructures: {}'.format(unique_old))
if unique_old != len(np.unique(self.microstructure)):
message[-1] = util.delete(message[-1])
message.append('# microstructures: {}'.format(len(np.unique(self.microstructure))))
message.append(util.emph('# microstructures: {}'.format(len(np.unique(self.microstructure)))))
message.append('max microstructure: {}'.format(max_old))
if max_old != np.nanmax(self.microstructure):
message[-1] = util.delete(message[-1])
message.append('max microstructure: {}'.format(np.nanmax(self.microstructure)))
message.append(util.emph('max microstructure: {}'.format(np.nanmax(self.microstructure))))
return util.return_message(message)
def set_comments(self,comments):
"""
Replaces all existing comments.
Parameters
----------
comments : list of str
new comments.
"""
self.comments = []
self.add_comments(comments)
def add_comments(self,comments):
"""
Appends comments to existing comments.
Parameters
----------
comments : list of str
new comments.
"""
self.comments += [str(c) for c in comments] if isinstance(comments,list) else [str(comments)]
def set_microstructure(self,microstructure):
"""
Replaces the existing microstructure representation.
Parameters
----------
microstructure : numpy.ndarray
microstructure array (3D).
"""
if microstructure is not None:
if len(microstructure.shape) != 3:
raise ValueError('Invalid microstructure shape {}'.format(*microstructure.shape))
@ -93,6 +155,15 @@ class Geom():
self.microstructure = np.copy(microstructure)
def set_size(self,size):
"""
Replaces the existing size information.
Parameters
----------
size : list or numpy.ndarray
physical size of the microstructure in meter.
"""
if size is None:
grid = np.asarray(self.microstructure.shape)
self.size = grid/np.max(grid)
@ -103,6 +174,15 @@ class Geom():
self.size = np.array(size)
def set_origin(self,origin):
"""
Replaces the existing origin information.
Parameters
----------
origin : list or numpy.ndarray
physical origin of the microstructure in meter
"""
if origin is not None:
if len(origin) != 3:
raise ValueError('Invalid origin {}'.format(*origin))
@ -110,6 +190,15 @@ class Geom():
self.origin = np.array(origin)
def set_homogenization(self,homogenization):
"""
Replaces the existing homogenization index.
Parameters
----------
homogenization : integer
homogenization index
"""
if homogenization is not None:
if not isinstance(homogenization,int) or homogenization < 1:
raise TypeError('Invalid homogenization {}'.format(homogenization))
@ -118,24 +207,31 @@ class Geom():
def get_microstructure(self):
"""Return the microstructure representation."""
return np.copy(self.microstructure)
def get_size(self):
"""Return the physical size in meter."""
return np.copy(self.size)
def get_origin(self):
"""Return the origin in meter."""
return np.copy(self.origin)
def get_grid(self):
"""Return the grid discretization."""
return np.array(self.microstructure.shape)
def get_homogenization(self):
"""Return the homogenization index."""
return self.homogenization
def get_comments(self):
"""Return the comments."""
return self.comments[:]
def get_header(self):
"""Return the full header (grid, size, origin, homogenization, comments)."""
header = ['{} header'.format(len(self.comments)+4)] + self.comments
header.append('grid a {} b {} c {}'.format(*self.get_grid()))
header.append('size x {} y {} z {}'.format(*self.get_size()))
@ -145,7 +241,15 @@ class Geom():
@classmethod
def from_file(cls,fname):
"""Reads a geom file"""
"""
Reads a geom file.
Parameters
----------
fname : str or file handle
geometry file to read.
"""
with (open(fname) if isinstance(fname,str) else fname) as f:
f.seek(0)
header_length,keyword = f.readline().split()[:2]
@ -190,13 +294,21 @@ class Geom():
raise TypeError('Invalid file: expected {} entries,found {}'.format(grid.prod(),i))
microstructure = microstructure.reshape(grid,order='F')
if not np.any(np.mod(microstructure.flatten(),1) != 0.0): # no float present
if not np.any(np.mod(microstructure.flatten(),1) != 0.0): # no float present
microstructure = microstructure.astype('int')
return cls(microstructure.reshape(grid),size,origin,homogenization,comments)
def to_file(self,fname):
"""Writes to file"""
"""
Writes a geom file.
Parameters
----------
fname : str or file handle
geometry file to write.
"""
header = self.get_header()
grid = self.get_grid()
format_string = '%{}i'.format(1+int(np.floor(np.log10(np.nanmax(self.microstructure))))) if self.microstructure.dtype == int \
@ -208,7 +320,15 @@ class Geom():
def to_vtk(self,fname=None):
"""Generates vtk file. If file name is given, stored in file otherwise returned as string"""
"""
Generates vtk file.
Parameters
----------
fname : str, optional
vtk file to write. If no file is given, a string is returned.
"""
grid = self.get_grid() + np.ones(3,dtype=int)
size = self.get_size()
origin = self.get_origin()
@ -262,7 +382,7 @@ class Geom():
def show(self):
"""Show raw content (as in file)"""
"""Show raw content (as in file)."""
f=StringIO()
self.to_file(f)
f.seek(0)

View File

@ -10,35 +10,44 @@ from .quaternion import P
####################################################################################################
class Rotation:
u"""
Orientation stored as unit quaternion.
Following: D Rowenhorst et al. Consistent representations of and conversions between 3D rotations
10.1088/0965-0393/23/8/083501
Convention 1: coordinate frames are right-handed
Convention 2: a rotation angle ω is taken to be positive for a counterclockwise rotation
when viewing from the end point of the rotation axis towards the origin
Convention 3: rotations will be interpreted in the passive sense
Convention 4: Euler angle triplets are implemented using the Bunge convention,
with the angular ranges as [0, 2π],[0, π],[0, 2π]
Convention 5: the rotation angle ω is limited to the interval [0, π]
Convention 6: P = -1 (as default)
q is the real part, p = (x, y, z) are the imaginary parts.
Orientation stored with functionality for conversion to different representations.
References
----------
D. Rowenhorst et al., Modelling and Simulation in Materials Science and Engineering 23:083501, 2015
https://doi.org/10.1088/0965-0393/23/8/083501
Conventions
-----------
Convention 1: Coordinate frames are right-handed.
Convention 2: A rotation angle ω is taken to be positive for a counterclockwise rotation
when viewing from the end point of the rotation axis towards the origin.
Convention 3: Rotations will be interpreted in the passive sense.
Convention 4: Euler angle triplets are implemented using the Bunge convention,
with the angular ranges as [0, 2π],[0, π],[0, 2π].
Convention 5: The rotation angle ω is limited to the interval [0, π].
Convention 6: P = -1 (as default).
Usage
-----
Vector "a" (defined in coordinate system "A") is passively rotated
resulting in new coordinates "b" when expressed in system "B".
b = Q * a
b = np.dot(Q.asMatrix(),a)
"""
__slots__ = ['quaternion']
def __init__(self,quaternion = np.array([1.0,0.0,0.0,0.0])):
"""
Initializes to identity unless specified
Initializes to identity unless specified.
If a quaternion is given, it needs to comply with the convection. Use .fromQuaternion
to check the input.
Parameters
----------
quaternion : numpy.ndarray, optional
Unit quaternion that follows the conventions. Use .fromQuaternion to perform a sanity check.
"""
if isinstance(quaternion,Quaternion):
self.quaternion = quaternion.copy()
@ -46,14 +55,14 @@ class Rotation:
self.quaternion = Quaternion(q=quaternion[0],p=quaternion[1:4])
def __copy__(self):
"""Copy"""
"""Copy."""
return self.__class__(self.quaternion)
copy = __copy__
def __repr__(self):
"""Value in selected representation"""
"""Orientation displayed as unit quaternion, rotation matrix, and Bunge-Euler angles."""
return '\n'.join([
'{}'.format(self.quaternion),
'Matrix:\n{}'.format( '\n'.join(['\t'.join(list(map(str,self.asMatrix()[i,:]))) for i in range(3)]) ),
@ -62,9 +71,18 @@ class Rotation:
def __mul__(self, other):
"""
Multiplication
Multiplication.
Rotation: Details needed (active/passive), rotation of (3,3,3,3)-matrix should be considered
Parameters
----------
other : numpy.ndarray or Rotation
Vector, second or fourth order tensor, or rotation object that is rotated.
Todo
----
Document details active/passive)
considere rotation of (3,3,3,3)-matrix
"""
if isinstance(other, Rotation): # rotate a rotation
return self.__class__(self.quaternion * other.quaternion).standardize()
@ -104,32 +122,48 @@ class Rotation:
def inverse(self):
"""In-place inverse rotation/backward rotation"""
"""In-place inverse rotation/backward rotation."""
self.quaternion.conjugate()
return self
def inversed(self):
"""Inverse rotation/backward rotation"""
"""Inverse rotation/backward rotation."""
return self.copy().inverse()
def standardize(self):
"""In-place quaternion representation with positive q"""
"""In-place quaternion representation with positive q."""
if self.quaternion.q < 0.0: self.quaternion.homomorph()
return self
def standardized(self):
"""Quaternion representation with positive q"""
"""Quaternion representation with positive q."""
return self.copy().standardize()
def misorientation(self,other):
"""Misorientation"""
"""
Get Misorientation.
Parameters
----------
other : Rotation
Rotation to which the misorientation is computed.
"""
return other*self.inversed()
def average(self,other):
"""Calculate the average rotation"""
"""
Calculate the average rotation.
Parameters
----------
other : Rotation
Rotation from which the average is rotated.
"""
return Rotation.fromAverage([self,other])
@ -137,41 +171,75 @@ class Rotation:
# convert to different orientation representations (numpy arrays)
def asQuaternion(self):
"""Unit quaternion: (q, [p_1, p_2, p_3])"""
"""Unit quaternion: (q, p_1, p_2, p_3)."""
return self.quaternion.asArray()
def asEulers(self,
degrees = False):
"""Bunge-Euler angles: (φ_1, ϕ, φ_2)"""
"""
Bunge-Euler angles: (φ_1, ϕ, φ_2).
Parameters
----------
degrees : bool, optional
return angles in degrees.
"""
eu = qu2eu(self.quaternion.asArray())
if degrees: eu = np.degrees(eu)
return eu
def asAxisAngle(self,
degrees = False):
"""Axis-angle pair: ([n_1, n_2, n_3], ω)"""
"""
Axis angle pair: ([n_1, n_2, n_3], ω).
Parameters
----------
degrees : bool, optional
return rotation angle in degrees.
"""
ax = qu2ax(self.quaternion.asArray())
if degrees: ax[3] = np.degrees(ax[3])
return ax
def asMatrix(self):
"""Rotation matrix"""
"""Rotation matrix."""
return qu2om(self.quaternion.asArray())
def asRodrigues(self,vector=False):
"""Rodrigues-Frank vector: ([n_1, n_2, n_3], tan(ω/2))"""
def asRodrigues(self,
vector=False):
"""
Rodrigues-Frank vector: ([n_1, n_2, n_3], tan(ω/2)).
Parameters
----------
vector : bool, optional
return as array of length 3, i.e. scale the unit vector giving the rotation axis.
"""
ro = qu2ro(self.quaternion.asArray())
return ro[:3]*ro[3] if vector else ro
def asHomochoric(self):
"""Homochoric vector: (h_1, h_2, h_3)"""
"""Homochoric vector: (h_1, h_2, h_3)."""
return qu2ho(self.quaternion.asArray())
def asCubochoric(self):
"""Cubochoric vector: (c_1, c_2, c_3)."""
return qu2cu(self.quaternion.asArray())
def asM(self):
"""Intermediate representation supporting quaternion averaging (see F. Landis Markley et al.)"""
"""
Intermediate representation supporting quaternion averaging.
References
----------
F. Landis Markley et al., Journal of Guidance, Control, and Dynamics 30(4):1193-1197, 2007
https://doi.org/10.2514/1.28949
"""
return self.quaternion.asM()
@ -299,14 +367,20 @@ class Rotation:
rotations,
weights = []):
"""
Average rotation
ref: F. Landis Markley, Yang Cheng, John Lucas Crassidis, and Yaakov Oshman.
Averaging Quaternions,
Journal of Guidance, Control, and Dynamics, Vol. 30, No. 4 (2007), pp. 1193-1197.
doi: 10.2514/1.28949
Average rotation.
References
----------
F. Landis Markley et al., Journal of Guidance, Control, and Dynamics 30(4):1193-1197, 2007
https://doi.org/10.2514/1.28949
usage: input a list of rotations and optional weights
Parameters
----------
rotations : list of Rotations
Rotations to average from
weights : list of floats, optional
Weights for each rotation used for averaging
"""
if not all(isinstance(item, Rotation) for item in rotations):
raise TypeError("Only instances of Rotation can be averaged.")
@ -339,42 +413,78 @@ class Rotation:
# ******************************************************************************************
class Symmetry:
"""
Symmetry operations for lattice systems
Symmetry operations for lattice systems.
References
----------
https://en.wikipedia.org/wiki/Crystal_system
"""
lattices = [None,'orthorhombic','tetragonal','hexagonal','cubic',]
def __init__(self, symmetry = None):
if isinstance(symmetry, str) and symmetry.lower() in Symmetry.lattices:
self.lattice = symmetry.lower()
else:
self.lattice = None
"""
Symmetry Definition.
Parameters
----------
symmetry : str, optional
label of the crystal system
"""
if symmetry is not None and symmetry.lower() not in Symmetry.lattices:
raise KeyError('Symmetry/crystal system "{}" is unknown'.format(symmetry))
self.lattice = symmetry.lower() if isinstance(symmetry,str) else symmetry
def __copy__(self):
"""Copy"""
"""Copy."""
return self.__class__(self.lattice)
copy = __copy__
def __repr__(self):
"""Readable string"""
"""Readable string."""
return '{}'.format(self.lattice)
def __eq__(self, other):
"""Equal to other"""
"""
Equal to other.
Parameters
----------
other : Symmetry
Symmetry to check for equality.
"""
return self.lattice == other.lattice
def __neq__(self, other):
"""Not equal to other"""
"""
Not Equal to other.
Parameters
----------
other : Symmetry
Symmetry to check for inequality.
"""
return not self.__eq__(other)
def __cmp__(self,other):
"""Linear ordering"""
"""
Linear ordering.
Parameters
----------
other : Symmetry
Symmetry to check for for order.
"""
myOrder = Symmetry.lattices.index(self.lattice)
otherOrder = Symmetry.lattices.index(other.lattice)
return (myOrder > otherOrder) - (myOrder < otherOrder)
@ -458,7 +568,7 @@ class Symmetry:
def inFZ(self,rodrigues):
"""
Check whether given Rodrigues vector falls into fundamental zone of own symmetry.
Check whether given Rodriques-Frank vector falls into fundamental zone of own symmetry.
Fundamental zone in Rodrigues space is point symmetric around origin.
"""
@ -491,11 +601,13 @@ class Symmetry:
def inDisorientationSST(self,rodrigues):
"""
Check whether given Rodrigues vector (of misorientation) falls into standard stereographic triangle of own symmetry.
Check whether given Rodriques-Frank vector (of misorientation) falls into standard stereographic triangle of own symmetry.
References
----------
A. Heinz and P. Neumann, Acta Crystallographica Section A 47:780-789, 1991
https://doi.org/10.1107/S0108767391006864
Determination of disorientations follow the work of A. Heinz and P. Neumann:
Representation of Orientation and Disorientation Data for Cubic, Hexagonal, Tetragonal and Orthorhombic Crystals
Acta Cryst. (1991). A47, 780-789
"""
if (len(rodrigues) != 3):
raise ValueError('Input is not a Rodriques-Frank vector.\n')
@ -611,11 +723,15 @@ class Symmetry:
# ******************************************************************************************
class Lattice:
"""
Lattice system
Lattice system.
Currently, this contains only a mapping from Bravais lattice to symmetry
and orientation relationships. It could include twin and slip systems.
References
----------
https://en.wikipedia.org/wiki/Bravais_lattice
"""
lattices = {
@ -628,18 +744,27 @@ class Lattice:
def __init__(self, lattice):
"""
New lattice of given type.
Parameters
----------
lattice : str
Bravais lattice.
"""
self.lattice = lattice
self.symmetry = Symmetry(self.lattices[lattice]['symmetry'])
def __repr__(self):
"""Report basic lattice information"""
"""Report basic lattice information."""
return 'Bravais lattice {} ({} symmetry)'.format(self.lattice,self.symmetry)
# Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation
# from S. Morito et al. Journal of Alloys and Compounds 577 (2013) 587-S592
# also see K. Kitahara et al. Acta Materialia 54 (2006) 1279-1288
# from S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013
# also see K. Kitahara et al., Acta Materialia 54:1279-1288, 2006
KS = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 1, 1, 1],[ 0, 1, 1]],
@ -693,7 +818,7 @@ class Lattice:
[[ 1, 0, 1],[ -1, 1, -1]]],dtype='float')}
# Greninger--Troiano orientation relationship for fcc <-> bcc transformation
# from Y. He et al. Journal of Applied Crystallography 39 (2006) 72-81
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
GT = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 1, 1, 1],[ 1, 0, 1]],
@ -747,7 +872,7 @@ class Lattice:
[[-17,-12, 5],[-17, 7, 17]]],dtype='float')}
# Greninger--Troiano' orientation relationship for fcc <-> bcc transformation
# from Y. He et al. Journal of Applied Crystallography 39 (2006) 72-81
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
GTprime = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 7, 17, 17],[ 12, 5, 17]],
@ -801,7 +926,7 @@ class Lattice:
[[ 1, 1, 0],[ 1, 1, -1]]],dtype='float')}
# Nishiyama--Wassermann orientation relationship for fcc <-> bcc transformation
# from H. Kitahara et al. Materials Characterization 54 (2005) 378-386
# from H. Kitahara et al., Materials Characterization 54:378-386, 2005
NW = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 1, 1, 1],[ 0, 1, 1]],
@ -831,7 +956,7 @@ class Lattice:
[[ -1, -1, -2],[ 0, -1, 1]]],dtype='float')}
# Pitsch orientation relationship for fcc <-> bcc transformation
# from Y. He et al. Acta Materialia 53 (2005) 1179-1190
# from Y. He et al., Acta Materialia 53:1179-1190, 2005
Pitsch = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 0, 1, 0],[ -1, 0, 1]],
@ -861,7 +986,7 @@ class Lattice:
[[ 1, 1, 0],[ 1, 1, -1]]],dtype='float')}
# Bain orientation relationship for fcc <-> bcc transformation
# from Y. He et al./Journal of Applied Crystallography (2006). 39, 72-81
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
Bain = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 1, 0, 0],[ 1, 0, 0]],
@ -873,12 +998,32 @@ class Lattice:
[[ 1, 0, 0],[ 1, 1, 0]]],dtype='float')}
def relationOperations(self,model):
"""
Crystallographic orientation relationships for phase transformations.
References
----------
S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013
https://doi.org/10.1016/j.jallcom.2012.02.004
K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006
https://doi.org/10.1016/j.actamat.2005.11.001
Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
https://doi.org/10.1107/S0021889805038276
H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005
https://doi.org/10.1016/j.matchar.2004.12.015
Y. He et al., Acta Materialia 53(4):1179-1190, 2005
https://doi.org/10.1016/j.actamat.2004.11.021
"""
models={'KS':self.KS, 'GT':self.GT, "GT'":self.GTprime,
'NW':self.NW, 'Pitsch': self.Pitsch, 'Bain':self.Bain}
try:
relationship = models[model]
except:
except KeyError :
raise KeyError('Orientation relationship "{}" is unknown'.format(model))
if self.lattice not in relationship['mapping']:
@ -909,19 +1054,29 @@ class Lattice:
class Orientation:
"""
Crystallographic orientation
Crystallographic orientation.
A crystallographic orientation contains a rotation and a lattice
A crystallographic orientation contains a rotation and a lattice.
"""
__slots__ = ['rotation','lattice']
def __repr__(self):
"""Report lattice type and orientation"""
"""Report lattice type and orientation."""
return self.lattice.__repr__()+'\n'+self.rotation.__repr__()
def __init__(self, rotation, lattice):
"""
New orientation from rotation and lattice.
Parameters
----------
rotation : Rotation
Rotation specifying the lattice orientation.
lattice : Lattice
Lattice type of the crystal.
"""
if isinstance(lattice, Lattice):
self.lattice = lattice
else:
@ -971,7 +1126,7 @@ class Orientation:
return self.lattice.symmetry.inFZ(self.rotation.asRodrigues(vector=True))
def equivalentOrientations(self,members=[]):
"""List of orientations which are symmetrically equivalent"""
"""List of orientations which are symmetrically equivalent."""
try:
iter(members) # asking for (even empty) list of members?
except TypeError:
@ -981,12 +1136,12 @@ class Orientation:
for q in self.lattice.symmetry.symmetryOperations(members)] # yes, return list of rotations
def relatedOrientations(self,model):
"""List of orientations related by the given orientation relationship"""
"""List of orientations related by the given orientation relationship."""
r = self.lattice.relationOperations(model)
return [self.__class__(self.rotation*o,r['lattice']) for o in r['rotations']]
def reduced(self):
"""Transform orientation to fall into fundamental zone according to symmetry"""
"""Transform orientation to fall into fundamental zone according to symmetry."""
for me in self.equivalentOrientations():
if self.lattice.symmetry.inFZ(me.rotation.asRodrigues(vector=True)): break
@ -996,7 +1151,7 @@ class Orientation:
axis,
proper = False,
SST = True):
"""Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST)"""
"""Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST)."""
if SST: # pole requested to be within SST
for i,o in enumerate(self.equivalentOrientations()): # test all symmetric equivalent quaternions
pole = o.rotation*axis # align crystal direction to axis
@ -1008,7 +1163,7 @@ class Orientation:
def IPFcolor(self,axis):
"""TSL color of inverse pole figure for given axis"""
"""TSL color of inverse pole figure for given axis."""
color = np.zeros(3,'d')
for o in self.equivalentOrientations():
@ -1023,7 +1178,7 @@ class Orientation:
def fromAverage(cls,
orientations,
weights = []):
"""Create orientation from average of list of orientations"""
"""Create orientation from average of list of orientations."""
if not all(isinstance(item, Orientation) for item in orientations):
raise TypeError("Only instances of Orientation can be averaged.")
@ -1039,7 +1194,7 @@ class Orientation:
def average(self,other):
"""Calculate the average rotation"""
"""Calculate the average rotation."""
return Orientation.fromAverage([self,other])
@ -1080,10 +1235,10 @@ def isone(a):
def iszero(a):
return np.isclose(a,0.0,atol=1.0e-12,rtol=0.0)
#---------- quaternion ----------
#---------- Quaternion ----------
def qu2om(qu):
"""Quaternion to orientation matrix"""
"""Quaternion to rotation matrix."""
qq = qu[0]**2-(qu[1]**2 + qu[2]**2 + qu[3]**2)
om = np.diag(qq + 2.0*np.array([qu[1],qu[2],qu[3]])**2)
@ -1097,7 +1252,7 @@ def qu2om(qu):
def qu2eu(qu):
"""Quaternion to Euler angles"""
"""Quaternion to Bunge-Euler angles."""
q03 = qu[0]**2+qu[3]**2
q12 = qu[1]**2+qu[2]**2
chi = np.sqrt(q03*q12)
@ -1117,7 +1272,7 @@ def qu2eu(qu):
def qu2ax(qu):
"""
Quaternion to axis angle
Quaternion to axis angle pair.
Modified version of the original formulation, should be numerically more stable
"""
@ -1134,7 +1289,7 @@ def qu2ax(qu):
def qu2ro(qu):
"""Quaternion to Rodrigues vector"""
"""Quaternion to Rodriques-Frank vector."""
if iszero(qu[0]):
ro = [qu[1], qu[2], qu[3], np.inf]
else:
@ -1146,7 +1301,7 @@ def qu2ro(qu):
def qu2ho(qu):
"""Quaternion to homochoric"""
"""Quaternion to homochoric vector."""
omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) # avoid numerical difficulties
if iszero(omega):
@ -1160,15 +1315,15 @@ def qu2ho(qu):
def qu2cu(qu):
"""Quaternion to cubochoric"""
"""Quaternion to cubochoric vector."""
return ho2cu(qu2ho(qu))
#---------- orientation matrix ----------
#---------- Rotation matrix ----------
def om2qu(om):
"""
Orientation matrix to quaternion
Rotation matrix to quaternion.
The original formulation (direct conversion) had (numerical?) issues
"""
@ -1176,7 +1331,7 @@ def om2qu(om):
def om2eu(om):
"""Orientation matrix to Euler angles"""
"""Rotation matrix to Bunge-Euler angles."""
if abs(om[2,2]) < 1.0:
zeta = 1.0/np.sqrt(1.0-om[2,2]**2)
eu = np.array([np.arctan2(om[2,0]*zeta,-om[2,1]*zeta),
@ -1191,7 +1346,7 @@ def om2eu(om):
def om2ax(om):
"""Orientation matrix to axis angle"""
"""Rotation matrix to axis angle pair."""
ax=np.empty(4)
# first get the rotation angle
@ -1212,24 +1367,24 @@ def om2ax(om):
def om2ro(om):
"""Orientation matrix to Rodriques vector"""
"""Rotation matrix to Rodriques-Frank vector."""
return eu2ro(om2eu(om))
def om2ho(om):
"""Orientation matrix to homochoric"""
"""Rotation matrix to homochoric vector."""
return ax2ho(om2ax(om))
def om2cu(om):
"""Orientation matrix to cubochoric"""
"""Rotation matrix to cubochoric vector."""
return ho2cu(om2ho(om))
#---------- Euler angles ----------
#---------- Bunge-Euler angles ----------
def eu2qu(eu):
"""Euler angles to quaternion"""
"""Bunge-Euler angles to quaternion."""
ee = 0.5*eu
cPhi = np.cos(ee[1])
sPhi = np.sin(ee[1])
@ -1242,7 +1397,7 @@ def eu2qu(eu):
def eu2om(eu):
"""Euler angles to orientation matrix"""
"""Bunge-Euler angles to rotation matrix."""
c = np.cos(eu)
s = np.sin(eu)
@ -1255,7 +1410,7 @@ def eu2om(eu):
def eu2ax(eu):
"""Euler angles to axis angle"""
"""Bunge-Euler angles to axis angle pair."""
t = np.tan(eu[1]*0.5)
sigma = 0.5*(eu[0]+eu[2])
delta = 0.5*(eu[0]-eu[2])
@ -1266,7 +1421,7 @@ def eu2ax(eu):
if iszero(alpha):
ax = np.array([ 0.0, 0.0, 1.0, 0.0 ])
else:
ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis-angle pair so a minus sign in front
ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front
ax = np.append(ax,alpha)
if alpha < 0.0: ax *= -1.0 # ensure alpha is positive
@ -1274,8 +1429,8 @@ def eu2ax(eu):
def eu2ro(eu):
"""Euler angles to Rodrigues vector"""
ro = eu2ax(eu) # convert to axis angle representation
"""Bunge-Euler angles to Rodriques-Frank vector."""
ro = eu2ax(eu) # convert to axis angle pair representation
if ro[3] >= np.pi: # Differs from original implementation. check convention 5
ro[3] = np.inf
elif iszero(ro[3]):
@ -1287,19 +1442,19 @@ def eu2ro(eu):
def eu2ho(eu):
"""Euler angles to homochoric"""
"""Bunge-Euler angles to homochoric vector."""
return ax2ho(eu2ax(eu))
def eu2cu(eu):
"""Euler angles to cubochoric"""
"""Bunge-Euler angles to cubochoric vector."""
return ho2cu(eu2ho(eu))
#---------- axis angle ----------
#---------- Axis angle pair ----------
def ax2qu(ax):
"""Axis angle to quaternion"""
"""Axis angle pair to quaternion."""
if iszero(ax[3]):
qu = np.array([ 1.0, 0.0, 0.0, 0.0 ])
else:
@ -1311,7 +1466,7 @@ def ax2qu(ax):
def ax2om(ax):
"""Axis angle to orientation matrix"""
"""Axis angle pair to rotation matrix."""
c = np.cos(ax[3])
s = np.sin(ax[3])
omc = 1.0-c
@ -1326,12 +1481,12 @@ def ax2om(ax):
def ax2eu(ax):
"""Orientation matrix to Euler angles"""
"""Rotation matrix to Bunge Euler angles."""
return om2eu(ax2om(ax))
def ax2ro(ax):
"""Axis angle to Rodrigues vector"""
"""Axis angle pair to Rodriques-Frank vector."""
if iszero(ax[3]):
ro = [ 0.0, 0.0, P, 0.0 ]
else:
@ -1344,36 +1499,36 @@ def ax2ro(ax):
def ax2ho(ax):
"""Axis angle to homochoric"""
"""Axis angle pair to homochoric vector."""
f = (0.75 * ( ax[3] - np.sin(ax[3]) ))**(1.0/3.0)
ho = ax[0:3] * f
return ho
def ax2cu(ax):
"""Axis angle to cubochoric"""
"""Axis angle pair to cubochoric vector."""
return ho2cu(ax2ho(ax))
#---------- Rodrigues--Frank ----------
#---------- Rodrigues-Frank vector ----------
def ro2qu(ro):
"""Rodrigues vector to quaternion"""
"""Rodriques-Frank vector to quaternion."""
return ax2qu(ro2ax(ro))
def ro2om(ro):
"""Rodgrigues vector to orientation matrix"""
"""Rodgrigues-Frank vector to rotation matrix."""
return ax2om(ro2ax(ro))
def ro2eu(ro):
"""Rodrigues vector to orientation matrix"""
"""Rodriques-Frank vector to Bunge-Euler angles."""
return om2eu(ro2om(ro))
def ro2ax(ro):
"""Rodrigues vector to axis angle"""
"""Rodriques-Frank vector to axis angle pair."""
ta = ro[3]
if iszero(ta):
@ -1389,7 +1544,7 @@ def ro2ax(ro):
def ro2ho(ro):
"""Rodrigues vector to homochoric"""
"""Rodriques-Frank vector to homochoric vector."""
if iszero(np.sum(ro[0:3]**2.0)):
ho = [ 0.0, 0.0, 0.0 ]
else:
@ -1400,29 +1555,29 @@ def ro2ho(ro):
def ro2cu(ro):
"""Rodrigues vector to cubochoric"""
"""Rodriques-Frank vector to cubochoric vector."""
return ho2cu(ro2ho(ro))
#---------- homochoric ----------
#---------- Homochoric vector----------
def ho2qu(ho):
"""Homochoric to quaternion"""
"""Homochoric vector to quaternion."""
return ax2qu(ho2ax(ho))
def ho2om(ho):
"""Homochoric to orientation matrix"""
"""Homochoric vector to rotation matrix."""
return ax2om(ho2ax(ho))
def ho2eu(ho):
"""Homochoric to Euler angles"""
"""Homochoric vector to Bunge-Euler angles."""
return ax2eu(ho2ax(ho))
def ho2ax(ho):
"""Homochoric to axis angle"""
"""Homochoric vector to axis angle pair."""
tfit = np.array([+1.0000000000018852, -0.5000000002194847,
-0.024999992127593126, -0.003928701544781374,
-0.0008152701535450438, -0.0002009500426119712,
@ -1448,42 +1603,42 @@ def ho2ax(ho):
def ho2ro(ho):
"""Axis angle to Rodriques vector"""
"""Axis angle pair to Rodriques-Frank vector."""
return ax2ro(ho2ax(ho))
def ho2cu(ho):
"""Homochoric to cubochoric"""
"""Homochoric vector to cubochoric vector."""
return Lambert.BallToCube(ho)
#---------- cubochoric ----------
#---------- Cubochoric ----------
def cu2qu(cu):
"""Cubochoric to quaternion"""
"""Cubochoric vector to quaternion."""
return ho2qu(cu2ho(cu))
def cu2om(cu):
"""Cubochoric to orientation matrix"""
"""Cubochoric vector to rotation matrix."""
return ho2om(cu2ho(cu))
def cu2eu(cu):
"""Cubochoric to Euler angles"""
"""Cubochoric vector to Bunge-Euler angles."""
return ho2eu(cu2ho(cu))
def cu2ax(cu):
"""Cubochoric to axis angle"""
"""Cubochoric vector to axis angle pair."""
return ho2ax(cu2ho(cu))
def cu2ro(cu):
"""Cubochoric to Rodrigues vector"""
"""Cubochoric vector to Rodriques-Frank vector."""
return ho2ro(cu2ho(cu))
def cu2ho(cu):
"""Cubochoric to homochoric"""
"""Cubochoric vector to homochoric vector."""
return Lambert.CubeToBall(cu)

View File

@ -1,7 +1,5 @@
# -*- coding: UTF-8 no BOM -*-
"""Tools to control the various BVP solvers"""
"""Tools to control the various solvers."""
from .solver import Solver # noqa
from .spectral import Spectral # noqa
from .marc import Marc # noqa
from .abaqus import Abaqus # noqa

View File

@ -1,17 +1,24 @@
# -*- coding: UTF-8 no BOM -*-
import subprocess
from .solver import Solver
import damask
import subprocess
class Abaqus(Solver):
"""Wrapper to run DAMASK with Abaqus."""
def __init__(self,version=''): # example version string: 2017
self.solver='Abaqus'
if version =='':
version = damask.Environment().options['ABAQUS_VERSION']
else:
self.version = version
def __init__(self,version=int(damask.Environment().options['ABAQUS_VERSION'])):
"""
Create a Abaqus solver object.
Parameters
----------
version : integer
Abaqus version
"""
self.solver ='Abaqus'
self.version = int(version)
def return_run_command(self,model):
env=damask.Environment()
@ -21,7 +28,7 @@ class Abaqus(Solver):
except OSError: # link to abqXXX not existing
cmd='abaqus'
process = subprocess.Popen(['abaqus','information=release'],stdout = subprocess.PIPE,stderr = subprocess.PIPE)
detectedVersion = process.stdout.readlines()[1].split()[1].decode('utf-8')
detectedVersion = int(process.stdout.readlines()[1].split()[1].decode('utf-8'))
if self.version != detectedVersion:
raise Exception('found Abaqus version {}, but requested {}'.format(detectedVersion,self.version))
return '{} -job {} -user {}/src/DAMASK_abaqus interactive'.format(cmd,model,env.rootDir())

View File

@ -1,49 +1,47 @@
# -*- coding: UTF-8 no BOM -*-
import os
import subprocess
import shlex
from .solver import Solver
import damask
class Marc(Solver):
"""Wrapper to run DAMASK with MSCMarc."""
def __init__(self):
self.solver = 'Marc'
#--------------------------
def version(self):
import damask.environment
return damask.environment.Environment().options['MARC_VERSION']
def __init__(self,version=float(damask.Environment().options['MARC_VERSION'])):
"""
Create a Marc solver object.
#--------------------------
def libraryPath(self,release = ''):
import os,damask.environment
Parameters
----------
version : float
Marc version
MSCpath = damask.environment.Environment().options['MSC_ROOT']
if len(release) == 0: release = self.version()
path = '{}/mentat{}/shlib/linux64'.format(MSCpath,release)
return path if os.path.exists(path) else ''
"""
self.solver ='Marc'
self.version = float(damask.environment.Environment().options['MARC_VERSION'])
#--------------------------
def toolsPath(self,release = ''):
import os,damask.environment
def libraryPath(self):
MSCpath = damask.environment.Environment().options['MSC_ROOT']
if len(release) == 0: release = self.version()
path_MSC = damask.environment.Environment().options['MSC_ROOT']
path_lib = '{}/mentat{}/shlib/linux64'.format(path_MSC,self.version)
path = '%s/marc%s/tools'%(MSCpath,release)
return path_lib if os.path.exists(path_lib) else ''
#--------------------------
def toolsPath(self):
path_MSC = damask.environment.Environment().options['MSC_ROOT']
path_tools = '{}/marc{}/tools'.format(path_MSC,self.version)
return path_tools if os.path.exists(path_tools) else ''
return path if os.path.exists(path) else ''
#--------------------------
def submit_job(self,
release = '',
model = 'model',
job = 'job1',
logfile = None,
@ -51,25 +49,22 @@ class Marc(Solver):
optimization ='',
):
import os,damask.environment
import subprocess,shlex
if len(release) == 0: release = self.version()
damaskEnv = damask.environment.Environment()
user = 'not found'
if compile:
if os.path.isfile(os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}.f90'.format(release))):
user = os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}'.format(release))
if os.path.isfile(os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}.f90'.format(self.version))):
user = os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}'.format(self.version))
else:
if os.path.isfile(os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}.marc'.format(release))):
user = os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}'.format(release))
if os.path.isfile(os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}.marc'.format(self.version))):
user = os.path.join(damaskEnv.relPath('src/'),'DAMASK_marc{}'.format(self.version))
# Define options [see Marc Installation and Operation Guide, pp 23]
script = 'run_damask_{}mp'.format(optimization)
cmd = os.path.join(self.toolsPath(release),script) + \
cmd = os.path.join(self.toolsPath(),script) + \
' -jid ' + model + '_' + job + \
' -nprocd 1 -autorst 0 -ci n -cr n -dcoup 0 -b no -v no'

View File

@ -1,21 +1,6 @@
# -*- coding: UTF-8 no BOM -*-
import damask.solver
class Solver():
"""
General class for solver specific functionality.
Sub-classed by the individual solvers.
"""
def __init__(self,solver=''):
solverClass = {
'spectral': damask.solver.Spectral,
'marc': damask.solver.Marc,
}
if solver.lower() in list(solverClass.keys()):
self.__class__=solverClass[solver.lower()]
self.__init__()

View File

@ -1,9 +0,0 @@
# -*- coding: UTF-8 no BOM -*-
from .solver import Solver
class Spectral(Solver):
def __init__(self):
self.solver='Spectral'

View File

@ -1274,7 +1274,7 @@ logical function integrateStress(ipc,ip,el,timeFraction)
if (iand(debug_level(debug_crystallite), debug_levelExtensive) /= 0 &
.and. ((el == debug_e .and. ip == debug_i .and. ipc == debug_g) &
.or. .not. iand(debug_level(debug_crystallite), debug_levelSelective) /= 0)) then
write(6,'(a,i3,/)') '<< CRYST integrateStress >> iteration ', NiterationStressLp
write(6,'(a,i3,/)') '<< CRYST integrateStress >> Lp iteration ', NiterationStressLp
write(6,'(a,/,3(12x,3(e20.10,1x)/))') '<< CRYST integrateStress >> Lpguess', transpose(Lpguess)
write(6,'(a,/,3(12x,3(e20.10,1x)/))') '<< CRYST integrateStress >> Lp_constitutive', transpose(Lp_constitutive)
write(6,'(a,/,3(12x,3(e20.10,1x)/))') '<< CRYST integrateStress >> Fi', transpose(Fi_new)
@ -1377,6 +1377,7 @@ logical function integrateStress(ipc,ip,el,timeFraction)
if (iand(debug_level(debug_crystallite), debug_levelExtensive) /= 0 &
.and. ((el == debug_e .and. ip == debug_i .and. ipc == debug_g) &
.or. .not. iand(debug_level(debug_crystallite), debug_levelSelective) /= 0)) then
write(6,'(a,i3,/)') '<< CRYST integrateStress >> Li iteration ', NiterationStressLi
write(6,'(a,/,3(12x,3(e20.7,1x)/))') '<< CRYST integrateStress >> Li_constitutive', transpose(Li_constitutive)
write(6,'(a,/,3(12x,3(e20.7,1x)/))') '<< CRYST integrateStress >> Liguess', transpose(Liguess)
endif
@ -1405,6 +1406,13 @@ logical function integrateStress(ipc,ip,el,timeFraction)
else ! not converged and residuum not improved...
steplengthLi = num%subStepSizeLi * steplengthLi ! ...try with smaller step length in same direction
Liguess = Liguess_old + steplengthLi * deltaLi
#ifdef DEBUG
if (iand(debug_level(debug_crystallite), debug_levelExtensive) /= 0 &
.and. ((el == debug_e .and. ip == debug_i .and. ipc == debug_g) &
.or. .not. iand(debug_level(debug_crystallite), debug_levelSelective) /= 0)) then
write(6,'(a,1x,f7.4)') '<< CRYST integrateStress >> linear search for Liguess with step', steplengthLi
endif
#endif
cycle LiLoop
endif
@ -1450,6 +1458,13 @@ logical function integrateStress(ipc,ip,el,timeFraction)
jacoCounterLi = jacoCounterLi + 1
Liguess = Liguess + steplengthLi * deltaLi
#ifdef DEBUG
if (iand(debug_level(debug_crystallite), debug_levelExtensive) /= 0 &
.and. ((el == debug_e .and. ip == debug_i .and. ipc == debug_g) &
.or. .not. iand(debug_level(debug_crystallite), debug_levelSelective) /= 0)) then
write(6,'(a,/,3(12x,3(e20.7,1x)/))') '<< CRYST integrateStress >> corrected Liguess by', transpose(deltaLi)
endif
#endif
enddo LiLoop
!* calculate new plastic and elastic deformation gradient

View File

@ -118,6 +118,7 @@ program DAMASK_spectral
! assign mechanics solver depending on selected type
select case (trim(config_numerics%getString('spectral_solver',defaultVal='basic')))
case ('basic')
write(6,'(a)') ' Basic solution algorithm'
mech_init => grid_mech_spectral_basic_init
mech_forward => grid_mech_spectral_basic_forward
mech_solution => grid_mech_spectral_basic_solution
@ -125,6 +126,7 @@ program DAMASK_spectral
case ('polarisation')
if(iand(debug_level(debug_spectral),debug_levelBasic)/= 0) &
call IO_warning(42, ext_msg='debug Divergence')
write(6,'(a)') ' Polarisation solution algorithm'
mech_init => grid_mech_spectral_polarisation_init
mech_forward => grid_mech_spectral_polarisation_forward
mech_solution => grid_mech_spectral_polarisation_solution
@ -132,6 +134,7 @@ program DAMASK_spectral
case ('fem')
if(iand(debug_level(debug_spectral),debug_levelBasic)/= 0) &
call IO_warning(42, ext_msg='debug Divergence')
write(6,'(a)') ' FEM solution algorithm'
mech_init => grid_mech_FEM_init
mech_forward => grid_mech_FEM_forward
mech_solution => grid_mech_FEM_solution

View File

@ -283,49 +283,52 @@ end subroutine plastic_isotropic_LpAndItsTangent
!--------------------------------------------------------------------------------------------------
!> @brief calculates plastic velocity gradient and its tangent
! ToDo: Rename Tstar to Mi?
!--------------------------------------------------------------------------------------------------
subroutine plastic_isotropic_LiAndItsTangent(Li,dLi_dTstar,Tstar,instance,of)
subroutine plastic_isotropic_LiAndItsTangent(Li,dLi_dMi,Mi,instance,of)
real(pReal), dimension(3,3), intent(out) :: &
Li !< inleastic velocity gradient
real(pReal), dimension(3,3,3,3), intent(out) :: &
dLi_dTstar !< derivative of Li with respect to the Mandel stress
dLi_dMi !< derivative of Li with respect to the Mandel stress
real(pReal), dimension(3,3), intent(in) :: &
Tstar !< Mandel stress ToDo: Mi?
Mi !< Mandel stress
integer, intent(in) :: &
instance, &
of
real(pReal), dimension(3,3) :: &
Tstar_sph !< sphiatoric part of the Mandel stress
Mi_sph !< spherical part of the Mandel stress
real(pReal) :: &
dot_gamma, & !< strainrate
norm_Tstar_sph, & !< euclidean norm of Tstar_sph
squarenorm_Tstar_sph !< square of the euclidean norm of Tstar_sph
dot_gamma, & !< shear rate
tr !< pressure
integer :: &
k, l, m, n
associate(prm => param(instance), stt => state(instance))
Tstar_sph = math_spherical33(Tstar)
squarenorm_Tstar_sph = math_mul33xx33(Tstar_sph,Tstar_sph)
norm_Tstar_sph = sqrt(squarenorm_Tstar_sph)
tr=math_trace33(math_spherical33(Mi))
if (prm%dilatation .and. abs(tr) > 0.0_pReal) then ! no stress or J2 plasticity --> Li and its derivative are zero
Li = math_I3 &
* prm%dot_gamma_0/prm%M * (3.0_pReal*prm%M*stt%xi(of))**(-prm%n) &
* tr * abs(tr)**(prm%n-1.0_pReal)
if (prm%dilatation .and. norm_Tstar_sph > 0.0_pReal) then ! no stress or J2 plastitiy --> Li and its derivative are zero
dot_gamma = prm%dot_gamma_0 * (sqrt(1.5_pReal) * norm_Tstar_sph /(prm%M*stt%xi(of))) **prm%n
Li = math_I3/sqrt(3.0_pReal) * dot_gamma/prm%M
#ifdef DEBUG
if (iand(debug_level(debug_constitutive), debug_levelExtensive) /= 0 &
.and. (of == prm%of_debug .or. .not. iand(debug_level(debug_constitutive),debug_levelSelective) /= 0)) then
write(6,'(/,a,/,f12.5)') '<< CONST isotropic >> pressure / MPa', tr/3.0_pReal*1.0e-6_pReal
write(6,'(/,a,/,f12.5)') '<< CONST isotropic >> gdot', prm%dot_gamma_0 * (3.0_pReal*prm%M*stt%xi(of))**(-prm%n) &
* tr * abs(tr)**(prm%n-1.0_pReal)
end if
#endif
forall (k=1:3,l=1:3,m=1:3,n=1:3) &
dLi_dTstar(k,l,m,n) = (prm%n-1.0_pReal) * Tstar_sph(k,l)*Tstar_sph(m,n) / squarenorm_Tstar_sph
forall (k=1:3,l=1:3) &
dLi_dTstar(k,l,k,l) = dLi_dTstar(k,l,k,l) + 1.0_pReal
dLi_dTstar = dot_gamma / prm%M * dLi_dTstar / norm_Tstar_sph
dLi_dMi(k,l,m,n) = n / tr * Li(k,l) * math_I3(m,n)
else
Li = 0.0_pReal
dLi_dTstar = 0.0_pReal
Li = 0.0_pReal
dLi_dMi = 0.0_pReal
endif
end associate

View File

@ -250,6 +250,7 @@ subroutine plastic_phenopowerlaw_init
!--------------------------------------------------------------------------------------------------
! slip-twin related parameters
slipAndTwinActive: if (prm%totalNslip > 0 .and. prm%totalNtwin > 0) then
prm%h0_TwinSlip = config%getFloat('h0_twinslip')
prm%interaction_SlipTwin = lattice_interaction_SlipByTwin(prm%Nslip,prm%Ntwin,&
config%getFloats('interaction_sliptwin'), &
config%getString('lattice_structure'))