Merge branch 'python-improvements' into 'development'

Python improvements

See merge request damask/DAMASK!279
This commit is contained in:
Philip Eisenlohr 2020-11-17 07:04:30 +01:00
commit a13a9d0e9e
27 changed files with 404 additions and 289 deletions

View File

@ -136,7 +136,7 @@ def shapeMismatch(size,F,nodes,centres):
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """
Add column(s) containing the shape and volume mismatch resulting from given deformation gradient. Add column(s) containing the shape and volume mismatch resulting from given deformation gradient.
Operates on periodic three-dimensional x,y,z-ordered data sets. Operates on periodic three-dimensional x,y,z-ordered data sets.

View File

@ -16,7 +16,7 @@ scriptID = ' '.join([scriptName,damask.version])
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """
Add displacments resulting from deformation gradient field. Add displacments resulting from deformation gradient field.
Operates on periodic three-dimensional x,y,z-ordered data sets. Operates on periodic three-dimensional x,y,z-ordered data sets.
Outputs at cell centers or cell nodes (into separate file). Outputs at cell centers or cell nodes (into separate file).

View File

@ -98,7 +98,7 @@ slipSystems = {
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """
Add columns listing Schmid factors (and optional trace vector of selected system) for given Euler angles. Add columns listing Schmid factors (and optional trace vector of selected system) for given Euler angles.
""", version = scriptID) """, version = scriptID)

View File

@ -14,7 +14,7 @@ scriptID = ' '.join([scriptName,damask.version])
# MAIN # MAIN
#-------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [DREAM.3Dfile(s)]', description = """ parser = OptionParser(usage='%prog options [DREAM.3Dfile(s)]', description = """
Converts DREAM.3D file. Input can be cell data (direct pointwise takeover) or grain data (individual Converts DREAM.3D file. Input can be cell data (direct pointwise takeover) or grain data (individual
grains are segmented). Requires orientation data as quaternion. grains are segmented). Requires orientation data as quaternion.

View File

@ -17,7 +17,7 @@ scriptID = ' '.join([scriptName,damask.version])
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [geomfile]', description = """ parser = OptionParser(usage='%prog options [geomfile]', description = """
Generate description of an osteon enclosing the Harvesian canal and separated by interstitial tissue. Generate description of an osteon enclosing the Harvesian canal and separated by interstitial tissue.
The osteon phase is lamellar with a twisted plywood structure. The osteon phase is lamellar with a twisted plywood structure.
Its fiber orientation is oscillating by +/- amplitude within one period. Its fiber orientation is oscillating by +/- amplitude within one period.

View File

@ -208,7 +208,7 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to
# MAIN # MAIN
#-------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ parser = OptionParser(usage='%prog options [file[s]]', description = """
Set up servo linking to achieve periodic boundary conditions for a regular hexahedral mesh. Set up servo linking to achieve periodic boundary conditions for a regular hexahedral mesh.
Use *py_connection to operate on model presently opened in MSC.Mentat. Use *py_connection to operate on model presently opened in MSC.Mentat.
""", version = scriptID) """, version = scriptID)

View File

@ -168,7 +168,7 @@ def initial_conditions(material):
# MAIN # MAIN
#-------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ parser = OptionParser(usage='%prog options [file[s]]', description = """
Generate MSC.Marc FE hexahedral mesh from geom file. Generate MSC.Marc FE hexahedral mesh from geom file.
""", version = scriptID) """, version = scriptID)

View File

@ -164,7 +164,7 @@ class myThread (threading.Thread):
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ parser = OptionParser(usage='%prog options [file[s]]', description = """
Monte Carlo simulation to produce seed file that gives same size distribution like given geometry file. Monte Carlo simulation to produce seed file that gives same size distribution like given geometry file.
""", version = scriptID) """, version = scriptID)

View File

@ -16,7 +16,7 @@ scriptID = ' '.join([scriptName,damask.version])
# MAIN # MAIN
# -------------------------------------------------------------------- # --------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ parser = OptionParser(usage='%prog options [file[s]]', description = """
Create seeds file by poking at 45 degree through given geom file. Create seeds file by poking at 45 degree through given geom file.
Mimics APS Beamline 34-ID-E DAXM poking. Mimics APS Beamline 34-ID-E DAXM poking.

View File

