Merge branch 'development' into YAML-compatible-numerics

This commit is contained in:
Sharan Roongta 2020-06-25 11:02:13 +02:00
commit 78b6b3ecdb
23 changed files with 450 additions and 301 deletions

View File

@ -1 +1 @@
v2.0.3-2650-g512e54a7 v2.0.3-2692-g3d93a5ff

View File

@ -27,7 +27,7 @@ SolidSolutionStrength 0.0 # Strength due to elements in solid solution
### Dislocation glide parameters ### ### Dislocation glide parameters ###
#per family #per family
Nslip 12 0 Nslip 12
slipburgers 2.72e-10 # Burgers vector of slip system [m] slipburgers 2.72e-10 # Burgers vector of slip system [m]
rhoedge0 1.0e12 # Initial edge dislocation density [m/m**3] rhoedge0 1.0e12 # Initial edge dislocation density [m/m**3]
rhoedgedip0 1.0 # Initial edged dipole dislocation density [m/m**3] rhoedgedip0 1.0 # Initial edged dipole dislocation density [m/m**3]

View File

@ -1,56 +1,30 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Makes postprocessing routines accessible from everywhere. # Makes postprocessing routines accessible from everywhere.
import os
import sys import sys
from pathlib import Path
import damask import damask
damaskEnv = damask.Environment() env = damask.Environment()
baseDir = damaskEnv.relPath('processing/') bin_dir = env.root_dir/Path('bin')
binDir = damaskEnv.relPath('bin/')
if not os.path.isdir(binDir): if not bin_dir.exists():
os.mkdir(binDir) bin_dir.mkdir()
#define ToDo list
processing_subDirs = ['pre',
'post',
]
sys.stdout.write('\nsymbolic linking...\n') sys.stdout.write('\nsymbolic linking...\n')
for sub_dir in ['pre','post']:
the_dir = env.root_dir/Path('processing')/Path(sub_dir)
for subDir in processing_subDirs: for the_file in the_dir.glob('*.py'):
theDir = os.path.abspath(os.path.join(baseDir,subDir)) src = the_dir/the_file
dst = bin_dir/Path(the_file.with_suffix('').name)
sys.stdout.write('\n'+binDir+' ->\n'+theDir+damask.util.deemph(' ...')+'\n') if dst.is_file(): dst.unlink() # dst.unlink(True) for Python >3.8
dst.symlink_to(src)
for theFile in os.listdir(theDir):
theName,theExt = os.path.splitext(theFile)
if theExt in ['.py']:
src = os.path.abspath(os.path.join(theDir,theFile))
sym_link = os.path.abspath(os.path.join(binDir,theName))
if os.path.lexists(sym_link):
os.remove(sym_link)
output = theName+damask.util.deemph(theExt)
else:
output = damask.util.emph(theName)+damask.util.deemph(theExt)
sys.stdout.write(damask.util.deemph('... ')+output+'\n')
os.symlink(src,sym_link)
sys.stdout.write('\npruning broken links...\n') sys.stdout.write('\npruning broken links...\n')
for filename in bin_dir.glob('*'):
brokenLinks = 0 if not filename.is_file():
filename.unlink
for filename in os.listdir(binDir):
path = os.path.join(binDir,filename)
if os.path.islink(path) and not os.path.exists(path):
sys.stdout.write(' '+damask.util.delete(path)+'\n')
os.remove(path)
brokenLinks += 1
sys.stdout.write(('none.' if brokenLinks == 0 else '')+'\n')

View File

@ -6,7 +6,7 @@ import numpy as np
from optparse import OptionParser from optparse import OptionParser
import damask import damask
sys.path.append(damask.solver.Marc().libraryPath()) sys.path.append(str(damask.solver.Marc().library_path))
scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptName = os.path.splitext(os.path.basename(__file__))[0]
scriptID = ' '.join([scriptName,damask.version]) scriptID = ' '.join([scriptName,damask.version])
@ -197,14 +197,15 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to
if mfd_data[i]['uid'] == 1705: del mfd_data[i] if mfd_data[i]['uid'] == 1705: del mfd_data[i]
mfd_data.insert(i, links) mfd_data.insert(i, links)
#-------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------
# MAIN # MAIN
#-------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ parser = OptionParser(option_class=damask.extendableOption, 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)
parser.add_option('-p', '--port', parser.add_option('-p', '--port',
@ -229,10 +230,7 @@ if remote and filenames != []:
if filenames == []: filenames = [None] if filenames == []: filenames = [None]
if remote: if remote:
try: import py_mentat import py_mentat
except:
damask.util.croak('no valid Mentat release found.')
sys.exit(-1)
damask.util.report(scriptName, 'waiting to connect...') damask.util.report(scriptName, 'waiting to connect...')
filenames = [os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names()) + '.mfd')] filenames = [os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names()) + '.mfd')]
@ -240,14 +238,14 @@ if remote:
py_mentat.py_connect('',options.port) py_mentat.py_connect('',options.port)
py_mentat.py_send('*set_save_formatted on') py_mentat.py_send('*set_save_formatted on')
py_mentat.py_send('*save_as_model "{}" yes'.format(filenames[0])) py_mentat.py_send('*save_as_model "{}" yes'.format(filenames[0]))
py_mentat.py_get_int("nnodes()") # hopefully blocks until file is written py_mentat.py_get_int("nnodes()")
except: except py_mentat.InputError as err:
damask.util.croak('failed. try setting Tools/Python/"Run as Separate Process" & "Initiate".') damask.util.croak('{}. Try Tools/Python/"Run as Separate Process" & "Initiate".'.format(err))
sys.exit() sys.exit(-1)
damask.util.croak( 'connected...') damask.util.croak( 'connected...')
for name in filenames: for name in filenames:
while remote and not os.path.exists(name): time.sleep(0.5) # wait for Mentat to write MFD file while remote and not os.path.exists(name): time.sleep(0.5)
with open( name,'r') if name is not None else sys.stdin as fileIn: with open( name,'r') if name is not None else sys.stdin as fileIn:
damask.util.report(scriptName, name) damask.util.report(scriptName, name)
mfd = parseMFD(fileIn) mfd = parseMFD(fileIn)
@ -257,5 +255,4 @@ for name in filenames:
fileOut.write(asMFD(mfd)) fileOut.write(asMFD(mfd))
if remote: if remote:
try: py_mentat.py_send('*open_model "{}"'.format(filenames[0])) py_mentat.py_send('*open_model "{}"'.format(filenames[0]))
except: damask.util.croak('lost connection on sending open command for "{}".'.format(filenames[0]))

View File

@ -9,7 +9,7 @@ import damask
scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptName = os.path.splitext(os.path.basename(__file__))[0]
scriptID = ' '.join([scriptName,damask.version]) scriptID = ' '.join([scriptName,damask.version])
sys.path.append(damask.solver.Marc().libraryPath()) sys.path.append(str(damask.solver.Marc().library_path))
#------------------------------------------------------------------------------------------------- #-------------------------------------------------------------------------------------------------
def outMentat(cmd,locals): def outMentat(cmd,locals):

View File

@ -1,9 +1,9 @@
"""Tools for pre and post processing of DAMASK simulations.""" """Tools for pre and post processing of DAMASK simulations."""
import os as _os from pathlib import Path as _Path
import re as _re import re as _re
name = 'damask' name = 'damask'
with open(_os.path.join(_os.path.dirname(__file__),'VERSION')) as _f: with open(_Path(__file__).parent/_Path('VERSION')) as _f:
version = _re.sub(r'^v','',_f.readline().strip()) version = _re.sub(r'^v','',_f.readline().strip())
# make classes directly accessible as damask.Class # make classes directly accessible as damask.Class

View File