@ -22,6 +22,19 @@ _ref_white = np.array([.95047, 1.00000, 1.08883])
# - support NaN color (paraview) # - support NaN color (paraview)
class Colormap(mpl.colors.ListedColormap): class Colormap(mpl.colors.ListedColormap):
"""
Enhance matplotlib colormap functionality to be used within DAMASK.
References
----------
[1] DAMASK colormap theory
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
[2] DAMASK colormaps first use
https://doi.org/10.1016/j.ijplas.2012.09.012
[3] Matplotlib colormaps overview
https://matplotlib.org/tutorials/colors/colormaps.html
"""
def __add__(self,other): def __add__(self,other):
"""Concatenate colormaps.""" """Concatenate colormaps."""
@ -36,6 +49,17 @@ class Colormap(mpl.colors.ListedColormap):
"""Return inverted colormap.""" """Return inverted colormap."""
return self.reversed() return self.reversed()
def __repr__(self):
"""Show colormap as matplotlib figure."""
fig = plt.figure(self.name,figsize=(5,.5))
ax1 = fig.add_axes([0, 0, 1, 1])
ax1.set_axis_off()
ax1.imshow(np.linspace(0,1,self.N).reshape(1,-1),
aspect='auto', cmap=self, interpolation='nearest')
plt.show(block = False)
return self.name
@staticmethod @staticmethod
def from_range(low,high,name='DAMASK colormap',N=256,model='rgb'): def from_range(low,high,name='DAMASK colormap',N=256,model='rgb'):
""" """
@ -126,40 +150,16 @@ class Colormap(mpl.colors.ListedColormap):
""" """
# matplotlib presets # matplotlib presets
for cat in Colormap._predefined_mpl: try:
for n in cat[1]: colormap = cm.__dict__[name]
if n == name: return Colormap(np.array(list(map(colormap,np.linspace(0,1,N)))
colormap = cm.__dict__[name] if isinstance(colormap,mpl.colors.LinearSegmentedColormap) else
if isinstance(colormap,mpl.colors.LinearSegmentedColormap): colormap.colors),
return Colormap(np.array(list(map(colormap,np.linspace(0,1,N)))),name=name) name=name)
else: except KeyError:
return Colormap(np.array(colormap.colors),name=name) # DAMASK presets
definition = Colormap._predefined_DAMASK[name]
# DAMASK presets return Colormap.from_range(definition['low'],definition['high'],name,N)
definition = Colormap._predefined_DAMASK[name]
return Colormap.from_range(definition['low'],definition['high'],name,N)
@staticmethod
def list_predefined():
"""
List predefined colormaps by category.
References
----------
[1] DAMASK colormap theory
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
[2] DAMASK colormaps first use
https://doi.org/10.1016/j.ijplas.2012.09.012
[3] Matplotlib colormaps overview
https://matplotlib.org/tutorials/colors/colormaps.html
"""
print('DAMASK colormaps')
print(' '+', '.join(Colormap._predefined_DAMASK.keys()))
for cat in Colormap._predefined_mpl:
print(f'{cat[0]}')
print(' '+', '.join(cat[1]))
def shade(self,field,bounds=None,gap=None): def shade(self,field,bounds=None,gap=None):
@ -168,9 +168,9 @@ class Colormap(mpl.colors.ListedColormap):
Parameters Parameters
---------- ----------
field : numpy.array of shape(:,:) field : numpy.array of shape (:,:)
Data to be shaded. Data to be shaded.
bounds : iterable of len(2), optional bounds : iterable of len (2), optional
Colormap value range (low,high). Colormap value range (low,high).
gap : field.dtype, optional gap : field.dtype, optional
Transparent value. NaN will always be rendered transparent. Transparent value. NaN will always be rendered transparent.
@ -203,18 +203,6 @@ class Colormap(mpl.colors.ListedColormap):
mode='RGBA') mode='RGBA')
def show(self,aspect=10,vertical=False):
"""Show colormap as matplotlib figure."""
fig = plt.figure(figsize=(5/aspect,5) if vertical else (5,5/aspect))
ax1 = fig.add_axes([0, 0, 1, 1])
ax1.set_axis_off()
ax1.imshow(np.linspace(1 if vertical else 0,
0 if vertical else 1,
self.N).reshape((-1,1) if vertical else (1,-1)),
aspect='auto', cmap=self, interpolation='nearest')
plt.show()
def reversed(self,name=None): def reversed(self,name=None):
""" """
Make a reversed instance of the colormap. Make a reversed instance of the colormap.
@ -235,7 +223,6 @@ class Colormap(mpl.colors.ListedColormap):
return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name) return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name)
def save_paraview(self,fname=None): def save_paraview(self,fname=None):
""" """
Write colormap to JSON file for Paraview. Write colormap to JSON file for Paraview.
@ -247,13 +234,13 @@ class Colormap(mpl.colors.ListedColormap):
consist of the name of the colormap and extension '.json'. consist of the name of the colormap and extension '.json'.
""" """
if fname is not None: if fname is None:
fhandle = None
else:
try: try:
fhandle = open(fname,'w') fhandle = open(fname,'w')
except TypeError: except TypeError:
fhandle = fname fhandle = fname
else:
fhandle = None
colors = [] colors = []
for i,c in enumerate(np.round(self.colors,6).tolist()): for i,c in enumerate(np.round(self.colors,6).tolist()):
@ -266,11 +253,9 @@ class Colormap(mpl.colors.ListedColormap):
'DefaultMap':True, 'DefaultMap':True,
'RGBPoints':colors 'RGBPoints':colors
}] }]
if fhandle is None:
with open(self.name.replace(' ','_')+'.json', 'w') as f: with open(self.name.replace(' ','_')+'.json', 'w') if fhandle is None else fhandle as f:
json.dump(out, f,indent=4) json.dump(out, f,indent=4)
else:
json.dump(out,fhandle,indent=4)
def save_ASCII(self,fname=None): def save_ASCII(self,fname=None):
@ -284,22 +269,19 @@ class Colormap(mpl.colors.ListedColormap):
consist of the name of the colormap and extension '.txt'. consist of the name of the colormap and extension '.txt'.
""" """
if fname is not None: if fname is None:
fhandle = None
else:
try: try:
fhandle = open(fname,'w') fhandle = open(fname,'w')
except TypeError: except TypeError:
fhandle = fname fhandle = fname
else:
fhandle = None
labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3} labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3}
t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}') t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}')
if fhandle is None: with open(self.name.replace(' ','_')+'.txt', 'w') if fhandle is None else fhandle as f:
with open(self.name.replace(' ','_')+'.txt', 'w') as f: t.save(f)
t.save(f)
else:
t.save(fhandle)
def save_GOM(self,fname=None): def save_GOM(self,fname=None):
@ -313,24 +295,21 @@ class Colormap(mpl.colors.ListedColormap):
consist of the name of the colormap and extension '.legend'. consist of the name of the colormap and extension '.legend'.
""" """
if fname is not None: if fname is None:
fhandle = None
else:
try: try:
fhandle = open(fname,'w') fhandle = open(fname,'w')
except TypeError: except TypeError:
fhandle = fname fhandle = fname
else:
fhandle = None
# ToDo: test in GOM # ToDo: test in GOM
GOM_str = '1 1 {name} 9 {name} '.format(name=self.name.replace(" ","_")) \ GOM_str = '1 1 {name} 9 {name} '.format(name=self.name.replace(" ","_")) \
+ '0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 ' \ + '0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 ' \
+ f'30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 {len(self.colors)}' \ + f'30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 {len(self.colors)}' \
+ ' '.join([f' 0 {c[0]} {c[1]} {c[2]} 255 1' for c in reversed((self.colors*255).astype(int))]) \ + ' '.join([f' 0 {c[0]} {c[1]} {c[2]} 255 1' for c in reversed((self.colors*255).astype(int))]) \
+ '\n' + '\n'
if fhandle is None: with open(self.name.replace(' ','_')+'.legend', 'w') if fhandle is None else fhandle as f:
with open(self.name.replace(' ','_')+'.legend', 'w') as f: f.write(GOM_str)
f.write(GOM_str)
else:
fhandle.write(GOM_str)
def save_gmsh(self,fname=None): def save_gmsh(self,fname=None):
@ -344,22 +323,19 @@ class Colormap(mpl.colors.ListedColormap):
consist of the name of the colormap and extension '.msh'. consist of the name of the colormap and extension '.msh'.
""" """
if fname is not None: if fname is None:
fhandle = None
else:
try: try:
fhandle = open(fname,'w') fhandle = open(fname,'w')
except TypeError: except TypeError:
fhandle = fname fhandle = fname
else:
fhandle = None
# ToDo: test in gmsh # ToDo: test in gmsh
gmsh_str = 'View.ColorTable = {\n' \ gmsh_str = 'View.ColorTable = {\n' \
+'\n'.join([f'{c[0]},{c[1]},{c[2]},' for c in self.colors[:,:3]*255]) \ +'\n'.join([f'{c[0]},{c[1]},{c[2]},' for c in self.colors[:,:3]*255]) \
+'\n}\n' +'\n}\n'
if fhandle is None: with open(self.name.replace(' ','_')+'.msh', 'w') if fhandle is None else fhandle as f:
with open(self.name.replace(' ','_')+'.msh', 'w') as f: f.write(gmsh_str)
f.write(gmsh_str)
else:
fhandle.write(gmsh_str)
@staticmethod @staticmethod
@ -387,7 +363,6 @@ class Colormap(mpl.colors.ListedColormap):
if msh_sat[2] < - np.pi/3.0: hSpin *= -1.0 if msh_sat[2] < - np.pi/3.0: hSpin *= -1.0
return msh_sat[2] + hSpin return msh_sat[2] + hSpin
lo = np.array(low) lo = np.array(low)
hi = np.array(high) hi = np.array(high)
@ -407,28 +382,28 @@ class Colormap(mpl.colors.ListedColormap):
return (1.0 - frac) * lo + frac * hi return (1.0 - frac) * lo + frac * hi
_predefined_mpl= [('Perceptually Uniform Sequential', [ _predefined_mpl= {'Perceptually Uniform Sequential': [
'viridis', 'plasma', 'inferno', 'magma', 'cividis']), 'viridis', 'plasma', 'inferno', 'magma', 'cividis'],
('Sequential', [ 'Sequential': [
'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'],
('Sequential (2)', [ 'Sequential (2)': [
'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
'hot', 'afmhot', 'gist_heat', 'copper']), 'hot', 'afmhot', 'gist_heat', 'copper'],
('Diverging', [ 'Diverging': [
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic'],
('Cyclic', ['twilight', 'twilight_shifted', 'hsv']), 'Cyclic': ['twilight', 'twilight_shifted', 'hsv'],
('Qualitative', [ 'Qualitative': [
'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3', 'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']), 'tab10', 'tab20', 'tab20b', 'tab20c'],
('Miscellaneous', [ 'Miscellaneous': [
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']}
_predefined_DAMASK = {'orientation': {'low': [0.933334,0.878432,0.878431], _predefined_DAMASK = {'orientation': {'low': [0.933334,0.878432,0.878431],
'high': [0.250980,0.007843,0.000000]}, 'high': [0.250980,0.007843,0.000000]},
@ -437,6 +412,9 @@ class Colormap(mpl.colors.ListedColormap):
'stress': {'low': [0.878432,0.874511,0.949019], 'stress': {'low': [0.878432,0.874511,0.949019],
'high': [0.000002,0.000000,0.286275]}} 'high': [0.000002,0.000000,0.286275]}}
predefined = dict(**{'DAMASK':list(_predefined_DAMASK)},**_predefined_mpl)
@staticmethod @staticmethod
def _hsv2rgb(hsv): def _hsv2rgb(hsv):
""" """

View File

@ -226,13 +226,13 @@ class ConfigMaterial(Config):
return dup return dup
def material_add(self,constituents,**kwargs): def material_add(self,constituents=None,**kwargs):
""" """
Add material entries. Add material entries.
Parameters Parameters
---------- ----------
constituents : dict constituents : dict, optional
Entries for 'constituents' as key-value pair. Entries for 'constituents' as key-value pair.
**kwargs **kwargs
Key-value pairs. Key-value pairs.
@ -263,13 +263,26 @@ class ConfigMaterial(Config):
homogenization: SX homogenization: SX
""" """
c = [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)] length = -1
for v in kwargs.values():
if hasattr(v,'__len__') and not isinstance(v,str):
if length != -1 and len(v) != length:
raise ValueError('Cannot add entries of different length')
else:
length = len(v)
length = max(1,length)
c = [{} for _ in range(length)] if constituents is None else \
[{'constituents':u} for u in ConfigMaterial._constituents(**constituents)]
if len(c) == 1: c = [copy.deepcopy(c[0]) for _ in range(length)]
if length != 1 and length != len(c):
raise ValueError('Cannot add entries of different length')
for k,v in kwargs.items(): for k,v in kwargs.items():
if hasattr(v,'__len__') and not isinstance(v,str): if hasattr(v,'__len__') and not isinstance(v,str):
if len(v) != len(c):
raise ValueError('Cannot add entries of different length')
for i,vv in enumerate(v): for i,vv in enumerate(v):
c[i][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() c[i][k] = vv.item() if isinstance(vv,np.generic) else vv
else: else:
for i in range(len(c)): for i in range(len(c)):
c[i][k] = v c[i][k] = v
@ -293,7 +306,7 @@ class ConfigMaterial(Config):
if len(v) != N_material: if len(v) != N_material:
raise ValueError('Cannot add entries of different length') raise ValueError('Cannot add entries of different length')
for i,vv in enumerate(np.array(v)): for i,vv in enumerate(np.array(v)):
m[i][0][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() m[i][0][k] = vv.item() if isinstance(vv,np.generic) else vv
else: else:
for i in range(N_material): for i in range(N_material):
m[i][0][k] = v m[i][0][k] = v

View File

@ -5,8 +5,6 @@ class Environment:
@property @property
def screen_size(self): def screen_size(self):
width = 1024
height = 768
try: try:
import wx import wx
_ = wx.App(False) # noqa _ = wx.App(False) # noqa
@ -19,7 +17,9 @@ class Environment:
height = tk.winfo_screenheight() height = tk.winfo_screenheight()
tk.destroy() tk.destroy()
except Exception as e: except Exception as e:
pass width = 1024
height = 768
return (width,height) return (width,height)

View File

@ -2,6 +2,7 @@ import copy
import multiprocessing as mp import multiprocessing as mp
from functools import partial from functools import partial
from os import path from os import path
import warnings
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -73,23 +74,23 @@ class Geom:
""" """
message = [] message = []
if np.any(other.grid != self.grid): if np.any(other.grid != self.grid):
message.append(util.delete(f'grid a b c: {util.srepr(other.grid," x ")}')) message.append(util.deemph(f'grid a b c: {util.srepr(other.grid," x ")}'))
message.append(util.emph( f'grid a b c: {util.srepr( self.grid," x ")}')) message.append(util.emph( f'grid a b c: {util.srepr( self.grid," x ")}'))
if not np.allclose(other.size,self.size): if not np.allclose(other.size,self.size):
message.append(util.delete(f'size x y z: {util.srepr(other.size," x ")}')) message.append(util.deemph(f'size x y z: {util.srepr(other.size," x ")}'))
message.append(util.emph( f'size x y z: {util.srepr( self.size," x ")}')) message.append(util.emph( f'size x y z: {util.srepr( self.size," x ")}'))
if not np.allclose(other.origin,self.origin): if not np.allclose(other.origin,self.origin):
message.append(util.delete(f'origin x y z: {util.srepr(other.origin," ")}')) message.append(util.deemph(f'origin x y z: {util.srepr(other.origin," ")}'))
message.append(util.emph( f'origin x y z: {util.srepr( self.origin," ")}')) message.append(util.emph( f'origin x y z: {util.srepr( self.origin," ")}'))
if other.N_materials != self.N_materials: if other.N_materials != self.N_materials:
message.append(util.delete(f'# materials: {other.N_materials}')) message.append(util.deemph(f'# materials: {other.N_materials}'))
message.append(util.emph( f'# materials: { self.N_materials}')) message.append(util.emph( f'# materials: { self.N_materials}'))
if np.nanmax(other.material) != np.nanmax(self.material): if np.nanmax(other.material) != np.nanmax(self.material):
message.append(util.delete(f'max material: {np.nanmax(other.material)}')) message.append(util.deemph(f'max material: {np.nanmax(other.material)}'))
message.append(util.emph( f'max material: {np.nanmax( self.material)}')) message.append(util.emph( f'max material: {np.nanmax( self.material)}'))
return util.return_message(message) return util.return_message(message)
@ -188,12 +189,16 @@ class Geom:
""" """
Read a geom file. Read a geom file.
Storing geometry files in ASCII format is deprecated.
This function will be removed in a future version of DAMASK.
Parameters Parameters
---------- ----------
fname : str, pathlib.Path, or file handle fname : str, pathlib.Path, or file handle
Geometry file to read. Geometry file to read.
""" """
warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning)
try: try:
f = open(fname) f = open(fname)
except TypeError: except TypeError:
@ -247,7 +252,6 @@ class Geom:
return Geom(material.reshape(grid,order='F'),size,origin,comments) return Geom(material.reshape(grid,order='F'),size,origin,comments)
@staticmethod @staticmethod
def load_DREAM3D(fname,base_group,point_data=None,material='FeatureIds'): def load_DREAM3D(fname,base_group,point_data=None,material='FeatureIds'):
""" """
@ -523,6 +527,9 @@ class Geom:
""" """
Write a geom file. Write a geom file.
Storing geometry files in ASCII format is deprecated.
This function will be removed in a future version of DAMASK.
Parameters Parameters
---------- ----------
fname : str or file handle fname : str or file handle
@ -531,6 +538,7 @@ class Geom:
Compress geometry with 'x of y' and 'a to b'. Compress geometry with 'x of y' and 'a to b'.
""" """
warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning)
header = [f'{len(self.comments)+4} header'] + self.comments \ header = [f'{len(self.comments)+4} header'] + self.comments \
+ ['grid a {} b {} c {}'.format(*self.grid), + ['grid a {} b {} c {}'.format(*self.grid),
'size x {} y {} z {}'.format(*self.size), 'size x {} y {} z {}'.format(*self.size),
@ -547,8 +555,7 @@ class Geom:
def show(self): def show(self):
"""Show on screen.""" """Show on screen."""
v = VTK.from_rectilinear_grid(self.grid,self.size,self.origin) VTK.from_rectilinear_grid(self.grid,self.size,self.origin).show()
v.show()
def add_primitive(self,dimension,center,exponent, def add_primitive(self,dimension,center,exponent,

View File

@ -4,7 +4,7 @@ from . import Rotation
from . import util from . import util
from . import mechanics from . import mechanics
__parameter_doc__ = \ _parameter_doc = \
"""lattice : str """lattice : str
Either a crystal family out of [triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic] Either a crystal family out of [triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic]
or a Bravais lattice out of [aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF]. or a Bravais lattice out of [aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF].
@ -27,22 +27,6 @@ __parameter_doc__ = \
""" """
def extend_docstring():
"""Decorator: Append Orientation parameter documentation to function's docstring."""
def _decorator(func):
func.__doc__ += __parameter_doc__
return func
return _decorator
def extended_docstring(f):
"""Decorator: Combine Orientation parameter documentation with another function's docstring."""
def _decorator(func):
func.__doc__ = f.__doc__ + __parameter_doc__
return func
return _decorator
class Orientation(Rotation): class Orientation(Rotation):
""" """
Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice.
@ -85,18 +69,6 @@ class Orientation(Rotation):
An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry: An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry:
>>> damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced >>> damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced
Disorientation between two specific orientations of hexagonal symmetry:
>>> a = damask.Orientation.from_Eulers(phi=[123,32,21],degrees=True,lattice='hexagonal')
>>> b = damask.Orientation.from_Eulers(phi=[104,11,87],degrees=True,lattice='hexagonal')
>>> a.disorientation(b)
Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry:
>>> o = damask.Orientation(lattice='cubic')
>>> o.IPF_color(o.to_SST(np.array([0,0,1])))
Schmid matrix (in lab frame) of slip systems of a face-centered cubic crystal in "Goss" orientation:
>>> damask.Orientation.from_Eulers(phi=[0,45,0],degrees=True,lattice='cF').Schmid('slip')
""" """
crystal_families = ['triclinic', crystal_families = ['triclinic',
@ -128,7 +100,7 @@ class Orientation(Rotation):
} }
@extend_docstring() @util.extend_docstring(_parameter_doc)
def __init__(self, def __init__(self,
rotation = None, rotation = None,
lattice = None, lattice = None,
@ -279,73 +251,73 @@ class Orientation(Rotation):
@classmethod @classmethod
@extended_docstring(Rotation.from_random) @util.extended_docstring(Rotation.from_random,_parameter_doc)
def from_random(cls,**kwargs): def from_random(cls,**kwargs):
return cls(rotation=Rotation.from_random(**kwargs),**kwargs) return cls(rotation=Rotation.from_random(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_quaternion) @util.extended_docstring(Rotation.from_quaternion,_parameter_doc)
def from_quaternion(cls,**kwargs): def from_quaternion(cls,**kwargs):
return cls(rotation=Rotation.from_quaternion(**kwargs),**kwargs) return cls(rotation=Rotation.from_quaternion(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_Eulers) @util.extended_docstring(Rotation.from_Eulers,_parameter_doc)
def from_Eulers(cls,**kwargs): def from_Eulers(cls,**kwargs):
return cls(rotation=Rotation.from_Eulers(**kwargs),**kwargs) return cls(rotation=Rotation.from_Eulers(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_axis_angle) @util.extended_docstring(Rotation.from_axis_angle,_parameter_doc)
def from_axis_angle(cls,**kwargs): def from_axis_angle(cls,**kwargs):
return cls(rotation=Rotation.from_axis_angle(**kwargs),**kwargs) return cls(rotation=Rotation.from_axis_angle(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_basis) @util.extended_docstring(Rotation.from_basis,_parameter_doc)
def from_basis(cls,**kwargs): def from_basis(cls,**kwargs):
return cls(rotation=Rotation.from_basis(**kwargs),**kwargs) return cls(rotation=Rotation.from_basis(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_matrix) @util.extended_docstring(Rotation.from_matrix,_parameter_doc)
def from_matrix(cls,**kwargs): def from_matrix(cls,**kwargs):
return cls(rotation=Rotation.from_matrix(**kwargs),**kwargs) return cls(rotation=Rotation.from_matrix(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_Rodrigues) @util.extended_docstring(Rotation.from_Rodrigues,_parameter_doc)
def from_Rodrigues(cls,**kwargs): def from_Rodrigues(cls,**kwargs):
return cls(rotation=Rotation.from_Rodrigues(**kwargs),**kwargs) return cls(rotation=Rotation.from_Rodrigues(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_homochoric) @util.extended_docstring(Rotation.from_homochoric,_parameter_doc)
def from_homochoric(cls,**kwargs): def from_homochoric(cls,**kwargs):
return cls(rotation=Rotation.from_homochoric(**kwargs),**kwargs) return cls(rotation=Rotation.from_homochoric(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_cubochoric) @util.extended_docstring(Rotation.from_cubochoric,_parameter_doc)
def from_cubochoric(cls,**kwargs): def from_cubochoric(cls,**kwargs):
return cls(rotation=Rotation.from_cubochoric(**kwargs),**kwargs) return cls(rotation=Rotation.from_cubochoric(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_spherical_component) @util.extended_docstring(Rotation.from_spherical_component,_parameter_doc)
def from_spherical_component(cls,**kwargs): def from_spherical_component(cls,**kwargs):
return cls(rotation=Rotation.from_spherical_component(**kwargs),**kwargs) return cls(rotation=Rotation.from_spherical_component(**kwargs),**kwargs)
@classmethod @classmethod
@extended_docstring(Rotation.from_fiber_component) @util.extended_docstring(Rotation.from_fiber_component,_parameter_doc)
def from_fiber_component(cls,**kwargs): def from_fiber_component(cls,**kwargs):
return cls(rotation=Rotation.from_fiber_component(**kwargs),**kwargs) return cls(rotation=Rotation.from_fiber_component(**kwargs),**kwargs)
@classmethod @classmethod
@extend_docstring() @util.extend_docstring(_parameter_doc)
def from_directions(cls,uvw,hkl,**kwargs): def from_directions(cls,uvw,hkl,**kwargs):
""" """
Initialize orientation object from two crystallographic directions. Initialize orientation object from two crystallographic directions.
@ -847,6 +819,14 @@ class Orientation(Rotation):
rgb : numpy.ndarray of shape (...,3) rgb : numpy.ndarray of shape (...,3)
RGB array of IPF colors. RGB array of IPF colors.
Examples
--------
Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry:
>>> o = damask.Orientation(lattice='cubic')
>>> o.IPF_color(o.to_SST([0,0,1]))
array([1., 0., 0.])
References References
---------- ----------
Bases are computed from Bases are computed from
@ -867,7 +847,8 @@ class Orientation(Rotation):
... } ... }
""" """
if vector.shape[-1] != 3: vector_ = np.array(vector)
if vector_.shape[-1] != 3:
raise ValueError('Input is not a field of three-dimensional vectors.') raise ValueError('Input is not a field of three-dimensional vectors.')
if self.family == 'cubic': if self.family == 'cubic':
@ -903,23 +884,23 @@ class Orientation(Rotation):
[ 0., 1., 0.] ]), [ 0., 1., 0.] ]),
} }
else: # direct exit for unspecified symmetry else: # direct exit for unspecified symmetry
return np.zeros_like(vector) return np.zeros_like(vector_)
if proper: if proper:
components_proper = np.around(np.einsum('...ji,...i', components_proper = np.around(np.einsum('...ji,...i',
np.broadcast_to(basis['proper'], vector.shape+(3,)), np.broadcast_to(basis['proper'], vector_.shape+(3,)),
vector), 12) vector_), 12)
components_improper = np.around(np.einsum('...ji,...i', components_improper = np.around(np.einsum('...ji,...i',
np.broadcast_to(basis['improper'], vector.shape+(3,)), np.broadcast_to(basis['improper'], vector_.shape+(3,)),
vector), 12) vector_), 12)
in_SST = np.all(components_proper >= 0.0,axis=-1) \ in_SST = np.all(components_proper >= 0.0,axis=-1) \
| np.all(components_improper >= 0.0,axis=-1) | np.all(components_improper >= 0.0,axis=-1)
components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis], components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis],
components_proper,components_improper) components_proper,components_improper)
else: else:
components = np.around(np.einsum('...ji,...i', components = np.around(np.einsum('...ji,...i',
np.broadcast_to(basis['improper'], vector.shape+(3,)), np.broadcast_to(basis['improper'], vector_.shape+(3,)),
np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12)
in_SST = np.all(components >= 0.0,axis=-1) in_SST = np.all(components >= 0.0,axis=-1)
@ -957,6 +938,22 @@ class Orientation(Rotation):
Currently requires same crystal family for both orientations. Currently requires same crystal family for both orientations.
For extension to cases with differing symmetry see A. Heinz and P. Neumann 1991 and 10.1107/S0021889808016373. For extension to cases with differing symmetry see A. Heinz and P. Neumann 1991 and 10.1107/S0021889808016373.
Examples
--------
Disorientation between two specific orientations of hexagonal symmetry:
>>> import damask
>>> a = damask.Orientation.from_Eulers(phi=[123,32,21],degrees=True,lattice='hexagonal')
>>> b = damask.Orientation.from_Eulers(phi=[104,11,87],degrees=True,lattice='hexagonal')
>>> a.disorientation(b)
Crystal family hexagonal
Quaternion: (real=0.976, imag=<+0.189, +0.018, +0.103>)
Matrix:
[[ 0.97831006 0.20710935 0.00389135]
[-0.19363288 0.90765544 0.37238141]
[ 0.07359167 -0.36505797 0.92807163]]
Bunge Eulers / deg: (11.40, 21.86, 0.60)
""" """
if self.family is None or other.family is None: if self.family is None or other.family is None:
raise ValueError('Missing crystal symmetry') raise ValueError('Missing crystal symmetry')
@ -1065,8 +1062,8 @@ class Orientation(Rotation):
raise ValueError('Missing crystal symmetry') raise ValueError('Missing crystal symmetry')
eq = self.equivalent eq = self.equivalent
blend = util.shapeblender(eq.shape,vector.shape[:-1]) blend = util.shapeblender(eq.shape,np.array(vector).shape[:-1])
poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(vector,blend+(3,)) poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(np.array(vector),blend+(3,))
ok = self.in_SST(poles,proper=proper) ok = self.in_SST(poles,proper=proper)
ok &= np.cumsum(ok,axis=0) == 1 ok &= np.cumsum(ok,axis=0) == 1
loc = np.where(ok) loc = np.where(ok)
@ -1085,12 +1082,12 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
uvtw | hkil : numpy.ndarray of shape (...,4) uvtw|hkil : numpy.ndarray of shape (...,4)
MillerBravais indices of crystallographic direction [uvtw] or plane normal (hkil). MillerBravais indices of crystallographic direction [uvtw] or plane normal (hkil).
Returns Returns
------- -------
uvw | hkl : numpy.ndarray of shape (...,3) uvw|hkl : numpy.ndarray of shape (...,3)
Miller indices of [uvw] direction or (hkl) plane normal. Miller indices of [uvw] direction or (hkl) plane normal.
""" """
@ -1113,12 +1110,12 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
uvw | hkl : numpy.ndarray of shape (...,3) uvw|hkl : numpy.ndarray of shape (...,3)
Miller indices of crystallographic direction [uvw] or plane normal (hkl). Miller indices of crystallographic direction [uvw] or plane normal (hkl).
Returns Returns
------- -------
uvtw | hkil : numpy.ndarray of shape (...,4) uvtw|hkil : numpy.ndarray of shape (...,4)
MillerBravais indices of [uvtw] direction or (hkil) plane normal. MillerBravais indices of [uvtw] direction or (hkil) plane normal.
""" """
@ -1142,7 +1139,7 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
direction | normal : numpy.ndarray of shape (...,3) direction|normal : numpy.ndarray of shape (...,3)
Vector along direction or plane normal. Vector along direction or plane normal.
Returns Returns
@ -1166,7 +1163,7 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
uvw | hkl : numpy.ndarray of shape (...,3) uvw|hkl : numpy.ndarray of shape (...,3)
Miller indices of crystallographic direction or plane normal. Miller indices of crystallographic direction or plane normal.
with_symmetry : bool, optional with_symmetry : bool, optional
Calculate all N symmetrically equivalent vectors. Calculate all N symmetrically equivalent vectors.
@ -1194,7 +1191,7 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
uvw | hkl : numpy.ndarray of shape (...,3) uvw|hkl : numpy.ndarray of shape (...,3)
Miller indices of crystallographic direction or plane normal. Miller indices of crystallographic direction or plane normal.
with_symmetry : bool, optional with_symmetry : bool, optional
Calculate all N symmetrically equivalent vectors. Calculate all N symmetrically equivalent vectors.
@ -1217,13 +1214,26 @@ class Orientation(Rotation):
Parameters Parameters
---------- ----------
mode : str mode : str
Type of kinematics, e.g. 'slip' or 'twin'. Type of kinematics, i.e. 'slip' or 'twin'.
Returns Returns
------- -------
P : numpy.ndarray of shape (...,N,3,3) P : numpy.ndarray of shape (...,N,3,3)
Schmid matrix for each of the N deformation systems. Schmid matrix for each of the N deformation systems.
Examples
--------
Schmid matrix (in lab frame) of slip systems of a face-centered
cubic crystal in "Goss" orientation.
>>> import damask
>>> import numpy as np
>>> np.set_printoptions(3,suppress=True,floatmode='fixed')
>>> damask.Orientation.from_Eulers(phi=[0,45,0],degrees=True,lattice='cF').Schmid('slip')[0]
array([[ 0.000, 0.000, 0.000],
[ 0.577, -0.000, 0.816],
[ 0.000, 0.000, 0.000]])
""" """
d = self.to_frame(uvw=self.kinematics[mode]['direction'],with_symmetry=False) d = self.to_frame(uvw=self.kinematics[mode]['direction'],with_symmetry=False)
p = self.to_frame(hkl=self.kinematics[mode]['plane'] ,with_symmetry=False) p = self.to_frame(hkl=self.kinematics[mode]['plane'] ,with_symmetry=False)

View File

@ -167,9 +167,7 @@ class Result:
def allow_modification(self): def allow_modification(self):
print(util.bcolors().WARNING+util.bcolors().BOLD+ print(util.warn('Warning: Modification of existing datasets allowed!'))
'Warning: Modification of existing datasets allowed!'+
util.bcolors().ENDC)
self._allow_modification = True self._allow_modification = True
def disallow_modification(self): def disallow_modification(self):

View File

@ -107,22 +107,6 @@ class Rotation:
and np.allclose(self.quaternion,other.quaternion) and np.allclose(self.quaternion,other.quaternion)
def __neq__(self,other):
"""
Not Equal to other.
Equality is determined taking limited floating point precision into
account. See numpy.allclose for details.
Parameters
----------
other : Rotation
Rotation to check for inequality.
"""
return not self.__eq__(other)
@property @property
def shape(self): def shape(self):
return self.quaternion.shape[:-1] return self.quaternion.shape[:-1]
@ -404,7 +388,7 @@ class Rotation:
Returns Returns
------- -------
h : numpy.ndarray of shape (...,3) h : numpy.ndarray of shape (...,3)
Homochoric vector: (h_1, h_2, h_3), ǀhǀ < 1/2*π^(2/3). Homochoric vector: (h_1, h_2, h_3), ǀhǀ < (3/4*π)^(1/3).
""" """
return Rotation._qu2ho(self.quaternion) return Rotation._qu2ho(self.quaternion)
@ -698,7 +682,7 @@ class Rotation:
@staticmethod @staticmethod
def from_random(shape = None, def from_random(shape = None,
seed = None, rng_seed = None,
**kwargs): **kwargs):
""" """
Draw random rotation. Draw random rotation.
@ -710,12 +694,12 @@ class Rotation:
shape : tuple of ints, optional shape : tuple of ints, optional
Shape of the sample. Defaults to None which gives a Shape of the sample. Defaults to None which gives a
single rotation single rotation
seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None. A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS. If None, then fresh, unpredictable entropy will be pulled from the OS.
""" """
rng = np.random.default_rng(seed) rng = np.random.default_rng(rng_seed)
r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3))
A = np.sqrt(r[...,2]) A = np.sqrt(r[...,2])
@ -734,7 +718,7 @@ class Rotation:
N = 500, N = 500,
degrees = True, degrees = True,
fractions = True, fractions = True,
seed = None, rng_seed = None,
**kwargs): **kwargs):
""" """
Sample discrete values from a binned ODF. Sample discrete values from a binned ODF.
@ -753,7 +737,7 @@ class Rotation:
fractions : boolean, optional fractions : boolean, optional
ODF values correspond to volume fractions, not probability density. ODF values correspond to volume fractions, not probability density.
Defaults to True. Defaults to True.
seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy
will be pulled from the OS. will be pulled from the OS.
@ -784,7 +768,7 @@ class Rotation:
dg = 1.0 if fractions else _dg(Eulers,degrees) dg = 1.0 if fractions else _dg(Eulers,degrees)
dV_V = dg * np.maximum(0.0,weights.squeeze()) dV_V = dg * np.maximum(0.0,weights.squeeze())
return Rotation.from_Eulers(Eulers[util.hybrid_IA(dV_V,N,seed)],degrees) return Rotation.from_Eulers(Eulers[util.hybrid_IA(dV_V,N,rng_seed)],degrees)
@staticmethod @staticmethod
@ -792,7 +776,7 @@ class Rotation:
sigma, sigma,
N = 500, N = 500,
degrees = True, degrees = True,
seed = None, rng_seed = None,
**kwargs): **kwargs):
""" """
Calculate set of rotations with Gaussian distribution around center. Calculate set of rotations with Gaussian distribution around center.
@ -807,12 +791,12 @@ class Rotation:
Number of samples, defaults to 500. Number of samples, defaults to 500.
degrees : boolean, optional degrees : boolean, optional
sigma is given in degrees. sigma is given in degrees.
seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy
will be pulled from the OS. will be pulled from the OS.
""" """
rng = np.random.default_rng(seed) rng = np.random.default_rng(rng_seed)
sigma = np.radians(sigma) if degrees else sigma sigma = np.radians(sigma) if degrees else sigma
u,Theta = (rng.random((N,2)) * 2.0 * np.array([1,np.pi]) - np.array([1.0, 0])).T u,Theta = (rng.random((N,2)) * 2.0 * np.array([1,np.pi]) - np.array([1.0, 0])).T
omega = abs(rng.normal(scale=sigma,size=N)) omega = abs(rng.normal(scale=sigma,size=N))
@ -829,7 +813,7 @@ class Rotation:
sigma = 0.0, sigma = 0.0,
N = 500, N = 500,
degrees = True, degrees = True,
seed = None, rng_seed = None,
**kwargs): **kwargs):
""" """
Calculate set of rotations with Gaussian distribution around direction. Calculate set of rotations with Gaussian distribution around direction.
@ -847,12 +831,12 @@ class Rotation:
Number of samples, defaults to 500. Number of samples, defaults to 500.
degrees : boolean, optional degrees : boolean, optional
sigma, alpha, and beta are given in degrees. sigma, alpha, and beta are given in degrees.
seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy
will be pulled from the OS. will be pulled from the OS.
""" """
rng = np.random.default_rng(seed) rng = np.random.default_rng(rng_seed)
sigma_,alpha_,beta_ = map(np.radians,(sigma,alpha,beta)) if degrees else (sigma,alpha,beta) sigma_,alpha_,beta_ = map(np.radians,(sigma,alpha,beta)) if degrees else (sigma,alpha,beta)
d_cr = np.array([np.sin(alpha_[0])*np.cos(alpha_[1]), np.sin(alpha_[0])*np.sin(alpha_[1]), np.cos(alpha_[0])]) d_cr = np.array([np.sin(alpha_[0])*np.cos(alpha_[1]), np.sin(alpha_[0])*np.sin(alpha_[1]), np.cos(alpha_[0])])

View File

@ -5,7 +5,7 @@ from . import util
from . import grid_filters from . import grid_filters
def from_random(size,N_seeds,grid=None,seed=None): def from_random(size,N_seeds,grid=None,rng_seed=None):
""" """
Random seeding in space. Random seeding in space.
@ -18,12 +18,12 @@ def from_random(size,N_seeds,grid=None,seed=None):
grid : numpy.ndarray of shape (3), optional. grid : numpy.ndarray of shape (3), optional.
If given, ensures that all seeds initiate one grain if using a If given, ensures that all seeds initiate one grain if using a
standard Voronoi tessellation. standard Voronoi tessellation.
seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None. A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS. If None, then fresh, unpredictable entropy will be pulled from the OS.
""" """
rng = _np.random.default_rng(seed) rng = _np.random.default_rng(rng_seed)
if grid is None: if grid is None:
coords = rng.random((N_seeds,3)) * size coords = rng.random((N_seeds,3)) * size
else: else:
@ -34,7 +34,7 @@ def from_random(size,N_seeds,grid=None,seed=None):
return coords return coords
def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,seed=None): def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,rng_seed=None):
""" """
Seeding in space according to a Poisson disc distribution. Seeding in space according to a Poisson disc distribution.
@ -50,12 +50,12 @@ def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,seed=None
Minimum acceptable distance to other seeds. Minimum acceptable distance to other seeds.
periodic : boolean, optional periodic : boolean, optional
Calculate minimum distance for periodically repeated grid. Calculate minimum distance for periodically repeated grid.
seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None. A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS. If None, then fresh, unpredictable entropy will be pulled from the OS.
""" """
rng = _np.random.default_rng(seed) rng = _np.random.default_rng(rng_seed)
coords = _np.empty((N_seeds,3)) coords = _np.empty((N_seeds,3))
coords[0] = rng.random(3) * size coords[0] = rng.random(3) * size

View File

@ -17,7 +17,7 @@ __all__=[
'srepr', 'srepr',
'croak', 'croak',
'report', 'report',
'emph','deemph','delete','strikeout', 'emph','deemph','warn','strikeout',
'execute', 'execute',
'show_progress', 'show_progress',
'scale_to_coprime', 'scale_to_coprime',
@ -26,8 +26,8 @@ __all__=[
'return_message', 'return_message',
'extendableOption', 'extendableOption',
'execution_stamp', 'execution_stamp',
'shapeshifter', 'shapeshifter', 'shapeblender',
'shapeblender', 'extend_docstring', 'extended_docstring'
] ]
#################################################################################################### ####################################################################################################
@ -42,7 +42,7 @@ def srepr(arg,glue = '\n'):
arg : iterable arg : iterable
Items to join. Items to join.
glue : str, optional glue : str, optional
Defaults to \n. Glue used for joining operation. Defaults to \n.
""" """
if (not hasattr(arg, "strip") and if (not hasattr(arg, "strip") and
@ -56,6 +56,8 @@ def croak(what, newline = True):
""" """
Write formated to stderr. Write formated to stderr.
DEPRECATED
Parameters Parameters
---------- ----------
what : str or iterable what : str or iterable
@ -72,7 +74,7 @@ def croak(what, newline = True):
def report(who = None, def report(who = None,
what = None): what = None):
""" """
Reports script and file name. Report script and file name.
DEPRECATED DEPRECATED
@ -84,16 +86,13 @@ def emph(what):
"""Formats string with emphasis.""" """Formats string with emphasis."""
return bcolors.BOLD+srepr(what)+bcolors.ENDC return bcolors.BOLD+srepr(what)+bcolors.ENDC
def deemph(what): def deemph(what):
"""Formats string with deemphasis.""" """Formats string with deemphasis."""
return bcolors.DIM+srepr(what)+bcolors.ENDC return bcolors.DIM+srepr(what)+bcolors.ENDC
def warn(what):
def delete(what): """Formats string for warning."""
"""Formats string as deleted.""" return bcolors.WARNING+emph(what)+bcolors.ENDC
return bcolors.DIM+srepr(what)+bcolors.ENDC
def strikeout(what): def strikeout(what):
"""Formats string as strikeout.""" """Formats string as strikeout."""
@ -164,7 +163,15 @@ def show_progress(iterable,N_iter=None,prefix='',bar_length=50):
def scale_to_coprime(v): def scale_to_coprime(v):
"""Scale vector to co-prime (relatively prime) integers.""" """
Scale vector to co-prime (relatively prime) integers.
Parameters
----------
v : numpy.ndarray of shape (:)
Vector to scale.
"""
MAX_DENOMINATOR = 1000000 MAX_DENOMINATOR = 1000000
def get_square_denominator(x): def get_square_denominator(x):
@ -215,7 +222,21 @@ def execution_stamp(class_name,function_name=None):
return f'damask.{class_name}{_function_name} v{version} ({now})' return f'damask.{class_name}{_function_name} v{version} ({now})'
def hybrid_IA(dist,N,seed=None): def hybrid_IA(dist,N,rng_seed=None):
"""
Hybrid integer approximation.
Parameters
----------
dist : numpy.ndarray
Distribution to be approximated
N : int
Number of samples to draw.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS.
"""
N_opt_samples,N_inv_samples = (max(np.count_nonzero(dist),N),0) # random subsampling if too little samples requested N_opt_samples,N_inv_samples = (max(np.count_nonzero(dist),N),0) # random subsampling if too little samples requested
scale_,scale,inc_factor = (0.0,float(N_opt_samples),1.0) scale_,scale,inc_factor = (0.0,float(N_opt_samples),1.0)
@ -226,7 +247,7 @@ def hybrid_IA(dist,N,seed=None):
if N_inv_samples < N_opt_samples else \ if N_inv_samples < N_opt_samples else \
(scale_,0.5*(scale_ + scale), 1.0) (scale_,0.5*(scale_ + scale), 1.0)
return np.repeat(np.arange(len(dist)),repeats)[np.random.default_rng(seed).permutation(N_inv_samples)[:N]] return np.repeat(np.arange(len(dist)),repeats)[np.random.default_rng(rng_seed).permutation(N_inv_samples)[:N]]
def shapeshifter(fro,to,mode='left',keep_ones=False): def shapeshifter(fro,to,mode='left',keep_ones=False):
@ -300,6 +321,40 @@ def shapeblender(a,b):
return a + b[i:] return a + b[i:]
def extend_docstring(extra_docstring):
"""
Decorator: Append to function's docstring.
Parameters
----------
extra_docstring : str
Docstring to append.
"""
def _decorator(func):
func.__doc__ += extra_docstring
return func
return _decorator
def extended_docstring(f,extra_docstring):
"""
Decorator: Combine another function's docstring with a given docstring.
Parameters
----------
f : function
Function of which the docstring is taken.
extra_docstring : str
Docstring to append.
"""
def _decorator(func):
func.__doc__ = f.__doc__ + extra_docstring
return func
return _decorator
#################################################################################################### ####################################################################################################
# Classes # Classes
#################################################################################################### ####################################################################################################
@ -393,17 +448,6 @@ class bcolors:
UNDERLINE = '\033[4m' UNDERLINE = '\033[4m'
CROSSOUT = '\033[9m' CROSSOUT = '\033[9m'
def disable(self):
self.HEADER = ''
self.OKBLUE = ''
self.OKGREEN = ''
self.WARNING = ''
self.FAIL = ''
self.ENDC = ''
self.BOLD = ''
self.UNDERLINE = ''
self.CROSSOUT = ''
class return_message: class return_message:
"""Object with formatted return message.""" """Object with formatted return message."""

View File

@ -1,8 +1,13 @@
from pathlib import Path from pathlib import Path
import datetime import datetime
import os
import numpy as np import numpy as np
import pytest import pytest
import matplotlib as mpl
if os.name == 'posix' and 'DISPLAY' not in os.environ:
mpl.use('Agg')
import matplotlib.pyplot as plt
import damask import damask
@ -25,8 +30,9 @@ def patch_datetime_now(monkeypatch):
monkeypatch.setattr(datetime, 'datetime', mydatetime) monkeypatch.setattr(datetime, 'datetime', mydatetime)
@pytest.fixture @pytest.fixture
def execution_stamp(monkeypatch): def patch_execution_stamp(monkeypatch):
"""Set damask.util.execution_stamp for reproducible tests results.""" """Set damask.util.execution_stamp for reproducible tests results."""
def execution_stamp(class_name,function_name=None): def execution_stamp(class_name,function_name=None):
_function_name = '' if function_name is None else f'.{function_name}' _function_name = '' if function_name is None else f'.{function_name}'
@ -35,21 +41,31 @@ def execution_stamp(monkeypatch):
monkeypatch.setattr(damask.util, 'execution_stamp', execution_stamp) monkeypatch.setattr(damask.util, 'execution_stamp', execution_stamp)
@pytest.fixture
def patch_plt_show(monkeypatch):
def _None(block=None):
pass
monkeypatch.setattr(plt, 'show', _None, raising=True)
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--update", parser.addoption("--update",
action="store_true", action="store_true",
default=False) default=False)
@pytest.fixture @pytest.fixture
def update(request): def update(request):
"""Store current results as new reference results.""" """Store current results as new reference results."""
return request.config.getoption("--update") return request.config.getoption("--update")
@pytest.fixture @pytest.fixture
def reference_dir_base(): def reference_dir_base():
"""Directory containing reference results.""" """Directory containing reference results."""
return Path(__file__).parent/'reference' return Path(__file__).parent/'reference'
@pytest.fixture @pytest.fixture
def set_of_quaternions(): def set_of_quaternions():
"""A set of n random rotations.""" """A set of n random rotations."""

View File

@ -17,9 +17,12 @@ def reference_dir(reference_dir_base):
class TestColormap: class TestColormap:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _execution_stamp(self, execution_stamp): def _patch_execution_stamp(self, patch_execution_stamp):
print('patched damask.util.execution_stamp') print('patched damask.util.execution_stamp')
def test_repr(self,patch_plt_show):
print(Colormap.from_predefined('stress'))
def test_conversion(self): def test_conversion(self):
specials = np.array([[0.,0.,0.], specials = np.array([[0.,0.,0.],
[1.,0.,0.], [1.,0.,0.],
@ -138,8 +141,8 @@ class TestColormap:
diff = ImageChops.difference(img_reference.convert('RGB'),img_current.convert('RGB')) diff = ImageChops.difference(img_reference.convert('RGB'),img_current.convert('RGB'))
assert not diff.getbbox() assert not diff.getbbox()
def test_list(self): def test_predefined(self):
Colormap.list_predefined() assert (isinstance(Colormap.predefined,dict))
@pytest.mark.parametrize('format,ext',[('ASCII','.txt'), @pytest.mark.parametrize('format,ext',[('ASCII','.txt'),
('paraview','.json'), ('paraview','.json'),

View File

@ -62,6 +62,12 @@ class TestConfigMaterial:
del material_config['material'][0]['homogenization'] del material_config['material'][0]['homogenization']
assert not material_config.is_complete assert not material_config.is_complete
def test_incomplete_homogenization_N_constituents(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
for h in material_config['homogenization'].keys():
del material_config['homogenization'][h]['N_constituents']
assert not material_config.is_complete
def test_incomplete_phase_lattice(self,reference_dir): def test_incomplete_phase_lattice(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml') material_config = ConfigMaterial.load(reference_dir/'material.yaml')
del material_config['phase']['Aluminum']['lattice'] del material_config['phase']['Aluminum']['lattice']
@ -85,9 +91,36 @@ class TestConfigMaterial:
assert len(c['material']) == N assert len(c['material']) == N
for i,m in enumerate(c['material']): for i,m in enumerate(c['material']):
c = m['constituents'][0] c = m['constituents'][0]
assert m['c'] == 1 and c['b'] == 0 and c['a'] == [i,1] assert m['c'] == 1 and c['b'] == 0 and (c['a'] == [i,1]).all()
def test__constituents(self): def test_constituents(self):
c = ConfigMaterial._constituents(c=1,v=[2,3]) c = ConfigMaterial._constituents(c=1,v=[2,3])
assert c[0][0]['c'] == c[1][0]['c'] == 1 assert c[0][0]['c'] == c[1][0]['c'] == 1
assert c[0][0]['v'] == c[1][0]['v'] -1 ==2 assert c[0][0]['v'] == c[1][0]['v'] -1 ==2
@pytest.mark.parametrize('constituents',[{'W':1,'X':[2,3]},{'Y':4},{'Z':[5,6]}])
@pytest.mark.parametrize('a',[[7.,8.],9.])
@pytest.mark.parametrize('b',['bd',['efg','hi']])
def test_material_add(self,tmp_path,constituents,a,b):
len_c = len(ConfigMaterial()._constituents(1,**constituents))
len_a = len(a) if isinstance(a,list) else 1
len_b = len(b) if isinstance(b,list) else 1
m = ConfigMaterial().material_add(constituents,a=a,b=b)
m.save()
assert len(m['material']) == np.max([len_a,len_b,len_c])
@pytest.mark.parametrize('constituents',[{'W':1,'X':np.array([2,3])},{'Y':4},{'Z':np.array([5,6])}])
@pytest.mark.parametrize('a',[np.array([7,8]),9])
def test_material_add_np(self,tmp_path,constituents,a):
len_c = len(ConfigMaterial()._constituents(1,**constituents))
len_a = len(a) if isinstance(a,np.ndarray) else 1
m = ConfigMaterial().material_add(constituents,ld=a)
m.save()
assert len(m['material']) == np.max([len_a,len_c])
@pytest.mark.parametrize('constituents',[{'X':np.array([2,3,4,5])},{'Y':4}])
@pytest.mark.parametrize('a',[np.array([1,2,3]),[4,5,6]])
@pytest.mark.parametrize('b',[np.array([6.,7.]),[8.,9.]])
def test_material_add_invalid(self,constituents,a,b):
with pytest.raises(ValueError):
ConfigMaterial().material_add(constituents,a=a,u=b)

View File

@ -34,7 +34,7 @@ def reference_dir(reference_dir_base):
class TestGeom: class TestGeom:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _execution_stamp(self, execution_stamp): def _patch_execution_stamp(self, patch_execution_stamp):
print('patched damask.util.execution_stamp') print('patched damask.util.execution_stamp')
def test_diff_equal(self,default): def test_diff_equal(self,default):
@ -45,6 +45,8 @@ class TestGeom:
new = Geom(default.material[1:,1:,1:]+1,default.size*.9,np.ones(3)-default.origin,comments=['modified']) new = Geom(default.material[1:,1:,1:]+1,default.size*.9,np.ones(3)-default.origin,comments=['modified'])
assert str(default.diff(new)) != '' assert str(default.diff(new)) != ''
def test_repr(self,default):
print(default)
def test_read_write_vtr(self,default,tmp_path): def test_read_write_vtr(self,default,tmp_path):
default.save(tmp_path/'default') default.save(tmp_path/'default')
@ -70,6 +72,9 @@ class TestGeom:
Geom(default.material[1:,1:,1:], Geom(default.material[1:,1:,1:],
size=np.ones(2)) size=np.ones(2))
def test_save_load_ASCII(self,default,tmp_path):
default.save_ASCII(tmp_path/'ASCII')
assert geom_equal(Geom.load_ASCII(tmp_path/'ASCII'),default)
def test_invalid_origin(self,default): def test_invalid_origin(self,default):
with pytest.raises(ValueError): with pytest.raises(ValueError):

View File

@ -125,9 +125,9 @@ class TestOrientation:
def test_from_fiber_component(self): def test_from_fiber_component(self):
r = Rotation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), r = Rotation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2),
sigma=0.0,N=1,seed=0) sigma=0.0,N=1,rng_seed=0)
assert np.all(Orientation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), assert np.all(Orientation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2),
sigma=0.0,N=1,seed=0,lattice='triclinic').quaternion sigma=0.0,N=1,rng_seed=0,lattice='triclinic').quaternion
== r.quaternion) == r.quaternion)
@pytest.mark.parametrize('kwargs',[ @pytest.mark.parametrize('kwargs',[
@ -175,8 +175,8 @@ class TestOrientation:
@pytest.mark.parametrize('lattice',Orientation.crystal_families) @pytest.mark.parametrize('lattice',Orientation.crystal_families)
@pytest.mark.parametrize('N',[1,8,32]) @pytest.mark.parametrize('N',[1,8,32])
def test_disorientation(self,lattice,N): def test_disorientation(self,lattice,N):
o = Orientation.from_random(lattice=lattice,shape=N,seed=0) o = Orientation.from_random(lattice=lattice,shape=N)
p = Orientation.from_random(lattice=lattice,shape=N,seed=1) p = Orientation.from_random(lattice=lattice,shape=N)
d,ops = o.disorientation(p,return_operators=True) d,ops = o.disorientation(p,return_operators=True)
@ -198,8 +198,8 @@ class TestOrientation:
(None,None), (None,None),
]) ])
def test_disorientation_blending(self,lattice,a,b): def test_disorientation_blending(self,lattice,a,b):
o = Orientation.from_random(lattice=lattice,shape=a,seed=0) o = Orientation.from_random(lattice=lattice,shape=a)
p = Orientation.from_random(lattice=lattice,shape=b,seed=1) p = Orientation.from_random(lattice=lattice,shape=b)
blend = util.shapeblender(o.shape,p.shape) blend = util.shapeblender(o.shape,p.shape)
for loc in np.random.randint(0,blend,(10,len(blend))): for loc in np.random.randint(0,blend,(10,len(blend))):
assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \
@ -214,7 +214,7 @@ class TestOrientation:
@pytest.mark.parametrize('lattice',Orientation.crystal_families) @pytest.mark.parametrize('lattice',Orientation.crystal_families)
@pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)])
def test_reduced_vectorization(self,lattice,shape): def test_reduced_vectorization(self,lattice,shape):
o = Orientation.from_random(lattice=lattice,shape=shape,seed=0) o = Orientation.from_random(lattice=lattice,shape=shape)
for r, theO in zip(o.reduced.flatten(),o.flatten()): for r, theO in zip(o.reduced.flatten(),o.flatten()):
assert r == theO.reduced assert r == theO.reduced
@ -223,7 +223,7 @@ class TestOrientation:
@pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]]))
@pytest.mark.parametrize('proper',[True,False]) @pytest.mark.parametrize('proper',[True,False])
def test_to_SST_vectorization(self,lattice,shape,vector,proper): def test_to_SST_vectorization(self,lattice,shape,vector,proper):
o = Orientation.from_random(lattice=lattice,shape=shape,seed=0) o = Orientation.from_random(lattice=lattice,shape=shape)
for r, theO in zip(o.to_SST(vector=vector,proper=proper).reshape((-1,3)),o.flatten()): 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)) assert np.allclose(r,theO.to_SST(vector=vector,proper=proper))
@ -232,7 +232,7 @@ class TestOrientation:
@pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]]))
@pytest.mark.parametrize('proper',[True,False]) @pytest.mark.parametrize('proper',[True,False])
def test_IPF_color_vectorization(self,lattice,shape,vector,proper): def test_IPF_color_vectorization(self,lattice,shape,vector,proper):
o = Orientation.from_random(lattice=lattice,shape=shape,seed=0) o = Orientation.from_random(lattice=lattice,shape=shape)
poles = o.to_SST(vector=vector,proper=proper) poles = o.to_SST(vector=vector,proper=proper)
for r, theO in zip(o.IPF_color(poles,proper=proper).reshape((-1,3)),o.flatten()): for r, theO in zip(o.IPF_color(poles,proper=proper).reshape((-1,3)),o.flatten()):
assert np.allclose(r,theO.IPF_color(theO.to_SST(vector=vector,proper=proper),proper=proper)) assert np.allclose(r,theO.IPF_color(theO.to_SST(vector=vector,proper=proper),proper=proper))
@ -245,7 +245,7 @@ class TestOrientation:
(None,(3,)), (None,(3,)),
]) ])
def test_to_SST_blending(self,lattice,a,b): def test_to_SST_blending(self,lattice,a,b):
o = Orientation.from_random(lattice=lattice,shape=a,seed=0) o = Orientation.from_random(lattice=lattice,shape=a)
v = np.random.random(b+(3,)) v = np.random.random(b+(3,))
blend = util.shapeblender(o.shape,b) blend = util.shapeblender(o.shape,b)
for loc in np.random.randint(0,blend,(10,len(blend))): for loc in np.random.randint(0,blend,(10,len(blend))):