@ -1,11 +1,11 @@
import os import os
from pathlib import Path
import tkinter import tkinter
class Environment: class Environment:
def __init__(self): def __init__(self):
"""Read and provide values of DAMASK configuration.""" """Read and provide values of DAMASK configuration."""
self.options = self._get_options()
try: try:
tk = tkinter.Tk() tk = tkinter.Tk()
self.screen_width = tk.winfo_screenwidth() self.screen_width = tk.winfo_screenwidth()
@ -15,17 +15,8 @@ class Environment:
self.screen_width = 1024 self.screen_width = 1024
self.screen_height = 768 self.screen_height = 768
def relPath(self,relative = '.'): @property
"""Return absolute path from path relative to DAMASK root.""" def options(self):
return os.path.join(self.rootDir(),relative)
def rootDir(self):
"""Return DAMASK root path."""
return os.path.normpath(os.path.join(os.path.realpath(__file__),'../../../'))
def _get_options(self):
options = {} options = {}
for item in ['DAMASK_NUM_THREADS', for item in ['DAMASK_NUM_THREADS',
'MSC_ROOT', 'MSC_ROOT',
@ -34,3 +25,13 @@ class Environment:
options[item] = os.environ[item] if item in os.environ else None options[item] = os.environ[item] if item in os.environ else None
return options return options
@property
def root_dir(self):
"""Return DAMASK root path."""
return Path(__file__).parents[2]
# for compatibility
def rootDir(self):
return str(self.root_dir)

View File

@ -6,6 +6,7 @@ import os
import datetime import datetime
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import xml.dom.minidom import xml.dom.minidom
from pathlib import Path
from functools import partial from functools import partial
import h5py import h5py
@ -88,7 +89,7 @@ class Result:
'con_physics': self.con_physics, 'mat_physics': self.mat_physics 'con_physics': self.con_physics, 'mat_physics': self.mat_physics
} }
self.fname = os.path.abspath(fname) self.fname = Path(fname).absolute()
self._allow_modification = False self._allow_modification = False
@ -1056,14 +1057,17 @@ class Result:
Parameters Parameters
---------- ----------
func : function func : function
Callback function that calculates a new dataset from one or more datasets per HDF5 group. Callback function that calculates a new dataset from one or
more datasets per HDF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: label (in HDF5 file) and arg (argument to which the data is parsed in func). Details of the datasets to be used: label (in HDF5 file) and
arg (argument to which the data is parsed in func).
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
""" """
pool = multiprocessing.Pool(int(Environment().options['DAMASK_NUM_THREADS'])) num_threads = Environment().options['DAMASK_NUM_THREADS']
pool = multiprocessing.Pool(int(num_threads) if num_threads is not None else None)
lock = multiprocessing.Manager().Lock() lock = multiprocessing.Manager().Lock()
groups = self.groups_with_datasets(datasets.values()) groups = self.groups_with_datasets(datasets.values())
@ -1190,7 +1194,7 @@ class Result:
'Dimensions': '{} {} {} {}'.format(*self.grid,np.prod(shape))} 'Dimensions': '{} {} {} {}'.format(*self.grid,np.prod(shape))}
data_items[-1].text='{}:{}'.format(os.path.split(self.fname)[1],name) data_items[-1].text='{}:{}'.format(os.path.split(self.fname)[1],name)
with open(os.path.splitext(self.fname)[0]+'.xdmf','w') as f: with open(self.fname.with_suffix('.xdmf').name,'w') as f:
f.write(xml.dom.minidom.parseString(ET.tostring(xdmf).decode()).toprettyxml()) f.write(xml.dom.minidom.parseString(ET.tostring(xdmf).decode()).toprettyxml())
@ -1266,7 +1270,4 @@ class Result:
u = self.read_dataset(self.get_dataset_location('u_n' if mode.lower() == 'cell' else 'u_p')) u = self.read_dataset(self.get_dataset_location('u_n' if mode.lower() == 'cell' else 'u_p'))
v.add(u,'u') v.add(u,'u')
file_out = '{}_inc{}'.format(os.path.splitext(os.path.basename(self.fname))[0], v.write('{}_inc{}'.format(self.fname.stem,inc[3:].zfill(N_digits)))
inc[3:].zfill(N_digits))
v.write(file_out)

View File

@ -20,8 +20,8 @@ class Rotation:
when viewing from the end point of the rotation axis towards the origin. when viewing from the end point of the rotation axis towards the origin.
- rotations will be interpreted in the passive sense. - rotations will be interpreted in the passive sense.
- Euler angle triplets are implemented using the Bunge convention, - Euler angle triplets are implemented using the Bunge convention,
with the angular ranges as [0, 2π],[0, π],[0, 2π]. with the angular ranges as [0,2π], [0,π], [0,2π].
- the rotation angle ω is limited to the interval [0, π]. - the rotation angle ω is limited to the interval [0,π].
- the real part of a quaternion is positive, Re(q) > 0 - the real part of a quaternion is positive, Re(q) > 0
- P = -1 (as default). - P = -1 (as default).
@ -49,7 +49,8 @@ class Rotation:
Parameters Parameters
---------- ----------
quaternion : numpy.ndarray, optional quaternion : numpy.ndarray, optional
Unit quaternion that follows the conventions. Use .from_quaternion to perform a sanity check. Unit quaternion in positive real hemisphere.
Use .from_quaternion to perform a sanity check.
""" """
self.quaternion = quaternion.copy() self.quaternion = quaternion.copy()
@ -73,7 +74,7 @@ class Rotation:
raise NotImplementedError('Support for multiple rotations missing') raise NotImplementedError('Support for multiple rotations missing')
return '\n'.join([ return '\n'.join([
'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)), 'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)),
'Matrix:\n{}'.format(self.as_matrix()), 'Matrix:\n{}'.format(np.round(self.as_matrix(),8)),
'Bunge Eulers / deg: ({:3.2f}, {:3.2f}, {:3.2f})'.format(*self.as_Eulers(degrees=True)), 'Bunge Eulers / deg: ({:3.2f}, {:3.2f}, {:3.2f})'.format(*self.as_Eulers(degrees=True)),
]) ])
@ -87,10 +88,6 @@ class Rotation:
other : numpy.ndarray or Rotation other : numpy.ndarray or Rotation
Vector, second or fourth order tensor, or rotation object that is rotated. Vector, second or fourth order tensor, or rotation object that is rotated.
Todo
----
Check rotation of 4th order tensor
""" """
if isinstance(other, Rotation): if isinstance(other, Rotation):
q_m = self.quaternion[...,0:1] q_m = self.quaternion[...,0:1]
@ -99,7 +96,7 @@ class Rotation:
p_o = other.quaternion[...,1:] p_o = other.quaternion[...,1:]
q = (q_m*q_o - np.einsum('...i,...i',p_m,p_o).reshape(self.shape+(1,))) q = (q_m*q_o - np.einsum('...i,...i',p_m,p_o).reshape(self.shape+(1,)))
p = q_m*p_o + q_o*p_m + _P * np.cross(p_m,p_o) p = q_m*p_o + q_o*p_m + _P * np.cross(p_m,p_o)
return self.__class__(np.block([q,p])).standardize() return self.__class__(np.block([q,p]))._standardize()
elif isinstance(other,np.ndarray): elif isinstance(other,np.ndarray):
if self.shape + (3,) == other.shape: if self.shape + (3,) == other.shape:
@ -124,24 +121,24 @@ class Rotation:
else: else:
raise TypeError('Cannot rotate {}'.format(type(other))) raise TypeError('Cannot rotate {}'.format(type(other)))
def inverse(self):
"""In-place inverse rotation/backward rotation."""
self.quaternion[...,1:] *= -1
return self
def inversed(self): def _standardize(self):
"""Inverse rotation/backward rotation.""" """Standardize (ensure positive real hemisphere)."""
return self.copy().inverse()
def standardize(self):
"""In-place quaternion representation with positive real part."""
self.quaternion[self.quaternion[...,0] < 0.0] *= -1 self.quaternion[self.quaternion[...,0] < 0.0] *= -1
return self return self
def standardized(self): def inverse(self):
"""Quaternion representation with positive real part.""" """In-place inverse rotation (backward rotation)."""
return self.copy().standardize() self.quaternion[...,1:] *= -1
return self
def __invert__(self):
"""Inverse rotation (backward rotation)."""
return self.copy().inverse()
def inversed(self):
"""Inverse rotation (backward rotation)."""
return ~ self
def misorientation(self,other): def misorientation(self,other):
@ -154,7 +151,7 @@ class Rotation:
Rotation to which the misorientation is computed. Rotation to which the misorientation is computed.
""" """
return other*self.inversed() return other@~self
def broadcast_to(self,shape): def broadcast_to(self,shape):
@ -169,7 +166,7 @@ class Rotation:
return self.__class__(q) return self.__class__(q)
def average(self,other): def average(self,other): #ToDo: discuss calling for vectors
""" """
Calculate the average rotation. Calculate the average rotation.
@ -189,25 +186,31 @@ class Rotation:
def as_quaternion(self): def as_quaternion(self):
""" """
Unit quaternion [q, p_1, p_2, p_3]. Represent as unit quaternion.
Parameters Returns
---------- -------
quaternion : bool, optional q : numpy.ndarray of shape (...,4)
return quaternion as DAMASK object. Unit quaternion in positive real hemisphere: (q_0, q_1, q_2, q_3), |q|=1, q_0 0.
""" """
return self.quaternion return self.quaternion.copy()
def as_Eulers(self, def as_Eulers(self,
degrees = False): degrees = False):
""" """
Bunge-Euler angles: (φ_1, ϕ, φ_2). Represent as Bunge-Euler angles.
Parameters Parameters
---------- ----------
degrees : bool, optional degrees : bool, optional
return angles in degrees. Return angles in degrees.
Returns
-------
phi : numpy.ndarray of shape (...,3)
Bunge-Euler angles: (φ_1, ϕ, φ_2), φ_1 [0,2π], ϕ [0,π], φ_2 [0,2π]
unless degrees == True: φ_1 [0,360], ϕ [0,180], φ_2 [0,360]
""" """
eu = Rotation._qu2eu(self.quaternion) eu = Rotation._qu2eu(self.quaternion)
@ -218,14 +221,21 @@ class Rotation:
degrees = False, degrees = False,
pair = False): pair = False):
""" """
Axis angle representation [n_1, n_2, n_3, ω] unless pair == True: ([n_1, n_2, n_3], ω). Represent as axis angle pair.
Parameters Parameters
---------- ----------
degrees : bool, optional degrees : bool, optional
return rotation angle in degrees. Return rotation angle in degrees. Defaults to False.
pair : bool, optional pair : bool, optional
return tuple of axis and angle. Return tuple of axis and angle. Defaults to False.
Returns
-------
axis_angle : numpy.ndarray of shape (...,4) unless pair == True:
tuple containing numpy.ndarray of shapes (...,3) and (...)
Axis angle pair: (n_1, n_2, n_3, ω), |n| = 1 and ω [0,π]
unless degrees = True: ω [0,180].
""" """
ax = Rotation._qu2ax(self.quaternion) ax = Rotation._qu2ax(self.quaternion)
@ -233,29 +243,60 @@ class Rotation:
return (ax[...,:3],ax[...,3]) if pair else ax return (ax[...,:3],ax[...,3]) if pair else ax
def as_matrix(self): def as_matrix(self):
"""Rotation matrix.""" """
Represent as rotation matrix.
Returns
-------
R : numpy.ndarray of shape (...,3,3)
Rotation matrix R, det(R) = 1, R.TR=I.
"""
return Rotation._qu2om(self.quaternion) return Rotation._qu2om(self.quaternion)
def as_Rodrigues(self, def as_Rodrigues(self,
vector = False): vector = False):
""" """
Rodrigues-Frank vector representation [n_1, n_2, n_3, tan(ω/2)] unless vector == True: [n_1, n_2, n_3] * tan(ω/2). Represent as Rodrigues-Frank vector with separated axis and angle argument.
Parameters Parameters
---------- ----------
vector : bool, optional vector : bool, optional
return as actual Rodrigues--Frank vector, i.e. rotation axis scaled by tan(ω/2). Return as actual Rodrigues-Frank vector, i.e. axis
and angle argument are not separated.
Returns
-------
rho : numpy.ndarray of shape (...,4) unless vector == True:
numpy.ndarray of shape (...,3)
Rodrigues-Frank vector: [n_1, n_2, n_3, tan(ω/2)], |n| = 1 and ω [0,π].
""" """
ro = Rotation._qu2ro(self.quaternion) ro = Rotation._qu2ro(self.quaternion)
return ro[...,:3]*ro[...,3] if vector else ro return ro[...,:3]*ro[...,3] if vector else ro
def as_homochoric(self): def as_homochoric(self):
"""Homochoric vector: (h_1, h_2, h_3).""" """
Represent as homochoric vector.
Returns
-------
h : numpy.ndarray of shape (...,3)
Homochoric vector: (h_1, h_2, h_3), |h| < 1/2*π^(2/3).
"""
return Rotation._qu2ho(self.quaternion) return Rotation._qu2ho(self.quaternion)
def as_cubochoric(self): def as_cubochoric(self):
"""Cubochoric vector: (c_1, c_2, c_3).""" """
Represent as cubochoric vector.
Returns
-------
c : numpy.ndarray of shape (...,3)
Cubochoric vector: (c_1, c_2, c_3), max(c_i) < 1/2*π^(2/3).
"""
return Rotation._qu2cu(self.quaternion) return Rotation._qu2cu(self.quaternion)
def M(self): # ToDo not sure about the name: as_M or M? we do not have a from_M def M(self): # ToDo not sure about the name: as_M or M? we do not have a from_M
@ -275,18 +316,34 @@ class Rotation:
# Static constructors. The input data needs to follow the conventions, options allow to # Static constructors. The input data needs to follow the conventions, options allow to
# relax the conventions. # relax the conventions.
@staticmethod @staticmethod
def from_quaternion(quaternion, def from_quaternion(q,
accept_homomorph = False, accept_homomorph = False,
P = -1, P = -1,
acceptHomomorph = None): acceptHomomorph = None): # old name (for compatibility)
"""
Initialize from quaternion.
Parameters
----------
q : numpy.ndarray of shape (...,4)
Unit quaternion in positive real hemisphere: (q_0, q_1, q_2, q_3),
|q|=1, q_0 0.
accept_homomorph : boolean, optional
Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere).
Defaults to False.
P : integer {-1,1}, optional
Convention used. Defaults to -1.
"""
if acceptHomomorph is not None: if acceptHomomorph is not None:
accept_homomorph = acceptHomomorph accept_homomorph = acceptHomomorph # for compatibility
qu = np.array(quaternion,dtype=float) qu = np.array(q,dtype=float)
if qu.shape[:-2:-1] != (4,): if qu.shape[:-2:-1] != (4,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
if abs(P) != 1:
raise ValueError('P ∉ {-1,1}')
if P > 0: qu[...,1:4] *= -1 # convert from P=1 to P=-1 if P == 1: qu[...,1:4] *= -1
if accept_homomorph: if accept_homomorph:
qu[qu[...,0] < 0.0] *= -1 qu[qu[...,0] < 0.0] *= -1
else: else:
@ -298,10 +355,21 @@ class Rotation:
return Rotation(qu) return Rotation(qu)
@staticmethod @staticmethod
def from_Eulers(eulers, def from_Eulers(phi,
degrees = False): degrees = False):
"""
Initialize from Bunge-Euler angles.
eu = np.array(eulers,dtype=float) Parameters
----------
phi : numpy.ndarray of shape (...,3)
Bunge-Euler angles: (φ_1, ϕ, φ_2), φ_1 [0,2π], ϕ [0,π], φ_2 [0,2π]
unless degrees == True: φ_1 [0,360], ϕ [0,180], φ_2 [0,360].
degrees : boolean, optional
Bunge-Euler angles are given in degrees. Defaults to False.
"""
eu = np.array(phi,dtype=float)
if eu.shape[:-2:-1] != (3,): if eu.shape[:-2:-1] != (3,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
@ -316,12 +384,29 @@ class Rotation:
degrees = False, degrees = False,
normalise = False, normalise = False,
P = -1): P = -1):
"""
Initialize from Axis angle pair.
Parameters
----------
axis_angle : numpy.ndarray of shape (...,4)
Axis angle pair: [n_1, n_2, n_3, ω], |n| = 1 and ω [0,π]
unless degrees = True: ω [0,180].
degrees : boolean, optional
Angle ω is given in degrees. Defaults to False.
normalize: boolean, optional
Allow |n| 1. Defaults to False.
P : integer {-1,1}, optional
Convention used. Defaults to -1.
"""
ax = np.array(axis_angle,dtype=float) ax = np.array(axis_angle,dtype=float)
if ax.shape[:-2:-1] != (4,): if ax.shape[:-2:-1] != (4,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
if abs(P) != 1:
raise ValueError('P ∉ {-1,1}')
if P > 0: ax[...,0:3] *= -1 # convert from P=1 to P=-1 if P == 1: ax[...,0:3] *= -1
if degrees: ax[..., 3] = np.radians(ax[...,3]) if degrees: ax[..., 3] = np.radians(ax[...,3])
if normalise: ax[...,0:3] /= np.linalg.norm(ax[...,0:3],axis=-1) if normalise: ax[...,0:3] /= np.linalg.norm(ax[...,0:3],axis=-1)
if np.any(ax[...,3] < 0.0) or np.any(ax[...,3] > np.pi): if np.any(ax[...,3] < 0.0) or np.any(ax[...,3] > np.pi):
@ -335,7 +420,19 @@ class Rotation:
def from_basis(basis, def from_basis(basis,
orthonormal = True, orthonormal = True,
reciprocal = False): reciprocal = False):
"""
Initialize from lattice basis vectors.
Parameters
----------
basis : numpy.ndarray of shape (...,3,3)
Three lattice basis vectors in three dimensions.
orthonormal : boolean, optional
Basis is strictly orthonormal, i.e. is free of stretch components. Defaults to True.
reciprocal : boolean, optional
Basis vectors are given in reciprocal (instead of real) space. Defaults to False.
"""
om = np.array(basis,dtype=float) om = np.array(basis,dtype=float)
if om.shape[:-3:-1] != (3,3): if om.shape[:-3:-1] != (3,3):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
@ -356,20 +453,43 @@ class Rotation:
return Rotation(Rotation._om2qu(om)) return Rotation(Rotation._om2qu(om))
@staticmethod @staticmethod
def from_matrix(om): def from_matrix(R):
"""
Initialize from rotation matrix.
return Rotation.from_basis(om) Parameters
----------
R : numpy.ndarray of shape (...,3,3)
Rotation matrix: det(R) = 1, R.TR=I.
"""
return Rotation.from_basis(R)
@staticmethod @staticmethod
def from_Rodrigues(rodrigues, def from_Rodrigues(rho,
normalise = False, normalise = False,
P = -1): P = -1):
"""
Initialize from Rodrigues-Frank vector.
ro = np.array(rodrigues,dtype=float) Parameters
----------
rho : numpy.ndarray of shape (...,4)
Rodrigues-Frank vector (angle separated from axis).
(n_1, n_2, n_3, tan(ω/2)), |n| = 1 and ω [0,π].
normalize : boolean, optional
Allow |n| 1. Defaults to False.
P : integer {-1,1}, optional
Convention used. Defaults to -1.
"""
ro = np.array(rho,dtype=float)
if ro.shape[:-2:-1] != (4,): if ro.shape[:-2:-1] != (4,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
if abs(P) != 1:
raise ValueError('P ∉ {-1,1}')
if P > 0: ro[...,0:3] *= -1 # convert from P=1 to P=-1 if P == 1: ro[...,0:3] *= -1
if normalise: ro[...,0:3] /= np.linalg.norm(ro[...,0:3],axis=-1) if normalise: ro[...,0:3] /= np.linalg.norm(ro[...,0:3],axis=-1)
if np.any(ro[...,3] < 0.0): if np.any(ro[...,3] < 0.0):
raise ValueError('Rodrigues vector rotation angle not positive.') raise ValueError('Rodrigues vector rotation angle not positive.')
@ -379,14 +499,26 @@ class Rotation:
return Rotation(Rotation._ro2qu(ro)) return Rotation(Rotation._ro2qu(ro))
@staticmethod @staticmethod
def from_homochoric(homochoric, def from_homochoric(h,
P = -1): P = -1):
"""
Initialize from homochoric vector.
ho = np.array(homochoric,dtype=float) Parameters
----------
h : numpy.ndarray of shape (...,3)
Homochoric vector: (h_1, h_2, h_3), |h| < (3/4*π)^(1/3).
P : integer {-1,1}, optional
Convention used. Defaults to -1.
"""
ho = np.array(h,dtype=float)
if ho.shape[:-2:-1] != (3,): if ho.shape[:-2:-1] != (3,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
if abs(P) != 1:
raise ValueError('P ∉ {-1,1}')
if P > 0: ho *= -1 # convert from P=1 to P=-1 if P == 1: ho *= -1
if np.any(np.linalg.norm(ho,axis=-1) >_R1+1e-9): if np.any(np.linalg.norm(ho,axis=-1) >_R1+1e-9):
raise ValueError('Homochoric coordinate outside of the sphere.') raise ValueError('Homochoric coordinate outside of the sphere.')
@ -394,18 +526,30 @@ class Rotation:
return Rotation(Rotation._ho2qu(ho)) return Rotation(Rotation._ho2qu(ho))
@staticmethod @staticmethod
def from_cubochoric(cubochoric, def from_cubochoric(c,
P = -1): P = -1):
"""
Initialize from cubochoric vector.
cu = np.array(cubochoric,dtype=float) Parameters
----------
c : numpy.ndarray of shape (...,3)
Cubochoric vector: (c_1, c_2, c_3), max(c_i) < 1/2*π^(2/3).
P : integer {-1,1}, optional
Convention used. Defaults to -1.
"""
cu = np.array(c,dtype=float)
if cu.shape[:-2:-1] != (3,): if cu.shape[:-2:-1] != (3,):
raise ValueError('Invalid shape.') raise ValueError('Invalid shape.')
if abs(P) != 1:
raise ValueError('P ∉ {-1,1}')
if np.abs(np.max(cu))>np.pi**(2./3.) * 0.5+1e-9: if np.abs(np.max(cu)) > np.pi**(2./3.) * 0.5+1e-9:
raise ValueError('Cubochoric coordinate outside of the cube: {} {} {}.'.format(*cu)) raise ValueError('Cubochoric coordinate outside of the cube.')
ho = Rotation._cu2ho(cu) ho = Rotation._cu2ho(cu)
if P > 0: ho *= -1 # convert from P=1 to P=-1 if P == 1: ho *= -1
return Rotation(Rotation._ho2qu(ho)) return Rotation(Rotation._ho2qu(ho))
@ -458,7 +602,7 @@ class Rotation:
np.cos(2.0*np.pi*r[...,1])*B, np.cos(2.0*np.pi*r[...,1])*B,
np.sin(2.0*np.pi*r[...,0])*A],axis=-1) np.sin(2.0*np.pi*r[...,0])*A],axis=-1)
return Rotation(q.reshape(r.shape[:-1]+(4,)) if shape is not None else q).standardize() return Rotation(q.reshape(r.shape[:-1]+(4,)) if shape is not None else q)._standardize()
# for compatibility (old names do not follow convention) # for compatibility (old names do not follow convention)
@ -471,7 +615,7 @@ class Rotation:
#################################################################################################### ####################################################################################################
# Code below available according to the following 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) 2017-2020, Martin Diehl/Max-Planck-Institut für Eisenforschung GmbH
# Copyright (c) 2013-2014, Marc De Graef/Carnegie Mellon University # Copyright (c) 2013-2014, Marc De Graef/Carnegie Mellon University
# All rights reserved. # All rights reserved.
# #
@ -530,7 +674,7 @@ class Rotation:
np.zeros(qu.shape[:-1]+(2,))]), np.zeros(qu.shape[:-1]+(2,))]),
np.where(np.abs(q03_s) < 1.0e-8, np.where(np.abs(q03_s) < 1.0e-8,
np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2),
np.broadcast_to(np.pi,qu.shape[:-1]+(1,)), np.broadcast_to(np.pi,qu[...,0:1].shape),
np.zeros(qu.shape[:-1]+(1,))]), np.zeros(qu.shape[:-1]+(1,))]),
np.block([np.arctan2((-_P*q02+q13)*chi, (-_P*q01-q23)*chi), np.block([np.arctan2((-_P*q02+q13)*chi, (-_P*q01-q23)*chi),
np.arctan2( 2.0*chi, q03_s-q12_s ), np.arctan2( 2.0*chi, q03_s-q12_s ),
@ -553,7 +697,7 @@ class Rotation:
s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2)
omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0))
ax = np.where(np.broadcast_to(qu[...,0:1] < 1.0e-8,qu.shape), ax = np.where(np.broadcast_to(qu[...,0:1] < 1.0e-8,qu.shape),
np.block([qu[...,1:4],np.broadcast_to(np.pi,qu.shape[:-1]+(1,))]), np.block([qu[...,1:4],np.broadcast_to(np.pi,qu[...,0:1].shape)]),
np.block([qu[...,1:4]*s,omega])) np.block([qu[...,1:4]*s,omega]))
ax[np.isclose(qu[...,0],1.,rtol=0.0)] = [0.0, 0.0, 1.0, 0.0] ax[np.isclose(qu[...,0],1.,rtol=0.0)] = [0.0, 0.0, 1.0, 0.0]
return ax return ax
@ -564,7 +708,7 @@ class Rotation:
with np.errstate(invalid='ignore',divide='ignore'): with np.errstate(invalid='ignore',divide='ignore'):
s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True) s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True)
ro = np.where(np.broadcast_to(np.abs(qu[...,0:1]) < 1.0e-12,qu.shape), ro = np.where(np.broadcast_to(np.abs(qu[...,0:1]) < 1.0e-12,qu.shape),
np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.broadcast_to(np.inf,qu.shape[:-1]+(1,))]), np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.broadcast_to(np.inf,qu[...,0:1].shape)]),
np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s,
np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0))) np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0)))
]) ])