View File

@ -769,18 +769,19 @@ class TestRotation:
@pytest.mark.parametrize('shape',[None,1,(4,4)]) @pytest.mark.parametrize('shape',[None,1,(4,4)])
def test_random(self,shape): def test_random(self,shape):
Rotation.from_random(shape) r = Rotation.from_random(shape)
if shape is None:
assert r.shape == ()
elif shape == 1:
assert r.shape == (1,)
else:
assert r.shape == shape
def test_equal(self): def test_equal(self):
r = Rotation.from_random(seed=0) assert Rotation.from_random(rng_seed=1) == Rotation.from_random(rng_seed=1)
assert r == r
def test_unequal(self):
r = Rotation.from_random(seed=0)
assert not (r != r)
def test_inversion(self): def test_inversion(self):
r = Rotation.from_random(seed=0) r = Rotation.from_random()
assert r == ~~r assert r == ~~r
@pytest.mark.parametrize('shape',[None,1,(1,),(4,2),(1,1,1)]) @pytest.mark.parametrize('shape',[None,1,(1,),(4,2),(1,1,1)])

View File

@ -17,6 +17,12 @@ def reference_dir(reference_dir_base):
class TestTable: class TestTable:
def test_repr(self,default):
print(default)
def test_len(self):
len(Table(np.random.rand(7,3),{'X':3})) == 7
def test_get_scalar(self,default): def test_get_scalar(self,default):
d = default.get('s') d = default.get('s')
assert np.allclose(d,1.0) and d.shape[1:] == (1,) assert np.allclose(d,1.0) and d.shape[1:] == (1,)

View File

@ -23,7 +23,7 @@ def default():
class TestVTK: class TestVTK:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _execution_stamp(self, execution_stamp): def _patch_execution_stamp(self, patch_execution_stamp):
print('patched damask.util.execution_stamp') print('patched damask.util.execution_stamp')
def test_rectilinearGrid(self,tmp_path): def test_rectilinearGrid(self,tmp_path):
@ -84,6 +84,15 @@ class TestVTK:
time.sleep(.5) time.sleep(.5)
assert(False) assert(False)
def test_compress(self,tmp_path):
points = np.random.rand(102,3)
v = VTK.from_poly_data(points)
fname_c = tmp_path/'compressed.vtp'
fname_p = tmp_path/'plain.vtp'
v.save(fname_c,parallel=False,compress=False)
v.save(fname_p,parallel=False,compress=True)
assert(VTK.load(fname_c).__repr__() == VTK.load(fname_p).__repr__())
@pytest.mark.parametrize('fname',['a','a.vtp','a.b','a.b.vtp']) @pytest.mark.parametrize('fname',['a','a.vtp','a.b','a.b.vtp'])
def test_filename_variations(self,tmp_path,fname): def test_filename_variations(self,tmp_path,fname):

View File

@ -15,6 +15,10 @@ class TestUtil:
out,err = util.execute('sh -c "echo $test_for_execute"',env={'test_for_execute':'test'}) out,err = util.execute('sh -c "echo $test_for_execute"',env={'test_for_execute':'test'})
assert out=='test\n' and err=='' assert out=='test\n' and err==''
def test_execute_invalid(self):
with pytest.raises(RuntimeError):
util.execute('/bin/false')
def test_croak(self): def test_croak(self):
util.croak('Burp!') util.croak('Burp!')
@ -93,3 +97,7 @@ class TestUtil:
]) ])
def test_shapeblender(self,a,b,answer): def test_shapeblender(self,a,b,answer):
assert util.shapeblender(a,b) == answer assert util.shapeblender(a,b) == answer
@pytest.mark.parametrize('style',[util.emph,util.deemph,util.warn,util.strikeout])
def test_decorate(self,style):
assert 'DAMASK' in style('DAMASK')