View File

@ -1,4 +1,4 @@
import os from pathlib import Path
import pandas as pd import pandas as pd
import numpy as np import numpy as np
@ -126,8 +126,8 @@ class VTK:
vtkUnstructuredGrid, and vtkPolyData. vtkUnstructuredGrid, and vtkPolyData.
""" """
ext = os.path.splitext(fname)[1] ext = Path(fname).suffix
if ext == '.vtk': if ext == '.vtk' or dataset_type:
reader = vtk.vtkGenericDataObjectReader() reader = vtk.vtkGenericDataObjectReader()
reader.SetFileName(fname) reader.SetFileName(fname)
reader.Update() reader.Update()
@ -176,10 +176,10 @@ class VTK:
writer = vtk.vtkXMLPolyDataWriter() writer = vtk.vtkXMLPolyDataWriter()
default_ext = writer.GetDefaultFileExtension() default_ext = writer.GetDefaultFileExtension()
name, ext = os.path.splitext(fname) ext = Path(fname).suffix
if ext and ext != '.'+default_ext: if ext and ext != '.'+default_ext:
raise ValueError('Given extension {} is not .{}'.format(ext,default_ext)) raise ValueError('Given extension {} does not match default .{}'.format(ext,default_ext))
writer.SetFileName('{}.{}'.format(name,default_ext)) writer.SetFileName(str(Path(fname).with_suffix('.'+default_ext)))
writer.SetCompressorTypeToZLib() writer.SetCompressorTypeToZLib()
writer.SetDataModeToBinary() writer.SetDataModeToBinary()
writer.SetInputData(self.geom) writer.SetInputData(self.geom)

View File

@ -1,7 +1,7 @@
import os
import subprocess import subprocess
import shlex import shlex
import string import string
from pathlib import Path
from .._environment import Environment from .._environment import Environment
@ -19,28 +19,24 @@ class Marc:
""" """
self.solver = 'Marc' self.solver = 'Marc'
try: self.version = version
self.version = int(version)
except TypeError:
self.version = -1
@property
#-------------------------- def library_path(self):
def libraryPath(self):
path_MSC = Environment().options['MSC_ROOT'] path_MSC = Environment().options['MSC_ROOT']
path_lib = '{}/mentat{}/shlib/linux64'.format(path_MSC,self.version) path_lib = Path('{}/mentat{}/shlib/linux64'.format(path_MSC,self.version))
return path_lib if os.path.exists(path_lib) else '' return path_lib if path_lib.is_dir() else None
#-------------------------- @property
def toolsPath(self): def tools_path(self):
path_MSC = Environment().options['MSC_ROOT'] path_MSC = Environment().options['MSC_ROOT']
path_tools = '{}/marc{}/tools'.format(path_MSC,self.version) path_tools = Path('{}/marc{}/tools'.format(path_MSC,self.version))
return path_tools if os.path.exists(path_tools) else '' return path_tools if path_tools.is_dir() else None
#-------------------------- #--------------------------
@ -53,21 +49,21 @@ class Marc:
): ):
damaskEnv = Environment() env = Environment()
user = os.path.join(damaskEnv.relPath('src'),'DAMASK_marc{}.{}'.format(self.version,'f90' if compile else 'marc')) user = env.root_dir/Path('src/DAMASK_marc{}'.format(self.version)).with_suffix('.f90' if compile else '.marc')
if not os.path.isfile(user): if not user.is_file():
raise FileNotFoundError("DAMASK4Marc ({}) '{}' not found".format(('source' if compile else 'binary'),user)) raise FileNotFoundError("DAMASK4Marc ({}) '{}' not found".format(('source' if compile else 'binary'),user))
# Define options [see Marc Installation and Operation Guide, pp 23] # Define options [see Marc Installation and Operation Guide, pp 23]
script = 'run_damask_{}mp'.format(optimization) script = 'run_damask_{}mp'.format(optimization)
cmd = os.path.join(self.toolsPath(),script) + \ cmd = str(self.tools_path/Path(script)) + \
' -jid ' + model + '_' + job + \ ' -jid ' + model + '_' + job + \
' -nprocd 1 -autorst 0 -ci n -cr n -dcoup 0 -b no -v no' ' -nprocd 1 -autorst 0 -ci n -cr n -dcoup 0 -b no -v no'
if compile: cmd += ' -u ' + user + ' -save y' if compile: cmd += ' -u ' + str(user) + ' -save y'
else: cmd += ' -prog ' + os.path.splitext(user)[0] else: cmd += ' -prog ' + str(user.with_suffix(''))
print('job submission {} compilation: {}'.format('with' if compile else 'without',user)) print('job submission {} compilation: {}'.format('with' if compile else 'without',user))
if logfile: log = open(logfile, 'w') if logfile: log = open(logfile, 'w')

View File

@ -27,7 +27,7 @@ __all__=[
#################################################################################################### ####################################################################################################
def srepr(arg,glue = '\n'): def srepr(arg,glue = '\n'):
r""" r"""
Join arguments as individual lines. Join arguments with glue string.
Parameters Parameters
---------- ----------
@ -93,7 +93,7 @@ def strikeout(what):
def execute(cmd, def execute(cmd,
streamIn = None, stream_in = None,
wd = './', wd = './',
env = None): env = None):
""" """
@ -103,7 +103,7 @@ def execute(cmd,
---------- ----------
cmd : str cmd : str
Command to be executed. Command to be executed.
streanIn :, optional stream_in : file object, optional
Input (via pipe) for executed process. Input (via pipe) for executed process.
wd : str, optional wd : str, optional
Working directory of process. Defaults to ./ . Working directory of process. Defaults to ./ .
@ -119,14 +119,14 @@ def execute(cmd,
stderr = subprocess.PIPE, stderr = subprocess.PIPE,
stdin = subprocess.PIPE, stdin = subprocess.PIPE,
env = myEnv) env = myEnv)
out,error = [i for i in (process.communicate() if streamIn is None stdout, stderr = [i for i in (process.communicate() if stream_in is None
else process.communicate(streamIn.read().encode('utf-8')))] else process.communicate(stream_in.read().encode('utf-8')))]
os.chdir(initialPath) os.chdir(initialPath)
out = out.decode('utf-8').replace('\x08','') stdout = stdout.decode('utf-8').replace('\x08','')
error = error.decode('utf-8').replace('\x08','') stderr = stderr.decode('utf-8').replace('\x08','')
if process.returncode != 0: if process.returncode != 0:
raise RuntimeError('{} failed with returncode {}'.format(cmd,process.returncode)) raise RuntimeError('{} failed with returncode {}'.format(cmd,process.returncode))
return out,error return stdout, stderr
def show_progress(iterable,N_iter=None,prefix='',bar_length=50): def show_progress(iterable,N_iter=None,prefix='',bar_length=50):

View File

@ -302,8 +302,8 @@ class TestResult:
@pytest.mark.parametrize('allowed',['off','on']) @pytest.mark.parametrize('allowed',['off','on'])
def test_rename(self,default,allowed): def test_rename(self,default,allowed):
F = default.read_dataset(default.get_dataset_location('F'))
if allowed == 'on': if allowed == 'on':
F = default.read_dataset(default.get_dataset_location('F'))
default.allow_modification() default.allow_modification()
default.rename('F','new_name') default.rename('F','new_name')
assert np.all(F == default.read_dataset(default.get_dataset_location('new_name'))) assert np.all(F == default.read_dataset(default.get_dataset_location('new_name')))

View File

@ -14,7 +14,7 @@ scatter=1.e-2
@pytest.fixture @pytest.fixture
def default(): def default():
"""A set of n random rotations.""" """A set of n rotations (corner cases and random)."""
specials = np.array([ specials = np.array([
[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0],
#---------------------- #----------------------
@ -170,7 +170,7 @@ def qu2ax(qu):
Modified version of the original formulation, should be numerically more stable Modified version of the original formulation, should be numerically more stable
""" """
if np.isclose(qu[0],1.,rtol=0.0): # set axis to [001] if the angle is 0/360 if np.isclose(qu[0],1.,rtol=0.0): # set axis to [001] if the angle is 0/360
ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) ax = np.array([ 0.0, 0.0, 1.0, 0.0 ])
elif qu[0] > 1.e-8: elif qu[0] > 1.e-8:
s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2)
@ -534,7 +534,7 @@ def mul(me, other):
other_p = other.quaternion[1:] other_p = other.quaternion[1:]
R = me.__class__(np.append(me_q*other_q - np.dot(me_p,other_p), R = me.__class__(np.append(me_q*other_q - np.dot(me_p,other_p),
me_q*other_p + other_q*me_p + _P * np.cross(me_p,other_p))) me_q*other_p + other_q*me_p + _P * np.cross(me_p,other_p)))
return R.standardize() return R._standardize()
elif isinstance(other, np.ndarray): elif isinstance(other, np.ndarray):
if other.shape == (3,): if other.shape == (3,):
A = me.quaternion[0]**2.0 - np.dot(me.quaternion[1:],me.quaternion[1:]) A = me.quaternion[0]**2.0 - np.dot(me.quaternion[1:],me.quaternion[1:])
@ -554,7 +554,7 @@ def mul(me, other):
axes = ((0, 2, 4, 6), (0, 1, 2, 3)) axes = ((0, 2, 4, 6), (0, 1, 2, 3))
return np.tensordot(RRRR, other, axes) return np.tensordot(RRRR, other, axes)
else: else:
raise ValueError('Can only rotate vectors, 2nd order ternsors, and 4th order tensors') raise ValueError('Can only rotate vectors, 2nd order tensors, and 4th order tensors')
else: else:
raise TypeError('Cannot rotate {}'.format(type(other))) raise TypeError('Cannot rotate {}'.format(type(other)))
@ -854,6 +854,10 @@ class TestRotation:
rot = Rotation.from_basis(om,False,reciprocal=reciprocal) rot = Rotation.from_basis(om,False,reciprocal=reciprocal)
assert np.isclose(np.linalg.det(rot.as_matrix()),1.0) assert np.isclose(np.linalg.det(rot.as_matrix()),1.0)
@pytest.mark.parametrize('shape',[None,1,(4,4)])
def test_random(self,shape):
Rotation.from_random(shape)
@pytest.mark.parametrize('function',[Rotation.from_quaternion, @pytest.mark.parametrize('function',[Rotation.from_quaternion,
Rotation.from_Eulers, Rotation.from_Eulers,
Rotation.from_axis_angle, Rotation.from_axis_angle,
@ -866,6 +870,16 @@ class TestRotation:
with pytest.raises(ValueError): with pytest.raises(ValueError):
function(invalid_shape) function(invalid_shape)
@pytest.mark.parametrize('fr,to',[(Rotation.from_quaternion,'as_quaternion'),
(Rotation.from_axis_angle,'as_axis_angle'),
(Rotation.from_Rodrigues, 'as_Rodrigues'),
(Rotation.from_homochoric,'as_homochoric'),
(Rotation.from_cubochoric,'as_cubochoric')])
def test_invalid_P(self,fr,to):
R = Rotation.from_random(np.random.randint(8,32,(3))) # noqa
with pytest.raises(ValueError):
fr(eval('R.{}()'.format(to)),P=-30)
@pytest.mark.parametrize('shape',[None,(3,),(4,2)]) @pytest.mark.parametrize('shape',[None,(3,),(4,2)])
def test_broadcast(self,shape): def test_broadcast(self,shape):
rot = Rotation.from_random(shape) rot = Rotation.from_random(shape)
@ -932,14 +946,18 @@ class TestRotation:
phi_2 = 2*np.pi - phi_1 phi_2 = 2*np.pi - phi_1
R_1 = Rotation.from_Eulers(np.array([phi_1,0.,0.])) R_1 = Rotation.from_Eulers(np.array([phi_1,0.,0.]))
R_2 = Rotation.from_Eulers(np.array([0.,0.,phi_2])) R_2 = Rotation.from_Eulers(np.array([0.,0.,phi_2]))
assert np.allclose(data,R_2*(R_1*data)) assert np.allclose(data,R_2@(R_1@data))
def test_rotate_inverse(self):
R = Rotation.from_random()
assert np.allclose(np.eye(3),(~R@R).as_matrix())
@pytest.mark.parametrize('data',[np.random.rand(3), @pytest.mark.parametrize('data',[np.random.rand(3),
np.random.rand(3,3), np.random.rand(3,3),
np.random.rand(3,3,3,3)]) np.random.rand(3,3,3,3)])
def test_rotate_inverse(self,data): def test_rotate_inverse_array(self,data):
R = Rotation.from_random() R = Rotation.from_random()
assert np.allclose(data,R.inversed()*(R*data)) assert np.allclose(data,~R@(R@data))
@pytest.mark.parametrize('data',[np.random.rand(4), @pytest.mark.parametrize('data',[np.random.rand(4),
np.random.rand(3,2), np.random.rand(3,2),
@ -956,3 +974,7 @@ class TestRotation:
R = Rotation.from_random() R = Rotation.from_random()
with pytest.raises(TypeError): with pytest.raises(TypeError):
R*data R*data
def test_misorientation(self):
R = Rotation.from_random()
assert np.allclose(R.misorientation(R).as_matrix(),np.eye(3))

View File

@ -17,18 +17,24 @@ class TestVTK:
size = np.random.random(3) + 1.0 size = np.random.random(3) + 1.0
origin = np.random.random(3) origin = np.random.random(3)
v = VTK.from_rectilinearGrid(grid,size,origin) v = VTK.from_rectilinearGrid(grid,size,origin)
s = v.__repr__() string = v.__repr__()
v.write(os.path.join(tmp_path,'rectilinearGrid')) v.write(os.path.join(tmp_path,'rectilinearGrid'))
v = VTK.from_file(os.path.join(tmp_path,'rectilinearGrid.vtr')) vtr = VTK.from_file(os.path.join(tmp_path,'rectilinearGrid.vtr'))
assert(s == v.__repr__()) with open(os.path.join(tmp_path,'rectilinearGrid.vtk'),'w') as f:
f.write(string)
vtk = VTK.from_file(os.path.join(tmp_path,'rectilinearGrid.vtk'),'VTK_rectilinearGrid')
assert(string == vtr.__repr__() == vtk.__repr__())
def test_polyData(self,tmp_path): def test_polyData(self,tmp_path):
points = np.random.rand(3,100) points = np.random.rand(3,100)
v = VTK.from_polyData(points) v = VTK.from_polyData(points)
s = v.__repr__() string = v.__repr__()
v.write(os.path.join(tmp_path,'polyData')) v.write(os.path.join(tmp_path,'polyData'))
v = VTK.from_file(os.path.join(tmp_path,'polyData.vtp')) vtp = VTK.from_file(os.path.join(tmp_path,'polyData.vtp'))
assert(s == v.__repr__()) with open(os.path.join(tmp_path,'polyData.vtk'),'w') as f:
f.write(string)
vtk = VTK.from_file(os.path.join(tmp_path,'polyData.vtk'),'polyData')
assert(string == vtp.__repr__() == vtk.__repr__())
@pytest.mark.parametrize('cell_type,n',[ @pytest.mark.parametrize('cell_type,n',[
('VTK_hexahedron',8), ('VTK_hexahedron',8),
@ -41,7 +47,17 @@ class TestVTK:
nodes = np.random.rand(n,3) nodes = np.random.rand(n,3)
connectivity = np.random.choice(np.arange(n),n,False).reshape(-1,n) connectivity = np.random.choice(np.arange(n),n,False).reshape(-1,n)
v = VTK.from_unstructuredGrid(nodes,connectivity,cell_type) v = VTK.from_unstructuredGrid(nodes,connectivity,cell_type)
s = v.__repr__() string = v.__repr__()
v.write(os.path.join(tmp_path,'unstructuredGrid')) v.write(os.path.join(tmp_path,'unstructuredGrid'))
v = VTK.from_file(os.path.join(tmp_path,'unstructuredGrid.vtu')) vtu = VTK.from_file(os.path.join(tmp_path,'unstructuredGrid.vtu'))
assert(s == v.__repr__()) with open(os.path.join(tmp_path,'unstructuredGrid.vtk'),'w') as f:
f.write(string)
vtk = VTK.from_file(os.path.join(tmp_path,'unstructuredGrid.vtk'),'unstructuredgrid')
assert(string == vtu.__repr__() == vtk.__repr__())
@pytest.mark.parametrize('name,dataset_type',[('this_file_does_not_exist.vtk',None),
('this_file_does_not_exist.vtk','vtk'),
('this_file_does_not_exist.vtx', None)])
def test_invalid_dataset_type(self,dataset_type,name):
with pytest.raises(TypeError):
VTK.from_file('this_file_does_not_exist.vtk',dataset_type)

11
python/tests/test_util.py Normal file
View File

@ -0,0 +1,11 @@
from damask import util
class TestUtil:
def test_execute_direct(self):
out,err = util.execute('echo test')
assert out=='test\n' and err==''
def test_execute_env(self):
out,err = util.execute('sh -c "echo $test_for_execute"',env={'test_for_execute':'test'})
assert out=='test\n' and err==''

View File

@ -11,7 +11,6 @@ module IO
implicit none implicit none
private private
character(len=*), parameter, public :: & character(len=*), parameter, public :: &
IO_EOF = '#EOF#', & !< end of file string
IO_WHITESPACE = achar(44)//achar(32)//achar(9)//achar(10)//achar(13) !< whitespace characters IO_WHITESPACE = achar(44)//achar(32)//achar(9)//achar(10)//achar(13) !< whitespace characters
character, parameter, public :: & character, parameter, public :: &
IO_EOL = new_line('DAMASK'), & !< end of line character IO_EOL = new_line('DAMASK'), & !< end of line character
@ -106,7 +105,7 @@ function IO_readlines(fileName) result(fileContent)
endif endif
startPos = endPos + 2 ! jump to next line start startPos = endPos + 2 ! jump to next line start
fileContent(l) = line fileContent(l) = trim(line)//''
l = l + 1 l = l + 1
enddo enddo
@ -114,7 +113,8 @@ end function IO_readlines
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
!> @brief reads an entire ASCII file into a string !> @brief read ASCII file into a string
!> @details ensures that the string ends with a new line (expected UNIX behavior)
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
function IO_read(fileName) result(fileContent) function IO_read(fileName) result(fileContent)
@ -130,10 +130,14 @@ function IO_read(fileName) result(fileContent)
status='old', position='rewind', action='read',iostat=myStat) status='old', position='rewind', action='read',iostat=myStat)
if(myStat /= 0) call IO_error(100,ext_msg=trim(fileName)) if(myStat /= 0) call IO_error(100,ext_msg=trim(fileName))
allocate(character(len=fileLength)::fileContent) allocate(character(len=fileLength)::fileContent)
if(fileLength==0) return
read(fileUnit,iostat=myStat) fileContent read(fileUnit,iostat=myStat) fileContent
if(myStat > 0) call IO_error(102,ext_msg=trim(fileName)) ! <0 for ifort (https://software.intel.com/en-us/comment/1960081) if(myStat /= 0) call IO_error(102,ext_msg=trim(fileName))
close(fileUnit) close(fileUnit)
if(fileContent(fileLength:fileLength) /= IO_EOL) fileContent = fileContent//IO_EOL ! ensure EOL@EOF
end function IO_read end function IO_read

View File

@ -830,7 +830,7 @@ module subroutine plastic_dislotwin_results(instance,group)
'mobile dislocation density','1/m²') 'mobile dislocation density','1/m²')
case('rho_dip') case('rho_dip')
if(prm%sum_N_sl>0) call results_writeDataset(group,stt%rho_dip,'rho_dip',& if(prm%sum_N_sl>0) call results_writeDataset(group,stt%rho_dip,'rho_dip',&
'dislocation dipole density''1/m²') 'dislocation dipole density','1/m²')
case('gamma_sl') case('gamma_sl')
if(prm%sum_N_sl>0) call results_writeDataset(group,stt%gamma_sl,'gamma_sl',& if(prm%sum_N_sl>0) call results_writeDataset(group,stt%gamma_sl,'gamma_sl',&
'plastic shear','1') 'plastic shear','1')

View File

@ -284,11 +284,9 @@ end subroutine crystallite_init
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
!> @brief calculate stress (P) !> @brief calculate stress (P)
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
function crystallite_stress(dummyArgumentToPreventInternalCompilerErrorWithGCC) function crystallite_stress()
logical, dimension(discretization_nIP,discretization_nElem) :: crystallite_stress logical, dimension(discretization_nIP,discretization_nElem) :: crystallite_stress
real(pReal), intent(in), optional :: &
dummyArgumentToPreventInternalCompilerErrorWithGCC
real(pReal) :: & real(pReal) :: &
formerSubStep formerSubStep
integer :: & integer :: &

View File

@ -49,7 +49,7 @@ subroutine discretization_init(homogenizationAt,microstructureAt,&
IPcoords0, & IPcoords0, &
NodeCoords0 NodeCoords0
integer, optional, intent(in) :: & integer, optional, intent(in) :: &
sharedNodesBegin sharedNodesBegin !< index of first node shared among different processes (MPI)
write(6,'(/,a)') ' <<<+- discretization init -+>>>'; flush(6) write(6,'(/,a)') ' <<<+- discretization init -+>>>'; flush(6)

View File

@ -321,7 +321,6 @@ program DAMASK_grid
loadCases = [loadCases,newLoadCase] ! load case is ok, append it loadCases = [loadCases,newLoadCase] ! load case is ok, append it
enddo enddo
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
! doing initialization depending on active solvers ! doing initialization depending on active solvers
call spectral_Utilities_init call spectral_Utilities_init

View File

@ -8,54 +8,50 @@ module discretization_mesh
#include <petsc/finclude/petscdmplex.h> #include <petsc/finclude/petscdmplex.h>
#include <petsc/finclude/petscis.h> #include <petsc/finclude/petscis.h>
#include <petsc/finclude/petscdmda.h> #include <petsc/finclude/petscdmda.h>
use PETScdmplex use PETScdmplex
use PETScdmda use PETScdmda
use PETScis use PETScis
use DAMASK_interface use DAMASK_interface
use IO use IO
use debug use debug
use discretization use discretization
use numerics use numerics
use FEsolving use FEsolving
use FEM_quadrature use FEM_quadrature
use prec use prec
use YAML_types
implicit none
private
integer, public, protected :: &
mesh_Nboundaries, &
mesh_NcpElemsGlobal
integer :: & implicit none
mesh_NcpElems !< total number of CP elements in mesh private
integer, public, protected :: &
mesh_Nboundaries, &
mesh_NcpElemsGlobal
integer :: &
mesh_NcpElems !< total number of CP elements in mesh
!!!! BEGIN DEPRECATED !!!!! !!!! BEGIN DEPRECATED !!!!!
integer, public, protected :: & integer, public, protected :: &
mesh_maxNips !< max number of IPs in any CP element mesh_maxNips !< max number of IPs in any CP element
!!!! BEGIN DEPRECATED !!!!! !!!! BEGIN DEPRECATED !!!!!
integer, dimension(:,:), allocatable :: & real(pReal), dimension(:,:), allocatable :: &
mesh_element !DEPRECATED mesh_ipVolume, & !< volume associated with IP (initially!)
mesh_node0 !< node x,y,z coordinates (initially!)
real(pReal), dimension(:,:), allocatable :: & real(pReal), dimension(:,:,:), allocatable :: &
mesh_ipVolume, & !< volume associated with IP (initially!) mesh_ipCoordinates !< IP x,y,z coordinates (after deformation!)
mesh_node0 !< node x,y,z coordinates (initially!)
real(pReal), dimension(:,:,:), allocatable :: &
mesh_ipCoordinates !< IP x,y,z coordinates (after deformation!)
DM, public :: geomMesh DM, public :: geomMesh
PetscInt, dimension(:), allocatable, public, protected :: &
mesh_boundaries
public :: & PetscInt, dimension(:), allocatable, public, protected :: &
discretization_mesh_init, & mesh_boundaries
mesh_FEM_build_ipVolumes, &
mesh_FEM_build_ipCoordinates public :: &
discretization_mesh_init, &
mesh_FEM_build_ipVolumes, &
mesh_FEM_build_ipCoordinates
contains contains
@ -68,46 +64,40 @@ subroutine discretization_mesh_init(restart)
logical, intent(in) :: restart logical, intent(in) :: restart
integer, dimension(1), parameter:: FE_geomtype = [1] !< geometry type of particular element type
integer, dimension(1) :: FE_Nips !< number of IPs in a specific type of element
integer, allocatable, dimension(:) :: chunkPos integer, allocatable, dimension(:) :: chunkPos
integer :: dimPlex, & integer :: dimPlex, &
mesh_Nnodes, & !< total number of nodes in mesh mesh_Nnodes, & !< total number of nodes in mesh
j, l j, l
integer, parameter :: &
mesh_ElemType=1 !< Element type of the mesh (only support homogeneous meshes)
PetscSF :: sf PetscSF :: sf
DM :: globalMesh DM :: globalMesh
PetscInt :: nFaceSets PetscInt :: nFaceSets
PetscInt, pointer, dimension(:) :: pFaceSets PetscInt, pointer, dimension(:) :: pFaceSets
character(len=pStringLen), dimension(:), allocatable :: fileContent character(len=pStringLen), dimension(:), allocatable :: fileContent
IS :: faceSetIS IS :: faceSetIS
PetscErrorCode :: ierr PetscErrorCode :: ierr
integer, dimension(:), allocatable :: &
homogenizationAt, &
microstructureAt
class(tNode), pointer :: & class(tNode), pointer :: &
num_mesh num_mesh
integer :: integrationOrder !< order of quadrature rule required integer :: integrationOrder !< order of quadrature rule required
num_mesh => numerics_root%get('mesh',defaultVal=emptyDict)
integrationOrder = num_mesh%get_asInt('integrationorder',defaultVal = 2)
write(6,'(/,a)') ' <<<+- mesh init -+>>>' write(6,'(/,a)') ' <<<+- mesh init -+>>>'
! read in file num_mesh => numerics_root%get('mesh',defaultVal=emptyDict)
integrationOrder = num_mesh%get_asInt('integrationorder',defaultVal = 2)
call DMPlexCreateFromFile(PETSC_COMM_WORLD,geometryFile,PETSC_TRUE,globalMesh,ierr) call DMPlexCreateFromFile(PETSC_COMM_WORLD,geometryFile,PETSC_TRUE,globalMesh,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
! get spatial dimension (2 or 3?)
call DMGetDimension(globalMesh,dimPlex,ierr) call DMGetDimension(globalMesh,dimPlex,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
write(6,*) 'dimension',dimPlex;flush(6)
call DMGetStratumSize(globalMesh,'depth',dimPlex,mesh_NcpElemsGlobal,ierr) call DMGetStratumSize(globalMesh,'depth',dimPlex,mesh_NcpElemsGlobal,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
! get number of IDs in face sets (for boundary conditions?) ! get number of IDs in face sets (for boundary conditions?)
call DMGetLabelSize(globalMesh,'Face Sets',mesh_Nboundaries,ierr) call DMGetLabelSize(globalMesh,'Face Sets',mesh_Nboundaries,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
write(6,*) 'number of "Face Sets"',mesh_Nboundaries;flush(6)
call MPI_Bcast(mesh_Nboundaries,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) call MPI_Bcast(mesh_Nboundaries,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr)
call MPI_Bcast(mesh_NcpElemsGlobal,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) call MPI_Bcast(mesh_NcpElemsGlobal,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr)
call MPI_Bcast(dimPlex,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) call MPI_Bcast(dimPlex,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr)
@ -127,7 +117,7 @@ subroutine discretization_mesh_init(restart)
call MPI_Bcast(mesh_boundaries,mesh_Nboundaries,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) call MPI_Bcast(mesh_boundaries,mesh_Nboundaries,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr)
if (worldrank == 0) then if (worldrank == 0) then
fileContent = IO_read_ASCII(geometryFile) fileContent = IO_readlines(geometryFile)
l = 0 l = 0
do do
l = l + 1 l = l + 1
@ -150,48 +140,42 @@ subroutine discretization_mesh_init(restart)
enddo enddo
call DMClone(globalMesh,geomMesh,ierr) call DMClone(globalMesh,geomMesh,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
else else
call DMPlexDistribute(globalMesh,0,sf,geomMesh,ierr) call DMPlexDistribute(globalMesh,0,sf,geomMesh,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
endif endif
call DMDestroy(globalMesh,ierr); CHKERRQ(ierr) call DMDestroy(globalMesh,ierr); CHKERRQ(ierr)
call DMGetStratumSize(geomMesh,'depth',dimPlex,mesh_NcpElems,ierr) call DMGetStratumSize(geomMesh,'depth',dimPlex,mesh_NcpElems,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
call DMGetStratumSize(geomMesh,'depth',0,mesh_Nnodes,ierr) call DMGetStratumSize(geomMesh,'depth',0,mesh_Nnodes,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
FE_Nips(FE_geomtype(1)) = FEM_nQuadrature(dimPlex,integrationOrder) mesh_maxNips = FEM_nQuadrature(dimPlex,integrationOrder)
mesh_maxNips = FE_Nips(1)
write(6,*) 'mesh_maxNips',mesh_maxNips
call mesh_FEM_build_ipCoordinates(dimPlex,FEM_quadrature_points(dimPlex,integrationOrder)%p) call mesh_FEM_build_ipCoordinates(dimPlex,FEM_quadrature_points(dimPlex,integrationOrder)%p)
call mesh_FEM_build_ipVolumes(dimPlex) call mesh_FEM_build_ipVolumes(dimPlex)
allocate (mesh_element (4,mesh_NcpElems)); mesh_element = 0
do j = 1, mesh_NcpElems
mesh_element( 1,j) = -1 ! DEPRECATED
mesh_element( 2,j) = mesh_elemType ! elem type
mesh_element( 3,j) = 1 ! homogenization
call DMGetLabelValue(geomMesh,'material',j-1,mesh_element(4,j),ierr)
CHKERRQ(ierr)
end do
if (debug_e < 1 .or. debug_e > mesh_NcpElems) & allocate(microstructureAt(mesh_NcpElems))
call IO_error(602,ext_msg='element') ! selected element does not exist allocate(homogenizationAt(mesh_NcpElems),source=1)
if (debug_i < 1 .or. debug_i > FE_Nips(FE_geomtype(mesh_element(2,debug_e)))) & do j = 1, mesh_NcpElems
call IO_error(602,ext_msg='IP') ! selected element does not have requested IP call DMGetLabelValue(geomMesh,'material',j-1,microstructureAt(j),ierr)
CHKERRQ(ierr)
FEsolving_execElem = [ 1,mesh_NcpElems ] ! parallel loop bounds set to comprise all DAMASK elements end do
FEsolving_execIP = [1,FE_Nips(FE_geomtype(mesh_element(2,1)))]
if (debug_e < 1 .or. debug_e > mesh_NcpElems) call IO_error(602,ext_msg='element')
if (debug_i < 1 .or. debug_i > mesh_maxNips) call IO_error(602,ext_msg='IP')
FEsolving_execElem = [1,mesh_NcpElems] ! parallel loop bounds set to comprise all DAMASK elements
FEsolving_execIP = [1,mesh_maxNips]
allocate(mesh_node0(3,mesh_Nnodes),source=0.0_pReal) allocate(mesh_node0(3,mesh_Nnodes),source=0.0_pReal)
call discretization_init(mesh_element(3,:),mesh_element(4,:),& call discretization_init(microstructureAt,homogenizationAt,&
reshape(mesh_ipCoordinates,[3,mesh_maxNips*mesh_NcpElems]), & reshape(mesh_ipCoordinates,[3,mesh_maxNips*mesh_NcpElems]), &
mesh_node0) mesh_node0)
end subroutine discretization_mesh_init end subroutine discretization_mesh_init
@ -199,23 +183,23 @@ end subroutine discretization_mesh_init
!> @brief Calculates IP volume. Allocates global array 'mesh_ipVolume' !> @brief Calculates IP volume. Allocates global array 'mesh_ipVolume'
!-------------------------------------------------------------------------------------------------- !--------------------------------------------------------------------------------------------------
subroutine mesh_FEM_build_ipVolumes(dimPlex) subroutine mesh_FEM_build_ipVolumes(dimPlex)
PetscInt,intent(in):: dimPlex PetscInt,intent(in):: dimPlex
PetscReal :: vol PetscReal :: vol
PetscReal, pointer,dimension(:) :: pCent, pNorm PetscReal, pointer,dimension(:) :: pCent, pNorm
PetscInt :: cellStart, cellEnd, cell PetscInt :: cellStart, cellEnd, cell
PetscErrorCode :: ierr PetscErrorCode :: ierr
allocate(mesh_ipVolume(mesh_maxNips,mesh_NcpElems),source=0.0_pReal) allocate(mesh_ipVolume(mesh_maxNips,mesh_NcpElems),source=0.0_pReal)
call DMPlexGetHeightStratum(geomMesh,0,cellStart,cellEnd,ierr); CHKERRQ(ierr) call DMPlexGetHeightStratum(geomMesh,0,cellStart,cellEnd,ierr); CHKERRQ(ierr)
allocate(pCent(dimPlex)) allocate(pCent(dimPlex))
allocate(pNorm(dimPlex)) allocate(pNorm(dimPlex))
do cell = cellStart, cellEnd-1 do cell = cellStart, cellEnd-1
call DMPlexComputeCellGeometryFVM(geomMesh,cell,vol,pCent,pNorm,ierr) call DMPlexComputeCellGeometryFVM(geomMesh,cell,vol,pCent,pNorm,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
mesh_ipVolume(:,cell+1) = vol/real(mesh_maxNips,pReal) mesh_ipVolume(:,cell+1) = vol/real(mesh_maxNips,pReal)
enddo enddo
end subroutine mesh_FEM_build_ipVolumes end subroutine mesh_FEM_build_ipVolumes
@ -227,20 +211,20 @@ subroutine mesh_FEM_build_ipCoordinates(dimPlex,qPoints)
PetscInt, intent(in) :: dimPlex PetscInt, intent(in) :: dimPlex
PetscReal, intent(in) :: qPoints(mesh_maxNips*dimPlex) PetscReal, intent(in) :: qPoints(mesh_maxNips*dimPlex)
PetscReal, pointer,dimension(:) :: pV0, pCellJ, pInvcellJ PetscReal, pointer,dimension(:) :: pV0, pCellJ, pInvcellJ
PetscReal :: detJ PetscReal :: detJ
PetscInt :: cellStart, cellEnd, cell, qPt, dirI, dirJ, qOffset PetscInt :: cellStart, cellEnd, cell, qPt, dirI, dirJ, qOffset
PetscErrorCode :: ierr PetscErrorCode :: ierr
allocate(mesh_ipCoordinates(3,mesh_maxNips,mesh_NcpElems),source=0.0_pReal) allocate(mesh_ipCoordinates(3,mesh_maxNips,mesh_NcpElems),source=0.0_pReal)
allocate(pV0(dimPlex)) allocate(pV0(dimPlex))
allocatE(pCellJ(dimPlex**2)) allocatE(pCellJ(dimPlex**2))
allocatE(pinvCellJ(dimPlex**2)) allocatE(pinvCellJ(dimPlex**2))
call DMPlexGetHeightStratum(geomMesh,0,cellStart,cellEnd,ierr); CHKERRQ(ierr) call DMPlexGetHeightStratum(geomMesh,0,cellStart,cellEnd,ierr); CHKERRQ(ierr)
do cell = cellStart, cellEnd-1 !< loop over all elements do cell = cellStart, cellEnd-1 !< loop over all elements
call DMPlexComputeCellGeometryAffineFEM(geomMesh,cell,pV0,pCellJ,pInvcellJ,detJ,ierr) call DMPlexComputeCellGeometryAffineFEM(geomMesh,cell,pV0,pCellJ,pInvcellJ,detJ,ierr)
CHKERRQ(ierr) CHKERRQ(ierr)
qOffset = 0 qOffset = 0
@ -254,7 +238,7 @@ subroutine mesh_FEM_build_ipCoordinates(dimPlex,qPoints)
enddo enddo
qOffset = qOffset + dimPlex qOffset = qOffset + dimPlex
enddo enddo
enddo enddo
end subroutine mesh_FEM_build_ipCoordinates end subroutine mesh_FEM_build_ipCoordinates

View File

@ -101,6 +101,8 @@ module quaternions
assignment(=), & assignment(=), &
conjg, aimag, & conjg, aimag, &
log, exp, & log, exp, &
abs, dot_product, &
inverse, &
real real
contains contains