From 78a2941436dfcb0be765a9cca4a0d134882db58b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 21 Oct 2019 16:59:49 +0200 Subject: [PATCH 001/148] avoid numerical issues --- python/damask/orientation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index 85a5c1866..688a3fa3f 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -702,14 +702,14 @@ class Symmetry: v = np.array(vector,dtype=float) if proper: # check both improper ... theComponents = np.dot(basis['improper'],v) - inSST = np.all(theComponents >= 0.0) + inSST = np.all(np.around(theComponents,12) >= 0.0) if not inSST: # ... and proper SST - theComponents = np.dot(basis['proper'],v) - inSST = np.all(theComponents >= 0.0) + theComponents = np.dot(basis['proper'],v) + inSST = np.all(np.around(theComponents,12) >= 0.0) else: v[2] = abs(v[2]) # z component projects identical theComponents = np.dot(basis['improper'],v) # for positive and negative values - inSST = np.all(theComponents >= 0.0) + inSST = np.all(np.around(theComponents,12) >= 0.0) if color: # have to return color array if inSST: From ecc51e34d3bf19d838f51079fda39270f826ae13 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 21 Oct 2019 22:44:12 +0200 Subject: [PATCH 002/148] rouding should affect color calculation avoid NaN in math.power(x,0.5). math.power(-0.0,0.5) is ok --- python/damask/orientation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index 688a3fa3f..dc851b35c 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -701,15 +701,15 @@ class Symmetry: v = np.array(vector,dtype=float) if proper: # check both improper ... - theComponents = np.dot(basis['improper'],v) - inSST = np.all(np.around(theComponents,12) >= 0.0) + theComponents = np.around(np.dot(basis['improper'],v),12) + inSST = np.all(theComponents >= 0.0) if not inSST: # ... and proper SST - theComponents = np.dot(basis['proper'],v) - inSST = np.all(np.around(theComponents,12) >= 0.0) + theComponents = np.around(np.dot(basis['proper'],v),12) + inSST = np.all(theComponents >= 0.0) else: v[2] = abs(v[2]) # z component projects identical - theComponents = np.dot(basis['improper'],v) # for positive and negative values - inSST = np.all(np.around(theComponents,12) >= 0.0) + theComponents = np.around(np.dot(basis['improper'],v),12) # for positive and negative values + inSST = np.all(theComponents >= 0.0) if color: # have to return color array if inSST: From 9489c04ccbfe3ec88e6502c14d1e4bb025a388d1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 21 Oct 2019 23:02:31 +0200 Subject: [PATCH 003/148] less confusing --- python/damask/orientation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index dc851b35c..97464f9e6 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -1046,13 +1046,13 @@ class Lattice: for miller in np.hstack((relationship['planes'],relationship['directions'])): myPlane = miller[myPlane_id]/ np.linalg.norm(miller[myPlane_id]) myDir = miller[myDir_id]/ np.linalg.norm(miller[myDir_id]) - myMatrix = np.array([myDir,np.cross(myPlane,myDir),myPlane]).T + myMatrix = np.array([myDir,np.cross(myPlane,myDir),myPlane]) otherPlane = miller[otherPlane_id]/ np.linalg.norm(miller[otherPlane_id]) otherDir = miller[otherDir_id]/ np.linalg.norm(miller[otherDir_id]) - otherMatrix = np.array([otherDir,np.cross(otherPlane,otherDir),otherPlane]).T + otherMatrix = np.array([otherDir,np.cross(otherPlane,otherDir),otherPlane]) - r['rotations'].append(Rotation.fromMatrix(np.dot(otherMatrix,myMatrix.T))) + r['rotations'].append(Rotation.fromMatrix(np.dot(otherMatrix.T,myMatrix))) return r From 5e7f9a223b55f9f8c56ba6fa35a0c46ae5db5b68 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 21 Oct 2019 23:17:58 +0200 Subject: [PATCH 004/148] should be a passive rotation --- python/damask/orientation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index 97464f9e6..fc601b608 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -1144,7 +1144,7 @@ class Orientation: def relatedOrientations(self,model): """List of orientations related by the given orientation relationship.""" r = self.lattice.relationOperations(model) - return [self.__class__(self.rotation*o,r['lattice']) for o in r['rotations']] + return [self.__class__(o*self.rotation,r['lattice']) for o in r['rotations']] def reduced(self): """Transform orientation to fall into fundamental zone according to symmetry.""" From 23f5e0fa58e8ca0137236bd8499a53534f11a2cb Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 26 Nov 2019 10:25:39 +0100 Subject: [PATCH 005/148] filters for operations on regular grids (in fourier space) --- python/damask/grid_filters.py | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 python/damask/grid_filters.py diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py new file mode 100644 index 000000000..6588e59bc --- /dev/null +++ b/python/damask/grid_filters.py @@ -0,0 +1,113 @@ +import numpy as np + + +def curl(size,field): + """Calculate curl of a vector or tensor field in Fourier space.""" + shapeFFT = np.array(np.shape(field))[0:3] + grid = np.array(np.shape(field)[2::-1]) + N = grid.prod() # field size + n = np.array(np.shape(field)[3:]).prod() # data size + + field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) + curl_fourier = np.empty(field_fourier.shape,'c16') + + k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] + if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] + if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_si = np.arange(grid[0]//2+1)/size[2] + + kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') + k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') + + e = np.zeros((3, 3, 3)) + e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol + e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 + + curl_fourier = np.einsum('slm,ijkl,ijkm, ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else# vector, 3 -> 3 + np.einsum('slm,ijkl,ijknm,->ijksn',e,k_s,field_fourier)*2.0j*np.pi # tensor, 3x3 -> 3x3 + + return np.fft.irfftn(curl_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n]) + + +def divergence(size,field): + """Calculate divergence of a vector or tensor field in Fourier space.""" + shapeFFT = np.array(np.shape(field))[0:3] + grid = np.array(np.shape(field)[2::-1]) + N = grid.prod() # field size + n = np.array(np.shape(field)[3:]).prod() # data size + + field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) + div_fourier = np.empty(field_fourier.shape[0:len(np.shape(field))-1],'c16') + + k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] + if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] + if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_si = np.arange(grid[0]//2+1)/size[2] + + kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') + k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') + + div_fourier = np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 + np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi # tensor, 3x3 -> 3 + + return np.fft.irfftn(div_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n//3]) + + +def gradient(size,field): + """Calculate gradient of a vector or scalar field in Fourier space.""" + shapeFFT = np.array(np.shape(field))[0:3] + grid = np.array(np.shape(field)[2::-1]) + N = grid.prod() # field size + n = np.array(np.shape(field)[3:]).prod() # data size + + field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) + grad_fourier = np.empty(field_fourier.shape+(3,),'c16') + + k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] + if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] + if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + + k_si = np.arange(grid[0]//2+1)/size[2] + + kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') + k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') + grad_fourier = np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 + np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi # vector, 3 -> 3x3 + + return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,3*n]) + + +#-------------------------------------------------------------------------------------------------- +def displacementFluctFFT(F,size): + """Calculate displacement field from deformation gradient field.""" + integrator = 0.5j * size / np.pi + + kk, kj, ki = np.meshgrid(np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2])), + np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1])), + np.arange(grid[0]//2+1), + indexing = 'ij') + k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3) + k_sSquared = np.einsum('...l,...l',k_s,k_s) + k_sSquared[0,0,0] = 1.0 # ignore global average frequency + +#-------------------------------------------------------------------------------------------------- +# integration in Fourier space + + displacement_fourier = -np.einsum('ijkml,ijkl,l->ijkm', + np.fft.rfftn(F,axes=(0,1,2)), + k_s, + integrator, + ) / k_sSquared[...,np.newaxis] + +#-------------------------------------------------------------------------------------------------- +# backtransformation to real space + + return np.fft.irfftn(displacement_fourier,grid[::-1],axes=(0,1,2)) From b85049cb811b70d71d497001af07b7e73b4fbc25 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 28 Nov 2019 05:41:53 +0100 Subject: [PATCH 006/148] use brackets for line continuation with comments --- python/damask/__init__.py | 1 + python/damask/grid_filters.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 2cd26cf1c..77666561c 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -23,4 +23,5 @@ from .util import extendableOption # noqa # functions in modules from . import mechanics # noqa +from . import grid_filters # noqa diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 6588e59bc..26ee2cfcc 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -26,10 +26,10 @@ def curl(size,field): e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 - curl_fourier = np.einsum('slm,ijkl,ijkm, ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else# vector, 3 -> 3 - np.einsum('slm,ijkl,ijknm,->ijksn',e,k_s,field_fourier)*2.0j*np.pi # tensor, 3x3 -> 3x3 + curl = (np.einsum('slm,ijkl,ijkm, ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 3 + np.einsum('slm,ijkl,ijknm,->ijksn',e,k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3x3 - return np.fft.irfftn(curl_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n]) + return np.fft.irfftn(curl,axes=(0,1,2),s=shapeFFT).reshape([N,n]) def divergence(size,field): @@ -53,8 +53,8 @@ def divergence(size,field): kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - div_fourier = np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 - np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi # tensor, 3x3 -> 3 + divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 + np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3 return np.fft.irfftn(div_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n//3]) @@ -79,8 +79,9 @@ def gradient(size,field): kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - grad_fourier = np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 - np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi # vector, 3 -> 3x3 + + gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 + np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi) # vector, 3 -> 3x3 return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,3*n]) From 4c4ccfe72e88053822c83886089b96bfda0f5a35 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 28 Nov 2019 06:27:19 +0100 Subject: [PATCH 007/148] not needed --- processing/post/addCurl.py | 1 - processing/post/addDivergence.py | 1 - processing/post/addGradient.py | 1 - 3 files changed, 3 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 484af9677..b4dd465a9 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -27,7 +27,6 @@ def curlFFT(geomdim,field): n = np.array(np.shape(field)[3:]).prod() # data size field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - curl_fourier = np.empty(field_fourier.shape,'c16') # differentiation in Fourier space TWOPIIMG = 2.0j*np.pi diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 31a18f8e1..2d6af2036 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -27,7 +27,6 @@ def divFFT(geomdim,field): n = np.array(np.shape(field)[3:]).prod() # data size field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - div_fourier = np.empty(field_fourier.shape[0:len(np.shape(field))-1],'c16') # differentiation in Fourier space TWOPIIMG = 2.0j*np.pi diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index bfadb578e..252b72eb8 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -27,7 +27,6 @@ def gradFFT(geomdim,field): n = np.array(np.shape(field)[3:]).prod() # data size field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - grad_fourier = np.empty(field_fourier.shape+(3,),'c16') # differentiation in Fourier space TWOPIIMG = 2.0j*np.pi From 80b50f460e24ad6ef7a49ac2ac88a5d2946aa74e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 28 Nov 2019 10:09:22 +0100 Subject: [PATCH 008/148] cleaning trying to get rid of strange re-ordering related to ASCII table data layout --- python/damask/grid_filters.py | 47 ++++++++++++++--------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 26ee2cfcc..69ee85033 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -3,21 +3,18 @@ import numpy as np def curl(size,field): """Calculate curl of a vector or tensor field in Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] - grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size + grid = np.array(np.shape(field)[0:3]) n = np.array(np.shape(field)[3:]).prod() # data size - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - curl_fourier = np.empty(field_fourier.shape,'c16') + field_fourier = np.fft.rfftn(field,axes=(0,1,2)) - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] + if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - k_si = np.arange(grid[0]//2+1)/size[2] + k_si = np.arange(grid[2]//2+1)/size[2] kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') @@ -26,29 +23,26 @@ def curl(size,field): e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 - curl = (np.einsum('slm,ijkl,ijkm, ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 3 - np.einsum('slm,ijkl,ijknm,->ijksn',e,k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3x3 + curl = (np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 3 + np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3x3 - return np.fft.irfftn(curl,axes=(0,1,2),s=shapeFFT).reshape([N,n]) + return np.fft.irfftn(curl,axes=(0,1,2)) def divergence(size,field): """Calculate divergence of a vector or tensor field in Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] - grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size + grid = np.array(np.shape(field)[0:3]) n = np.array(np.shape(field)[3:]).prod() # data size - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - div_fourier = np.empty(field_fourier.shape[0:len(np.shape(field))-1],'c16') + field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=grid) - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] + if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - k_si = np.arange(grid[0]//2+1)/size[2] + k_si = np.arange(grid[2]//2+1)/size[2] kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') @@ -56,26 +50,23 @@ def divergence(size,field): divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3 - return np.fft.irfftn(div_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n//3]) + return np.fft.irfftn(div_fourier,axes=(0,1,2),s=grid) def gradient(size,field): """Calculate gradient of a vector or scalar field in Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size n = np.array(np.shape(field)[3:]).prod() # data size - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - grad_fourier = np.empty(field_fourier.shape+(3,),'c16') + field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=grid) - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/size[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] + if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - k_si = np.arange(grid[0]//2+1)/size[2] + k_si = np.arange(grid[2]//2+1)/size[2] kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') @@ -83,7 +74,7 @@ def gradient(size,field): gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi) # vector, 3 -> 3x3 - return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,3*n]) + return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=grid) #-------------------------------------------------------------------------------------------------- From 3e65d44e073f925c4bbd8ed0c53b4a5308c1e8ee Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 28 Nov 2019 15:46:22 +0100 Subject: [PATCH 009/148] centralized facilities for differential operations note the need to reverse the grid shape in data from the ASCII table. If x is fastest, z is slowest we require x to be the rightmost index --- processing/post/addCurl.py | 138 ++++-------------------------- processing/post/addDivergence.py | 140 +++++-------------------------- processing/post/addGradient.py | 135 ++++------------------------- python/damask/grid_filters.py | 63 +++++--------- 4 files changed, 74 insertions(+), 402 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index b4dd465a9..2fcd107c0 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -12,47 +13,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -def merge_dicts(*dict_args): - """Given any number of dicts, shallow copy and merge into a new dict, with precedence going to key value pairs in latter dicts.""" - result = {} - for dictionary in dict_args: - result.update(dictionary) - return result - -def curlFFT(geomdim,field): - """Calculate curl of a vector or tensor field by transforming into Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] - grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size - n = np.array(np.shape(field)[3:]).prod() # data size - - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - - # differentiation in Fourier space - TWOPIIMG = 2.0j*np.pi - einsums = { - 3:'slm,ijkl,ijkm->ijks', # vector, 3 -> 3 - 9:'slm,ijkl,ijknm->ijksn', # tensor, 3x3 -> 3x3 - } - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/geomdim[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/geomdim[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_si = np.arange(grid[0]//2+1)/geomdim[2] - - kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - - e = np.zeros((3, 3, 3)) - e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = 1.0 # Levi-Civita symbols - e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 - - curl_fourier = np.einsum(einsums[n],e,k_s,field_fourier)*TWOPIIMG - - return np.fft.irfftn(curl_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n]) - # -------------------------------------------------------------------- # MAIN @@ -60,8 +20,7 @@ def curlFFT(geomdim,field): parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ Add column(s) containing curl of requested column(s). -Operates on periodic ordered three-dimensional data sets -of vector and tensor fields. +Operates on periodic ordered three-dimensional data sets of vector and tensor fields. """, version = scriptID) parser.add_option('-p','--pos','--periodiccellcenter', @@ -69,93 +28,30 @@ parser.add_option('-p','--pos','--periodiccellcenter', type = 'string', metavar = 'string', help = 'label of coordinates [%default]') parser.add_option('-l','--label', - dest = 'data', + dest = 'labels', action = 'extend', metavar = '', help = 'label(s) of field values') parser.set_defaults(pos = 'pos', ) - (options,filenames) = parser.parse_args() - -if options.data is None: parser.error('no data column specified.') - -# --- define possible data types ------------------------------------------------------------------- - -datatypes = { - 3: {'name': 'vector', - 'shape': [3], - }, - 9: {'name': 'tensor', - 'shape': [3,3], - }, - } - -# --- loop over input files ------------------------------------------------------------------------ - if filenames == []: filenames = [None] +if options.labels is None: parser.error('no data column specified.') + for name in filenames: - try: table = damask.ASCIItable(name = name,buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) - table.head_read() - - remarks = [] - errors = [] - active = [] - - coordDim = table.label_dimension(options.pos) - if coordDim != 3: - errors.append('coordinates "{}" must be three-dimensional.'.format(options.pos)) - else: coordCol = table.label_index(options.pos) - - for me in options.data: - dim = table.label_dimension(me) - if dim in datatypes: - active.append(merge_dicts({'label':me},datatypes[dim])) - remarks.append('differentiating {} "{}"...'.format(datatypes[dim]['name'],me)) - else: - remarks.append('skipping "{}" of dimension {}...'.format(me,dim) if dim != -1 else \ - '"{}" not found...'.format(me) ) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - for data in active: - table.labels_append(['{}_curlFFT({})'.format(i+1,data['label']) - for i in range(np.prod(np.array(data['shape'])))]) # extend ASCII header with new labels - table.head_write() - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray() - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) - -# ------------------------------------------ process value field ----------------------------------- - - stack = [table.data] - for data in active: - # we need to reverse order here, because x is fastest,ie rightmost, but leftmost in our x,y,z notation - stack.append(curlFFT(size[::-1], - table.data[:,table.label_indexrange(data['label'])]. - reshape(grid[::-1].tolist()+data['shape']))) - -# ------------------------------------------ output result ----------------------------------------- - - if len(stack) > 1: table.data = np.hstack(tuple(stack)) - table.data_writeArray('%.12g') - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + for label in options.labels: + field = table.get_array(label) + shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor + field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + table.add_array('curlFFT({})'.format(label), + damask.grid_filters.curl(size[::-1],field).reshape((-1,np.prod(shape))), + scriptID+' '+' '.join(sys.argv[1:])) + + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 2d6af2036..562ab7532 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -12,52 +13,14 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -def merge_dicts(*dict_args): - """Given any number of dicts, shallow copy and merge into a new dict, with precedence going to key value pairs in latter dicts.""" - result = {} - for dictionary in dict_args: - result.update(dictionary) - return result - -def divFFT(geomdim,field): - """Calculate divergence of a vector or tensor field by transforming into Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] - grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size - n = np.array(np.shape(field)[3:]).prod() # data size - - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - - # differentiation in Fourier space - TWOPIIMG = 2.0j*np.pi - einsums = { - 3:'ijkl,ijkl->ijk', # vector, 3 -> 1 - 9:'ijkm,ijklm->ijkl', # tensor, 3x3 -> 3 - } - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/geomdim[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/geomdim[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_si = np.arange(grid[0]//2+1)/geomdim[2] - - kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - - div_fourier = np.einsum(einsums[n],k_s,field_fourier)*TWOPIIMG - - return np.fft.irfftn(div_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,n//3]) - # -------------------------------------------------------------------- # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog option(s) [ASCIItable(s)]', description = """ -Add column(s) containing curl of requested column(s). -Operates on periodic ordered three-dimensional data sets -of vector and tensor fields. +parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ +Add column(s) containing divergence of requested column(s). +Operates on periodic ordered three-dimensional data sets of vector and tensor fields. """, version = scriptID) parser.add_option('-p','--pos','--periodiccellcenter', @@ -65,95 +28,30 @@ parser.add_option('-p','--pos','--periodiccellcenter', type = 'string', metavar = 'string', help = 'label of coordinates [%default]') parser.add_option('-l','--label', - dest = 'data', + dest = 'labels', action = 'extend', metavar = '', help = 'label(s) of field values') parser.set_defaults(pos = 'pos', ) - (options,filenames) = parser.parse_args() - -if options.data is None: parser.error('no data column specified.') - -# --- define possible data types ------------------------------------------------------------------- - -datatypes = { - 3: {'name': 'vector', - 'shape': [3], - }, - 9: {'name': 'tensor', - 'shape': [3,3], - }, - } - -# --- loop over input files ------------------------------------------------------------------------ - if filenames == []: filenames = [None] +if options.labels is None: parser.error('no data column specified.') + for name in filenames: - try: table = damask.ASCIItable(name = name,buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) - table.head_read() - - remarks = [] - errors = [] - active = [] - - coordDim = table.label_dimension(options.pos) - if coordDim != 3: - errors.append('coordinates "{}" must be three-dimensional.'.format(options.pos)) - else: coordCol = table.label_index(options.pos) - - for me in options.data: - dim = table.label_dimension(me) - if dim in datatypes: - active.append(merge_dicts({'label':me},datatypes[dim])) - remarks.append('differentiating {} "{}"...'.format(datatypes[dim]['name'],me)) - else: - remarks.append('skipping "{}" of dimension {}...'.format(me,dim) if dim != -1 else \ - '"{}" not found...'.format(me) ) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - for data in active: - table.labels_append(['divFFT({})'.format(data['label']) if data['shape'] == [3] \ - else '{}_divFFT({})'.format(i+1,data['label']) - for i in range(np.prod(np.array(data['shape']))//3)]) # extend ASCII header with new labels - table.head_write() - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray() - - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) - -# ------------------------------------------ process value field ----------------------------------- - - stack = [table.data] - for data in active: - # we need to reverse order here, because x is fastest,ie rightmost, but leftmost in our x,y,z notation - stack.append(divFFT(size[::-1], - table.data[:,table.label_indexrange(data['label'])]. - reshape(grid[::-1].tolist()+data['shape']))) - -# ------------------------------------------ output result ----------------------------------------- - - if len(stack) > 1: table.data = np.hstack(tuple(stack)) - table.data_writeArray('%.12g') - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + for label in options.labels: + field = table.get_array(label) + shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor + field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + table.add_array('divFFT({})'.format(label), + damask.grid_filters.divergence(size[::-1],field).reshape((-1,np.prod(shape)//3)), + scriptID+' '+' '.join(sys.argv[1:])) + + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index 252b72eb8..d6b537ddd 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -12,43 +13,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -def merge_dicts(*dict_args): - """Given any number of dicts, shallow copy and merge into a new dict, with precedence going to key value pairs in latter dicts.""" - result = {} - for dictionary in dict_args: - result.update(dictionary) - return result - -def gradFFT(geomdim,field): - """Calculate gradient of a vector or scalar field by transforming into Fourier space.""" - shapeFFT = np.array(np.shape(field))[0:3] - grid = np.array(np.shape(field)[2::-1]) - N = grid.prod() # field size - n = np.array(np.shape(field)[3:]).prod() # data size - - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=shapeFFT) - - # differentiation in Fourier space - TWOPIIMG = 2.0j*np.pi - einsums = { - 1:'ijkl,ijkm->ijkm', # scalar, 1 -> 3 - 3:'ijkl,ijkm->ijklm', # vector, 3 -> 3x3 - } - - k_sk = np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2]))/geomdim[0] - if grid[2]%2 == 0: k_sk[grid[2]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/geomdim[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_si = np.arange(grid[0]//2+1)/geomdim[2] - - kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - grad_fourier = np.einsum(einsums[n],field_fourier,k_s)*TWOPIIMG - - return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=shapeFFT).reshape([N,3*n]) - # -------------------------------------------------------------------- # MAIN @@ -56,9 +20,7 @@ def gradFFT(geomdim,field): parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ Add column(s) containing gradient of requested column(s). -Operates on periodic ordered three-dimensional data sets -of vector and scalar fields. - +Operates on periodic ordered three-dimensional data sets of scalar and vector fields. """, version = scriptID) parser.add_option('-p','--pos','--periodiccellcenter', @@ -66,7 +28,7 @@ parser.add_option('-p','--pos','--periodiccellcenter', type = 'string', metavar = 'string', help = 'label of coordinates [%default]') parser.add_option('-l','--label', - dest = 'data', + dest = 'labels', action = 'extend', metavar = '', help = 'label(s) of field values') @@ -74,85 +36,22 @@ parser.set_defaults(pos = 'pos', ) (options,filenames) = parser.parse_args() - -if options.data is None: parser.error('no data column specified.') - -# --- define possible data types ------------------------------------------------------------------- - -datatypes = { - 1: {'name': 'scalar', - 'shape': [1], - }, - 3: {'name': 'vector', - 'shape': [3], - }, - } - -# --- loop over input files ------------------------------------------------------------------------ - if filenames == []: filenames = [None] +if options.labels is None: parser.error('no data column specified.') + for name in filenames: - try: table = damask.ASCIItable(name = name,buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) - table.head_read() - - remarks = [] - errors = [] - active = [] - - coordDim = table.label_dimension(options.pos) - if coordDim != 3: - errors.append('coordinates "{}" must be three-dimensional.'.format(options.pos)) - else: coordCol = table.label_index(options.pos) - - for me in options.data: - dim = table.label_dimension(me) - if dim in datatypes: - active.append(merge_dicts({'label':me},datatypes[dim])) - remarks.append('differentiating {} "{}"...'.format(datatypes[dim]['name'],me)) - else: - remarks.append('skipping "{}" of dimension {}...'.format(me,dim) if dim != -1 else \ - '"{}" not found...'.format(me) ) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - for data in active: - table.labels_append(['{}_gradFFT({})'.format(i+1,data['label']) - for i in range(coordDim*np.prod(np.array(data['shape'])))]) # extend ASCII header with new labels - table.head_write() - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray() - - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) - -# ------------------------------------------ process value field ----------------------------------- - - stack = [table.data] - for data in active: - # we need to reverse order here, because x is fastest,ie rightmost, but leftmost in our x,y,z notation - stack.append(gradFFT(size[::-1], - table.data[:,table.label_indexrange(data['label'])]. - reshape(grid[::-1].tolist()+data['shape']))) - -# ------------------------------------------ output result ----------------------------------------- - - if len(stack) > 1: table.data = np.hstack(tuple(stack)) - table.data_writeArray('%.12g') - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + for label in options.labels: + field = table.get_array(label) + shape = (1,) if np.prod(field.shape)//np.prod(grid) == 1 else (3,) # scalar or vector + field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + table.add_array('gradFFT({})'.format(label), + damask.grid_filters.gradient(size[::-1],field).reshape((-1,np.prod(shape)*3)), + scriptID+' '+' '.join(sys.argv[1:])) + + table.to_ASCII(sys.stdout if name is None else name) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 69ee85033..c7e96f468 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -1,12 +1,8 @@ import numpy as np - -def curl(size,field): - """Calculate curl of a vector or tensor field in Fourier space.""" +def __ks(size,field): + """Get differential operator.""" grid = np.array(np.shape(field)[0:3]) - n = np.array(np.shape(field)[3:]).prod() # data size - - field_fourier = np.fft.rfftn(field,axes=(0,1,2)) k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) @@ -17,64 +13,47 @@ def curl(size,field): k_si = np.arange(grid[2]//2+1)/size[2] kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') + return np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3) + + +def curl(size,field): + """Calculate curl of a vector or tensor field in Fourier space.""" + n = np.prod(field.shape[3:]) + k_s = __ks(size,field) e = np.zeros((3, 3, 3)) e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 + field_fourier = np.fft.rfftn(field,axes=(0,1,2)) curl = (np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 3 np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3x3 - return np.fft.irfftn(curl,axes=(0,1,2)) + return np.fft.irfftn(curl,axes=(0,1,2),s=field.shape[0:3]) def divergence(size,field): """Calculate divergence of a vector or tensor field in Fourier space.""" - grid = np.array(np.shape(field)[0:3]) - n = np.array(np.shape(field)[3:]).prod() # data size + n = np.prod(field.shape[3:]) + k_s = __ks(size,field) - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=grid) + field_fourier = np.fft.rfftn(field,axes=(0,1,2)) + divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 + np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3 - k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] - if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_si = np.arange(grid[2]//2+1)/size[2] - - kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') - - divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 - np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3 - - return np.fft.irfftn(div_fourier,axes=(0,1,2),s=grid) + return np.fft.irfftn(divergence,axes=(0,1,2),s=field.shape[0:3]) def gradient(size,field): """Calculate gradient of a vector or scalar field in Fourier space.""" - grid = np.array(np.shape(field)[2::-1]) - n = np.array(np.shape(field)[3:]).prod() # data size - - field_fourier = np.fft.rfftn(field,axes=(0,1,2),s=grid) - - k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] - if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) - - k_si = np.arange(grid[2]//2+1)/size[2] - - kk, kj, ki = np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3).astype('c16') + n = np.prod(field.shape[3:]) + k_s = __ks(size,field) + field_fourier = np.fft.rfftn(field,axes=(0,1,2)) gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi) # vector, 3 -> 3x3 - return np.fft.irfftn(grad_fourier,axes=(0,1,2),s=grid) + return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[0:3]) #-------------------------------------------------------------------------------------------------- From f2e722ed2e92a9b61c9d18d8430619673ec1192c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 28 Nov 2019 18:22:34 +0100 Subject: [PATCH 010/148] polishing --- python/damask/grid_filters.py | 64 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index c7e96f468..e49ff47a9 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -1,14 +1,14 @@ import numpy as np -def __ks(size,field): - """Get differential operator.""" +def __ks(size,field,first_order=False): + """Get wave numbers operator.""" grid = np.array(np.shape(field)[0:3]) k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] - if grid[0]%2 == 0: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + if grid[0]%2 == 0 and first_order: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) k_sj = np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1]))/size[1] - if grid[1]%2 == 0: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) + if grid[1]%2 == 0 and first_order: k_sj[grid[1]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) k_si = np.arange(grid[2]//2+1)/size[2] @@ -19,7 +19,7 @@ def __ks(size,field): def curl(size,field): """Calculate curl of a vector or tensor field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field) + k_s = __ks(size,field,True) e = np.zeros((3, 3, 3)) e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol @@ -35,7 +35,7 @@ def curl(size,field): def divergence(size,field): """Calculate divergence of a vector or tensor field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field) + k_s = __ks(size,field,True) field_fourier = np.fft.rfftn(field,axes=(0,1,2)) divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 @@ -47,7 +47,7 @@ def divergence(size,field): def gradient(size,field): """Calculate gradient of a vector or scalar field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field) + k_s = __ks(size,field,True) field_fourier = np.fft.rfftn(field,axes=(0,1,2)) gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 @@ -56,29 +56,37 @@ def gradient(size,field): return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[0:3]) -#-------------------------------------------------------------------------------------------------- -def displacementFluctFFT(F,size): - """Calculate displacement field from deformation gradient field.""" - integrator = 0.5j * size / np.pi +def coord_node(grid,size): + """Positions of nodes (undeformed).""" + x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), + np.linspace(0,size[1],1+grid[1]), + np.linspace(0,size[0],1+grid[0]), + indexing = 'ij') + + return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) - kk, kj, ki = np.meshgrid(np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2])), - np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1])), - np.arange(grid[0]//2+1), - indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3) - k_sSquared = np.einsum('...l,...l',k_s,k_s) - k_sSquared[0,0,0] = 1.0 # ignore global average frequency -#-------------------------------------------------------------------------------------------------- -# integration in Fourier space +def coord_cell(grid,size): + """Positions of cell centers (undeformed).""" + delta = size/grid*0.5 + x, y, z = np.meshgrid(np.linspace(delta[2],size[2]-delta[2],grid[2]), + np.linspace(delta[1],size[1]-delta[1],grid[1]), + np.linspace(delta[0],size[0]-delta[0],grid[0]), + indexing = 'ij') - displacement_fourier = -np.einsum('ijkml,ijkl,l->ijkm', - np.fft.rfftn(F,axes=(0,1,2)), - k_s, - integrator, - ) / k_sSquared[...,np.newaxis] + return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) -#-------------------------------------------------------------------------------------------------- -# backtransformation to real space - return np.fft.irfftn(displacement_fourier,grid[::-1],axes=(0,1,2)) +def displacement_fluct(size,F): + """Calculate displacement field from deformation gradient field.""" + integrator = 0.5j * size / np.pi + + k_s = __ks(size,F,False) + + displacement = -np.einsum('ijkml,ijkl,l->ijkm', + np.fft.rfftn(F,axes=(0,1,2)), + k_s, + integrator, + ) / k_sSquared[...,np.newaxis] + + return np.fft.irfftn(displacement,axes=(0,1,2)) From 62ca2952fce464016d721cf3ed2be5344d56a541 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 3 Dec 2019 11:27:14 +0100 Subject: [PATCH 011/148] polishing --- processing/post/addCurl.py | 2 +- processing/post/addDivergence.py | 2 +- processing/post/addGradient.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 2fcd107c0..b3dfedaf4 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -49,7 +49,7 @@ for name in filenames: for label in options.labels: field = table.get_array(label) shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor - field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + field = field.reshape(np.append(grid[::-1],shape)) table.add_array('curlFFT({})'.format(label), damask.grid_filters.curl(size[::-1],field).reshape((-1,np.prod(shape))), scriptID+' '+' '.join(sys.argv[1:])) diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 562ab7532..7ecaf10f0 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -49,7 +49,7 @@ for name in filenames: for label in options.labels: field = table.get_array(label) shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor - field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + field = field.reshape(np.append(grid[::-1],shape)) table.add_array('divFFT({})'.format(label), damask.grid_filters.divergence(size[::-1],field).reshape((-1,np.prod(shape)//3)), scriptID+' '+' '.join(sys.argv[1:])) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index d6b537ddd..d3081db66 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -49,7 +49,7 @@ for name in filenames: for label in options.labels: field = table.get_array(label) shape = (1,) if np.prod(field.shape)//np.prod(grid) == 1 else (3,) # scalar or vector - field = table.get_array(label).reshape(np.append(grid[::-1],shape)) + field = field.reshape(np.append(grid[::-1],shape)) table.add_array('gradFFT({})'.format(label), damask.grid_filters.gradient(size[::-1],field).reshape((-1,np.prod(shape)*3)), scriptID+' '+' '.join(sys.argv[1:])) From e006e0ebec0a6e7a662f3f453040e2aa83d7f844 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 3 Dec 2019 18:59:59 +0100 Subject: [PATCH 012/148] functions for spatial coordinates on regular grids --- python/damask/grid_filters.py | 67 +++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index e49ff47a9..bf757bdb9 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -2,7 +2,7 @@ import numpy as np def __ks(size,field,first_order=False): """Get wave numbers operator.""" - grid = np.array(np.shape(field)[0:3]) + grid = np.array(np.shape(field)[:3]) k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] if grid[0]%2 == 0 and first_order: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) @@ -29,7 +29,7 @@ def curl(size,field): curl = (np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 3 np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3x3 - return np.fft.irfftn(curl,axes=(0,1,2),s=field.shape[0:3]) + return np.fft.irfftn(curl,axes=(0,1,2),s=field.shape[:3]) def divergence(size,field): @@ -41,7 +41,7 @@ def divergence(size,field): divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*np.pi) # tensor, 3x3 -> 3 - return np.fft.irfftn(divergence,axes=(0,1,2),s=field.shape[0:3]) + return np.fft.irfftn(divergence,axes=(0,1,2),s=field.shape[:3]) def gradient(size,field): @@ -53,21 +53,11 @@ def gradient(size,field): gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*np.pi) # vector, 3 -> 3x3 - return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[0:3]) + return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[:3]) -def coord_node(grid,size): - """Positions of nodes (undeformed).""" - x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), - np.linspace(0,size[1],1+grid[1]), - np.linspace(0,size[0],1+grid[0]), - indexing = 'ij') - - return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) - - -def coord_cell(grid,size): - """Positions of cell centers (undeformed).""" +def coord0_cell(grid,size): + """Cell center positions (undeformed).""" delta = size/grid*0.5 x, y, z = np.meshgrid(np.linspace(delta[2],size[2]-delta[2],grid[2]), np.linspace(delta[1],size[1]-delta[1],grid[1]), @@ -76,17 +66,50 @@ def coord_cell(grid,size): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) - -def displacement_fluct(size,F): - """Calculate displacement field from deformation gradient field.""" - integrator = 0.5j * size / np.pi +def displacement_fluct_cell(size,F): + """Cell center displacement field from fluctuation part of the deformation gradient field.""" + integrator = 0.5j*size/np.pi k_s = __ks(size,F,False) + k_s_squared = np.einsum('...l,...l',k_s,k_s) + k_s_squared[0,0,0] = 1.0 displacement = -np.einsum('ijkml,ijkl,l->ijkm', np.fft.rfftn(F,axes=(0,1,2)), k_s, integrator, - ) / k_sSquared[...,np.newaxis] + ) / k_s_squared[...,np.newaxis] - return np.fft.irfftn(displacement,axes=(0,1,2)) + return np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3]) + +def displacement_avg_cell(size,F): + """Cell center displacement field from average part of the deformation gradient field.""" + F_avg = np.average(F,axis=(0,1,2)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_cell(F.shape[:3],size)) + + +def coord0_node(grid,size): + """Nodal positions (undeformed).""" + x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), + np.linspace(0,size[1],1+grid[1]), + np.linspace(0,size[0],1+grid[0]), + indexing = 'ij') + + return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) + +def displacement_fluct_node(size,F): + return cell_2_node(displacement_fluct_cell(size,F)) + +def displacement_avg_node(size,F): + F_avg = np.average(F,axis=(0,1,2)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_node(F.shape[0:3],size)) + + +def cell_2_node(cell_data): + """Interpolate cell data to nodal data.""" + + n = ( cell_data + np.roll(cell_data,1,(0,1,2)) + + np.roll(cell_data,1,(0,)) + np.roll(cell_data,1,(1,)) + np.roll(cell_data,1,(2,)) + + np.roll(cell_data,1,(0,1)) + np.roll(cell_data,1,(1,2)) + np.roll(cell_data,1,(2,0))) *0.125 + + return np.pad(n,((0,1),(0,1),(0,1))+((0,0),)*len(cell_data.shape[3:]),mode='wrap') From 9ad874339647b8271f5b66c5b9dc21554ee853fd Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 3 Dec 2019 19:30:55 +0100 Subject: [PATCH 013/148] using central functionality --- processing/post/addDisplacement.py | 202 ++++------------------------- python/damask/grid_filters.py | 13 +- 2 files changed, 38 insertions(+), 177 deletions(-) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index 99d07fd18..ab25920b5 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -2,10 +2,10 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np -import scipy.ndimage import damask @@ -14,79 +14,6 @@ scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -#-------------------------------------------------------------------------------------------------- -def cell2node(cellData,grid): - - nodeData = 0.0 - datalen = np.array(cellData.shape[3:]).prod() - - for i in range(datalen): - node = scipy.ndimage.convolve(cellData.reshape(tuple(grid[::-1])+(datalen,))[...,i], - np.ones((2,2,2))/8., # 2x2x2 neighborhood of cells - mode = 'wrap', - origin = -1, # offset to have cell origin as center - ) # now averaged at cell origins - node = np.append(node,node[np.newaxis,0,:,:,...],axis=0) # wrap along z - node = np.append(node,node[:,0,np.newaxis,:,...],axis=1) # wrap along y - node = np.append(node,node[:,:,0,np.newaxis,...],axis=2) # wrap along x - - nodeData = node[...,np.newaxis] if i==0 else np.concatenate((nodeData,node[...,np.newaxis]),axis=-1) - - return nodeData - -#-------------------------------------------------------------------------------------------------- -def displacementAvgFFT(F,grid,size,nodal=False,transformed=False): - """Calculate average cell center (or nodal) displacement for deformation gradient field specified in each grid cell""" - if nodal: - x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), - np.linspace(0,size[1],1+grid[1]), - np.linspace(0,size[0],1+grid[0]), - indexing = 'ij') - else: - delta = size/grid*0.5 - x, y, z = np.meshgrid(np.linspace(delta[2],size[2]-delta[2],grid[2]), - np.linspace(delta[1],size[1]-delta[1],grid[1]), - np.linspace(delta[0],size[0]-delta[0],grid[0]), - indexing = 'ij') - - origCoords = np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) - - F_fourier = F if transformed else np.fft.rfftn(F,axes=(0,1,2)) # transform or use provided data - Favg = np.real(F_fourier[0,0,0,:,:])/grid.prod() # take zero freq for average - avgDisplacement = np.einsum('ml,ijkl->ijkm',Favg-np.eye(3),origCoords) # dX = Favg.X - - return avgDisplacement - -#-------------------------------------------------------------------------------------------------- -def displacementFluctFFT(F,grid,size,nodal=False,transformed=False): - """Calculate cell center (or nodal) displacement for deformation gradient field specified in each grid cell""" - integrator = 0.5j * size / np.pi - - kk, kj, ki = np.meshgrid(np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2])), - np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1])), - np.arange(grid[0]//2+1), - indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3) - k_sSquared = np.einsum('...l,...l',k_s,k_s) - k_sSquared[0,0,0] = 1.0 # ignore global average frequency - -#-------------------------------------------------------------------------------------------------- -# integration in Fourier space - - displacement_fourier = -np.einsum('ijkml,ijkl,l->ijkm', - F if transformed else np.fft.rfftn(F,axes=(0,1,2)), - k_s, - integrator, - ) / k_sSquared[...,np.newaxis] - -#-------------------------------------------------------------------------------------------------- -# backtransformation to real space - - displacement = np.fft.irfftn(displacement_fourier,grid[::-1],axes=(0,1,2)) - - return cell2node(displacement,grid) if nodal else displacement - - # -------------------------------------------------------------------- # MAIN # -------------------------------------------------------------------- @@ -100,7 +27,7 @@ Outputs at cell centers or cell nodes (into separate file). parser.add_option('-f', '--defgrad', - dest = 'defgrad', + dest = 'f', metavar = 'string', help = 'label of deformation gradient [%default]') parser.add_option('-p', @@ -113,108 +40,35 @@ parser.add_option('--nodal', action = 'store_true', help = 'output nodal (instead of cell-centered) displacements') -parser.set_defaults(defgrad = 'f', - pos = 'pos', +parser.set_defaults(f = 'f', + pos = 'pos', ) (options,filenames) = parser.parse_args() -# --- loop over input files ------------------------------------------------------------------------- - -if filenames == []: filenames = [None] - for name in filenames: - outname = (os.path.splitext(name)[0] + - '_nodal' + - os.path.splitext(name)[1]) if (options.nodal and name) else None - try: table = damask.ASCIItable(name = name, - outname = outname, - buffered = False) - except: continue - damask.util.report(scriptName,'{}{}'.format(name if name else '', - ' --> {}'.format(outname) if outname else '')) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ - - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - - if table.label_dimension(options.defgrad) != 9: - errors.append('deformation gradient "{}" is not a 3x3 tensor.'.format(options.defgrad)) - - coordDim = table.label_dimension(options.pos) - if not 3 >= coordDim >= 1: - errors.append('coordinates "{}" need to have one, two, or three dimensions.'.format(options.pos)) - elif coordDim < 3: - remarks.append('appending {} dimension{} to coordinates "{}"...'.format(3-coordDim, - 's' if coordDim < 2 else '', - options.pos)) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss=True) - continue - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray([options.defgrad,options.pos]) - table.data_rewind() - - if len(table.data.shape) < 2: table.data.shape += (1,) # expand to 2D shape - if table.data[:,9:].shape[1] < 3: - table.data = np.hstack((table.data, - np.zeros((table.data.shape[0], - 3-table.data[:,9:].shape[1]),dtype='f'))) # fill coords up to 3D with zeros - - grid,size = damask.util.coordGridAndSize(table.data[:,9:12]) - N = grid.prod() - - if N != len(table.data): errors.append('data count {} does not match grid {}x{}x{}.'.format(N,*grid)) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ process data ------------------------------------------ - - F_fourier = np.fft.rfftn(table.data[:,:9].reshape(grid[2],grid[1],grid[0],3,3),axes=(0,1,2)) # perform transform only once... - - fluctDisplacement = displacementFluctFFT(F_fourier,grid,size,options.nodal,transformed=True) - avgDisplacement = displacementAvgFFT (F_fourier,grid,size,options.nodal,transformed=True) - -# ------------------------------------------ assemble header --------------------------------------- - - if options.nodal: - table.info_clear() - table.labels_clear() - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.labels_append((['{}_pos' .format(i+1) for i in range(3)] if options.nodal else []) + - ['{}_avg({}).{}' .format(i+1,options.defgrad,options.pos) for i in range(3)] + - ['{}_fluct({}).{}'.format(i+1,options.defgrad,options.pos) for i in range(3)] ) - table.head_write() - -# ------------------------------------------ output data ------------------------------------------- - - Zrange = np.linspace(0,size[2],1+grid[2]) if options.nodal else range(grid[2]) - Yrange = np.linspace(0,size[1],1+grid[1]) if options.nodal else range(grid[1]) - Xrange = np.linspace(0,size[0],1+grid[0]) if options.nodal else range(grid[0]) - - for i,z in enumerate(Zrange): - for j,y in enumerate(Yrange): - for k,x in enumerate(Xrange): - if options.nodal: table.data_clear() - else: table.data_read() - table.data_append([x,y,z] if options.nodal else []) - table.data_append(list( avgDisplacement[i,j,k,:])) - table.data_append(list(fluctDisplacement[i,j,k,:])) - table.data_write() - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) + + F = table.get_array(options.f).reshape(np.append(grid[::-1],(3,3))) + if options.nodal: + table = damask.Table(damask.grid_filters.coord0_node(grid[::-1],size[::-1]).reshape((-1,3)), + {'pos':(3,)}) + table.add_array('avg({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_avg_node(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + table.add_array('fluct({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_fluct_node(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'_nodal.txt') + else: + table.add_array('avg({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_avg_cell(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + table.add_array('fluct({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_fluct_cell(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + + table.to_ASCII(sys.stdout if name is None else name) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index bf757bdb9..924242c58 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -102,14 +102,21 @@ def displacement_fluct_node(size,F): def displacement_avg_node(size,F): F_avg = np.average(F,axis=(0,1,2)) - return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_node(F.shape[0:3],size)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_node(F.shape[:3],size)) def cell_2_node(cell_data): """Interpolate cell data to nodal data.""" - n = ( cell_data + np.roll(cell_data,1,(0,1,2)) + np.roll(cell_data,1,(0,)) + np.roll(cell_data,1,(1,)) + np.roll(cell_data,1,(2,)) - + np.roll(cell_data,1,(0,1)) + np.roll(cell_data,1,(1,2)) + np.roll(cell_data,1,(2,0))) *0.125 + + np.roll(cell_data,1,(0,1)) + np.roll(cell_data,1,(1,2)) + np.roll(cell_data,1,(2,0)))*0.125 return np.pad(n,((0,1),(0,1),(0,1))+((0,0),)*len(cell_data.shape[3:]),mode='wrap') + +def node_2_cell(node_data): + """Interpolate nodal data to cell data.""" + c = ( node_data + np.roll(node_data,1,(0,1,2)) + + np.roll(node_data,1,(0,)) + np.roll(node_data,1,(1,)) + np.roll(node_data,1,(2,)) + + np.roll(node_data,1,(0,1)) + np.roll(node_data,1,(1,2)) + np.roll(node_data,1,(2,0)))*0.125 + + return c[:-1,:-1,:-1] From bc41bbbec52376d2a7b02449def6b0ffe6140c05 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 4 Dec 2019 09:23:08 +0100 Subject: [PATCH 014/148] test coordinates-related functions --- python/tests/test_grid_filters.py | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 python/tests/test_grid_filters.py diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py new file mode 100644 index 000000000..89b256dcc --- /dev/null +++ b/python/tests/test_grid_filters.py @@ -0,0 +1,42 @@ +import pytest +import numpy as np + +from damask import grid_filters + +class TestGridFilters: + + def test_coord0_cell(self): + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + coord = grid_filters.coord0_cell(grid,size) + assert np.allclose(coord[0,0,0],size/grid*.5) and coord.shape == tuple(grid[::-1]) + (3,) + + def test_coord0_node(self): + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + coord = grid_filters.coord0_node(grid,size) + assert np.allclose(coord[-1,-1,-1],size) and coord.shape == tuple(grid[::-1]+1) + (3,) + + def test_coord0(self): + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + c = grid_filters.coord0_cell(grid+1,size+size/grid) + n = grid_filters.coord0_node(grid,size) + size/grid*.5 + assert np.allclose(c,n) + + @pytest.mark.parametrize('mode',[('cell'),('node')]) + def test_displacement_avg_vanishes(self,mode): + """Ensure that random fluctuations in F do not result in average displacement.""" + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + F = np.random.random(tuple(grid)+(3,3)) + F += np.eye(3) - np.average(F,axis=(0,1,2)) + assert np.allclose(eval('grid_filters.displacement_avg_{}(size,F)'.format(mode)),0.0) + + @pytest.mark.parametrize('mode',[('cell'),('node')]) + def test_displacement_fluct_vanishes(self,mode): + """Ensure that constant F does not result in fluctuating displacement.""" + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + F = np.broadcast_to(np.random.random((3,3)), tuple(grid)+(3,3)) + assert np.allclose(eval('grid_filters.displacement_fluct_{}(size,F)'.format(mode)),0.0) From 381c95bd1e6c65600cb1d793942993c703085a9e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 4 Dec 2019 10:17:55 +0100 Subject: [PATCH 015/148] WIP: regrid functionality --- python/damask/grid_filters.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 924242c58..455e93aba 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -1,3 +1,4 @@ +from scipy import spatial import numpy as np def __ks(size,field,first_order=False): @@ -120,3 +121,18 @@ def node_2_cell(node_data): + np.roll(node_data,1,(0,1)) + np.roll(node_data,1,(1,2)) + np.roll(node_data,1,(2,0)))*0.125 return c[:-1,:-1,:-1] + +def regrid(size,F,new_grid): + """tbd.""" + c = coord0_cell(F.shape[:3][::-1],size) \ + + displacement_avg_cell(size,F) \ + + displacement_fluct_cell(size,F) + + outer = np.dot(np.average(F,axis=(0,1,2)),size) + for d in range(3): + c[np.where(c[:,:,:,d]<0)] += outer[d] + c[np.where(c[:,:,:,d]>outer[d])] -= outer[d] + + tree = spatial.cKDTree(c.reshape((-1,3)),boxsize=outer) + d,i = tree.query(coord0_cell(new_grid,outer)) + return i From e7358746dca1e8ab5f885374488121050b2ab469 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 5 Dec 2019 10:54:35 +0100 Subject: [PATCH 016/148] taking care of prospector complaints variables in 'eval' are hidden --- python/tests/test_grid_filters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index 89b256dcc..e816e0380 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -27,7 +27,7 @@ class TestGridFilters: @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_avg_vanishes(self,mode): """Ensure that random fluctuations in F do not result in average displacement.""" - size = np.random.random(3) + size = np.random.random(3) # noqa grid = np.random.randint(8,32,(3)) F = np.random.random(tuple(grid)+(3,3)) F += np.eye(3) - np.average(F,axis=(0,1,2)) @@ -36,7 +36,7 @@ class TestGridFilters: @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_fluct_vanishes(self,mode): """Ensure that constant F does not result in fluctuating displacement.""" - size = np.random.random(3) + size = np.random.random(3) # noqa grid = np.random.randint(8,32,(3)) - F = np.broadcast_to(np.random.random((3,3)), tuple(grid)+(3,3)) + F = np.broadcast_to(np.random.random((3,3)), tuple(grid)+(3,3)) # noqa assert np.allclose(eval('grid_filters.displacement_fluct_{}(size,F)'.format(mode)),0.0) From f475d1a0d021912c080f652a72f9c32e50da8b95 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 5 Dec 2019 13:35:06 +0100 Subject: [PATCH 017/148] adjusted to changes in table class --- processing/post/addCurl.py | 8 ++++---- processing/post/addDisplacement.py | 27 +++++++++++++-------------- processing/post/addDivergence.py | 8 ++++---- processing/post/addGradient.py | 8 ++++---- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index b3dfedaf4..6d4201cf3 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -47,11 +47,11 @@ for name in filenames: grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) for label in options.labels: - field = table.get_array(label) + field = table.get(label) shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor field = field.reshape(np.append(grid[::-1],shape)) - table.add_array('curlFFT({})'.format(label), - damask.grid_filters.curl(size[::-1],field).reshape((-1,np.prod(shape))), - scriptID+' '+' '.join(sys.argv[1:])) + table.add('curlFFT({})'.format(label), + damask.grid_filters.curl(size[::-1],field).reshape((-1,np.prod(shape))), + scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index ab25920b5..9c0e29fc5 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -52,23 +52,22 @@ for name in filenames: table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) - F = table.get_array(options.f).reshape(np.append(grid[::-1],(3,3))) + F = table.get(options.f).reshape(np.append(grid[::-1],(3,3))) if options.nodal: table = damask.Table(damask.grid_filters.coord0_node(grid[::-1],size[::-1]).reshape((-1,3)), {'pos':(3,)}) - table.add_array('avg({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_avg_node(size[::-1],F).reshape((-1,3)), - scriptID+' '+' '.join(sys.argv[1:])) - table.add_array('fluct({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_fluct_node(size[::-1],F).reshape((-1,3)), - scriptID+' '+' '.join(sys.argv[1:])) + table.add('avg({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_avg_node(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + table.add('fluct({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_fluct_node(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'_nodal.txt') else: - table.add_array('avg({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_avg_cell(size[::-1],F).reshape((-1,3)), - scriptID+' '+' '.join(sys.argv[1:])) - table.add_array('fluct({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_fluct_cell(size[::-1],F).reshape((-1,3)), - scriptID+' '+' '.join(sys.argv[1:])) - + table.add('avg({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_avg_cell(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) + table.add('fluct({}).{}'.format(options.f,options.pos), + damask.grid_filters.displacement_fluct_cell(size[::-1],F).reshape((-1,3)), + scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 7ecaf10f0..8c6f181f7 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -47,11 +47,11 @@ for name in filenames: grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) for label in options.labels: - field = table.get_array(label) + field = table.get(label) shape = (3,) if np.prod(field.shape)//np.prod(grid) == 3 else (3,3) # vector or tensor field = field.reshape(np.append(grid[::-1],shape)) - table.add_array('divFFT({})'.format(label), - damask.grid_filters.divergence(size[::-1],field).reshape((-1,np.prod(shape)//3)), - scriptID+' '+' '.join(sys.argv[1:])) + table.add('divFFT({})'.format(label), + damask.grid_filters.divergence(size[::-1],field).reshape((-1,np.prod(shape)//3)), + scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index d3081db66..f7f800c24 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -47,11 +47,11 @@ for name in filenames: grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) for label in options.labels: - field = table.get_array(label) + field = table.get(label) shape = (1,) if np.prod(field.shape)//np.prod(grid) == 1 else (3,) # scalar or vector field = field.reshape(np.append(grid[::-1],shape)) - table.add_array('gradFFT({})'.format(label), - damask.grid_filters.gradient(size[::-1],field).reshape((-1,np.prod(shape)*3)), - scriptID+' '+' '.join(sys.argv[1:])) + table.add('gradFFT({})'.format(label), + damask.grid_filters.gradient(size[::-1],field).reshape((-1,np.prod(shape)*3)), + scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else name) From 4ddfd82304678f79d345d4535035a422254b2adf Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 5 Dec 2019 18:32:21 +0100 Subject: [PATCH 018/148] better names --- python/damask/grid_filters.py | 26 +++++++++++++------------- python/tests/test_grid_filters.py | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 455e93aba..149d52194 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -57,7 +57,7 @@ def gradient(size,field): return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[:3]) -def coord0_cell(grid,size): +def cell_coord0(grid,size): """Cell center positions (undeformed).""" delta = size/grid*0.5 x, y, z = np.meshgrid(np.linspace(delta[2],size[2]-delta[2],grid[2]), @@ -67,7 +67,7 @@ def coord0_cell(grid,size): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) -def displacement_fluct_cell(size,F): +def cell_displacement_fluct(size,F): """Cell center displacement field from fluctuation part of the deformation gradient field.""" integrator = 0.5j*size/np.pi @@ -83,13 +83,13 @@ def displacement_fluct_cell(size,F): return np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3]) -def displacement_avg_cell(size,F): +def cell_displacement_avg(size,F): """Cell center displacement field from average part of the deformation gradient field.""" F_avg = np.average(F,axis=(0,1,2)) - return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_cell(F.shape[:3],size)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3],size)) -def coord0_node(grid,size): +def node_coord0(grid,size): """Nodal positions (undeformed).""" x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), np.linspace(0,size[1],1+grid[1]), @@ -98,12 +98,12 @@ def coord0_node(grid,size): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) -def displacement_fluct_node(size,F): - return cell_2_node(displacement_fluct_cell(size,F)) +def node_displacement_fluct(size,F): + return cell_2_node(cell_displacement_fluct(size,F)) -def displacement_avg_node(size,F): +def node_displacement_avg(size,F): F_avg = np.average(F,axis=(0,1,2)) - return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),coord0_node(F.shape[:3],size)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3],size)) def cell_2_node(cell_data): @@ -124,9 +124,9 @@ def node_2_cell(node_data): def regrid(size,F,new_grid): """tbd.""" - c = coord0_cell(F.shape[:3][::-1],size) \ - + displacement_avg_cell(size,F) \ - + displacement_fluct_cell(size,F) + c = cell_coord0(F.shape[:3][::-1],size) \ + + cell_displacement_avg(size,F) \ + + cell_displacement_fluct(size,F) outer = np.dot(np.average(F,axis=(0,1,2)),size) for d in range(3): @@ -134,5 +134,5 @@ def regrid(size,F,new_grid): c[np.where(c[:,:,:,d]>outer[d])] -= outer[d] tree = spatial.cKDTree(c.reshape((-1,3)),boxsize=outer) - d,i = tree.query(coord0_cell(new_grid,outer)) + d,i = tree.query(cell_coord0(new_grid,outer)) return i diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index e816e0380..4dff41542 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -5,23 +5,23 @@ from damask import grid_filters class TestGridFilters: - def test_coord0_cell(self): + def test_cell_coord0(self): size = np.random.random(3) grid = np.random.randint(8,32,(3)) - coord = grid_filters.coord0_cell(grid,size) + coord = grid_filters.cell_coord0(grid,size) assert np.allclose(coord[0,0,0],size/grid*.5) and coord.shape == tuple(grid[::-1]) + (3,) - def test_coord0_node(self): + def test_node_coord0(self): size = np.random.random(3) grid = np.random.randint(8,32,(3)) - coord = grid_filters.coord0_node(grid,size) + coord = grid_filters.node_coord0(grid,size) assert np.allclose(coord[-1,-1,-1],size) and coord.shape == tuple(grid[::-1]+1) + (3,) def test_coord0(self): size = np.random.random(3) grid = np.random.randint(8,32,(3)) - c = grid_filters.coord0_cell(grid+1,size+size/grid) - n = grid_filters.coord0_node(grid,size) + size/grid*.5 + c = grid_filters.cell_coord0(grid+1,size+size/grid) + n = grid_filters.node_coord0(grid,size) + size/grid*.5 assert np.allclose(c,n) @pytest.mark.parametrize('mode',[('cell'),('node')]) @@ -31,7 +31,7 @@ class TestGridFilters: grid = np.random.randint(8,32,(3)) F = np.random.random(tuple(grid)+(3,3)) F += np.eye(3) - np.average(F,axis=(0,1,2)) - assert np.allclose(eval('grid_filters.displacement_avg_{}(size,F)'.format(mode)),0.0) + assert np.allclose(eval('grid_filters.{}_displacement_avg(size,F)'.format(mode)),0.0) @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_fluct_vanishes(self,mode): @@ -39,4 +39,4 @@ class TestGridFilters: size = np.random.random(3) # noqa grid = np.random.randint(8,32,(3)) F = np.broadcast_to(np.random.random((3,3)), tuple(grid)+(3,3)) # noqa - assert np.allclose(eval('grid_filters.displacement_fluct_{}(size,F)'.format(mode)),0.0) + assert np.allclose(eval('grid_filters.{}_displacement_fluct(size,F)'.format(mode)),0.0) From f2ac87eb2f60e27881217f1586508c7df6904055 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 6 Dec 2019 04:22:18 +0100 Subject: [PATCH 019/148] follow changes in Table class --- processing/post/addCurl.py | 2 +- processing/post/addDisplacement.py | 2 +- processing/post/addDivergence.py | 2 +- processing/post/addGradient.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 6d4201cf3..010d24935 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) + grid,size = damask.util.coordGridAndSize(table.get(options.pos)) for label in options.labels: field = table.get(label) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index 9c0e29fc5..f85c2a914 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -50,7 +50,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) + grid,size = damask.util.coordGridAndSize(table.get(options.pos)) F = table.get(options.f).reshape(np.append(grid[::-1],(3,3))) if options.nodal: diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 8c6f181f7..d2d68ede6 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) + grid,size = damask.util.coordGridAndSize(table.get(options.pos)) for label in options.labels: field = table.get(label) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index f7f800c24..ed9397f02 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get_array(options.pos)) + grid,size = damask.util.coordGridAndSize(table.get(options.pos)) for label in options.labels: field = table.get(label) From 3aab154cdbef90ae4ba43657b336290a240b9d03 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 7 Dec 2019 10:55:06 +0100 Subject: [PATCH 020/148] include test for addDisplacement --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 524e86c11..806e1b32a 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 524e86c117d816e3bd873eed7663e258a6f2e139 +Subproject commit 806e1b32a17bf26c987f417116476a295d3936cd From e283acd6069d7f729f38bc835f2c33a92fd7986e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 7 Dec 2019 20:07:46 +0100 Subject: [PATCH 021/148] correct way of importing for newer python versions --- processing/post/addCalculation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processing/post/addCalculation.py b/processing/post/addCalculation.py index db0428753..6effc2489 100755 --- a/processing/post/addCalculation.py +++ b/processing/post/addCalculation.py @@ -4,7 +4,7 @@ import os import sys from optparse import OptionParser import re -import collections +from collections.abc import Iterable import math # noqa import scipy # noqa @@ -18,7 +18,7 @@ scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) def listify(x): - return x if isinstance(x, collections.Iterable) else [x] + return x if isinstance(x, Iterable) else [x] # -------------------------------------------------------------------- From c6c77b64d2b7d5347e90c5d334fec321cb7bee9f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 7 Dec 2019 20:08:31 +0100 Subject: [PATCH 022/148] following renames in grid_filter --- processing/post/addDisplacement.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index f85c2a914..4f0323504 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -54,20 +54,20 @@ for name in filenames: F = table.get(options.f).reshape(np.append(grid[::-1],(3,3))) if options.nodal: - table = damask.Table(damask.grid_filters.coord0_node(grid[::-1],size[::-1]).reshape((-1,3)), + table = damask.Table(damask.grid_filters.node_coord0(grid[::-1],size[::-1]).reshape((-1,3)), {'pos':(3,)}) table.add('avg({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_avg_node(size[::-1],F).reshape((-1,3)), + damask.grid_filters.node_displacement_avg(size[::-1],F).reshape((-1,3)), scriptID+' '+' '.join(sys.argv[1:])) table.add('fluct({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_fluct_node(size[::-1],F).reshape((-1,3)), + damask.grid_filters.node_displacement_fluct(size[::-1],F).reshape((-1,3)), scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'_nodal.txt') else: table.add('avg({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_avg_cell(size[::-1],F).reshape((-1,3)), + damask.grid_filters.cell_displacement_avg(size[::-1],F).reshape((-1,3)), scriptID+' '+' '.join(sys.argv[1:])) table.add('fluct({}).{}'.format(options.f,options.pos), - damask.grid_filters.displacement_fluct_cell(size[::-1],F).reshape((-1,3)), + damask.grid_filters.cell_displacement_fluct(size[::-1],F).reshape((-1,3)), scriptID+' '+' '.join(sys.argv[1:])) table.to_ASCII(sys.stdout if name is None else name) From b92cfbbd5bce653ec0bf0a5a0518f1c87bbfb60d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 7 Dec 2019 20:15:50 +0100 Subject: [PATCH 023/148] do not use bare except --- processing/post/addCalculation.py | 7 ++++--- processing/post/addCumulative.py | 6 +++--- processing/post/addOrientations.py | 7 ++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/processing/post/addCalculation.py b/processing/post/addCalculation.py index 6effc2489..b1eed3c6d 100755 --- a/processing/post/addCalculation.py +++ b/processing/post/addCalculation.py @@ -65,9 +65,10 @@ for i in range(len(options.formulas)): if filenames == []: filenames = [None] for name in filenames: - try: table = damask.ASCIItable(name = name, - buffered = False) - except: continue + try: + table = damask.ASCIItable(name = name, buffered = False) + except IOError: + continue damask.util.report(scriptName,name) # ------------------------------------------ read header ------------------------------------------- diff --git a/processing/post/addCumulative.py b/processing/post/addCumulative.py index b81a9d14f..c94737b94 100755 --- a/processing/post/addCumulative.py +++ b/processing/post/addCumulative.py @@ -41,9 +41,9 @@ if filenames == []: filenames = [None] for name in filenames: try: - table = damask.ASCIItable(name = name, - buffered = False) - except IOError: continue + table = damask.ASCIItable(name = name, buffered = False) + except IOError: + continue damask.util.report(scriptName,name) # ------------------------------------------ read header ------------------------------------------ diff --git a/processing/post/addOrientations.py b/processing/post/addOrientations.py index 31ce6aeb3..2c46ee5ee 100755 --- a/processing/post/addOrientations.py +++ b/processing/post/addOrientations.py @@ -125,9 +125,10 @@ R = damask.Rotation.fromAxisAngle(np.array(options.labrotation),options.degrees, if filenames == []: filenames = [None] for name in filenames: - try: table = damask.ASCIItable(name = name, - buffered = False) - except Exception: continue + try: + table = damask.ASCIItable(name = name, buffered = False) + except IOError: + continue damask.util.report(scriptName,name) # ------------------------------------------ read header ------------------------------------------ From ba69f5a631735075b372618503b0c31eed069a8e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 7 Dec 2019 22:33:31 +0100 Subject: [PATCH 024/148] polishing --- python/damask/grid_filters.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 149d52194..006167ccc 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -99,9 +99,11 @@ def node_coord0(grid,size): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) def node_displacement_fluct(size,F): + """Nodal displacement field from fluctuation part of the deformation gradient field.""" return cell_2_node(cell_displacement_fluct(size,F)) def node_displacement_avg(size,F): + """Nodal displacement field from average part of the deformation gradient field.""" F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3],size)) @@ -134,5 +136,4 @@ def regrid(size,F,new_grid): c[np.where(c[:,:,:,d]>outer[d])] -= outer[d] tree = spatial.cKDTree(c.reshape((-1,3)),boxsize=outer) - d,i = tree.query(cell_coord0(new_grid,outer)) - return i + return tree.query(cell_coord0(new_grid,outer))[1] From 9dc726ff53bf43e8e33f95d263e2e33bdbfe0524 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 09:17:57 +0100 Subject: [PATCH 025/148] polishing --- python/damask/geom.py | 3 +++ python/damask/table.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python/damask/geom.py b/python/damask/geom.py index 32ea2ed89..63ce20115 100644 --- a/python/damask/geom.py +++ b/python/damask/geom.py @@ -205,6 +205,9 @@ class Geom(): else: self.homogenization = homogenization + @property + def grid(self): + return self.get_grid() def get_microstructure(self): """Return the microstructure representation.""" diff --git a/python/damask/table.py b/python/damask/table.py index 6181fdb1f..d063599ab 100644 --- a/python/damask/table.py +++ b/python/damask/table.py @@ -97,7 +97,6 @@ class Table(): @property def labels(self): - """Return the labels of all columns.""" return list(self.shapes.keys()) From 12564557e65e83e6510ed626119d9a023220f0ee Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 09:18:15 +0100 Subject: [PATCH 026/148] using central functionality --- processing/pre/seeds_fromGeom.py | 106 +++++++++---------------------- 1 file changed, 29 insertions(+), 77 deletions(-) diff --git a/processing/pre/seeds_fromGeom.py b/processing/pre/seeds_fromGeom.py index 889ef6146..c543cb878 100755 --- a/processing/pre/seeds_fromGeom.py +++ b/processing/pre/seeds_fromGeom.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 -# -*- coding: UTF-8 no BOM -*- -import os,sys -import numpy as np +import os +import sys +from io import StringIO from optparse import OptionParser + +import numpy as np + import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] @@ -29,88 +32,37 @@ parser.add_option('-b', action = 'extend', metavar = '', dest = 'blacklist', help = 'blacklist of grain IDs') -parser.add_option('-p', - '--pos', '--seedposition', - dest = 'pos', - type = 'string', metavar = 'string', - help = 'label of coordinates [%default]') parser.set_defaults(whitelist = [], blacklist = [], - pos = 'pos', ) (options,filenames) = parser.parse_args() - -options.whitelist = list(map(int,options.whitelist)) -options.blacklist = list(map(int,options.blacklist)) - -# --- loop over output files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] +options.whitelist = [int(i) for i in options.whitelist] +options.blacklist = [int(i) for i in options.blacklist] + for name in filenames: - try: table = damask.ASCIItable(name = name, - outname = os.path.splitext(name)[0]+'.seeds' if name else name, - buffered = False, - labeled = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) + + geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) + microstructure = geom.get_microstructure().reshape((-1,1),order='F') -# --- interpret header ---------------------------------------------------------------------------- + mask = np.logical_and(np.in1d(microstructure,options.whitelist,invert=False) if options.whitelist else \ + np.full(geom.grid.prod(),True,dtype=bool), + np.in1d(microstructure,options.blacklist,invert=True) if options.blacklist else \ + np.full(geom.grid.prod(),True,dtype=bool)) + + seeds = np.concatenate((damask.grid_filters.cell_coord0(geom.grid,geom.size).reshape((-1,3)), + microstructure), + axis=1)[mask] + + comments = [scriptID + ' ' + ' '.join(sys.argv[1:]), + "grid\ta {}\tb {}\tc {}".format(*geom.grid), + "size\tx {}\ty {}\tz {}".format(*geom.size), + "origin\tx {}\ty {}\tz {}".format(*geom.origin), + "homogenization\t{}".format(geom.homogenization)] - table.head_read() - info,extra_header = table.head_getGeom() - damask.util.report_geom(info) - - errors = [] - if np.any(info['grid'] < 1): errors.append('invalid grid a b c.') - if np.any(info['size'] <= 0.0): errors.append('invalid size x y z.') - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# --- read data ------------------------------------------------------------------------------------ - - microstructure = table.microstructure_read(info['grid']) # read (linear) microstructure - -# --- generate grid -------------------------------------------------------------------------------- - - x = (0.5 + np.arange(info['grid'][0],dtype=float))/info['grid'][0]*info['size'][0]+info['origin'][0] - y = (0.5 + np.arange(info['grid'][1],dtype=float))/info['grid'][1]*info['size'][1]+info['origin'][1] - z = (0.5 + np.arange(info['grid'][2],dtype=float))/info['grid'][2]*info['size'][2]+info['origin'][2] - - xx = np.tile( x, info['grid'][1]* info['grid'][2]) - yy = np.tile(np.repeat(y,info['grid'][0] ),info['grid'][2]) - zz = np.repeat(z,info['grid'][0]*info['grid'][1]) - - mask = np.logical_and(np.in1d(microstructure,options.whitelist,invert=False) if options.whitelist != [] - else np.full_like(microstructure,True,dtype=bool), - np.in1d(microstructure,options.blacklist,invert=True ) if options.blacklist != [] - else np.full_like(microstructure,True,dtype=bool)) - -# ------------------------------------------ assemble header --------------------------------------- - - table.info_clear() - table.info_append(extra_header+[ - scriptID + ' ' + ' '.join(sys.argv[1:]), - "grid\ta {}\tb {}\tc {}".format(*info['grid']), - "size\tx {}\ty {}\tz {}".format(*info['size']), - "origin\tx {}\ty {}\tz {}".format(*info['origin']), - "homogenization\t{}".format(info['homogenization']), - "microstructures\t{}".format(info['microstructures']), - ]) - table.labels_clear() - table.labels_append(['{dim}_{label}'.format(dim = 1+i,label = options.pos) for i in range(3)]+['microstructure']) - table.head_write() - table.output_flush() - -# --- write seeds information ------------------------------------------------------------ - - table.data = np.squeeze(np.dstack((xx,yy,zz,microstructure)))[mask] - table.data_writeArray() - -# ------------------------------------------ finalize output --------------------------------------- - - table.close() + table = damask.Table(seeds,{'pos':(3,),'microstructure':(1,)},comments) + table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'.seeds') From 871ff4c218363649be2ad307e2144698c6cb5d5b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 09:31:56 +0100 Subject: [PATCH 027/148] use geom class --- processing/pre/mentat_spectralBox.py | 101 ++++++++++----------------- 1 file changed, 35 insertions(+), 66 deletions(-) diff --git a/processing/pre/mentat_spectralBox.py b/processing/pre/mentat_spectralBox.py index 89f4a7a43..f62622a6b 100755 --- a/processing/pre/mentat_spectralBox.py +++ b/processing/pre/mentat_spectralBox.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -# -*- coding: UTF-8 no BOM -*- -import os,sys -import numpy as np +import os +import sys from optparse import OptionParser + +import numpy as np + import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] @@ -191,78 +193,45 @@ parser.add_option('-p', '--port', dest = 'port', type = 'int', metavar = 'int', help = 'Mentat connection port [%default]') -parser.add_option('--homogenization', - dest = 'homogenization', - type = 'int', metavar = 'int', - help = 'homogenization index to be used [auto]') -parser.set_defaults(port = None, - homogenization = None, -) +parser.set_defaults(port = None, + ) (options, filenames) = parser.parse_args() -if options.port: - try: - import py_mentat - except: - parser.error('no valid Mentat release found.') +if options.port is not None: + try: + import py_mentat + except ImportError: + parser.error('no valid Mentat release found.') # --- loop over input files ------------------------------------------------------------------------ if filenames == []: filenames = [None] for name in filenames: - try: - table = damask.ASCIItable(name = name, - outname = os.path.splitext(name)[0]+'.proc' if name else name, - buffered = False, labeled = False) - except: continue - damask.util.report(scriptName,name) - -# --- interpret header ---------------------------------------------------------------------------- - - table.head_read() - info,extra_header = table.head_getGeom() - if options.homogenization: info['homogenization'] = options.homogenization + damask.util.report(scriptName,name) - damask.util.croak(['grid a b c: %s'%(' x '.join(map(str,info['grid']))), - 'size x y z: %s'%(' x '.join(map(str,info['size']))), - 'origin x y z: %s'%(' : '.join(map(str,info['origin']))), - 'homogenization: %i'%info['homogenization'], - 'microstructures: %i'%info['microstructures'], - ]) + geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) + microstructure = geom.get_microstructure().flatten(order='F') - errors = [] - if np.any(info['grid'] < 1): errors.append('invalid grid a b c.') - if np.any(info['size'] <= 0.0): errors.append('invalid size x y z.') - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# --- read data ------------------------------------------------------------------------------------ - - microstructure = table.microstructure_read(info['grid']).reshape(info['grid'].prod(),order='F') # read microstructure - - cmds = [\ - init(), - mesh(info['grid'],info['size']), - material(), - geometry(), - initial_conditions(info['homogenization'],microstructure), - '*identify_sets', - '*show_model', - '*redraw', - '*draw_automatic', - ] - - outputLocals = {} - if options.port: - py_mentat.py_connect('',options.port) - output(cmds,outputLocals,'Mentat') - py_mentat.py_disconnect() - else: - output(cmds,outputLocals,table.__IO__['out']) # bad hack into internals of table class... - - table.close() + cmds = [\ + init(), + mesh(geom.grid,geom.size), + material(), + geometry(), + initial_conditions(geom.homogenization,microstructure), + '*identify_sets', + '*show_model', + '*redraw', + '*draw_automatic', + ] + + outputLocals = {} + if options.port: + py_mentat.py_connect('',options.port) + output(cmds,outputLocals,'Mentat') + py_mentat.py_disconnect() + else: + with sys.stdout if name is None else open(os.path.splitext(name)[0]+'.proc','w') as f: + output(cmds,outputLocals,f) From 0292e8fcc78ff7c87036885192ed38a7e61758bf Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 11:00:38 +0100 Subject: [PATCH 028/148] preparing transition to Geom and Table classes --- processing/pre/seeds_fromPokes.py | 100 ++++++++++-------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/processing/pre/seeds_fromPokes.py b/processing/pre/seeds_fromPokes.py index 08e600ffe..4a6edc94d 100755 --- a/processing/pre/seeds_fromPokes.py +++ b/processing/pre/seeds_fromPokes.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 -# -*- coding: UTF-8 no BOM -*- -import os,math,sys -import numpy as np -import damask +import os +import sys from optparse import OptionParser +import numpy as np + +import damask + scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) @@ -35,109 +37,73 @@ parser.add_option('-y', action = 'store_true', dest = 'y', help = 'poke 45 deg along y') -parser.add_option('-p','--position', - dest = 'position', - type = 'string', metavar = 'string', - help = 'column label for coordinates [%default]') parser.set_defaults(x = False, y = False, box = [0.0,1.0,0.0,1.0,0.0,1.0], N = 16, - position = 'pos', ) (options,filenames) = parser.parse_args() +if filenames == []: filenames = [None] options.box = np.array(options.box).reshape(3,2) -# --- loop over output files ------------------------------------------------------------------------- - -if filenames == []: filenames = [None] - for name in filenames: - try: - table = damask.ASCIItable(name = name, - outname = os.path.splitext(name)[-2]+'_poked_{}.seeds'.format(options.N) if name else name, - buffered = False, labeled = False) - except: continue + table = damask.ASCIItable(name = name, + outname = os.path.splitext(name)[-2]+'_poked_{}.seeds'.format(options.N) if name else name, + buffered = False, labeled = False) damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- table.head_read() info,extra_header = table.head_getGeom() - - damask.util.croak(['grid a b c: %s'%(' x '.join(map(str,info['grid']))), - 'size x y z: %s'%(' x '.join(map(str,info['size']))), - 'origin x y z: %s'%(' : '.join(map(str,info['origin']))), - 'homogenization: %i'%info['homogenization'], - 'microstructures: %i'%info['microstructures'], - ]) - - errors = [] - if np.any(info['grid'] < 1): errors.append('invalid grid a b c.') - if np.any(info['size'] <= 0.0): errors.append('invalid size x y z.') - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# --- read data ------------------------------------------------------------------------------------ - - microstructure = table.microstructure_read(info['grid']).reshape(info['grid'],order='F') # read microstructure - -# --- do work ------------------------------------------------------------------------------------ - newInfo = { - 'microstructures': 0, - } - offset = (np.amin(options.box, axis=1)*info['grid']/info['size']).astype(int) - box = np.amax(options.box, axis=1) - np.amin(options.box, axis=1) + grid = info['grid'] + size = info['size'] - Nx = int(options.N/math.sqrt(options.N*info['size'][1]*box[1]/info['size'][0]/box[0])) - Ny = int(options.N/math.sqrt(options.N*info['size'][0]*box[0]/info['size'][1]/box[1])) - Nz = int(box[2]*info['grid'][2]) + microstructure = table.microstructure_read(grid).reshape(grid) # read microstructure + + offset =(np.amin(options.box, axis=1)*grid/size).astype(int) + box = np.amax(options.box, axis=1) \ + - np.amin(options.box, axis=1) + + Nx = int(options.N/np.sqrt(options.N*size[1]*box[1]/size[0]/box[0])) + Ny = int(options.N/np.sqrt(options.N*size[0]*box[0]/size[1]/box[1])) + Nz = int(box[2]*grid[2]) damask.util.croak('poking {} x {} x {} in box {} {} {}...'.format(Nx,Ny,Nz,*box)) seeds = np.zeros((Nx*Ny*Nz,4),'d') - grid = np.zeros(3,'i') + g = np.zeros(3,'i') n = 0 for i in range(Nx): for j in range(Ny): - grid[0] = round((i+0.5)*box[0]*info['grid'][0]/Nx-0.5)+offset[0] - grid[1] = round((j+0.5)*box[1]*info['grid'][1]/Ny-0.5)+offset[1] + g[0] = round((i+0.5)*box[0]*grid[0]/Nx-0.5)+offset[0] + g[1] = round((j+0.5)*box[1]*grid[1]/Ny-0.5)+offset[1] for k in range(Nz): - grid[2] = k + offset[2] - grid %= info['grid'] - seeds[n,0:3] = (0.5+grid)/info['grid'] # normalize coordinates to box - seeds[n, 3] = microstructure[grid[0],grid[1],grid[2]] - if options.x: grid[0] += 1 - if options.y: grid[1] += 1 + g[2] = k + offset[2] + g %= grid + seeds[n,0:3] = (g+0.5)/grid # normalize coordinates to box + seeds[n, 3] = microstructure[g[2],g[1],g[0]] + if options.x: g[0] += 1 + if options.y: g[1] += 1 n += 1 - newInfo['microstructures'] = len(np.unique(seeds[:,3])) - -# --- report --------------------------------------------------------------------------------------- - if (newInfo['microstructures'] != info['microstructures']): - damask.util.croak('--> microstructures: %i'%newInfo['microstructures']) - # ------------------------------------------ assemble header --------------------------------------- table.info_clear() table.info_append(extra_header+[ scriptID + ' ' + ' '.join(sys.argv[1:]), "poking\ta {}\tb {}\tc {}".format(Nx,Ny,Nz), - "grid\ta {}\tb {}\tc {}".format(*info['grid']), - "size\tx {}\ty {}\tz {}".format(*info['size']), + "grid\ta {}\tb {}\tc {}".format(*grid), + "size\tx {}\ty {}\tz {}".format(*size), "origin\tx {}\ty {}\tz {}".format(*info['origin']), "homogenization\t{}".format(info['homogenization']), - "microstructures\t{}".format(newInfo['microstructures']), ]) table.labels_clear() - table.labels_append(['{dim}_{label}'.format(dim = 1+i,label = options.position) for i in range(3)]+['microstructure']) + table.labels_append(['{dim}_{label}'.format(dim = 1+i,label = 'pos') for i in range(3)]+['microstructure']) table.head_write() table.output_flush() From f19694f734c401e4076af1294951d2ef140c45d2 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 11:20:47 +0100 Subject: [PATCH 029/148] starting to use central functionality --- processing/pre/seeds_fromPokes.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/processing/pre/seeds_fromPokes.py b/processing/pre/seeds_fromPokes.py index 4a6edc94d..2c359b0aa 100755 --- a/processing/pre/seeds_fromPokes.py +++ b/processing/pre/seeds_fromPokes.py @@ -50,11 +50,12 @@ if filenames == []: filenames = [None] options.box = np.array(options.box).reshape(3,2) for name in filenames: + damask.util.report(scriptName,name) + geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) + table = damask.ASCIItable(name = name, outname = os.path.splitext(name)[-2]+'_poked_{}.seeds'.format(options.N) if name else name, buffered = False, labeled = False) - damask.util.report(scriptName,name) - table.head_read() info,extra_header = table.head_getGeom() @@ -62,15 +63,13 @@ for name in filenames: grid = info['grid'] size = info['size'] - microstructure = table.microstructure_read(grid).reshape(grid) # read microstructure - - offset =(np.amin(options.box, axis=1)*grid/size).astype(int) + offset =(np.amin(options.box, axis=1)*geom.grid/geom.size).astype(int) box = np.amax(options.box, axis=1) \ - np.amin(options.box, axis=1) - Nx = int(options.N/np.sqrt(options.N*size[1]*box[1]/size[0]/box[0])) - Ny = int(options.N/np.sqrt(options.N*size[0]*box[0]/size[1]/box[1])) - Nz = int(box[2]*grid[2]) + Nx = int(options.N/np.sqrt(options.N*geom.size[1]*box[1]/geom.size[0]/box[0])) + Ny = int(options.N/np.sqrt(options.N*geom.size[0]*box[0]/geom.size[1]/box[1])) + Nz = int(box[2]*geom.grid[2]) damask.util.croak('poking {} x {} x {} in box {} {} {}...'.format(Nx,Ny,Nz,*box)) @@ -86,7 +85,7 @@ for name in filenames: g[2] = k + offset[2] g %= grid seeds[n,0:3] = (g+0.5)/grid # normalize coordinates to box - seeds[n, 3] = microstructure[g[2],g[1],g[0]] + seeds[n, 3] = geom.microstructure[g[0],g[1],g[2]] if options.x: g[0] += 1 if options.y: g[1] += 1 n += 1 @@ -94,12 +93,12 @@ for name in filenames: # ------------------------------------------ assemble header --------------------------------------- table.info_clear() - table.info_append(extra_header+[ + table.info_append(geom.comments+[ scriptID + ' ' + ' '.join(sys.argv[1:]), "poking\ta {}\tb {}\tc {}".format(Nx,Ny,Nz), - "grid\ta {}\tb {}\tc {}".format(*grid), - "size\tx {}\ty {}\tz {}".format(*size), - "origin\tx {}\ty {}\tz {}".format(*info['origin']), + "grid\ta {}\tb {}\tc {}".format(*geom.grid), + "size\tx {}\ty {}\tz {}".format(*geom.size), + "origin\tx {}\ty {}\tz {}".format(*geom.origin), "homogenization\t{}".format(info['homogenization']), ]) table.labels_clear() From 75e93d9f0c96d88fb2b204fcd14f000cd7954cf2 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 11:25:33 +0100 Subject: [PATCH 030/148] relying on central functionality improves readability --- processing/pre/seeds_fromGeom.py | 6 +- processing/pre/seeds_fromPokes.py | 104 ++++++++++++------------------ 2 files changed, 45 insertions(+), 65 deletions(-) diff --git a/processing/pre/seeds_fromGeom.py b/processing/pre/seeds_fromGeom.py index c543cb878..2118f049d 100755 --- a/processing/pre/seeds_fromGeom.py +++ b/processing/pre/seeds_fromGeom.py @@ -58,11 +58,13 @@ for name in filenames: microstructure), axis=1)[mask] - comments = [scriptID + ' ' + ' '.join(sys.argv[1:]), + comments = geom.comments \ + + [scriptID + ' ' + ' '.join(sys.argv[1:]), "grid\ta {}\tb {}\tc {}".format(*geom.grid), "size\tx {}\ty {}\tz {}".format(*geom.size), "origin\tx {}\ty {}\tz {}".format(*geom.origin), "homogenization\t{}".format(geom.homogenization)] table = damask.Table(seeds,{'pos':(3,),'microstructure':(1,)},comments) - table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'.seeds') + table.to_ASCII(sys.stdout if name is None else \ + os.path.splitext(name)[0]+'.seeds') diff --git a/processing/pre/seeds_fromPokes.py b/processing/pre/seeds_fromPokes.py index 2c359b0aa..ef7da63bf 100755 --- a/processing/pre/seeds_fromPokes.py +++ b/processing/pre/seeds_fromPokes.py @@ -50,67 +50,45 @@ if filenames == []: filenames = [None] options.box = np.array(options.box).reshape(3,2) for name in filenames: - damask.util.report(scriptName,name) - geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) - - table = damask.ASCIItable(name = name, - outname = os.path.splitext(name)[-2]+'_poked_{}.seeds'.format(options.N) if name else name, - buffered = False, labeled = False) - - table.head_read() - info,extra_header = table.head_getGeom() - - grid = info['grid'] - size = info['size'] - - offset =(np.amin(options.box, axis=1)*geom.grid/geom.size).astype(int) - box = np.amax(options.box, axis=1) \ - - np.amin(options.box, axis=1) - - Nx = int(options.N/np.sqrt(options.N*geom.size[1]*box[1]/geom.size[0]/box[0])) - Ny = int(options.N/np.sqrt(options.N*geom.size[0]*box[0]/geom.size[1]/box[1])) - Nz = int(box[2]*geom.grid[2]) - - damask.util.croak('poking {} x {} x {} in box {} {} {}...'.format(Nx,Ny,Nz,*box)) - - seeds = np.zeros((Nx*Ny*Nz,4),'d') - g = np.zeros(3,'i') - - n = 0 - for i in range(Nx): - for j in range(Ny): - g[0] = round((i+0.5)*box[0]*grid[0]/Nx-0.5)+offset[0] - g[1] = round((j+0.5)*box[1]*grid[1]/Ny-0.5)+offset[1] - for k in range(Nz): - g[2] = k + offset[2] - g %= grid - seeds[n,0:3] = (g+0.5)/grid # normalize coordinates to box - seeds[n, 3] = geom.microstructure[g[0],g[1],g[2]] - if options.x: g[0] += 1 - if options.y: g[1] += 1 - n += 1 - - -# ------------------------------------------ assemble header --------------------------------------- - table.info_clear() - table.info_append(geom.comments+[ - scriptID + ' ' + ' '.join(sys.argv[1:]), - "poking\ta {}\tb {}\tc {}".format(Nx,Ny,Nz), - "grid\ta {}\tb {}\tc {}".format(*geom.grid), - "size\tx {}\ty {}\tz {}".format(*geom.size), - "origin\tx {}\ty {}\tz {}".format(*geom.origin), - "homogenization\t{}".format(info['homogenization']), - ]) - table.labels_clear() - table.labels_append(['{dim}_{label}'.format(dim = 1+i,label = 'pos') for i in range(3)]+['microstructure']) - table.head_write() - table.output_flush() - -# --- write seeds information ------------------------------------------------------------ - - table.data = seeds - table.data_writeArray() + damask.util.report(scriptName,name) + geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) -# --- output finalization -------------------------------------------------------------------------- - - table.close() # close ASCII table + offset =(np.amin(options.box, axis=1)*geom.grid/geom.size).astype(int) + box = np.amax(options.box, axis=1) \ + - np.amin(options.box, axis=1) + + Nx = int(options.N/np.sqrt(options.N*geom.size[1]*box[1]/geom.size[0]/box[0])) + Ny = int(options.N/np.sqrt(options.N*geom.size[0]*box[0]/geom.size[1]/box[1])) + Nz = int(box[2]*geom.grid[2]) + + damask.util.croak('poking {} x {} x {} in box {} {} {}...'.format(Nx,Ny,Nz,*box)) + + seeds = np.zeros((Nx*Ny*Nz,4),'d') + g = np.zeros(3,'i') + + n = 0 + for i in range(Nx): + for j in range(Ny): + g[0] = round((i+0.5)*box[0]*geom.grid[0]/Nx-0.5)+offset[0] + g[1] = round((j+0.5)*box[1]*geom.grid[1]/Ny-0.5)+offset[1] + for k in range(Nz): + g[2] = k + offset[2] + g %= geom.grid + seeds[n,0:3] = (g+0.5)/geom.grid # normalize coordinates to box + seeds[n, 3] = geom.microstructure[g[0],g[1],g[2]] + if options.x: g[0] += 1 + if options.y: g[1] += 1 + n += 1 + + + comments = geom.comments \ + + [scriptID + ' ' + ' '.join(sys.argv[1:]), + "poking\ta {}\tb {}\tc {}".format(Nx,Ny,Nz), + "grid\ta {}\tb {}\tc {}".format(*geom.grid), + "size\tx {}\ty {}\tz {}".format(*geom.size), + "origin\tx {}\ty {}\tz {}".format(*geom.origin), + "homogenization\t{}".format(geom.homogenization)] + + table = damask.Table(seeds,{'pos':(3,),'microstructure':(1,)},comments) + table.to_ASCII(sys.stdout if name is None else \ + os.path.splitext(name)[0]+'_poked_{}.seeds'.format(options.N)) From 8d0c4310cf54acf3182258095ad68527de845498 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 17:57:02 +0100 Subject: [PATCH 031/148] improvements to grid generation - handling of origin - inverse functions: calculate grid,size,origin from regular coordinates (cell or node). should replace corresponding functionality in util --- python/damask/grid_filters.py | 66 ++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 006167ccc..cb593f449 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -57,12 +57,13 @@ def gradient(size,field): return np.fft.irfftn(gradient,axes=(0,1,2),s=field.shape[:3]) -def cell_coord0(grid,size): +def cell_coord0(grid,size,origin=np.zeros(3)): """Cell center positions (undeformed).""" - delta = size/grid*0.5 - x, y, z = np.meshgrid(np.linspace(delta[2],size[2]-delta[2],grid[2]), - np.linspace(delta[1],size[1]-delta[1],grid[1]), - np.linspace(delta[0],size[0]-delta[0],grid[0]), + start = origin + size/grid*.5 + end = origin - size/grid*.5 + size + x, y, z = np.meshgrid(np.linspace(start[2],end[2],grid[2]), + np.linspace(start[1],end[1],grid[1]), + np.linspace(start[0],end[0],grid[0]), indexing = 'ij') return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) @@ -88,12 +89,37 @@ def cell_displacement_avg(size,F): F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3],size)) +def cell_coord0_2_DNA(coord0,ordered=False): + coords = [np.unique(coord0[:,i]) for i in range(3)] + mincorner = np.array(list(map(min,coords))) + maxcorner = np.array(list(map(max,coords))) + grid = np.array(list(map(len,coords)),'i') + size = grid/np.maximum(grid-1,1) * (maxcorner-mincorner) + delta = size/grid + origin = mincorner - delta*.5 + + if grid.prod() != len(coord0): + raise ValueError('Data count {} does not match grid {}.'.format(len(coord0),grid)) -def node_coord0(grid,size): + start = origin + delta*.5 + end = origin + size -delta*.5 + + if not np.allclose(coords[0],np.linspace(start[0],end[0],grid[0])) and \ + np.allclose(coords[1],np.linspace(start[1],end[1],grid[1])) and \ + np.allclose(coords[2],np.linspace(start[2],end[2],grid[2])): + raise ValueError('Regular grid spacing violated.') + + if ordered: + if not np.allclose(coord0.reshape(tuple(grid[::-1])+(3,)),cell_coord0(grid,size,origin)): + raise ValueError('Input data is not a regular grid.') + return (grid,size,origin) + + +def node_coord0(grid,size,origin=np.zeros(3)): """Nodal positions (undeformed).""" - x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), - np.linspace(0,size[1],1+grid[1]), - np.linspace(0,size[0],1+grid[0]), + x, y, z = np.meshgrid(np.linspace(origin[2],size[2]+origin[2],1+grid[2]), + np.linspace(origin[1],size[1]+origin[1],1+grid[1]), + np.linspace(origin[0],size[0]+origin[0],1+grid[0]), indexing = 'ij') return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) @@ -124,6 +150,28 @@ def node_2_cell(node_data): return c[:-1,:-1,:-1] +def node_coord0_2_DNA(coord0,ordered=False): + coords = [np.unique(coord0[:,i]) for i in range(3)] + mincorner = np.array(list(map(min,coords))) + maxcorner = np.array(list(map(max,coords))) + grid = np.array(list(map(len,coords)),'i') - 1 + size = maxcorner-mincorner + origin = mincorner + + if (grid+1).prod() != len(coord0): + raise ValueError('Data count {} does not match grid {}.'.format(len(coord0),grid)) + + if not np.allclose(coords[0],np.linspace(mincorner[0],maxcorner[0],grid[0]+1)) and \ + np.allclose(coords[1],np.linspace(mincorner[1],maxcorner[1],grid[1]+1)) and \ + np.allclose(coords[2],np.linspace(mincorner[2],maxcorner[2],grid[2]+1)): + raise ValueError('Regular grid spacing violated.') + + if ordered: + if not np.allclose(coord0.reshape(tuple((grid+1)[::-1])+(3,)),node_coord0(grid,size,origin)): + raise ValueError('Input data is not a regular grid.') + return (grid,size,origin) + + def regrid(size,F,new_grid): """tbd.""" c = cell_coord0(F.shape[:3][::-1],size) \ From 828e82605ed401be736af10cd033b8ef474cd653 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 18:13:20 +0100 Subject: [PATCH 032/148] ensure that data is correctly ordered --- processing/post/addCurl.py | 2 +- processing/post/addDisplacement.py | 2 +- processing/post/addDivergence.py | 2 +- processing/post/addGradient.py | 2 +- python/damask/grid_filters.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 010d24935..5db47ce04 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get(options.pos)) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) for label in options.labels: field = table.get(label) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index 4f0323504..735e6d875 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -50,7 +50,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get(options.pos)) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) F = table.get(options.f).reshape(np.append(grid[::-1],(3,3))) if options.nodal: diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index d2d68ede6..ea4b134a4 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get(options.pos)) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) for label in options.labels: field = table.get(label) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index ed9397f02..75109a3e0 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size = damask.util.coordGridAndSize(table.get(options.pos)) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) for label in options.labels: field = table.get(label) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index cb593f449..96ae226dd 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -102,7 +102,7 @@ def cell_coord0_2_DNA(coord0,ordered=False): raise ValueError('Data count {} does not match grid {}.'.format(len(coord0),grid)) start = origin + delta*.5 - end = origin + size -delta*.5 + end = origin - delta*.5 + size if not np.allclose(coords[0],np.linspace(start[0],end[0],grid[0])) and \ np.allclose(coords[1],np.linspace(start[1],end[1],grid[1])) and \ From b0689340d060567d21b8b2b4ca0cdb3fc7bd0e60 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 18:20:28 +0100 Subject: [PATCH 033/148] updated tests --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 806e1b32a..b580fd4e9 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 806e1b32a17bf26c987f417116476a295d3936cd +Subproject commit b580fd4e992c2ed457f16d65bcba2e6d099dc29d From 3d09a82f41898d5deb96a8a35967a7f253726f2a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 18:21:16 +0100 Subject: [PATCH 034/148] fixing prospector complaints --- processing/pre/mentat_spectralBox.py | 3 +-- processing/pre/seeds_fromPokes.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processing/pre/mentat_spectralBox.py b/processing/pre/mentat_spectralBox.py index f62622a6b..a61bef57a 100755 --- a/processing/pre/mentat_spectralBox.py +++ b/processing/pre/mentat_spectralBox.py @@ -2,10 +2,9 @@ import os import sys +from io import StringIO from optparse import OptionParser -import numpy as np - import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] diff --git a/processing/pre/seeds_fromPokes.py b/processing/pre/seeds_fromPokes.py index ef7da63bf..1436841d0 100755 --- a/processing/pre/seeds_fromPokes.py +++ b/processing/pre/seeds_fromPokes.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np From 7dc128ad123afb3b2d9e4b608e7855d5d724903b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 18:33:43 +0100 Subject: [PATCH 035/148] polishing --- processing/post/addCurl.py | 2 +- processing/post/addDisplacement.py | 2 +- processing/post/addDivergence.py | 2 +- processing/post/addGradient.py | 2 +- python/damask/grid_filters.py | 36 ++++++++++++++++++++++++------ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 5db47ce04..25639dc7c 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) for label in options.labels: field = table.get(label) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index 735e6d875..59630a6c6 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -50,7 +50,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) F = table.get(options.f).reshape(np.append(grid[::-1],(3,3))) if options.nodal: diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index ea4b134a4..585ebb5a5 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) for label in options.labels: field = table.get(label) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index 75109a3e0..54b80ed26 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -44,7 +44,7 @@ for name in filenames: damask.util.report(scriptName,name) table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) - grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos),True) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) for label in options.labels: field = table.get(label) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 96ae226dd..b064d9a2d 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -89,7 +89,18 @@ def cell_displacement_avg(size,F): F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3],size)) -def cell_coord0_2_DNA(coord0,ordered=False): +def cell_coord0_2_DNA(coord0,ordered=True): + """ + Return grid 'DNA', i.e. grid, size, and origin from array of cell positions. + + Parameters + ---------- + coord0 : numpy.ndarray + array of undeformed cell coordinates + ordered : bool, optional + expect coord0 data to be ordered (x fast, z slow). + + """ coords = [np.unique(coord0[:,i]) for i in range(3)] mincorner = np.array(list(map(min,coords))) maxcorner = np.array(list(map(max,coords))) @@ -109,9 +120,9 @@ def cell_coord0_2_DNA(coord0,ordered=False): np.allclose(coords[2],np.linspace(start[2],end[2],grid[2])): raise ValueError('Regular grid spacing violated.') - if ordered: - if not np.allclose(coord0.reshape(tuple(grid[::-1])+(3,)),cell_coord0(grid,size,origin)): - raise ValueError('Input data is not a regular grid.') + if ordered and not np.allclose(coord0.reshape(tuple(grid[::-1])+(3,)),cell_coord0(grid,size,origin)): + raise ValueError('Input data is not a regular grid.') + return (grid,size,origin) @@ -151,6 +162,17 @@ def node_2_cell(node_data): return c[:-1,:-1,:-1] def node_coord0_2_DNA(coord0,ordered=False): + """ + Return grid 'DNA', i.e. grid, size, and origin from array of nodal positions. + + Parameters + ---------- + coord0 : numpy.ndarray + array of undeformed nodal coordinates + ordered : bool, optional + expect coord0 data to be ordered (x fast, z slow). + + """ coords = [np.unique(coord0[:,i]) for i in range(3)] mincorner = np.array(list(map(min,coords))) maxcorner = np.array(list(map(max,coords))) @@ -166,9 +188,9 @@ def node_coord0_2_DNA(coord0,ordered=False): np.allclose(coords[2],np.linspace(mincorner[2],maxcorner[2],grid[2]+1)): raise ValueError('Regular grid spacing violated.') - if ordered: - if not np.allclose(coord0.reshape(tuple((grid+1)[::-1])+(3,)),node_coord0(grid,size,origin)): - raise ValueError('Input data is not a regular grid.') + if ordered and not np.allclose(coord0.reshape(tuple((grid+1)[::-1])+(3,)),node_coord0(grid,size,origin)): + raise ValueError('Input data is not a regular grid.') + return (grid,size,origin) From 4e2e7d02f6c10ad80a2ce9f4fa49257e723ceec8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 18:54:41 +0100 Subject: [PATCH 036/148] more sensible interface --- python/damask/grid_filters.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index b064d9a2d..9e1170ec4 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -1,9 +1,8 @@ from scipy import spatial import numpy as np -def __ks(size,field,first_order=False): +def __ks(size,grid,first_order=False): """Get wave numbers operator.""" - grid = np.array(np.shape(field)[:3]) k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] if grid[0]%2 == 0 and first_order: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) @@ -20,7 +19,7 @@ def __ks(size,field,first_order=False): def curl(size,field): """Calculate curl of a vector or tensor field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field,True) + k_s = __ks(size,field.shape[:3],True) e = np.zeros((3, 3, 3)) e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol @@ -36,7 +35,7 @@ def curl(size,field): def divergence(size,field): """Calculate divergence of a vector or tensor field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field,True) + k_s = __ks(size,field.shape[:3],True) field_fourier = np.fft.rfftn(field,axes=(0,1,2)) divergence = (np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*np.pi if n == 3 else # vector, 3 -> 1 @@ -48,7 +47,7 @@ def divergence(size,field): def gradient(size,field): """Calculate gradient of a vector or scalar field in Fourier space.""" n = np.prod(field.shape[3:]) - k_s = __ks(size,field,True) + k_s = __ks(size,field.shape[:3],True) field_fourier = np.fft.rfftn(field,axes=(0,1,2)) gradient = (np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*np.pi if n == 1 else # scalar, 1 -> 3 @@ -72,7 +71,7 @@ def cell_displacement_fluct(size,F): """Cell center displacement field from fluctuation part of the deformation gradient field.""" integrator = 0.5j*size/np.pi - k_s = __ks(size,F,False) + k_s = __ks(size,F.shape[:3],False) k_s_squared = np.einsum('...l,...l',k_s,k_s) k_s_squared[0,0,0] = 1.0 From 21431295fbf8231e3d014f87a13deb7ac1b46a0d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 20:20:13 +0100 Subject: [PATCH 037/148] documenting --- python/damask/grid_filters.py | 113 ++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 9e1170ec4..a1e1ff06d 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -2,8 +2,15 @@ from scipy import spatial import numpy as np def __ks(size,grid,first_order=False): - """Get wave numbers operator.""" + """ + Get wave numbers operator. + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + + """ k_sk = np.where(np.arange(grid[0])>grid[0]//2,np.arange(grid[0])-grid[0],np.arange(grid[0]))/size[0] if grid[0]%2 == 0 and first_order: k_sk[grid[0]//2] = 0 # Nyquist freq=0 for even grid (Johnson, MIT, 2011) @@ -17,7 +24,15 @@ def __ks(size,grid,first_order=False): def curl(size,field): - """Calculate curl of a vector or tensor field in Fourier space.""" + """ + Calculate curl of a vector or tensor field in Fourier space. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + + """ n = np.prod(field.shape[3:]) k_s = __ks(size,field.shape[:3],True) @@ -33,7 +48,15 @@ def curl(size,field): def divergence(size,field): - """Calculate divergence of a vector or tensor field in Fourier space.""" + """ + Calculate divergence of a vector or tensor field in Fourier space. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + + """ n = np.prod(field.shape[3:]) k_s = __ks(size,field.shape[:3],True) @@ -45,7 +68,15 @@ def divergence(size,field): def gradient(size,field): - """Calculate gradient of a vector or scalar field in Fourier space.""" + """ + Calculate gradient of a vector or scalar field in Fourier space. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + + """ n = np.prod(field.shape[3:]) k_s = __ks(size,field.shape[:3],True) @@ -57,7 +88,17 @@ def gradient(size,field): def cell_coord0(grid,size,origin=np.zeros(3)): - """Cell center positions (undeformed).""" + """ + Cell center positions (undeformed). + + Parameters + ---------- + grid : numpy.ndarray + number of grid points. + size : numpy.ndarray + physical size of the periodic field. + + """ start = origin + size/grid*.5 end = origin - size/grid*.5 + size x, y, z = np.meshgrid(np.linspace(start[2],end[2],grid[2]), @@ -68,7 +109,17 @@ def cell_coord0(grid,size,origin=np.zeros(3)): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) def cell_displacement_fluct(size,F): - """Cell center displacement field from fluctuation part of the deformation gradient field.""" + """ + Cell center displacement field from fluctuation part of the deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ integrator = 0.5j*size/np.pi k_s = __ks(size,F.shape[:3],False) @@ -84,7 +135,17 @@ def cell_displacement_fluct(size,F): return np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3]) def cell_displacement_avg(size,F): - """Cell center displacement field from average part of the deformation gradient field.""" + """ + Cell center displacement field from average part of the deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3],size)) @@ -95,7 +156,7 @@ def cell_coord0_2_DNA(coord0,ordered=True): Parameters ---------- coord0 : numpy.ndarray - array of undeformed cell coordinates + array of undeformed cell coordinates. ordered : bool, optional expect coord0 data to be ordered (x fast, z slow). @@ -126,7 +187,17 @@ def cell_coord0_2_DNA(coord0,ordered=True): def node_coord0(grid,size,origin=np.zeros(3)): - """Nodal positions (undeformed).""" + """ + Nodal positions (undeformed). + + Parameters + ---------- + grid : numpy.ndarray + number of grid points. + size : numpy.ndarray + physical size of the periodic field. + + """ x, y, z = np.meshgrid(np.linspace(origin[2],size[2]+origin[2],1+grid[2]), np.linspace(origin[1],size[1]+origin[1],1+grid[1]), np.linspace(origin[0],size[0]+origin[0],1+grid[0]), @@ -135,11 +206,31 @@ def node_coord0(grid,size,origin=np.zeros(3)): return np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) def node_displacement_fluct(size,F): - """Nodal displacement field from fluctuation part of the deformation gradient field.""" + """ + Nodal displacement field from fluctuation part of the deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ return cell_2_node(cell_displacement_fluct(size,F)) def node_displacement_avg(size,F): - """Nodal displacement field from average part of the deformation gradient field.""" + """ + Nodal displacement field from average part of the deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3],size)) From b56864552f92ce306b23fcbf26a839bdf3c547d8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 21:05:34 +0100 Subject: [PATCH 038/148] testing forward <-> backward conversion --- python/tests/test_grid_filters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index 4dff41542..b23fad549 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -24,6 +24,18 @@ class TestGridFilters: n = grid_filters.node_coord0(grid,size) + size/grid*.5 assert np.allclose(c,n) + @pytest.mark.parametrize('mode',[('cell'),('node')]) + def test_grid_DNA(self,mode): + """Ensure that xx_coord0_2_DNA is the inverse of xx_coord0.""" + grid = np.random.randint(8,32,(3)) + size = np.random.random(3) + origin = np.random.random(3) + + coord0 = eval('grid_filters.{}_coord0(grid,size,origin)'.format(mode)) # noqa + _grid,_size,_origin = eval('grid_filters.{}_coord0_2_DNA(coord0.reshape((-1,3)))'.format(mode)) + assert np.allclose(grid,_grid) and np.allclose(size,_size) and np.allclose(origin,_origin) + + @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_avg_vanishes(self,mode): """Ensure that random fluctuations in F do not result in average displacement.""" From 53cb59fc474134d9aeedde79f0fe041267ef70c6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 21:29:26 +0100 Subject: [PATCH 039/148] use pytest instead of hand-written test class --- .gitlab-ci.yml | 7 ------- python/damask/orientation.py | 2 +- python/tests/test_Rotation.py | 18 ++++++++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e82561c5..d4c6923d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -115,13 +115,6 @@ Pytest: - release ################################################################################################### -OrientationRelationship: - stage: preprocessing - script: OrientationRelationship/test.py - except: - - master - - release - Pre_SeedGeneration: stage: preprocessing script: PreProcessing_SeedGeneration/test.py diff --git a/python/damask/orientation.py b/python/damask/orientation.py index fc601b608..65318f169 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -1025,7 +1025,7 @@ class Lattice: https://doi.org/10.1016/j.actamat.2004.11.021 """ - models={'KS':self.KS, 'GT':self.GT, "GT'":self.GTprime, + models={'KS':self.KS, 'GT':self.GT, 'GT_prime':self.GTprime, 'NW':self.NW, 'Pitsch': self.Pitsch, 'Bain':self.Bain} try: relationship = models[model] diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 72956c013..79d674bcd 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -2,6 +2,7 @@ import pytest import numpy as np from damask import Rotation +from damask import Orientation n = 1000 @@ -18,38 +19,43 @@ class TestRotation: assert np.allclose(rot.asQuaternion(), Rotation.fromEulers(rot.asEulers()).asQuaternion()) - def test_AxisAngle(self,default): for rot in default: assert np.allclose(rot.asEulers(), Rotation.fromAxisAngle(rot.asAxisAngle()).asEulers()) - def test_Matrix(self,default): for rot in default: assert np.allclose(rot.asAxisAngle(), Rotation.fromMatrix(rot.asMatrix()).asAxisAngle()) - def test_Rodriques(self,default): for rot in default: assert np.allclose(rot.asMatrix(), Rotation.fromRodrigues(rot.asRodrigues()).asMatrix()) - def test_Homochoric(self,default): for rot in default: assert np.allclose(rot.asRodrigues(), Rotation.fromHomochoric(rot.asHomochoric()).asRodrigues()) - def test_Cubochoric(self,default): for rot in default: assert np.allclose(rot.asHomochoric(), Rotation.fromCubochoric(rot.asCubochoric()).asHomochoric()) - def test_Quaternion(self,default): for rot in default: assert np.allclose(rot.asCubochoric(), Rotation.fromQuaternion(rot.asQuaternion()).asCubochoric()) + + + @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) + @pytest.mark.parametrize('lattice',['fcc','bcc']) + def test_relationship_forward_backward(self,model,lattice): + ori = Orientation(Rotation.fromRandom(),lattice) + for i,r in enumerate(ori.relatedOrientations(model)): + ori2 = r.relatedOrientations(model)[i] + misorientation = ori.rotation.misorientation(ori2.rotation) + assert misorientation.asAxisAngle(degrees=True)[3]<1.0e-5 + From fa9ee6b11679f1ecd91027573c0167359bef6062 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 21:48:32 +0100 Subject: [PATCH 040/148] using reference results for orientation relationships --- python/tests/reference/Rotation/bcc_Bain.txt | 5 ++++ python/tests/reference/Rotation/bcc_GT.txt | 26 +++++++++++++++++++ .../tests/reference/Rotation/bcc_GT_prime.txt | 26 +++++++++++++++++++ python/tests/reference/Rotation/bcc_KS.txt | 26 +++++++++++++++++++ python/tests/reference/Rotation/bcc_NW.txt | 14 ++++++++++ .../tests/reference/Rotation/bcc_Pitsch.txt | 14 ++++++++++ python/tests/reference/Rotation/fcc_Bain.txt | 5 ++++ python/tests/reference/Rotation/fcc_GT.txt | 26 +++++++++++++++++++ .../tests/reference/Rotation/fcc_GT_prime.txt | 26 +++++++++++++++++++ python/tests/reference/Rotation/fcc_KS.txt | 26 +++++++++++++++++++ python/tests/reference/Rotation/fcc_NW.txt | 14 ++++++++++ .../tests/reference/Rotation/fcc_Pitsch.txt | 14 ++++++++++ python/tests/test_Rotation.py | 18 +++++++++++++ 13 files changed, 240 insertions(+) create mode 100644 python/tests/reference/Rotation/bcc_Bain.txt create mode 100644 python/tests/reference/Rotation/bcc_GT.txt create mode 100644 python/tests/reference/Rotation/bcc_GT_prime.txt create mode 100644 python/tests/reference/Rotation/bcc_KS.txt create mode 100644 python/tests/reference/Rotation/bcc_NW.txt create mode 100644 python/tests/reference/Rotation/bcc_Pitsch.txt create mode 100644 python/tests/reference/Rotation/fcc_Bain.txt create mode 100644 python/tests/reference/Rotation/fcc_GT.txt create mode 100644 python/tests/reference/Rotation/fcc_GT_prime.txt create mode 100644 python/tests/reference/Rotation/fcc_KS.txt create mode 100644 python/tests/reference/Rotation/fcc_NW.txt create mode 100644 python/tests/reference/Rotation/fcc_Pitsch.txt diff --git a/python/tests/reference/Rotation/bcc_Bain.txt b/python/tests/reference/Rotation/bcc_Bain.txt new file mode 100644 index 000000000..e1675cd32 --- /dev/null +++ b/python/tests/reference/Rotation/bcc_Bain.txt @@ -0,0 +1,5 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +0.0 45.00000000000001 0.0 +90.0 45.00000000000001 270.0 +45.00000000000001 0.0 0.0 diff --git a/python/tests/reference/Rotation/bcc_GT.txt b/python/tests/reference/Rotation/bcc_GT.txt new file mode 100644 index 000000000..b4bfd5d8f --- /dev/null +++ b/python/tests/reference/Rotation/bcc_GT.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +283.60440567265294 9.976439066337804 33.24637065555936 +167.8261034151001 43.397849654402556 183.40022280897963 +262.1156357053931 43.82007387041961 104.07478363123654 +103.604405672653 9.976439066337804 213.24637065555936 +347.8261034151001 43.39784965440255 3.400222808979685 +82.11563570539313 43.82007387041961 284.0747836312365 +76.39559432734703 9.976439066337806 326.75362934444064 +192.17389658489986 43.397849654402556 176.59977719102034 +97.88436429460687 43.82007387041961 255.92521636876344 +256.395594327347 9.976439066337804 146.75362934444064 +12.173896584899929 43.39784965440254 356.59977719102034 +277.8843642946069 43.82007387041961 75.92521636876346 +102.17389658489992 43.39784965440254 266.59977719102034 +346.395594327347 9.976439066337804 56.75362934444064 +7.884364294606862 43.82007387041961 345.9252163687635 +282.17389658489986 43.39784965440254 86.59977719102032 +166.39559432734703 9.976439066337804 236.75362934444058 +187.88436429460683 43.82007387041961 165.92521636876344 +257.8261034151001 43.39784965440255 93.40022280897969 +13.604405672652977 9.976439066337804 303.24637065555936 +352.1156357053931 43.82007387041961 14.074783631236542 +77.82610341510008 43.397849654402556 273.4002228089796 +193.60440567265297 9.976439066337806 123.24637065555939 +153.65751914298576 65.6559553854118 185.90444335627936 diff --git a/python/tests/reference/Rotation/bcc_GT_prime.txt b/python/tests/reference/Rotation/bcc_GT_prime.txt new file mode 100644 index 000000000..73c3bb6b3 --- /dev/null +++ b/python/tests/reference/Rotation/bcc_GT_prime.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +303.24637065555936 9.976439066337804 13.604405672652977 +165.92521636876344 43.82007387041961 187.88436429460683 +266.59977719102034 43.39784965440254 102.17389658489992 +123.24637065555939 9.976439066337804 193.604405672653 +345.9252163687635 43.82007387041961 7.884364294606862 +86.59977719102032 43.39784965440254 282.17389658489986 +56.75362934444064 9.976439066337804 346.395594327347 +194.07478363123653 43.82007387041961 172.11563570539317 +93.40022280897969 43.39784965440255 257.8261034151001 +236.75362934444058 9.976439066337804 166.39559432734697 +14.074783631236542 43.82007387041961 352.1156357053931 +273.4002228089796 43.397849654402556 77.82610341510008 +104.07478363123654 43.82007387041961 262.1156357053931 +326.75362934444064 9.976439066337806 76.39559432734703 +3.400222808979685 43.39784965440255 347.8261034151001 +284.0747836312365 43.82007387041961 82.11563570539313 +146.75362934444064 9.976439066337804 256.395594327347 +183.40022280897963 43.397849654402556 167.8261034151001 +255.92521636876344 43.82007387041961 97.88436429460687 +33.24637065555936 9.976439066337804 283.60440567265294 +26.291675350407385 65.60048732963618 354.34378938496315 +75.92521636876346 43.82007387041961 277.8843642946069 +213.24637065555936 9.976439066337804 103.604405672653 +176.59977719102034 43.397849654402556 192.17389658489986 diff --git a/python/tests/reference/Rotation/bcc_KS.txt b/python/tests/reference/Rotation/bcc_KS.txt new file mode 100644 index 000000000..0add535bb --- /dev/null +++ b/python/tests/reference/Rotation/bcc_KS.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +335.7965716606702 10.528779365509317 65.79657166067024 +228.77270547567446 80.40593177313953 85.64260312151849 +131.22729452432552 80.40593177313954 4.357396878481506 +24.20342833932977 10.52877936550932 24.20342833932976 +221.95489158457983 85.70366403943002 80.37863910890589 +138.04510841542015 85.70366403943004 9.621360891094124 +131.22729452432552 80.40593177313953 94.35739687848151 +24.203428339329765 10.52877936550932 114.20342833932976 +221.95489158457983 85.70366403943004 170.37863910890587 +138.04510841542015 85.70366403943004 99.62136089109411 +335.7965716606702 10.52877936550932 155.79657166067025 +228.77270547567448 80.40593177313954 175.6426031215185 +335.7965716606702 10.52877936550932 335.7965716606702 +228.77270547567448 80.40593177313954 355.6426031215185 +131.2272945243255 80.40593177313954 274.35739687848144 +24.203428339329747 10.52877936550932 294.2034283393298 +221.95489158457985 85.70366403943004 350.3786391089059 +138.04510841542015 85.70366403943004 279.6213608910941 +41.95489158457986 94.29633596056998 9.621360891094133 +318.04510841542015 94.29633596056996 80.37863910890589 +155.79657166067025 169.4712206344907 24.203428339329754 +48.77270547567448 99.59406822686046 4.357396878481504 +311.2272945243255 99.59406822686046 85.64260312151852 +204.20342833932975 169.4712206344907 65.79657166067024 diff --git a/python/tests/reference/Rotation/bcc_NW.txt b/python/tests/reference/Rotation/bcc_NW.txt new file mode 100644 index 000000000..8d3dfbe84 --- /dev/null +++ b/python/tests/reference/Rotation/bcc_NW.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +225.41555594321144 83.13253115922213 83.08266205989301 +134.58444405678856 83.13253115922211 6.917337940107012 +4.702125169424418e-15 9.735610317245317 45.0 +134.58444405678856 83.13253115922213 276.91733794010696 +225.4155559432114 83.13253115922213 353.082662059893 +0.0 9.735610317245317 315.0 +134.58444405678858 83.13253115922213 96.91733794010702 +225.41555594321142 83.13253115922213 173.082662059893 +0.0 9.735610317245317 135.0 +260.40196970123213 45.81931182053556 283.6387072794765 +260.40196970123213 45.81931182053556 283.6387072794765 +180.0 99.73561031724535 225.0 diff --git a/python/tests/reference/Rotation/bcc_Pitsch.txt b/python/tests/reference/Rotation/bcc_Pitsch.txt new file mode 100644 index 000000000..dc9926ca1 --- /dev/null +++ b/python/tests/reference/Rotation/bcc_Pitsch.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +6.9173379401070045 83.13253115922213 44.58444405678856 +45.0 89.99999999999999 279.7356103172453 +166.36129272052352 45.819311820535574 279.59803029876787 +83.08266205989301 83.13253115922213 225.41555594321144 +256.3612927205235 45.819311820535574 189.59803029876787 +315.0 90.0 9.735610317245369 +186.917337940107 83.13253115922213 224.58444405678856 +315.0 90.0 80.26438968275463 +13.638707279476478 45.81931182053557 260.40196970123213 +263.082662059893 83.13253115922213 45.415555943211444 +103.63870727947646 45.819311820535574 170.40196970123213 +224.99999999999997 90.0 170.26438968275465 diff --git a/python/tests/reference/Rotation/fcc_Bain.txt b/python/tests/reference/Rotation/fcc_Bain.txt new file mode 100644 index 000000000..2c12eecc9 --- /dev/null +++ b/python/tests/reference/Rotation/fcc_Bain.txt @@ -0,0 +1,5 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +180.0 45.00000000000001 180.0 +270.0 45.00000000000001 90.0 +315.0 0.0 0.0 diff --git a/python/tests/reference/Rotation/fcc_GT.txt b/python/tests/reference/Rotation/fcc_GT.txt new file mode 100644 index 000000000..e695d0d6f --- /dev/null +++ b/python/tests/reference/Rotation/fcc_GT.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +146.75362934444064 9.976439066337804 256.395594327347 +356.59977719102034 43.39784965440254 12.173896584899929 +75.92521636876346 43.82007387041961 277.8843642946069 +326.75362934444064 9.976439066337806 76.39559432734703 +176.59977719102034 43.397849654402556 192.17389658489986 +255.92521636876344 43.82007387041961 97.88436429460687 +213.24637065555936 9.976439066337804 103.604405672653 +3.400222808979685 43.39784965440255 347.8261034151001 +284.0747836312365 43.82007387041961 82.11563570539313 +33.24637065555936 9.976439066337804 283.60440567265294 +183.40022280897963 43.397849654402556 167.8261034151001 +104.07478363123654 43.82007387041961 262.1156357053931 +273.4002228089796 43.397849654402556 77.82610341510008 +123.24637065555939 9.976439066337806 193.60440567265297 +194.07478363123653 43.82007387041961 172.11563570539317 +93.40022280897969 43.39784965440255 257.8261034151001 +303.24637065555936 9.976439066337804 13.604405672652977 +14.074783631236542 43.82007387041961 352.1156357053931 +86.59977719102032 43.39784965440254 282.17389658489986 +236.75362934444058 9.976439066337804 166.39559432734703 +165.92521636876344 43.82007387041961 187.88436429460683 +266.59977719102034 43.39784965440254 102.17389658489992 +56.75362934444064 9.976439066337804 346.395594327347 +354.0955566437206 65.6559553854118 26.342480857014277 diff --git a/python/tests/reference/Rotation/fcc_GT_prime.txt b/python/tests/reference/Rotation/fcc_GT_prime.txt new file mode 100644 index 000000000..7df7cb6c1 --- /dev/null +++ b/python/tests/reference/Rotation/fcc_GT_prime.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +166.39559432734697 9.976439066337804 236.75362934444058 +352.1156357053931 43.82007387041961 14.074783631236542 +77.82610341510008 43.397849654402556 273.4002228089796 +346.395594327347 9.976439066337804 56.75362934444064 +172.11563570539317 43.82007387041961 194.07478363123653 +257.8261034151001 43.39784965440255 93.40022280897969 +193.604405672653 9.976439066337804 123.24637065555939 +7.884364294606862 43.82007387041961 345.9252163687635 +282.17389658489986 43.39784965440254 86.59977719102032 +13.604405672652977 9.976439066337804 303.24637065555936 +187.88436429460683 43.82007387041961 165.92521636876344 +102.17389658489992 43.39784965440254 266.59977719102034 +277.8843642946069 43.82007387041961 75.92521636876346 +103.604405672653 9.976439066337804 213.24637065555936 +192.17389658489986 43.397849654402556 176.59977719102034 +97.88436429460687 43.82007387041961 255.92521636876344 +283.60440567265294 9.976439066337804 33.24637065555936 +12.173896584899929 43.39784965440254 356.59977719102034 +82.11563570539313 43.82007387041961 284.0747836312365 +256.395594327347 9.976439066337804 146.75362934444064 +185.65621061503683 65.60048732963617 153.70832464959264 +262.1156357053931 43.82007387041961 104.07478363123654 +76.39559432734703 9.976439066337806 326.75362934444064 +347.8261034151001 43.39784965440255 3.400222808979685 diff --git a/python/tests/reference/Rotation/fcc_KS.txt b/python/tests/reference/Rotation/fcc_KS.txt new file mode 100644 index 000000000..3a6e30358 --- /dev/null +++ b/python/tests/reference/Rotation/fcc_KS.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +114.20342833932975 10.52877936550932 204.20342833932972 +94.3573968784815 80.40593177313954 311.22729452432543 +175.6426031215185 80.40593177313954 48.77270547567447 +155.79657166067025 10.52877936550932 155.79657166067025 +99.62136089109411 85.70366403943004 318.04510841542015 +170.37863910890587 85.70366403943002 41.954891584579855 +85.64260312151852 80.40593177313954 48.77270547567448 +65.79657166067024 10.52877936550932 155.79657166067025 +9.621360891094124 85.70366403943004 318.04510841542015 +80.37863910890587 85.70366403943004 41.95489158457987 +24.203428339329758 10.52877936550932 204.20342833932975 +4.357396878481486 80.40593177313954 311.2272945243255 +204.20342833932972 10.52877936550932 204.20342833932972 +184.35739687848147 80.40593177313954 311.2272945243255 +265.64260312151845 80.40593177313953 48.77270547567449 +245.79657166067025 10.528779365509317 155.79657166067025 +189.62136089109413 85.70366403943004 318.04510841542015 +260.3786391089059 85.70366403943002 41.954891584579855 +170.37863910890587 94.29633596056996 138.04510841542015 +99.62136089109411 94.29633596056998 221.95489158457983 +155.79657166067025 169.4712206344907 24.203428339329754 +175.64260312151848 99.59406822686046 131.22729452432552 +94.35739687848151 99.59406822686046 228.77270547567446 +114.20342833932975 169.4712206344907 335.7965716606702 diff --git a/python/tests/reference/Rotation/fcc_NW.txt b/python/tests/reference/Rotation/fcc_NW.txt new file mode 100644 index 000000000..bf3631db0 --- /dev/null +++ b/python/tests/reference/Rotation/fcc_NW.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +96.91733794010702 83.13253115922213 314.5844440567886 +173.082662059893 83.13253115922211 45.41555594321143 +135.0 9.735610317245317 180.0 +263.082662059893 83.13253115922213 45.415555943211444 +186.91733794010702 83.13253115922211 314.5844440567886 +224.99999999999997 9.735610317245317 180.0 +83.082662059893 83.13253115922213 45.415555943211444 +6.917337940106983 83.13253115922211 314.5844440567886 +45.0 9.73561031724532 180.0 +256.36129272052347 45.81931182053556 279.59803029876775 +256.36129272052347 45.81931182053556 279.59803029876775 +315.0 99.73561031724536 0.0 diff --git a/python/tests/reference/Rotation/fcc_Pitsch.txt b/python/tests/reference/Rotation/fcc_Pitsch.txt new file mode 100644 index 000000000..a2369f0a4 --- /dev/null +++ b/python/tests/reference/Rotation/fcc_Pitsch.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers +135.41555594321144 83.13253115922213 173.082662059893 +260.26438968275465 90.0 135.0 +260.40196970123213 45.81931182053557 13.638707279476478 +314.5844440567886 83.13253115922213 96.91733794010702 +350.40196970123213 45.81931182053557 283.6387072794765 +170.26438968275465 90.0 224.99999999999997 +315.4155559432114 83.13253115922213 353.08266205989304 +99.73561031724536 90.0 225.0 +279.59803029876787 45.819311820535574 166.36129272052352 +134.58444405678856 83.13253115922213 276.91733794010696 +9.598030298767851 45.819311820535574 76.36129272052355 +9.735610317245369 90.0 315.0 diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 79d674bcd..a4dc7c61d 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -1,6 +1,9 @@ +import os + import pytest import numpy as np +import damask from damask import Rotation from damask import Orientation @@ -11,6 +14,11 @@ def default(): """A set of n random rotations.""" return [Rotation.fromRandom() for r in range(n)] +@pytest.fixture +def reference_dir(reference_dir_base): + """Directory containing reference results.""" + return os.path.join(reference_dir_base,'Rotation') + class TestRotation: @@ -59,3 +67,13 @@ class TestRotation: misorientation = ori.rotation.misorientation(ori2.rotation) assert misorientation.asAxisAngle(degrees=True)[3]<1.0e-5 + @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) + @pytest.mark.parametrize('lattice',['fcc','bcc']) + def test_relationship_reference(self,update,reference_dir,model,lattice): + reference = os.path.join(reference_dir,'{}_{}.txt'.format(lattice,model)) + ori = Orientation(Rotation(),lattice) + eu = np.array([o.rotation.asEulers(degrees=True) for o in ori.relatedOrientations(model)]) + if update: + table = damask.Table(eu,{'Eulers':(3,)}) + table.to_ASCII(reference) + assert np.allclose(eu,damask.Table.from_ASCII(reference).get('Eulers')) From 5a9173ccf446b80dec3fdc755b8e035a77a347b4 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 21:57:07 +0100 Subject: [PATCH 041/148] might simplify plotting in MTEX --- python/tests/reference/Rotation/bcc_Bain.txt | 8 +-- python/tests/reference/Rotation/bcc_GT.txt | 50 +++++++++---------- .../tests/reference/Rotation/bcc_GT_prime.txt | 50 +++++++++---------- python/tests/reference/Rotation/bcc_KS.txt | 50 +++++++++---------- python/tests/reference/Rotation/bcc_NW.txt | 26 +++++----- .../tests/reference/Rotation/bcc_Pitsch.txt | 26 +++++----- python/tests/reference/Rotation/fcc_Bain.txt | 8 +-- python/tests/reference/Rotation/fcc_GT.txt | 50 +++++++++---------- .../tests/reference/Rotation/fcc_GT_prime.txt | 50 +++++++++---------- python/tests/reference/Rotation/fcc_KS.txt | 50 +++++++++---------- python/tests/reference/Rotation/fcc_NW.txt | 26 +++++----- .../tests/reference/Rotation/fcc_Pitsch.txt | 26 +++++----- python/tests/test_Rotation.py | 2 + 13 files changed, 212 insertions(+), 210 deletions(-) diff --git a/python/tests/reference/Rotation/bcc_Bain.txt b/python/tests/reference/Rotation/bcc_Bain.txt index e1675cd32..e0bc4f6c7 100644 --- a/python/tests/reference/Rotation/bcc_Bain.txt +++ b/python/tests/reference/Rotation/bcc_Bain.txt @@ -1,5 +1,5 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -0.0 45.00000000000001 0.0 -90.0 45.00000000000001 270.0 -45.00000000000001 0.0 0.0 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +0.0 45.00000000000001 0.0 1 1 +90.0 45.00000000000001 270.0 1 2 +45.00000000000001 0.0 0.0 1 3 diff --git a/python/tests/reference/Rotation/bcc_GT.txt b/python/tests/reference/Rotation/bcc_GT.txt index b4bfd5d8f..d1fe2e1c8 100644 --- a/python/tests/reference/Rotation/bcc_GT.txt +++ b/python/tests/reference/Rotation/bcc_GT.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -283.60440567265294 9.976439066337804 33.24637065555936 -167.8261034151001 43.397849654402556 183.40022280897963 -262.1156357053931 43.82007387041961 104.07478363123654 -103.604405672653 9.976439066337804 213.24637065555936 -347.8261034151001 43.39784965440255 3.400222808979685 -82.11563570539313 43.82007387041961 284.0747836312365 -76.39559432734703 9.976439066337806 326.75362934444064 -192.17389658489986 43.397849654402556 176.59977719102034 -97.88436429460687 43.82007387041961 255.92521636876344 -256.395594327347 9.976439066337804 146.75362934444064 -12.173896584899929 43.39784965440254 356.59977719102034 -277.8843642946069 43.82007387041961 75.92521636876346 -102.17389658489992 43.39784965440254 266.59977719102034 -346.395594327347 9.976439066337804 56.75362934444064 -7.884364294606862 43.82007387041961 345.9252163687635 -282.17389658489986 43.39784965440254 86.59977719102032 -166.39559432734703 9.976439066337804 236.75362934444058 -187.88436429460683 43.82007387041961 165.92521636876344 -257.8261034151001 43.39784965440255 93.40022280897969 -13.604405672652977 9.976439066337804 303.24637065555936 -352.1156357053931 43.82007387041961 14.074783631236542 -77.82610341510008 43.397849654402556 273.4002228089796 -193.60440567265297 9.976439066337806 123.24637065555939 -153.65751914298576 65.6559553854118 185.90444335627936 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +283.60440567265294 9.976439066337804 33.24637065555936 1 1 +167.8261034151001 43.397849654402556 183.40022280897963 1 2 +262.1156357053931 43.82007387041961 104.07478363123654 1 3 +103.604405672653 9.976439066337804 213.24637065555936 1 4 +347.8261034151001 43.39784965440255 3.400222808979685 1 5 +82.11563570539313 43.82007387041961 284.0747836312365 1 6 +76.39559432734703 9.976439066337806 326.75362934444064 1 7 +192.17389658489986 43.397849654402556 176.59977719102034 1 8 +97.88436429460687 43.82007387041961 255.92521636876344 1 9 +256.395594327347 9.976439066337804 146.75362934444064 1 10 +12.173896584899929 43.39784965440254 356.59977719102034 1 11 +277.8843642946069 43.82007387041961 75.92521636876346 1 12 +102.17389658489992 43.39784965440254 266.59977719102034 1 13 +346.395594327347 9.976439066337804 56.75362934444064 1 14 +7.884364294606862 43.82007387041961 345.9252163687635 1 15 +282.17389658489986 43.39784965440254 86.59977719102032 1 16 +166.39559432734703 9.976439066337804 236.75362934444058 1 17 +187.88436429460683 43.82007387041961 165.92521636876344 1 18 +257.8261034151001 43.39784965440255 93.40022280897969 1 19 +13.604405672652977 9.976439066337804 303.24637065555936 1 20 +352.1156357053931 43.82007387041961 14.074783631236542 1 21 +77.82610341510008 43.397849654402556 273.4002228089796 1 22 +193.60440567265297 9.976439066337806 123.24637065555939 1 23 +153.65751914298576 65.6559553854118 185.90444335627936 1 24 diff --git a/python/tests/reference/Rotation/bcc_GT_prime.txt b/python/tests/reference/Rotation/bcc_GT_prime.txt index 73c3bb6b3..42f32bcbb 100644 --- a/python/tests/reference/Rotation/bcc_GT_prime.txt +++ b/python/tests/reference/Rotation/bcc_GT_prime.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -303.24637065555936 9.976439066337804 13.604405672652977 -165.92521636876344 43.82007387041961 187.88436429460683 -266.59977719102034 43.39784965440254 102.17389658489992 -123.24637065555939 9.976439066337804 193.604405672653 -345.9252163687635 43.82007387041961 7.884364294606862 -86.59977719102032 43.39784965440254 282.17389658489986 -56.75362934444064 9.976439066337804 346.395594327347 -194.07478363123653 43.82007387041961 172.11563570539317 -93.40022280897969 43.39784965440255 257.8261034151001 -236.75362934444058 9.976439066337804 166.39559432734697 -14.074783631236542 43.82007387041961 352.1156357053931 -273.4002228089796 43.397849654402556 77.82610341510008 -104.07478363123654 43.82007387041961 262.1156357053931 -326.75362934444064 9.976439066337806 76.39559432734703 -3.400222808979685 43.39784965440255 347.8261034151001 -284.0747836312365 43.82007387041961 82.11563570539313 -146.75362934444064 9.976439066337804 256.395594327347 -183.40022280897963 43.397849654402556 167.8261034151001 -255.92521636876344 43.82007387041961 97.88436429460687 -33.24637065555936 9.976439066337804 283.60440567265294 -26.291675350407385 65.60048732963618 354.34378938496315 -75.92521636876346 43.82007387041961 277.8843642946069 -213.24637065555936 9.976439066337804 103.604405672653 -176.59977719102034 43.397849654402556 192.17389658489986 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +303.24637065555936 9.976439066337804 13.604405672652977 1 1 +165.92521636876344 43.82007387041961 187.88436429460683 1 2 +266.59977719102034 43.39784965440254 102.17389658489992 1 3 +123.24637065555939 9.976439066337804 193.604405672653 1 4 +345.9252163687635 43.82007387041961 7.884364294606862 1 5 +86.59977719102032 43.39784965440254 282.17389658489986 1 6 +56.75362934444064 9.976439066337804 346.395594327347 1 7 +194.07478363123653 43.82007387041961 172.11563570539317 1 8 +93.40022280897969 43.39784965440255 257.8261034151001 1 9 +236.75362934444058 9.976439066337804 166.39559432734697 1 10 +14.074783631236542 43.82007387041961 352.1156357053931 1 11 +273.4002228089796 43.397849654402556 77.82610341510008 1 12 +104.07478363123654 43.82007387041961 262.1156357053931 1 13 +326.75362934444064 9.976439066337806 76.39559432734703 1 14 +3.400222808979685 43.39784965440255 347.8261034151001 1 15 +284.0747836312365 43.82007387041961 82.11563570539313 1 16 +146.75362934444064 9.976439066337804 256.395594327347 1 17 +183.40022280897963 43.397849654402556 167.8261034151001 1 18 +255.92521636876344 43.82007387041961 97.88436429460687 1 19 +33.24637065555936 9.976439066337804 283.60440567265294 1 20 +26.291675350407385 65.60048732963618 354.34378938496315 1 21 +75.92521636876346 43.82007387041961 277.8843642946069 1 22 +213.24637065555936 9.976439066337804 103.604405672653 1 23 +176.59977719102034 43.397849654402556 192.17389658489986 1 24 diff --git a/python/tests/reference/Rotation/bcc_KS.txt b/python/tests/reference/Rotation/bcc_KS.txt index 0add535bb..34b393358 100644 --- a/python/tests/reference/Rotation/bcc_KS.txt +++ b/python/tests/reference/Rotation/bcc_KS.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -335.7965716606702 10.528779365509317 65.79657166067024 -228.77270547567446 80.40593177313953 85.64260312151849 -131.22729452432552 80.40593177313954 4.357396878481506 -24.20342833932977 10.52877936550932 24.20342833932976 -221.95489158457983 85.70366403943002 80.37863910890589 -138.04510841542015 85.70366403943004 9.621360891094124 -131.22729452432552 80.40593177313953 94.35739687848151 -24.203428339329765 10.52877936550932 114.20342833932976 -221.95489158457983 85.70366403943004 170.37863910890587 -138.04510841542015 85.70366403943004 99.62136089109411 -335.7965716606702 10.52877936550932 155.79657166067025 -228.77270547567448 80.40593177313954 175.6426031215185 -335.7965716606702 10.52877936550932 335.7965716606702 -228.77270547567448 80.40593177313954 355.6426031215185 -131.2272945243255 80.40593177313954 274.35739687848144 -24.203428339329747 10.52877936550932 294.2034283393298 -221.95489158457985 85.70366403943004 350.3786391089059 -138.04510841542015 85.70366403943004 279.6213608910941 -41.95489158457986 94.29633596056998 9.621360891094133 -318.04510841542015 94.29633596056996 80.37863910890589 -155.79657166067025 169.4712206344907 24.203428339329754 -48.77270547567448 99.59406822686046 4.357396878481504 -311.2272945243255 99.59406822686046 85.64260312151852 -204.20342833932975 169.4712206344907 65.79657166067024 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +335.7965716606702 10.528779365509317 65.79657166067024 1 1 +228.77270547567446 80.40593177313953 85.64260312151849 1 2 +131.22729452432552 80.40593177313954 4.357396878481506 1 3 +24.20342833932977 10.52877936550932 24.20342833932976 1 4 +221.95489158457983 85.70366403943002 80.37863910890589 1 5 +138.04510841542015 85.70366403943004 9.621360891094124 1 6 +131.22729452432552 80.40593177313953 94.35739687848151 1 7 +24.203428339329765 10.52877936550932 114.20342833932976 1 8 +221.95489158457983 85.70366403943004 170.37863910890587 1 9 +138.04510841542015 85.70366403943004 99.62136089109411 1 10 +335.7965716606702 10.52877936550932 155.79657166067025 1 11 +228.77270547567448 80.40593177313954 175.6426031215185 1 12 +335.7965716606702 10.52877936550932 335.7965716606702 1 13 +228.77270547567448 80.40593177313954 355.6426031215185 1 14 +131.2272945243255 80.40593177313954 274.35739687848144 1 15 +24.203428339329747 10.52877936550932 294.2034283393298 1 16 +221.95489158457985 85.70366403943004 350.3786391089059 1 17 +138.04510841542015 85.70366403943004 279.6213608910941 1 18 +41.95489158457986 94.29633596056998 9.621360891094133 1 19 +318.04510841542015 94.29633596056996 80.37863910890589 1 20 +155.79657166067025 169.4712206344907 24.203428339329754 1 21 +48.77270547567448 99.59406822686046 4.357396878481504 1 22 +311.2272945243255 99.59406822686046 85.64260312151852 1 23 +204.20342833932975 169.4712206344907 65.79657166067024 1 24 diff --git a/python/tests/reference/Rotation/bcc_NW.txt b/python/tests/reference/Rotation/bcc_NW.txt index 8d3dfbe84..76e7c6182 100644 --- a/python/tests/reference/Rotation/bcc_NW.txt +++ b/python/tests/reference/Rotation/bcc_NW.txt @@ -1,14 +1,14 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -225.41555594321144 83.13253115922213 83.08266205989301 -134.58444405678856 83.13253115922211 6.917337940107012 -4.702125169424418e-15 9.735610317245317 45.0 -134.58444405678856 83.13253115922213 276.91733794010696 -225.4155559432114 83.13253115922213 353.082662059893 -0.0 9.735610317245317 315.0 -134.58444405678858 83.13253115922213 96.91733794010702 -225.41555594321142 83.13253115922213 173.082662059893 -0.0 9.735610317245317 135.0 -260.40196970123213 45.81931182053556 283.6387072794765 -260.40196970123213 45.81931182053556 283.6387072794765 -180.0 99.73561031724535 225.0 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +225.41555594321144 83.13253115922213 83.08266205989301 1 1 +134.58444405678856 83.13253115922211 6.917337940107012 1 2 +4.702125169424418e-15 9.735610317245317 45.0 1 3 +134.58444405678856 83.13253115922213 276.91733794010696 1 4 +225.4155559432114 83.13253115922213 353.082662059893 1 5 +0.0 9.735610317245317 315.0 1 6 +134.58444405678858 83.13253115922213 96.91733794010702 1 7 +225.41555594321142 83.13253115922213 173.082662059893 1 8 +0.0 9.735610317245317 135.0 1 9 +260.40196970123213 45.81931182053556 283.6387072794765 1 10 +260.40196970123213 45.81931182053556 283.6387072794765 1 11 +180.0 99.73561031724535 225.0 1 12 diff --git a/python/tests/reference/Rotation/bcc_Pitsch.txt b/python/tests/reference/Rotation/bcc_Pitsch.txt index dc9926ca1..ef28bbb4d 100644 --- a/python/tests/reference/Rotation/bcc_Pitsch.txt +++ b/python/tests/reference/Rotation/bcc_Pitsch.txt @@ -1,14 +1,14 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -6.9173379401070045 83.13253115922213 44.58444405678856 -45.0 89.99999999999999 279.7356103172453 -166.36129272052352 45.819311820535574 279.59803029876787 -83.08266205989301 83.13253115922213 225.41555594321144 -256.3612927205235 45.819311820535574 189.59803029876787 -315.0 90.0 9.735610317245369 -186.917337940107 83.13253115922213 224.58444405678856 -315.0 90.0 80.26438968275463 -13.638707279476478 45.81931182053557 260.40196970123213 -263.082662059893 83.13253115922213 45.415555943211444 -103.63870727947646 45.819311820535574 170.40196970123213 -224.99999999999997 90.0 170.26438968275465 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +6.9173379401070045 83.13253115922213 44.58444405678856 1 1 +45.0 89.99999999999999 279.7356103172453 1 2 +166.36129272052352 45.819311820535574 279.59803029876787 1 3 +83.08266205989301 83.13253115922213 225.41555594321144 1 4 +256.3612927205235 45.819311820535574 189.59803029876787 1 5 +315.0 90.0 9.735610317245369 1 6 +186.917337940107 83.13253115922213 224.58444405678856 1 7 +315.0 90.0 80.26438968275463 1 8 +13.638707279476478 45.81931182053557 260.40196970123213 1 9 +263.082662059893 83.13253115922213 45.415555943211444 1 10 +103.63870727947646 45.819311820535574 170.40196970123213 1 11 +224.99999999999997 90.0 170.26438968275465 1 12 diff --git a/python/tests/reference/Rotation/fcc_Bain.txt b/python/tests/reference/Rotation/fcc_Bain.txt index 2c12eecc9..876cf3888 100644 --- a/python/tests/reference/Rotation/fcc_Bain.txt +++ b/python/tests/reference/Rotation/fcc_Bain.txt @@ -1,5 +1,5 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -180.0 45.00000000000001 180.0 -270.0 45.00000000000001 90.0 -315.0 0.0 0.0 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +180.0 45.00000000000001 180.0 1 1 +270.0 45.00000000000001 90.0 1 2 +315.0 0.0 0.0 1 3 diff --git a/python/tests/reference/Rotation/fcc_GT.txt b/python/tests/reference/Rotation/fcc_GT.txt index e695d0d6f..b91a80c46 100644 --- a/python/tests/reference/Rotation/fcc_GT.txt +++ b/python/tests/reference/Rotation/fcc_GT.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -146.75362934444064 9.976439066337804 256.395594327347 -356.59977719102034 43.39784965440254 12.173896584899929 -75.92521636876346 43.82007387041961 277.8843642946069 -326.75362934444064 9.976439066337806 76.39559432734703 -176.59977719102034 43.397849654402556 192.17389658489986 -255.92521636876344 43.82007387041961 97.88436429460687 -213.24637065555936 9.976439066337804 103.604405672653 -3.400222808979685 43.39784965440255 347.8261034151001 -284.0747836312365 43.82007387041961 82.11563570539313 -33.24637065555936 9.976439066337804 283.60440567265294 -183.40022280897963 43.397849654402556 167.8261034151001 -104.07478363123654 43.82007387041961 262.1156357053931 -273.4002228089796 43.397849654402556 77.82610341510008 -123.24637065555939 9.976439066337806 193.60440567265297 -194.07478363123653 43.82007387041961 172.11563570539317 -93.40022280897969 43.39784965440255 257.8261034151001 -303.24637065555936 9.976439066337804 13.604405672652977 -14.074783631236542 43.82007387041961 352.1156357053931 -86.59977719102032 43.39784965440254 282.17389658489986 -236.75362934444058 9.976439066337804 166.39559432734703 -165.92521636876344 43.82007387041961 187.88436429460683 -266.59977719102034 43.39784965440254 102.17389658489992 -56.75362934444064 9.976439066337804 346.395594327347 -354.0955566437206 65.6559553854118 26.342480857014277 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +146.75362934444064 9.976439066337804 256.395594327347 1 1 +356.59977719102034 43.39784965440254 12.173896584899929 1 2 +75.92521636876346 43.82007387041961 277.8843642946069 1 3 +326.75362934444064 9.976439066337806 76.39559432734703 1 4 +176.59977719102034 43.397849654402556 192.17389658489986 1 5 +255.92521636876344 43.82007387041961 97.88436429460687 1 6 +213.24637065555936 9.976439066337804 103.604405672653 1 7 +3.400222808979685 43.39784965440255 347.8261034151001 1 8 +284.0747836312365 43.82007387041961 82.11563570539313 1 9 +33.24637065555936 9.976439066337804 283.60440567265294 1 10 +183.40022280897963 43.397849654402556 167.8261034151001 1 11 +104.07478363123654 43.82007387041961 262.1156357053931 1 12 +273.4002228089796 43.397849654402556 77.82610341510008 1 13 +123.24637065555939 9.976439066337806 193.60440567265297 1 14 +194.07478363123653 43.82007387041961 172.11563570539317 1 15 +93.40022280897969 43.39784965440255 257.8261034151001 1 16 +303.24637065555936 9.976439066337804 13.604405672652977 1 17 +14.074783631236542 43.82007387041961 352.1156357053931 1 18 +86.59977719102032 43.39784965440254 282.17389658489986 1 19 +236.75362934444058 9.976439066337804 166.39559432734703 1 20 +165.92521636876344 43.82007387041961 187.88436429460683 1 21 +266.59977719102034 43.39784965440254 102.17389658489992 1 22 +56.75362934444064 9.976439066337804 346.395594327347 1 23 +354.0955566437206 65.6559553854118 26.342480857014277 1 24 diff --git a/python/tests/reference/Rotation/fcc_GT_prime.txt b/python/tests/reference/Rotation/fcc_GT_prime.txt index 7df7cb6c1..1d6f171c4 100644 --- a/python/tests/reference/Rotation/fcc_GT_prime.txt +++ b/python/tests/reference/Rotation/fcc_GT_prime.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -166.39559432734697 9.976439066337804 236.75362934444058 -352.1156357053931 43.82007387041961 14.074783631236542 -77.82610341510008 43.397849654402556 273.4002228089796 -346.395594327347 9.976439066337804 56.75362934444064 -172.11563570539317 43.82007387041961 194.07478363123653 -257.8261034151001 43.39784965440255 93.40022280897969 -193.604405672653 9.976439066337804 123.24637065555939 -7.884364294606862 43.82007387041961 345.9252163687635 -282.17389658489986 43.39784965440254 86.59977719102032 -13.604405672652977 9.976439066337804 303.24637065555936 -187.88436429460683 43.82007387041961 165.92521636876344 -102.17389658489992 43.39784965440254 266.59977719102034 -277.8843642946069 43.82007387041961 75.92521636876346 -103.604405672653 9.976439066337804 213.24637065555936 -192.17389658489986 43.397849654402556 176.59977719102034 -97.88436429460687 43.82007387041961 255.92521636876344 -283.60440567265294 9.976439066337804 33.24637065555936 -12.173896584899929 43.39784965440254 356.59977719102034 -82.11563570539313 43.82007387041961 284.0747836312365 -256.395594327347 9.976439066337804 146.75362934444064 -185.65621061503683 65.60048732963617 153.70832464959264 -262.1156357053931 43.82007387041961 104.07478363123654 -76.39559432734703 9.976439066337806 326.75362934444064 -347.8261034151001 43.39784965440255 3.400222808979685 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +166.39559432734697 9.976439066337804 236.75362934444058 1 1 +352.1156357053931 43.82007387041961 14.074783631236542 1 2 +77.82610341510008 43.397849654402556 273.4002228089796 1 3 +346.395594327347 9.976439066337804 56.75362934444064 1 4 +172.11563570539317 43.82007387041961 194.07478363123653 1 5 +257.8261034151001 43.39784965440255 93.40022280897969 1 6 +193.604405672653 9.976439066337804 123.24637065555939 1 7 +7.884364294606862 43.82007387041961 345.9252163687635 1 8 +282.17389658489986 43.39784965440254 86.59977719102032 1 9 +13.604405672652977 9.976439066337804 303.24637065555936 1 10 +187.88436429460683 43.82007387041961 165.92521636876344 1 11 +102.17389658489992 43.39784965440254 266.59977719102034 1 12 +277.8843642946069 43.82007387041961 75.92521636876346 1 13 +103.604405672653 9.976439066337804 213.24637065555936 1 14 +192.17389658489986 43.397849654402556 176.59977719102034 1 15 +97.88436429460687 43.82007387041961 255.92521636876344 1 16 +283.60440567265294 9.976439066337804 33.24637065555936 1 17 +12.173896584899929 43.39784965440254 356.59977719102034 1 18 +82.11563570539313 43.82007387041961 284.0747836312365 1 19 +256.395594327347 9.976439066337804 146.75362934444064 1 20 +185.65621061503683 65.60048732963617 153.70832464959264 1 21 +262.1156357053931 43.82007387041961 104.07478363123654 1 22 +76.39559432734703 9.976439066337806 326.75362934444064 1 23 +347.8261034151001 43.39784965440255 3.400222808979685 1 24 diff --git a/python/tests/reference/Rotation/fcc_KS.txt b/python/tests/reference/Rotation/fcc_KS.txt index 3a6e30358..93fdcf07e 100644 --- a/python/tests/reference/Rotation/fcc_KS.txt +++ b/python/tests/reference/Rotation/fcc_KS.txt @@ -1,26 +1,26 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -114.20342833932975 10.52877936550932 204.20342833932972 -94.3573968784815 80.40593177313954 311.22729452432543 -175.6426031215185 80.40593177313954 48.77270547567447 -155.79657166067025 10.52877936550932 155.79657166067025 -99.62136089109411 85.70366403943004 318.04510841542015 -170.37863910890587 85.70366403943002 41.954891584579855 -85.64260312151852 80.40593177313954 48.77270547567448 -65.79657166067024 10.52877936550932 155.79657166067025 -9.621360891094124 85.70366403943004 318.04510841542015 -80.37863910890587 85.70366403943004 41.95489158457987 -24.203428339329758 10.52877936550932 204.20342833932975 -4.357396878481486 80.40593177313954 311.2272945243255 -204.20342833932972 10.52877936550932 204.20342833932972 -184.35739687848147 80.40593177313954 311.2272945243255 -265.64260312151845 80.40593177313953 48.77270547567449 -245.79657166067025 10.528779365509317 155.79657166067025 -189.62136089109413 85.70366403943004 318.04510841542015 -260.3786391089059 85.70366403943002 41.954891584579855 -170.37863910890587 94.29633596056996 138.04510841542015 -99.62136089109411 94.29633596056998 221.95489158457983 -155.79657166067025 169.4712206344907 24.203428339329754 -175.64260312151848 99.59406822686046 131.22729452432552 -94.35739687848151 99.59406822686046 228.77270547567446 -114.20342833932975 169.4712206344907 335.7965716606702 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +114.20342833932975 10.52877936550932 204.20342833932972 1 1 +94.3573968784815 80.40593177313954 311.22729452432543 1 2 +175.6426031215185 80.40593177313954 48.77270547567447 1 3 +155.79657166067025 10.52877936550932 155.79657166067025 1 4 +99.62136089109411 85.70366403943004 318.04510841542015 1 5 +170.37863910890587 85.70366403943002 41.954891584579855 1 6 +85.64260312151852 80.40593177313954 48.77270547567448 1 7 +65.79657166067024 10.52877936550932 155.79657166067025 1 8 +9.621360891094124 85.70366403943004 318.04510841542015 1 9 +80.37863910890587 85.70366403943004 41.95489158457987 1 10 +24.203428339329758 10.52877936550932 204.20342833932975 1 11 +4.357396878481486 80.40593177313954 311.2272945243255 1 12 +204.20342833932972 10.52877936550932 204.20342833932972 1 13 +184.35739687848147 80.40593177313954 311.2272945243255 1 14 +265.64260312151845 80.40593177313953 48.77270547567449 1 15 +245.79657166067025 10.528779365509317 155.79657166067025 1 16 +189.62136089109413 85.70366403943004 318.04510841542015 1 17 +260.3786391089059 85.70366403943002 41.954891584579855 1 18 +170.37863910890587 94.29633596056996 138.04510841542015 1 19 +99.62136089109411 94.29633596056998 221.95489158457983 1 20 +155.79657166067025 169.4712206344907 24.203428339329754 1 21 +175.64260312151848 99.59406822686046 131.22729452432552 1 22 +94.35739687848151 99.59406822686046 228.77270547567446 1 23 +114.20342833932975 169.4712206344907 335.7965716606702 1 24 diff --git a/python/tests/reference/Rotation/fcc_NW.txt b/python/tests/reference/Rotation/fcc_NW.txt index bf3631db0..e041ec0b0 100644 --- a/python/tests/reference/Rotation/fcc_NW.txt +++ b/python/tests/reference/Rotation/fcc_NW.txt @@ -1,14 +1,14 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -96.91733794010702 83.13253115922213 314.5844440567886 -173.082662059893 83.13253115922211 45.41555594321143 -135.0 9.735610317245317 180.0 -263.082662059893 83.13253115922213 45.415555943211444 -186.91733794010702 83.13253115922211 314.5844440567886 -224.99999999999997 9.735610317245317 180.0 -83.082662059893 83.13253115922213 45.415555943211444 -6.917337940106983 83.13253115922211 314.5844440567886 -45.0 9.73561031724532 180.0 -256.36129272052347 45.81931182053556 279.59803029876775 -256.36129272052347 45.81931182053556 279.59803029876775 -315.0 99.73561031724536 0.0 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +96.91733794010702 83.13253115922213 314.5844440567886 1 1 +173.082662059893 83.13253115922211 45.41555594321143 1 2 +135.0 9.735610317245317 180.0 1 3 +263.082662059893 83.13253115922213 45.415555943211444 1 4 +186.91733794010702 83.13253115922211 314.5844440567886 1 5 +224.99999999999997 9.735610317245317 180.0 1 6 +83.082662059893 83.13253115922213 45.415555943211444 1 7 +6.917337940106983 83.13253115922211 314.5844440567886 1 8 +45.0 9.73561031724532 180.0 1 9 +256.36129272052347 45.81931182053556 279.59803029876775 1 10 +256.36129272052347 45.81931182053556 279.59803029876775 1 11 +315.0 99.73561031724536 0.0 1 12 diff --git a/python/tests/reference/Rotation/fcc_Pitsch.txt b/python/tests/reference/Rotation/fcc_Pitsch.txt index a2369f0a4..aa0c32365 100644 --- a/python/tests/reference/Rotation/fcc_Pitsch.txt +++ b/python/tests/reference/Rotation/fcc_Pitsch.txt @@ -1,14 +1,14 @@ 1 header -1_Eulers 2_Eulers 3_Eulers -135.41555594321144 83.13253115922213 173.082662059893 -260.26438968275465 90.0 135.0 -260.40196970123213 45.81931182053557 13.638707279476478 -314.5844440567886 83.13253115922213 96.91733794010702 -350.40196970123213 45.81931182053557 283.6387072794765 -170.26438968275465 90.0 224.99999999999997 -315.4155559432114 83.13253115922213 353.08266205989304 -99.73561031724536 90.0 225.0 -279.59803029876787 45.819311820535574 166.36129272052352 -134.58444405678856 83.13253115922213 276.91733794010696 -9.598030298767851 45.819311820535574 76.36129272052355 -9.735610317245369 90.0 315.0 +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +135.41555594321144 83.13253115922213 173.082662059893 1 1 +260.26438968275465 90.0 135.0 1 2 +260.40196970123213 45.81931182053557 13.638707279476478 1 3 +314.5844440567886 83.13253115922213 96.91733794010702 1 4 +350.40196970123213 45.81931182053557 283.6387072794765 1 5 +170.26438968275465 90.0 224.99999999999997 1 6 +315.4155559432114 83.13253115922213 353.08266205989304 1 7 +99.73561031724536 90.0 225.0 1 8 +279.59803029876787 45.819311820535574 166.36129272052352 1 9 +134.58444405678856 83.13253115922213 276.91733794010696 1 10 +9.598030298767851 45.819311820535574 76.36129272052355 1 11 +9.735610317245369 90.0 315.0 1 12 diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index a4dc7c61d..08d543554 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -74,6 +74,8 @@ class TestRotation: ori = Orientation(Rotation(),lattice) eu = np.array([o.rotation.asEulers(degrees=True) for o in ori.relatedOrientations(model)]) if update: + coords = np.array([(1,i+1) for i,x in enumerate(eu)]) table = damask.Table(eu,{'Eulers':(3,)}) + table.add('pos',coords) table.to_ASCII(reference) assert np.allclose(eu,damask.Table.from_ASCII(reference).get('Eulers')) From 0bf22fd03c9e5512d61b4954df9eac0733a0ecd6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 22:35:39 +0100 Subject: [PATCH 042/148] using central functionality --- processing/pre/geom_toTable.py | 45 ++++++++++++---------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/processing/pre/geom_toTable.py b/processing/pre/geom_toTable.py index ed33d9c85..3d955ad53 100755 --- a/processing/pre/geom_toTable.py +++ b/processing/pre/geom_toTable.py @@ -2,10 +2,8 @@ import os import sys -from optparse import OptionParser from io import StringIO - -import numpy as np +from optparse import OptionParser import damask @@ -24,38 +22,25 @@ Translate geom description into ASCIItable containing position and microstructur """, version = scriptID) (options, filenames) = parser.parse_args() - - if filenames == []: filenames = [None] for name in filenames: - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) - geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) + geom = damask.Geom.from_file(StringIO(''.join(sys.stdin.read())) if name is None else name) + damask.util.croak(geom) - damask.util.croak(geom) + coord0 = damask.grid_filters.cell_coord0(geom.grid,geom.size,geom.origin).reshape((-1,3),order='F') -# --- generate grid -------------------------------------------------------------------------------- + comments = geom.comments \ + + [scriptID + ' ' + ' '.join(sys.argv[1:]), + "grid\ta {}\tb {}\tc {}".format(*geom.grid), + "size\tx {}\ty {}\tz {}".format(*geom.size), + "origin\tx {}\ty {}\tz {}".format(*geom.origin), + "homogenization\t{}".format(geom.homogenization)] - grid = geom.get_grid() - size = geom.get_size() - origin = geom.get_origin() + table = damask.Table(coord0,{'pos':(3,)},comments) + table.add('microstructure',geom.microstructure.reshape((-1,1))) - x = (0.5 + np.arange(grid[0],dtype=float))/grid[0]*size[0]+origin[0] - y = (0.5 + np.arange(grid[1],dtype=float))/grid[1]*size[1]+origin[1] - z = (0.5 + np.arange(grid[2],dtype=float))/grid[2]*size[2]+origin[2] - - xx = np.tile( x, grid[1]* grid[2]) - yy = np.tile(np.repeat(y,grid[0] ),grid[2]) - zz = np.repeat(z,grid[0]*grid[1]) - -# --- create ASCII table -------------------------------------------------------------------------- - - table = damask.ASCIItable(outname = os.path.splitext(name)[0]+'.txt' if name else name) - table.info_append(geom.get_comments() + [scriptID + '\t' + ' '.join(sys.argv[1:])]) - table.labels_append(['{}_{}'.format(1+i,'pos') for i in range(3)]+['microstructure']) - table.head_write() - table.output_flush() - table.data = np.squeeze(np.dstack((xx,yy,zz,geom.microstructure.flatten('F'))),axis=0) - table.data_writeArray() - table.close() + table.to_ASCII(sys.stdout if name is None else \ + os.path.splitext(name)[0]+'.txt') From 1955d09d3f92a3f6ddcd3956689567942eb1ef8c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Dec 2019 22:35:58 +0100 Subject: [PATCH 043/148] modernizing --- processing/pre/geom_fromTable.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/processing/pre/geom_fromTable.py b/processing/pre/geom_fromTable.py index f513c4834..40fd17437 100755 --- a/processing/pre/geom_fromTable.py +++ b/processing/pre/geom_fromTable.py @@ -78,36 +78,15 @@ for name in filenames: table = damask.ASCIItable(name = name,readonly=True) table.head_read() # read ASCII header info -# ------------------------------------------ sanity checks --------------------------------------- - - coordDim = table.label_dimension(options.pos) - - errors = [] - if not 3 >= coordDim >= 2: - errors.append('coordinates "{}" need to have two or three dimensions.'.format(options.pos)) - if not np.all(table.label_dimension(label) == dim): - errors.append('input "{}" needs to have dimension {}.'.format(label,dim)) - if options.phase and table.label_dimension(options.phase) != 1: - errors.append('phase column "{}" is not scalar.'.format(options.phase)) - - if errors != []: - damask.util.croak(errors) - continue table.data_readArray([options.pos] \ + (label if isinstance(label, list) else [label]) \ + ([options.phase] if options.phase else [])) - if coordDim == 2: - table.data = np.insert(table.data,2,np.zeros(len(table.data)),axis=1) # add zero z coordinate for two-dimensional input if options.phase is None: table.data = np.column_stack((table.data,np.ones(len(table.data)))) # add single phase if no phase column given - grid,size = damask.util.coordGridAndSize(table.data[:,0:3]) - coords = [np.unique(table.data[:,i]) for i in range(3)] - mincorner = np.array(list(map(min,coords))) - origin = mincorner - 0.5*size/grid # shift from cell center to corner - + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.data[:,0:3]) indices = np.lexsort((table.data[:,0],table.data[:,1],table.data[:,2])) # indices of position when sorting x fast, z slow microstructure = np.empty(grid,dtype = int) # initialize empty microstructure @@ -142,7 +121,6 @@ for name in filenames: config_header += [''] for i,data in enumerate(unique): config_header += ['[Grain{}]'.format(i+1), - 'crystallite 1', '(constituent)\tphase {}\ttexture {}\tfraction 1.0'.format(int(data[4]),i+1), ] From 1fcbc356114e833653eb430b718d55032693d21c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:25:22 +0100 Subject: [PATCH 044/148] untested python2.7 code complicated code. Easier to re-implement from scratch if really needed --- processing/misc/yieldSurface.py | 1431 ------------------------- processing/misc/yieldSurfaceFast.py | 1513 --------------------------- 2 files changed, 2944 deletions(-) delete mode 100755 processing/misc/yieldSurface.py delete mode 100755 processing/misc/yieldSurfaceFast.py diff --git a/processing/misc/yieldSurface.py b/processing/misc/yieldSurface.py deleted file mode 100755 index 28f52062f..000000000 --- a/processing/misc/yieldSurface.py +++ /dev/null @@ -1,1431 +0,0 @@ -#!/usr/bin/env python2.7 -# -*- coding: UTF-8 no BOM -*- - -import threading,time,os -import numpy as np -from optparse import OptionParser -import damask -from damask.util import leastsqBound - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -def runFit(exponent, eqStress, dimension, criterion): - global threads, myFit, myLoad - global fitResidual - global Guess, dDim - - dDim = dimension - 3 - nParas = len(fitCriteria[criterion]['bound'][dDim]) - nExpo = fitCriteria[criterion]['nExpo'] - - if exponent > 0.0: # User defined exponents - nParas = nParas-nExpo - fitCriteria[criterion]['bound'][dDim] = fitCriteria[criterion]['bound'][dDim][:nParas] - - for i in range(nParas): - temp = fitCriteria[criterion]['bound'][dDim][i] - if fitCriteria[criterion]['bound'][dDim][i] == (None,None): - Guess.append(1.0) - else: - g = (temp[0]+temp[1])/2.0 - if g == 0: g = temp[1]*0.5 - Guess.append(g) - - myLoad = Loadcase(options.load[0],options.load[1],options.load[2], - nSet = 10, dimension = dimension, vegter = options.criterion=='vegter') - - - myFit = Criterion(exponent,eqStress, dimension, criterion) - for t in range(options.threads): - threads.append(myThread(t)) - threads[t].start() - - for t in range(options.threads): - threads[t].join() - damask.util.croak('Residuals') - damask.util.croak(fitResidual) - -def principalStresses(sigmas): - """ - Computes principal stresses (i.e. eigenvalues) for a set of Cauchy stresses. - - sorted in descending order. - """ - lambdas=np.zeros(0,'d') - for i in range(np.shape(sigmas)[1]): - eigenvalues = np.linalg.eigvalsh(sym6toT33(sigmas[:,i])) - lambdas = np.append(lambdas,np.sort(eigenvalues)[::-1]) #append eigenvalues in descending order - lambdas = np.transpose(lambdas.reshape(np.shape(sigmas)[1],3)) - return lambdas - -def principalStress(p): - I = invariant(p) - - I1s3I2= (I[0]**2 - 3.0*I[1])**0.5 - numer = 2.0*I[0]**3 - 9.0*I[0]*I[1] + 27.0*I[2] - denom = 2.0*I1s3I2**3 - cs = numer/denom - - phi = np.arccos(cs)/3.0 - t1 = I[0]/3.0; t2 = 2.0/3.0*I1s3I2 - return np.array( [t1 + t2*np.cos(phi), - t1 + t2*np.cos(phi+np.pi*2.0/3.0), - t1 + t2*np.cos(phi+np.pi*4.0/3.0)]) - -def principalStrs_Der(p, s, dim, Karafillis=False): - """Derivative of principal stress with respect to stress""" - third = 1.0/3.0 - third2 = 2.0*third - - I = invariant(p) - I1s3I2= np.sqrt(I[0]**2 - 3.0*I[1]) - numer = 2.0*I[0]**3 - 9.0*I[0]*I[1] + 27.0*I[2] - denom = 2.0*I1s3I2**3 - cs = numer/denom - phi = np.arccos(cs)/3.0 - - dphidcs = -third/np.sqrt(1.0 - cs**2) - dcsddenom = 0.5*numer*(-1.5)*I1s3I2**(-5.0) - dcsdI1 = (6.0*I[0]**2 - 9.0*I[1])*denom + dcsddenom*(2.0*I[0]) - dcsdI2 = ( - 9.0*I[0])*denom + dcsddenom*(-3.0) - dcsdI3 = 27.0*denom - dphidI1, dphidI2, dphidI3 = dphidcs*dcsdI1, dphidcs*dcsdI2, dphidcs*dcsdI3 - - dI1s3I2dI1 = I[0]/I1s3I2 - dI1s3I2dI2 = -1.5/I1s3I2 - tcoeff = third2*I1s3I2 - - dSidIj = lambda theta : ( tcoeff*(-np.sin(theta))*dphidI1 + third2*dI1s3I2dI1*np.cos(theta) + third, - tcoeff*(-np.sin(theta))*dphidI2 + third2*dI1s3I2dI2*np.cos(theta), - tcoeff*(-np.sin(theta))*dphidI3) - dSdI = np.array([dSidIj(phi),dSidIj(phi+np.pi*2.0/3.0),dSidIj(phi+np.pi*4.0/3.0)]) # i=1,2,3; j=1,2,3 - -# calculate the derivation of principal stress with regards to the anisotropic coefficients - one = np.ones_like(s); zero = np.zeros_like(s); num = len(s) - dIdp = np.array([[one, one, one, zero, zero, zero], - [p[1]+p[2], p[2]+p[0], p[0]+p[1], -2.0*p[3], -2.0*p[4], -2.0*p[5]], - [p[1]*p[2]-p[4]**2, p[2]*p[0]-p[5]**2, p[0]*p[1]-p[3]**2, - -2.0*p[3]*p[2]+2.0*p[4]*p[5], -2.0*p[4]*p[0]+2.0*p[5]*p[3], -2.0*p[5]*p[1]+2.0*p[3]*p[4]] ]) - if Karafillis: - dpdc = np.array([[zero,s[0]-s[2],s[0]-s[1]], [s[1]-s[2],zero,s[1]-s[0]], [s[2]-s[1],s[2]-s[0],zero]])/3.0 - dSdp = np.array([np.dot(dSdI[:,:,i],dIdp[:,:,i]).T for i in range(num)]).T - if dim == 2: - temp = np.vstack([dSdp[:,3]*s[3]]).T.reshape(num,1,3).T - else: - temp = np.vstack([dSdp[:,3]*s[3],dSdp[:,4]*s[4],dSdp[:,5]*s[5]]).T.reshape(num,3,3).T - - return np.concatenate((np.array([np.dot(dSdp[:,0:3,i], dpdc[:,:,i]).T for i in range(num)]).T, - temp), axis=1) - else: - if dim == 2: - dIdc=np.array([[-dIdp[i,0]*s[1], -dIdp[i,1]*s[0], -dIdp[i,1]*s[2], - -dIdp[i,2]*s[1], -dIdp[i,2]*s[0], -dIdp[i,0]*s[2], - dIdp[i,3]*s[3] ] for i in range(3)]) - else: - dIdc=np.array([[-dIdp[i,0]*s[1], -dIdp[i,1]*s[0], -dIdp[i,1]*s[2], - -dIdp[i,2]*s[1], -dIdp[i,2]*s[0], -dIdp[i,0]*s[2], - dIdp[i,3]*s[3], dIdp[i,4]*s[4], dIdp[i,5]*s[5] ] for i in range(3)]) - return np.array([np.dot(dSdI[:,:,i],dIdc[:,:,i]).T for i in range(num)]).T - -def invariant(sigmas): - I = np.zeros(3) - s11,s22,s33,s12,s23,s31 = sigmas - I[0] = s11 + s22 + s33 - I[1] = s11*s22 + s22*s33 + s33*s11 - s12**2 - s23**2 - s31**2 - I[2] = s11*s22*s33 + 2.0*s12*s23*s31 - s12**2*s33 - s23**2*s11 - s31**2*s22 - return I - -def math_ln(x): - return np.log(x + 1.0e-32) - -def sym6toT33(sym6): - """Shape the symmetric stress tensor(6) into (3,3)""" - return np.array([[sym6[0],sym6[3],sym6[5]], - [sym6[3],sym6[1],sym6[4]], - [sym6[5],sym6[4],sym6[2]]]) - -def t33toSym6(t33): - """Shape the stress tensor(3,3) into symmetric (6)""" - return np.array([ t33[0,0], - t33[1,1], - t33[2,2], - (t33[0,1] + t33[1,0])/2.0, # 0 3 5 - (t33[1,2] + t33[2,1])/2.0, # * 1 4 - (t33[2,0] + t33[0,2])/2.0,]) # * * 2 - -class Criteria(object): - def __init__(self, criterion, uniaxialStress,exponent, dimension): - self.stress0 = uniaxialStress - if exponent < 0.0: # Fitting exponent m - self.mFix = [False, exponent] - else: # fixed exponent m - self.mFix = [True, exponent] - self.func = fitCriteria[criterion]['func'] - self.criteria = criterion - self.dim = dimension - def fun(self, paras, ydata, sigmas): - return self.func(self.stress0, paras, sigmas,self.mFix,self.criteria,self.dim) - def jac(self, paras, ydata, sigmas): - return self.func(self.stress0, paras, sigmas,self.mFix,self.criteria,self.dim,Jac=True) - -class Vegter(object): - """Vegter yield criterion""" - - def __init__(self, refPts, refNormals,nspace=11): - self.refPts, self.refNormals = self._getRefPointsNormals(refPts, refNormals) - self.hingePts = self._getHingePoints() - self.nspace = nspace - def _getRefPointsNormals(self,refPtsQtr,refNormalsQtr): - if len(refPtsQtr) == 12: - refPts = refPtsQtr - refNormals = refNormalsQtr - else: - refPts = np.empty([13,2]) - refNormals = np.empty([13,2]) - refPts[12] = refPtsQtr[0] - refNormals[12] = refNormalsQtr[0] - for i in range(3): - refPts[i] = refPtsQtr[i] - refPts[i+3] = refPtsQtr[3-i][::-1] - refPts[i+6] =-refPtsQtr[i] - refPts[i+9] =-refPtsQtr[3-i][::-1] - refNormals[i] = refNormalsQtr[i] - refNormals[i+3] = refNormalsQtr[3-i][::-1] - refNormals[i+6] =-refNormalsQtr[i] - refNormals[i+9] =-refNormalsQtr[3-i][::-1] - return refPts,refNormals - - def _getHingePoints(self): - """ - Calculate the hinge point B according to the reference points A,C and the normals n,m - - refPoints = np.array([[p1_x, p1_y], [p2_x, p2_y]]); - refNormals = np.array([[n1_x, n1_y], [n2_x, n2_y]]) - """ - def hingPoint(points, normals): - A1 = points[0][0]; A2 = points[0][1] - C1 = points[1][0]; C2 = points[1][1] - n1 = normals[0][0]; n2 = normals[0][1] - m1 = normals[1][0]; m2 = normals[1][1] - B1 = (m2*(n1*A1 + n2*A2) - n2*(m1*C1 + m2*C2))/(n1*m2-m1*n2) - B2 = (n1*(m1*C1 + m2*C2) - m1*(n1*A1 + n2*A2))/(n1*m2-m1*n2) - return np.array([B1,B2]) - return np.array([hingPoint(self.refPts[i:i+2],self.refNormals[i:i+2]) for i in range(len(self.refPts)-1)]) - - def getBezier(self): - def bezier(R,H): - b = [] - for mu in np.linspace(0.0,1.0,self.nspace): - b.append(np.array(R[0]*np.ones_like(mu) + 2.0*mu*(H - R[0]) + mu**2*(R[0]+R[1] - 2.0*H))) - return b - return np.array([bezier(self.refPts[i:i+2],self.hingePts[i]) for i in range(len(self.refPts)-1)]) - -def VetgerCriterion(stress,lankford, rhoBi0, theta=0.0): - """0-pure shear; 1-uniaxial; 2-plane strain; 3-equi-biaxial""" - def getFourierParas(r): - # get the value after Fourier transformation - nset = len(r) - lmatrix = np.empty([nset,nset]) - theta = np.linspace(0.0,np.pi/2,nset) - for i,th in enumerate(theta): - lmatrix[i] = np.array([np.cos(2*j*th) for j in range(nset)]) - return np.linalg.solve(lmatrix, r) - - nps = len(stress) - if nps%4 != 0: - damask.util.croak('Warning: the number of stress points is uncorrect, stress points of %s are missing in set %i'%( - ['eq-biaxial, plane strain & uniaxial', 'eq-biaxial & plane strain','eq-biaxial'][nps%4-1],nps/4+1)) - else: - nset = nps/4 - strsSet = stress.reshape(nset,4,2) - refPts = np.empty([4,2]) - - fouriercoeffs = np.array([np.cos(2.0*i*theta) for i in range(nset)]) - for i in range(2): - refPts[3,i] = sum(strsSet[:,3,i])/nset - for j in range(3): - refPts[j,i] = np.dot(getFourierParas(strsSet[:,j,i]), fouriercoeffs) - - -def Tresca(eqStress=None, #not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None, #not needed/supported - dim=3, - Jac=False): - """ - Tresca yield criterion - - the fitted parameter is paras(sigma0) - """ - if not Jac: - lambdas = principalStresses(sigmas) - r = np.amax(np.array([abs(lambdas[2,:]-lambdas[1,:]),\ - abs(lambdas[1,:]-lambdas[0,:]),\ - abs(lambdas[0,:]-lambdas[2,:])]),0) - paras - return r.ravel() - else: - return -np.ones(len(sigmas)) - -def Cazacu_Barlat(eqStress=None, - paras=None, - sigmas=None, - mFix=None,#not needed/supported - criteria=None, - dim=3, #2D also possible - Jac=False): - """ - Cazacu-Barlat (CB) yield criterion - - the fitted parameters are: - a1,a2,a3,a6; b1,b2,b3,b4,b5,b10; c for plane stress - a1,a2,a3,a4,a5,a6; b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11; c: for general case - mFix is ignored - """ - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: - (a1,a2,a3,a4), (b1,b2,b3,b4,b5,b10), c = paras[0:4],paras[4:10],paras[10] - a5 = a6 = b6 = b7 = b8 = b9 = b11 = 0.0 - s33 = s23 = s31 = np.zeros_like(s11) - else: - (a1,a2,a3,a4,a5,a6), (b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11), c = paras[0:6],paras[6:17],paras[17] - - s1_2, s2_2, s3_2, s12_2, s23_2, s31_2 = np.array([s11,s22,s33,s12,s23,s31])**2 - s1_3, s2_3, s3_3, s123, s321 = s11*s1_2, s22*s2_2, s33*s3_2,s11*s22*s33, s12*s23*s31 - d12_2,d23_2,d31_2 = (s11-s22)**2, (s22-s33)**2, (s33-s11)**2 - - J20 = ( a1*d12_2 + a2*d23_2 + a3*d31_2 )/6.0 + a4*s12_2 + a5*s23_2 + a6*s31_2 - J30 = ( (b1 +b2 )*s1_3 + (b3 +b4 )*s2_3 + ( b1+b4-b2 + b1+b4-b3 )*s3_3 )/27.0- \ - ( (b1*s22+b2*s33)*s1_2 + (b3*s33+b4*s11)*s2_2 + ((b1+b4-b2)*s11 + (b1+b4-b3)*s22)*s3_2 )/9.0 + \ - ( (b1+b4)*s123/9.0 + b11*s321 )*2.0 - \ - ( ( 2.0*b9 *s22 - b8*s33 - (2.0*b9 -b8)*s11 )*s31_2 + - ( 2.0*b10*s33 - b5*s22 - (2.0*b10-b5)*s11 )*s12_2 + - ( (b6+b7)*s11 - b6*s22 - b7*s33 )*s23_2 - )/3.0 - f0 = J20**3 - c*J30**2 - r = f0**(1.0/6.0)*np.sqrt(3.0)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - drdf = r/f0/6.0 - dj2, dj3 = drdf*3.0*J20**2, -drdf*2.0*J30*c - jc = -drdf*J30**2 - - ja1,ja2,ja3 = dj2*d12_2/6.0, dj2*d23_2/6.0, dj2*d31_2/6.0 - ja4,ja5,ja6 = dj2*s12_2, dj2*s23_2, dj2*s31_2 - jb1 = dj3*( (s1_3 + 2.0*s3_3)/27.0 - s22*s1_2/9.0 - (s11+s22)*s3_2/9.0 + s123/4.5 ) - jb2 = dj3*( (s1_3 - s3_3)/27.0 - s33*s1_2/9.0 + s11 *s3_2/9.0 ) - jb3 = dj3*( (s2_3 - s3_3)/27.0 - s33*s2_2/9.0 + s22 *s3_2/9.0 ) - jb4 = dj3*( (s2_3 + 2.0*s3_3)/27.0 - s11*s2_2/9.0 - (s11+s22)*s3_2/9.0 + s123/4.5 ) - - jb5, jb10 = dj3*(s22 - s11)*s12_2/3.0, dj3*(s11 - s33)*s12_2/1.5 - jb6, jb7 = dj3*(s22 - s11)*s23_2/3.0, dj3*(s33 - s11)*s23_2/3.0 - jb8, jb9 = dj3*(s33 - s11)*s31_2/3.0, dj3*(s11 - s22)*s31_2/1.5 - jb11 = dj3*s321*2.0 - if dim == 2: - return np.vstack((ja1,ja2,ja3,ja4,jb1,jb2,jb3,jb4,jb5,jb10,jc)).T - else: - return np.vstack((ja1,ja2,ja3,ja4,ja5,ja6,jb1,jb2,jb3,jb4,jb5,jb6,jb7,jb8,jb9,jb10,jb11,jc)).T - -def Drucker(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None, - dim=3, - Jac=False): - """ - Drucker yield criterion - - the fitted parameters are - sigma0, C_D for Drucker(p=1); - sigma0, C_D, p for general Drucker - eqStress, mFix are invalid inputs - """ - if criteria == 'drucker': - sigma0, C_D= paras - p = 1.0 - else: - sigma0, C_D = paras[0:2] - if mFix[0]: p = mFix[1] - else: p = paras[-1] - I = invariant(sigmas) - J = np.zeros([3]) - J[1] = I[0]**2/3.0 - I[1] - J[2] = I[0]**3/13.5 - I[0]*I[1]/3.0 + I[2] - J2_3p = J[1]**(3.0*p) - J3_2p = J[2]**(2.0*p) - left = J2_3p - C_D*J3_2p - r = left**(1.0/(6.0*p))*3.0**0.5/sigma0 - - if not Jac: - return (r - 1.0).ravel() - else: - drdl = r/left/(6.0*p) - if criteria == 'drucker': - return np.vstack((-r/sigma0, -drdl*J3_2p)).T - else: - dldp = 3.0*J2_3p*math_ln(J[1]) - 2.0*C_D*J3_2p*math_ln(J[2]) - jp = drdl*dldp + r*math_ln(left)/(-6.0*p*p) - - if mFix[0]: return np.vstack((-r/sigma0, -drdl*J3_2p)).T - else: return np.vstack((-r/sigma0, -drdl*J3_2p, jp)).T - -def Hill1948(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None,#not needed/supported - dim=3, - Jac=False): - """ - Hill 1948 yield criterion - - the fitted parameters are: - F, G, H, L, M, N for 3D - F, G, H, N for 2D - """ - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: # plane stress - jac = np.array([ s22**2, s11**2, (s11-s22)**2, 2.0*s12**2]) - else: # general case - jac = np.array([(s22-s33)**2,(s33-s11)**2,(s11-s22)**2, 2.0*s23**2,2.0*s31**2,2.0*s12**2]) - - if not Jac: - return (np.dot(paras,jac)/2.0-0.5).ravel() - else: - return jac.T - -def Hill1979(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, - criteria=None,#not needed/supported - dim=3, - Jac=False): - """ - Hill 1979 yield criterion - - the fitted parameters are: f,g,h,a,b,c,m - """ - if mFix[0]: - m = mFix[1] - else: - m = paras[-1] - - coeff = paras[0:6] - s = principalStresses(sigmas) - diffs = np.array([s[1]-s[2], s[2]-s[0], s[0]-s[1],\ - 2.0*s[0]-s[1]-s[2], 2.0*s[1]-s[2]-s[0], 2.0*s[2]-s[0]-s[1]])**2 - - diffsm = diffs**(m/2.0) - left = np.dot(coeff,diffsm) - r = (0.5*left)**(1.0/m)/eqStress #left = base**mi - - if not Jac: - return (r-1.0).ravel() - else: - drdl, dldm = r/left/m, np.dot(coeff,diffsm*math_ln(diffs))*0.5 - jm = drdl*dldm + r*math_ln(0.5*left)*(-1.0/m/m) #/(-m**2) - - if mFix[0]: return np.vstack((drdl*diffsm)).T - else: return np.vstack((drdl*diffsm, jm)).T - -def Hosford(eqStress=None, - paras=None, - sigmas=None, - mFix=None, - criteria=None, - dim=3, - Jac=False): - """ - Hosford family criteria - - the fitted parameters are: - von Mises: sigma0 - Hershey: (1) sigma0, a, when a is not fixed; (2) sigma0, when a is fixed - general Hosford: (1) F,G,H, a, when a is not fixed; (2) F,G,H, when a is fixed - """ - if criteria == 'vonmises': - sigma0 = paras - coeff = np.ones(3) - a = 2.0 - elif criteria == 'hershey': - sigma0 = paras[0] - coeff = np.ones(3) - if mFix[0]: a = mFix[1] - else: a = paras[1] - else: - sigma0 = eqStress - coeff = paras[0:3] - if mFix[0]: a = mFix[1] - else: a = paras[3] - - s = principalStresses(sigmas) - diffs = np.array([s[1]-s[2], s[2]-s[0], s[0]-s[1]])**2 - diffsm = diffs**(a/2.0) - left = np.dot(coeff,diffsm) - r = (0.5*left)**(1.0/a)/sigma0 - - if not Jac: - return (r-1.0).ravel() - else: - if criteria == 'vonmises': # von Mises - return -r/sigma0 - else: - drdl, dlda = r/left/a, np.dot(coeff,diffsm*math_ln(diffs))*0.5 - ja = drdl*dlda + r*math_ln(0.5*left)*(-1.0/a/a) - if criteria == 'hershey': # Hershey - if mFix[0]: return -r/sigma0 - else: return np.vstack((-r/sigma0, ja)).T - else: # Anisotropic Hosford - if mFix[0]: return np.vstack((drdl*diffsm)).T - else: return np.vstack((drdl*diffsm, ja)).T - -def Barlat1989(eqStress=None, - paras=None, - sigmas=None, - mFix=None, - criteria=None, - dim=3, - Jac=False): - """ - Barlat-Lian 1989 yield criteria - - the fitted parameters are: - Anisotropic: a, h, p, m; m is optional - """ - a, h, p = paras[0:3] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - c = 2.0-a - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k1,k2 = 0.5*(s11 + h*s22), (0.25*(s11 - h*s22)**2 + (p*s12)**2)**0.5 - fs = np.array([ (k1+k2)**2, (k1-k2)**2, 4.0*k2**2 ]); fm = fs**(m/2.0) - left = np.dot(np.array([a,a,c]),fm) - r = (0.5*left)**(1.0/m)/eqStress - - if not Jac: - return (r-1.0).ravel() - else: - dk1dh = 0.5*s22 - dk2dh, dk2dp = 0.25*(s11-h*s22)*(-s22)/k2, p*s12**2/k2 - dlda, dldc = fm[0]+fm[1], fm[2] - fm1 = fs**(m/2.0-1.0)*m - dldk1, dldk2 = a*fm1[0]*(k1+k2)+a*fm1[1]*(k1-k2), a*fm1[0]*(k1+k2)-a*fm1[1]*(k1-k2)+c*fm1[2]*k2*4.0 - drdl, drdm = r/m/left, r*math_ln(0.5*left)*(-1.0/m/m) - dldm = np.dot(np.array([a,a,c]),fm*math_ln(fs))*0.5 - - ja,jc = drdl*dlda, drdl*dldc - jh,jp = drdl*(dldk1*dk1dh + dldk2*dk2dh), drdl*dldk2*dk2dp - jm = drdl*dldm + drdm - - if mFix[0]: return np.vstack((ja,jc,jh,jp)).T - else: return np.vstack((ja,jc,jh,jp,jm)).T - -def Barlat1991(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Barlat 1991 criteria - - the fitted parameters are: - Anisotropic: a, b, c, f, g, h, m for 3D - a, b, c, h, m for plane stress - m is optional - """ - if dim == 2: coeff = paras[0:4] # plane stress - else: coeff = paras[0:6] # general case - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: - dXdx = np.array([s22,-s11,s11-s22,s12]) - A,B,C,H = np.array(coeff)[:,None]*dXdx; F=G=0.0 - else: - dXdx = np.array([s22-s33,s33-s11,s11-s22,s23,s31,s12]) - A,B,C,F,G,H = np.array(coeff)[:,None]*dXdx - - I2 = (F*F + G*G + H*H)/3.0+ ((A-C)**2+(C-B)**2+(B-A)**2)/54.0 - I3 = (C-B)*(A-C)*(B-A)/54.0 + F*G*H - ((C-B)*F*F + (A-C)*G*G + (B-A)*H*H)/6.0 - phi1 = np.arccos(I3/I2**1.5)/3.0 + np.pi/6.0; absc1 = 2.0*np.abs(np.cos(phi1)) - phi2 = phi1 + np.pi/3.0; absc2 = 2.0*np.abs(np.cos(phi2)) - phi3 = phi2 + np.pi/3.0; absc3 = 2.0*np.abs(np.cos(phi3)) - left = ( absc1**m + absc2**m + absc3**m ) - r = (0.5*left)**(1.0/m)*np.sqrt(3.0*I2)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - dfdl = r/left/m - jm = r*math_ln(0.5*left)*(-1.0/m/m) + dfdl*0.5*( - absc1**m*math_ln(absc1) + absc2**m*math_ln(absc2) + absc3**m*math_ln(absc3) ) - - da,db,dc = (2.0*A-B-C)/18.0, (2.0*B-C-A)/18.0, (2.0*C-A-B)/18.0 - if dim == 2: - dI2dx = np.array([da, db, dc, H])/1.5*dXdx - dI3dx = np.array([ da*(B-C) + (H**2-G**2)/2.0, - db*(C-A) + (F**2-H**2)/2.0, - dc*(A-B) + (G**2-F**2)/2.0, - (G*F + (A-B))*H ])/3.0*dXdx - else: - dI2dx = np.array([da, db, dc, F,G,H])/1.5*dXdx - dI3dx = np.array([ da*(B-C) + (H**2-G**2)/2.0, - db*(C-A) + (F**2-H**2)/2.0, - dc*(A-B) + (G**2-F**2)/2.0, - (H*G*3.0 + (B-C))*F, - (F*H*3.0 + (C-A))*G, - (G*F*3.0 + (A-B))*H ])/3.0*dXdx - darccos = -1.0/np.sqrt(1.0 - I3**2/I2**3) - - dfdcos = lambda phi : dfdl*m*(2.0*abs(np.cos(phi)))**(m-1.0)*np.sign(np.cos(phi))*(-np.sin(phi)/1.5) - - dfdthe= (dfdcos(phi1) + dfdcos(phi2) + dfdcos(phi3)) - dfdI2, dfdI3 = dfdthe*darccos*I3*(-1.5)*I2**(-2.5)+r/2.0/I2, dfdthe*darccos*I2**(-1.5) - - if mFix[0]: return np.vstack((dfdI2*dI2dx + dfdI3*dI3dx)).T - else: return np.vstack((dfdI2*dI2dx + dfdI3*dI3dx, jm)).T - -def BBC2000(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2000 yield criterion - - the fitted parameters are - d,e,f,g, b,c,a, k; k is optional - criteria are invalid input - """ - d,e,f,g, b,c,a= paras[0:7] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k2 = 2.0*k; k1 = k - 1.0 - M,N,P,Q,R = d+e, e+f, (d-e)/2.0, (e-f)/2.0, g**2 - Gamma = M*s11 + N*s22 - Psi = ( (P*s11 + Q*s22)**2 + s12**2*R )**0.5 - - l1, l2, l3 = b*Gamma + c*Psi, b*Gamma - c*Psi, 2.0*c*Psi - l1s,l2s,l3s = l1**2, l2**2, l3**2 - - left = a*l1s**k + a*l2s**k + (1-a)*l3s**k - r = left**(1.0/k2)/eqStress - if not Jac: - return (r - 1.0).ravel() - else: - drdl,drdk = r/left/k2, r*math_ln(left)*(-1.0/k2/k) - dldl1,dldl2,dldl3 = a*k2*(l1s**k1)*l1, a*k2*(l2s**k1)*l2, (1-a)*k2*(l3s**k1)*l3 - dldGama, dldPsi = (dldl1 + dldl2)*b, (dldl1 - dldl2 + 2.0*dldl3)*c - temp = (P*s11 + Q*s22)/Psi - dPsidP, dPsidQ, dPsidR = temp*s11, temp*s22, 0.5*s12**2/Psi - dlda = l1s**k + l2s**k - l3s**k - dldb = dldl1*Gamma + dldl2*Gamma - dldc = dldl1*Psi - dldl2*Psi + dldl3*2.0*Psi - dldk = a*math_ln(l1s)*l1s**k + a*math_ln(l2s)*l2s**k + (1-a)*math_ln(l3s)*l3s**k - - J = drdl*np.array([dldGama*s11+dldPsi*dPsidP*0.5, dldGama*(s11+s22)+dldPsi*(-dPsidP+dPsidQ)*0.5, #jd,je - dldGama*s22-dldPsi*dPsidQ*0.5, dldPsi*dPsidR*2.0*g, #jf,jg - dldb, dldc, dlda]) #jb,jc,ja - if mFix[0]: return np.vstack(J).T - else: return np.vstack((J, drdl*dldk + drdk)).T - - -def BBC2003(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2003 yield criterion - - the fitted parameters are - M,N,P,Q,R,S,T,a, k; k is optional - criteria are invalid input - """ - M,N,P,Q,R,S,T,a = paras[0:8] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k2 = 2.0*k; k1 = k - 1.0 - Gamma = 0.5 * (s11 + M*s22) - Psi = ( 0.25*(N*s11 - P*s22)**2 + Q*Q*s12**2 )**0.5 - Lambda = ( 0.25*(R*s11 - S*s22)**2 + T*T*s12**2 )**0.5 - - l1, l2, l3 = Gamma + Psi, Gamma - Psi, 2.0*Lambda - l1s,l2s,l3s = l1**2, l2**2, l3**2 - left = a*l1s**k + a*l2s**k + (1-a)*l3s**k - r = left**(1.0/k2)/eqStress - if not Jac: - return (r - 1.0).ravel() - else: - drdl,drdk = r/left/k2, r*math_ln(left)*(-1.0/k2/k) - dldl1,dldl2,dldl3 = a*k2*(l1s**k1)*l1, a*k2*(l2s**k1)*l2, (1-a)*k2*(l3s**k1)*l3 - - dldGamma, dldPsi, dldLambda = dldl1+dldl2, dldl1-dldl2, 2.0*dldl3 - temp = 0.25/Psi*(N*s11 - P*s22) - dPsidN, dPsidP, dPsidQ = s11*temp, -s22*temp, Q*s12**2/Psi - temp = 0.25/Lambda*(R*s11 - S*s22) - dLambdadR, dLambdadS, dLambdadT = s11*temp, -s22*temp, T*s12**2/Psi - dldk = a*math_ln(l1s)*l1s**k + a*math_ln(l2s)*l2s**k + (1-a)*math_ln(l3s)*l3s**k - - J = drdl * np.array([dldGamma*s22*0.5, #jM - dldPsi*dPsidN, dldPsi*dPsidP, dldPsi*dPsidQ, #jN, jP, jQ - dldLambda*dLambdadR, dldLambda*dLambdadS, dldLambda*dLambdadT, #jR, jS, jT - l1s**k + l2s**k - l3s**k ]) #ja - - if mFix[0]: return np.vstack(J).T - else : return np.vstack((J, drdl*dldk+drdk)).T - -def BBC2005(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2005 yield criterion - - the fitted parameters are - a, b, L ,M, N, P, Q, R, k k are optional - criteria is invalid input - """ - a,b,L, M, N, P, Q, R = paras[0:8] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11 = sigmas[0]; s22 = sigmas[1]; s12 = sigmas[3] - k2 = 2.0*k - Gamma = L*s11 + M*s22 - Lambda = ( (N*s11 - P*s22)**2 + s12**2 )**0.5 - Psi = ( (Q*s11 - R*s22)**2 + s12**2 )**0.5 - - l1 = Lambda + Gamma; l2 = Lambda - Gamma; l3 = Lambda + Psi; l4 = Lambda - Psi - l1s = l1**2; l2s = l2**2; l3s = l3**2; l4s = l4**2 - left = a*l1s**k + a*l2s**k + b*l3s**k + b*l4s**k - sBar = left**(1.0/k2); r = sBar/eqStress - 1.0 - if not Jac: - return r.ravel() - else: - ln = lambda x : np.log(x + 1.0e-32) - expo = 0.5/k; k1 = k-1.0 - - dsBardl = expo*sBar/left/eqStress - dsBarde = sBar*ln(left); dedk = expo/(-k) - dldl1 = a*k*(l1s**k1)*(2.0*l1) - dldl2 = a*k*(l2s**k1)*(2.0*l2) - dldl3 = b*k*(l3s**k1)*(2.0*l3) - dldl4 = b*k*(l4s**k1)*(2.0*l4) - - dldLambda = dldl1 + dldl2 + dldl3 + dldl4 - dldGama = dldl1 - dldl2 - dldPsi = dldl3 - dldl4 - temp = (N*s11 - P*s22)/Lambda - dLambdadN = s11*temp; dLambdadP = -s22*temp - temp = (Q*s11 - R*s22)/Psi - dPsidQ = s11*temp; dPsidR = -s22*temp - dldk = a*ln(l1s)*l1s**k + a*ln(l2s)*l2s**k + b*ln(l3s)*l3s**k + b*ln(l4s)*l4s**k - - J = dsBardl * np.array( [ - l1s**k+l2s**k, l3s**k+l4s**k,dldGama*s11,dldGama*s22,dldLambda*dLambdadN, - dldLambda*dLambdadP, dldPsi*dPsidQ, dldPsi*dPsidR]) - - if mFix[0]: return np.vstack(J).T - else : return np.vstack(J, dldk+dsBarde*dedk).T - -def Yld2000(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Yld2000 yield criterion - - C: c11,c22,c66 c12=c21=1.0 JAC NOT PASS - D: d11,d12,d21,d22,d66 - """ - C,D = paras[0:3], paras[3:8] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - s11, s22, s12 = sigmas[0],sigmas[1],sigmas[3] - X = np.array([ 2.0*C[0]*s11-C[0]*s22, 2.0*C[1]*s22-C[1]*s11, 3.0*C[2]*s12 ])/3.0 # a1,a2,a7 - Y = np.array([ (8.0*D[2]-2.0*D[0]-2.0*D[3]+2.0*D[1])*s11 + (4.0*D[3]-4.0*D[1]-4.0*D[2]+ D[0])*s22, - (4.0*D[0]-4.0*D[2]-4.0*D[1]+ D[3])*s11 + (8.0*D[1]-2.0*D[3]-2.0*D[0]+2.0*D[2])*s22, - 9.0*D[4]*s12 ])/9.0 - - def priStrs(s): - temp = np.sqrt( (s[0]-s[1])**2 + 4.0*s[2]**2 ) - return 0.5*(s[0]+s[1] + temp), 0.5*(s[0]+s[1] - temp) - m2 = m/2.0; m21 = m2 - 1.0 - (X1,X2), (Y1,Y2) = priStrs(X), priStrs(Y) # Principal values of X, Y - phi1s, phi21s, phi22s = (X1-X2)**2, (2.0*Y2+Y1)**2, (2.0*Y1+Y2)**2 - phi1, phi21, phi22 = phi1s**m2, phi21s**m2, phi22s**m2 - left = phi1 + phi21 + phi22 - r = (0.5*left)**(1.0/m)/eqStress - - if not Jac: - return (r-1.0).ravel() - else: - drdl, drdm = r/m/left, r*math_ln(0.5*left)*(-1.0/m/m) #/(-m*m) - dldm = ( phi1*math_ln(phi1s) + phi21*math_ln(phi21s) + phi22*math_ln(phi22s) )*0.5 - zero = np.zeros_like(s11); num = len(s11) - def dPrincipalds(X): - """Derivative of principla with respect to stress""" - temp = 1.0/np.sqrt( (X[0]-X[1])**2 + 4.0*X[2]**2 ) - dP1dsi = 0.5*np.array([ 1.0+temp*(X[0]-X[1]), 1.0-temp*(X[0]-X[1]), temp*4.0*X[2]]) - dP2dsi = 0.5*np.array([ 1.0-temp*(X[0]-X[1]), 1.0+temp*(X[0]-X[1]), -temp*4.0*X[2]]) - return np.array([dP1dsi, dP2dsi]) - - dXdXi, dYdYi = dPrincipalds(X), dPrincipalds(Y) - dXidC = np.array([ [ 2.0*s11-s22, zero, zero ], #dX11dC - [ zero, 2.0*s22-s11, zero ], #dX22dC - [ zero, zero, 3.0*s12 ] ])/3.0 #dX12dC - dYidD = np.array([ [ -2.0*s11+ s22, 2.0*s11-4.0*s22, 8.0*s11-4.0*s22, -2.0*s11+4.0*s22, zero ], #dY11dD - [ 4.0*s11-2.0*s22, -4.0*s11+8.0*s22, -4.0*s11+2.0*s22, s11-2.0*s22, zero ], #dY22dD - [ zero, zero, zero, zero, 9.0*s12 ] ])/9.0 #dY12dD - - dXdC=np.array([np.dot(dXdXi[:,:,i], dXidC[:,:,i]).T for i in range(num)]).T - dYdD=np.array([np.dot(dYdYi[:,:,i], dYidD[:,:,i]).T for i in range(num)]).T - - dldX = m*np.array([ phi1s**m21*(X1-X2), phi1s**m21*(X2-X1)]) - dldY = m*np.array([phi21s**m21*(2.0*Y2+Y1) + 2.0*phi22s**m21*(2.0*Y1+Y2), \ - phi22s**m21*(2.0*Y1+Y2) + 2.0*phi21s**m21*(2.0*Y2+Y1) ]) - jC = drdl*np.array([np.dot(dldX[:,i], dXdC[:,:,i]) for i in range(num)]).T - jD = drdl*np.array([np.dot(dldY[:,i], dYdD[:,:,i]) for i in range(num)]).T - - jm = drdl*dldm + drdm - if mFix[0]: return np.vstack((jC,jD)).T - else: return np.vstack((jC,jD,jm)).T - -def Yld200418p(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Yld2004-18p yield criterion - - the fitted parameters are - C: c12,c21,c23,c32,c31,c13,c44,c55,c66; D: d12,d21,d23,d32,d31,d13,d44,d55,d66 for 3D - C: c12,c21,c23,c32,c31,c13,c44; D: d12,d21,d23,d32,d31,d13,d44 for 2D - and m, m are optional - criteria is ignored - """ - if dim == 2: C,D = np.append(paras[0:7],[0.0,0.0]), np.append(paras[7:14],[0.0,0.0]) - else: C,D = paras[0:9], paras[9:18] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - sv = (sigmas[0] + sigmas[1] + sigmas[2])/3.0 - sdev = np.vstack((sigmas[0:3]-sv,sigmas[3:6])) - ys = lambda sdev, C: np.array([-C[0]*sdev[1]-C[5]*sdev[2], -C[1]*sdev[0]-C[2]*sdev[2], - -C[4]*sdev[0]-C[3]*sdev[1], C[6]*sdev[3], C[7]*sdev[4], C[8]*sdev[5]]) - p,q = ys(sdev, C), ys(sdev, D) - pLambdas, qLambdas = principalStress(p), principalStress(q) # no sort - - m2 = m/2.0; x3 = range(3); num = len(sv) - PiQj = np.array([(pLambdas[i,:]-qLambdas[j,:]) for i in x3 for j in x3]) - QiPj = np.array([(qLambdas[i,:]-pLambdas[j,:]) for i in x3 for j in x3]).reshape(3,3,num) - PiQjs = PiQj**2 - left = np.sum(PiQjs**m2,axis=0) - r = (0.25*left)**(1.0/m)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - drdl, drdm = r/m/left, r*math_ln(0.25*left)*(-1.0/m/m) - dldm = np.sum(PiQjs**m2*math_ln(PiQjs),axis=0)*0.5 - dPdc, dQdd = principalStrs_Der(p, sdev, dim), principalStrs_Der(q, sdev, dim) - PiQjs3d = ( PiQjs**(m2-1.0) ).reshape(3,3,num) - dldP = -m*np.array([np.diag(np.dot(PiQjs3d[:,:,i], QiPj [:,:,i])) for i in range(num)]).T - dldQ = m*np.array([np.diag(np.dot(QiPj [:,:,i], PiQjs3d[:,:,i])) for i in range(num)]).T - - jm = drdl*dldm + drdm - jc = drdl*np.sum([dldP[i]*dPdc[i] for i in x3],axis=0) - jd = drdl*np.sum([dldQ[i]*dQdd[i] for i in x3],axis=0) - - if mFix[0]: return np.vstack((jc,jd)).T - else: return np.vstack((jc,jd,jm)).T - -def KarafillisBoyce(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Karafillis-Boyce - - the fitted parameters are - c11,c12,c13,c14,c15,c16,c,m for 3D - c11,c12,c13,c14,c,m for plane stress - 0 1 and self.dimen == 2: - return fitCriteria[self.name]['labels'][1] - else: - return fitCriteria[self.name]['labels'][0] - - def report_name(self): - return fitCriteria[self.name]['name'] - - def fit(self,stress): - global fitResults; fitErrors; fitResidual - if options.exponent > 0.0: nExponent = options.exponent - else: nExponent = 0 - nameCriterion = self.name.lower() - criteria = Criteria(nameCriterion,self.uniaxial,self.expo, self.dimen) - bounds = fitCriteria[nameCriterion]['bound'][dDim] # Default bounds, no bound - guess0 = Guess # Default initial guess, depends on bounds - - if fitResults == []: - initialguess = guess0 - else: - initialguess = np.array(fitResults[-1]) - - ydata = np.zeros(np.shape(stress)[1]) - try: - popt, pcov, infodict, errmsg, ierr = \ - leastsqBound (criteria.fun, initialguess, args=(ydata,stress), - bounds=bounds, Dfun=criteria.jac, full_output=True) - if ierr not in [1, 2, 3, 4]: - raise RuntimeError("Optimal parameters not found: "+errmsg) - else: - residual = criteria.fun(popt, ydata, stress) - fitResidual.append(np.linalg.norm(residual)/np.sqrt(len(residual))) - if (len(ydata) > len(initialguess)) and pcov is not None: - s_sq = (criteria.fun(popt, *(ydata,stress))**2).sum()/(len(ydata)-len(initialguess)) - pcov = pcov * s_sq - perr = np.sqrt(np.diag(pcov)) - fitResults.append(popt.tolist()) - fitErrors .append(perr.tolist()) - - popt = np.concatenate((np.array(popt), np.repeat(options.exponent,nExponent))) - perr = np.concatenate((np.array(perr), np.repeat(0.0,nExponent))) - - damask.util.croak('Needed {} function calls for fitting'.format(infodict['nfev'])) - except Exception as detail: - damask.util.croak(detail) - pass - return popt - -#--------------------------------------------------------------------------------------------------- -class myThread (threading.Thread): - """Runner""" - - def __init__(self, threadID): - threading.Thread.__init__(self) - self.threadID = threadID - def run(self): - semaphore.acquire() - conv=converged() - semaphore.release() - while not conv: - doSim(self.name) - semaphore.acquire() - conv=converged() - semaphore.release() - -def doSim(thread): - semaphore.acquire() - global myLoad - loadNo=loadcaseNo() - if not os.path.isfile('%s.load'%loadNo): - damask.util.croak('Generating load case for simulation %s (%s)'%(loadNo,thread)) - f=open('%s.load'%loadNo,'w') - f.write(myLoad.getLoadcase(loadNo)) - f.close() - semaphore.release() - else: semaphore.release() - -# if spectralOut does not exist, run simulation - semaphore.acquire() - if not os.path.isfile('%s_%i.spectralOut'%(options.geometry,loadNo)): - damask.util.croak('Starting simulation %i (%s)'%(loadNo,thread)) - semaphore.release() - damask.util.execute('DAMASK_spectral -g %s -l %i'%(options.geometry,loadNo)) - else: semaphore.release() - -# if ASCII tables do not exist, run postprocessing - semaphore.acquire() - if not os.path.isfile('./postProc/%s_%i.txt'%(options.geometry,loadNo)): - damask.util.croak('Starting post processing for simulation %i (%s)'%(loadNo,thread)) - semaphore.release() - try: - damask.util.execute('postResults --cr f,p --co totalshear %s_%i.spectralOut'%(options.geometry,loadNo)) - except: - damask.util.execute('postResults --cr f,p %s_%i.spectralOut'%(options.geometry,loadNo)) - damask.util.execute('addCauchy ./postProc/%s_%i.txt'%(options.geometry,loadNo)) - damask.util.execute('addStrainTensors -0 -v ./postProc/%s_%i.txt'%(options.geometry,loadNo)) - damask.util.execute('addMises -s Cauchy -e ln(V) ./postProc/%s_%i.txt'%(options.geometry,loadNo)) - else: semaphore.release() - -# reading values from ASCII table (including linear interpolation between points) - semaphore.acquire() - damask.util.croak('Reading values from simulation %i (%s)'%(loadNo,thread)) - refFile = './postProc/%s_%i.txt'%(options.geometry,loadNo) - table = damask.ASCIItable(refFile,readonly=True) - table.head_read() - - thresholdKey = {'equivalentStrain':'Mises(ln(V))', - 'totalshear': 'totalshear', - }[options.fitting] - - for l in [thresholdKey,'1_Cauchy']: - if l not in table.labels(raw = True): damask.util.croak('%s not found'%l) - semaphore.release() - - table.data_readArray(['%i_Cauchy'%(i+1) for i in range(9)]+[thresholdKey]+['%i_ln(V)'%(i+1) for i in range(9)]) - - validity = np.zeros((int(options.yieldValue[2])), dtype=bool) # found data for desired threshold - yieldStress = np.empty((int(options.yieldValue[2]),6),'d') - deformationRate = np.empty((int(options.yieldValue[2]),6),'d') - - line = 0 - for i,threshold in enumerate(np.linspace(options.yieldValue[0],options.yieldValue[1],options.yieldValue[2])): - while line < np.shape(table.data)[0]: - if abs(table.data[line,9])>= threshold: - upper,lower = abs(table.data[line,9]),abs(table.data[line-1,9]) # values for linear interpolation - stress = np.array(table.data[line-1,0:9] * (upper-threshold)/(upper-lower) + \ - table.data[line ,0:9] * (threshold-lower)/(upper-lower)).reshape(3,3) # linear interpolation of stress values - yieldStress[i,:] = t33toSym6(stress) - - dstrain= np.array(table.data[line,10:] - table.data[line-1,10:]).reshape(3,3) - deformationRate[i,:] = t33toSym6(dstrain) - - validity[i] = True - break - else: - line+=1 - if not validity[i]: - semaphore.acquire() - damask.util.croak('The data of result %i at the threshold %f is invalid,'%(loadNo,threshold)\ - +'the fitting at this point is skipped') - semaphore.release() - -# do the actual fitting procedure and write results to file - semaphore.acquire() - global stressAll, strainAll - f=open(options.geometry+'_'+options.criterion+'_'+str(time.time())+'.txt','w') - f.write(' '.join([options.fitting]+myFit.report_labels())+'\n') - try: - for i,threshold in enumerate(np.linspace(options.yieldValue[0],options.yieldValue[1],options.yieldValue[2])): - if validity[i]: - stressAll[i]=np.append(stressAll[i], yieldStress[i]/stressUnit) - strainAll[i]=np.append(strainAll[i], deformationRate[i]) - f.write( str(threshold)+' '+ - ' '.join(map(str,myFit.fit(stressAll[i].reshape(len(stressAll[i])//6,6).transpose())))+'\n') - except Exception: - damask.util.croak('Could not fit results of simulation (%s)'%thread) - semaphore.release() - return - damask.util.croak('\n') - semaphore.release() - -def loadcaseNo(): - global N_simulations - N_simulations+=1 - return N_simulations - -def converged(): - global N_simulations; fitResidual - - if N_simulations < options.max: - if len(fitResidual) > 5 and N_simulations >= options.min: - residualList = np.array(fitResidual[len(fitResidual)-5:]) - if np.std(residualList)/np.max(residualList) < 0.05: - return True - return False - else: - return True - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ -Performs calculations with various loads on given geometry file and fits yield surface. - -""", version = scriptID) - -# maybe make an option to specifiy if 2D/3D fitting should be done? - -parser.add_option('-l','--load' , dest='load', type='float', nargs=3, - help='load: final strain; increments; time %default', metavar='float int float') -parser.add_option('-g','--geometry', dest='geometry', type='string', - help='name of the geometry file [%default]', metavar='string') -parser.add_option('-c','--criterion', dest='criterion', choices=fitCriteria.keys(), - help='criterion for stopping simulations [%default]', metavar='string') -parser.add_option('-f','--fitting', dest='fitting', choices=thresholdParameter, - help='yield criterion [%default]', metavar='string') -parser.add_option('-y','--yieldvalue', dest='yieldValue', type='float', nargs=3, - help='yield points: start; end; count %default', metavar='float float int') -parser.add_option('--min', dest='min', type='int', - help='minimum number of simulations [%default]', metavar='int') -parser.add_option('--max', dest='max', type='int', - help='maximum number of iterations [%default]', metavar='int') -parser.add_option('-t','--threads', dest='threads', type='int', - help='number of parallel executions [%default]', metavar='int') -parser.add_option('-b','--bound', dest='bounds', type='float', nargs=2, - help='yield points: start; end; count %default', metavar='float float') -parser.add_option('-d','--dimension', dest='dimension', type='choice', choices=['2','3'], - help='dimension of the virtual test [%default]', metavar='int') -parser.add_option('-e', '--exponent', dest='exponent', type='float', - help='exponent of non-quadratic criteria', metavar='int') -parser.add_option('-u', '--uniaxial', dest='eqStress', type='float', - help='Equivalent stress', metavar='float') - -parser.set_defaults(min = 12, - max = 30, - threads = 4, - yieldValue = (0.002,0.004,2), - load = (0.010,100,100.0), - criterion = 'vonmises', - fitting = 'totalshear', - geometry = '20grains16x16x16', - bounds = None, - dimension = '3', - exponent = -1.0, - ) - -options = parser.parse_args()[0] - -if options.threads < 1: - parser.error('invalid number of threads {}'.format(options.threads)) -if options.min < 0: - parser.error('invalid minimum number of simulations {}'.format(options.min)) -if options.max < options.min: - parser.error('invalid maximum number of simulations (below minimum)') -if options.yieldValue[0] > options.yieldValue[1]: - parser.error('invalid yield start (below yield end)') -if options.yieldValue[2] != int(options.yieldValue[2]): - parser.error('count must be an integer') - -for check in [options.geometry+'.geom','numerics.config','material.config']: - if not os.path.isfile(check): - damask.util.croak('"{}" file not found'.format(check)) - -options.dimension = int(options.dimension) - -stressUnit = 1.0e9 if options.criterion == 'hill1948' else 1.0e6 - - -if options.dimension not in fitCriteria[options.criterion]['dimen']: - parser.error('invalid dimension for selected criterion') - -if options.criterion not in ['vonmises','tresca','drucker','hill1948'] and options.eqStress is None: - parser.error('please specify an equivalent stress (e.g. fitting to von Mises)') - -# global variables -fitResults = [] -fitErrors = [] -fitResidual = [] -stressAll= [np.zeros(0,'d').reshape(0,0) for i in range(int(options.yieldValue[2]))] -strainAll= [np.zeros(0,'d').reshape(0,0) for i in range(int(options.yieldValue[2]))] -N_simulations=0 -Guess = [] -threads=[] -semaphore=threading.Semaphore(1) -dDim = None -myLoad = None -myFit = None - -run = runFit(options.exponent, options.eqStress, options.dimension, options.criterion) - -damask.util.croak('Finished fitting to yield criteria') diff --git a/processing/misc/yieldSurfaceFast.py b/processing/misc/yieldSurfaceFast.py deleted file mode 100755 index c58dca733..000000000 --- a/processing/misc/yieldSurfaceFast.py +++ /dev/null @@ -1,1513 +0,0 @@ -#!/usr/bin/env python2.7 -# -*- coding: UTF-8 no BOM -*- - -import threading,time,os -import numpy as np -from optparse import OptionParser -import damask -from damask.util import leastsqBound -from scipy.optimize import nnls - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -def runFit(exponent, eqStress, dimension, criterion): - global threads, myFit, myLoad - global fitResidual - global Guess, dDim - - if options.criterion!='facet': - dDim = dimension - 3 - nParas = len(fitCriteria[criterion]['bound'][dDim]) - nExpo = fitCriteria[criterion]['nExpo'] - - if exponent > 0.0: # User defined exponents - nParas = nParas-nExpo - fitCriteria[criterion]['bound'][dDim] = fitCriteria[criterion]['bound'][dDim][:nParas] - - for i in range(nParas): - temp = fitCriteria[criterion]['bound'][dDim][i] - if fitCriteria[criterion]['bound'][dDim][i] == (None,None): - Guess.append(1.0) - else: - g = (temp[0]+temp[1])/2.0 - if g == 0: g = temp[1]*0.5 - Guess.append(g) - - myLoad = Loadcase(options.load[0],options.load[1],options.load[2],options.flag,options.yieldValue, - nSet = 10, dimension = dimension, vegter = options.criterion=='vegter') - - - myFit = Criterion(exponent,eqStress, dimension, criterion) - for t in range(options.threads): - threads.append(myThread(t)) - threads[t].start() - - for t in range(options.threads): - threads[t].join() - - if options.criterion=='facet': - doFacetFit() - - damask.util.croak('Residuals') - damask.util.croak(fitResidual) - -def doFacetFit(): - n = options.order - Data = np.zeros((options.numpoints, 10)) - for i in range(options.numpoints): - fileName = options.geometry + '_' + str(i+1) + '.yield' - data_i = np.loadtxt(fileName) - - sv = (data_i[0,0] + data_i[1,1] + data_i[2,2])/3.0 - - #convert stress and strain form the 6D to 5D space - S1 = np.sqrt(2.0)*(data_i[0,0] - data_i[1,1])/2.0 - S2 = np.sqrt(6.0)*(data_i[0,0] + data_i[1,1] - 2.0*sv)/2.0 - S3 = np.sqrt(2.0)*data_i[1,2] - S4 = np.sqrt(2.0)*data_i[2,0] - S5 = np.sqrt(2.0)*data_i[0,1] - - E1 = np.sqrt(2.0)*(data_i[3,0]-data_i[4,1])/2.0 - E2 = np.sqrt(6.0)*(data_i[3,0]+data_i[4,1])/2.0 - E3 = np.sqrt(2.0)*data_i[4,2] - E4 = np.sqrt(2.0)*data_i[5,0] - E5 = np.sqrt(2.0)*data_i[3,1] - - Data[i,:] = [E1,E2,E3,E4,E5,S1,S2,S3,S4,S5] - - Data[:,5:] = Data[:,5:] / 100000000.0 - - path=os.path.join(os.getcwd(),'final.mmm') - np.savetxt(path, Data, header='', comments='', fmt='% 15.10f') - - if options.dimension == 2: - reducedIndices = [0,1,4,5,6,9] - elif options.dimension == 3: - reducedIndices = [i for i in range(10)] - - numDirections = Data.shape[0] - Indices = np.arange(numDirections) - sdPairs = Data[:,reducedIndices][Indices,:] - numPairs = sdPairs.shape[0] - dimensionality = sdPairs.shape[1] / 2 - ds = sdPairs[:,0:dimensionality] - s = sdPairs[:,dimensionality::] - - A = np.zeros((numPairs, numPairs)) - B = np.ones((numPairs,)) - for i in range(numPairs): - for j in range(numPairs): - lamb = 1.0 - s_i = s[i,:] - ds_j = ds[j,:] - A[i,j] = lamb * (np.dot(s_i.ravel(), ds_j.ravel()) ** n) - - lambdas, residuals = nnls(A, B) - nonZeroTerms = np.logical_not(np.isclose(lambdas, 0.)) - numNonZeroTerms = np.sum(nonZeroTerms) - dataOut = np.zeros((numNonZeroTerms, 6)) - - if options.dimension == 2: - dataOut[:,0] = lambdas[nonZeroTerms] - dataOut[:,1] = ds[nonZeroTerms,:][:,0] - dataOut[:,2] = ds[nonZeroTerms,:][:,1] - dataOut[:,5] = ds[nonZeroTerms,:][:,2] - elif options.dimension == 3: - dataOut[:,0] = lambdas[nonZeroTerms] - dataOut[:,1] = ds[nonZeroTerms,:][:,0] - dataOut[:,2] = ds[nonZeroTerms,:][:,1] - dataOut[:,3] = ds[nonZeroTerms,:][:,2] - dataOut[:,4] = ds[nonZeroTerms,:][:,3] - dataOut[:,5] = ds[nonZeroTerms,:][:,4] - - headerText = 'facet\n 1 \n F \n {0:<3d} \n {1:<3d} '.format(n, numNonZeroTerms) - path=os.path.join(os.getcwd(),'facet_o{0}.fac'.format(n)) - np.savetxt(path, dataOut, header=headerText, comments='', fmt='% 15.10f') - -def principalStresses(sigmas): - """ - Computes principal stresses (i.e. eigenvalues) for a set of Cauchy stresses. - - sorted in descending order. - """ - lambdas=np.zeros(0,'d') - for i in range(np.shape(sigmas)[1]): - eigenvalues = np.linalg.eigvalsh(sym6toT33(sigmas[:,i])) - lambdas = np.append(lambdas,np.sort(eigenvalues)[::-1]) #append eigenvalues in descending order - lambdas = np.transpose(lambdas.reshape(np.shape(sigmas)[1],3)) - return lambdas - -def principalStress(p): - I = invariant(p) - - I1s3I2= (I[0]**2 - 3.0*I[1])**0.5 - numer = 2.0*I[0]**3 - 9.0*I[0]*I[1] + 27.0*I[2] - denom = 2.0*I1s3I2**3 - cs = numer/denom - - phi = np.arccos(cs)/3.0 - t1 = I[0]/3.0; t2 = 2.0/3.0*I1s3I2 - return np.array( [t1 + t2*np.cos(phi), - t1 + t2*np.cos(phi+np.pi*2.0/3.0), - t1 + t2*np.cos(phi+np.pi*4.0/3.0)]) - -def principalStrs_Der(p, s, dim, Karafillis=False): - """Derivative of principal stress with respect to stress""" - third = 1.0/3.0 - third2 = 2.0*third - - I = invariant(p) - I1s3I2= np.sqrt(I[0]**2 - 3.0*I[1]) - numer = 2.0*I[0]**3 - 9.0*I[0]*I[1] + 27.0*I[2] - denom = 2.0*I1s3I2**3 - cs = numer/denom - phi = np.arccos(cs)/3.0 - - dphidcs = -third/np.sqrt(1.0 - cs**2) - dcsddenom = 0.5*numer*(-1.5)*I1s3I2**(-5.0) - dcsdI1 = (6.0*I[0]**2 - 9.0*I[1])*denom + dcsddenom*(2.0*I[0]) - dcsdI2 = ( - 9.0*I[0])*denom + dcsddenom*(-3.0) - dcsdI3 = 27.0*denom - dphidI1, dphidI2, dphidI3 = dphidcs*dcsdI1, dphidcs*dcsdI2, dphidcs*dcsdI3 - - dI1s3I2dI1 = I[0]/I1s3I2 - dI1s3I2dI2 = -1.5/I1s3I2 - tcoeff = third2*I1s3I2 - - dSidIj = lambda theta : ( tcoeff*(-np.sin(theta))*dphidI1 + third2*dI1s3I2dI1*np.cos(theta) + third, - tcoeff*(-np.sin(theta))*dphidI2 + third2*dI1s3I2dI2*np.cos(theta), - tcoeff*(-np.sin(theta))*dphidI3) - dSdI = np.array([dSidIj(phi),dSidIj(phi+np.pi*2.0/3.0),dSidIj(phi+np.pi*4.0/3.0)]) # i=1,2,3; j=1,2,3 - -# calculate the derivation of principal stress with regards to the anisotropic coefficients - one = np.ones_like(s); zero = np.zeros_like(s); num = len(s) - dIdp = np.array([[one, one, one, zero, zero, zero], - [p[1]+p[2], p[2]+p[0], p[0]+p[1], -2.0*p[3], -2.0*p[4], -2.0*p[5]], - [p[1]*p[2]-p[4]**2, p[2]*p[0]-p[5]**2, p[0]*p[1]-p[3]**2, - -2.0*p[3]*p[2]+2.0*p[4]*p[5], -2.0*p[4]*p[0]+2.0*p[5]*p[3], -2.0*p[5]*p[1]+2.0*p[3]*p[4]] ]) - if Karafillis: - dpdc = np.array([[zero,s[0]-s[2],s[0]-s[1]], [s[1]-s[2],zero,s[1]-s[0]], [s[2]-s[1],s[2]-s[0],zero]])/3.0 - dSdp = np.array([np.dot(dSdI[:,:,i],dIdp[:,:,i]).T for i in range(num)]).T - if dim == 2: - temp = np.vstack([dSdp[:,3]*s[3]]).T.reshape(num,1,3).T - else: - temp = np.vstack([dSdp[:,3]*s[3],dSdp[:,4]*s[4],dSdp[:,5]*s[5]]).T.reshape(num,3,3).T - - return np.concatenate((np.array([np.dot(dSdp[:,0:3,i], dpdc[:,:,i]).T for i in range(num)]).T, - temp), axis=1) - else: - if dim == 2: - dIdc=np.array([[-dIdp[i,0]*s[1], -dIdp[i,1]*s[0], -dIdp[i,1]*s[2], - -dIdp[i,2]*s[1], -dIdp[i,2]*s[0], -dIdp[i,0]*s[2], - dIdp[i,3]*s[3] ] for i in range(3)]) - else: - dIdc=np.array([[-dIdp[i,0]*s[1], -dIdp[i,1]*s[0], -dIdp[i,1]*s[2], - -dIdp[i,2]*s[1], -dIdp[i,2]*s[0], -dIdp[i,0]*s[2], - dIdp[i,3]*s[3], dIdp[i,4]*s[4], dIdp[i,5]*s[5] ] for i in range(3)]) - return np.array([np.dot(dSdI[:,:,i],dIdc[:,:,i]).T for i in range(num)]).T - -def invariant(sigmas): - I = np.zeros(3) - s11,s22,s33,s12,s23,s31 = sigmas - I[0] = s11 + s22 + s33 - I[1] = s11*s22 + s22*s33 + s33*s11 - s12**2 - s23**2 - s31**2 - I[2] = s11*s22*s33 + 2.0*s12*s23*s31 - s12**2*s33 - s23**2*s11 - s31**2*s22 - return I - -def math_ln(x): - return np.log(x + 1.0e-32) - -def sym6toT33(sym6): - """Shape the symmetric stress tensor(6) into (3,3)""" - return np.array([[sym6[0],sym6[3],sym6[5]], - [sym6[3],sym6[1],sym6[4]], - [sym6[5],sym6[4],sym6[2]]]) - -def t33toSym6(t33): - """Shape the stress tensor(3,3) into symmetric (6)""" - return np.array([ t33[0,0], - t33[1,1], - t33[2,2], - (t33[0,1] + t33[1,0])/2.0, # 0 3 5 - (t33[1,2] + t33[2,1])/2.0, # * 1 4 - (t33[2,0] + t33[0,2])/2.0,]) # * * 2 - -class Criteria(object): - def __init__(self, criterion, uniaxialStress,exponent, dimension): - self.stress0 = uniaxialStress - if exponent < 0.0: # Fitting exponent m - self.mFix = [False, exponent] - else: # fixed exponent m - self.mFix = [True, exponent] - self.func = fitCriteria[criterion]['func'] - self.criteria = criterion - self.dim = dimension - def fun(self, paras, ydata, sigmas): - return self.func(self.stress0, paras, sigmas,self.mFix,self.criteria,self.dim) - def jac(self, paras, ydata, sigmas): - return self.func(self.stress0, paras, sigmas,self.mFix,self.criteria,self.dim,Jac=True) - -class Vegter(object): - """Vegter yield criterion""" - - def __init__(self, refPts, refNormals,nspace=11): - self.refPts, self.refNormals = self._getRefPointsNormals(refPts, refNormals) - self.hingePts = self._getHingePoints() - self.nspace = nspace - def _getRefPointsNormals(self,refPtsQtr,refNormalsQtr): - if len(refPtsQtr) == 12: - refPts = refPtsQtr - refNormals = refNormalsQtr - else: - refPts = np.empty([13,2]) - refNormals = np.empty([13,2]) - refPts[12] = refPtsQtr[0] - refNormals[12] = refNormalsQtr[0] - for i in range(3): - refPts[i] = refPtsQtr[i] - refPts[i+3] = refPtsQtr[3-i][::-1] - refPts[i+6] =-refPtsQtr[i] - refPts[i+9] =-refPtsQtr[3-i][::-1] - refNormals[i] = refNormalsQtr[i] - refNormals[i+3] = refNormalsQtr[3-i][::-1] - refNormals[i+6] =-refNormalsQtr[i] - refNormals[i+9] =-refNormalsQtr[3-i][::-1] - return refPts,refNormals - - def _getHingePoints(self): - """ - Calculate the hinge point B according to the reference points A,C and the normals n,m - - refPoints = np.array([[p1_x, p1_y], [p2_x, p2_y]]); - refNormals = np.array([[n1_x, n1_y], [n2_x, n2_y]]) - """ - def hingPoint(points, normals): - A1 = points[0][0]; A2 = points[0][1] - C1 = points[1][0]; C2 = points[1][1] - n1 = normals[0][0]; n2 = normals[0][1] - m1 = normals[1][0]; m2 = normals[1][1] - B1 = (m2*(n1*A1 + n2*A2) - n2*(m1*C1 + m2*C2))/(n1*m2-m1*n2) - B2 = (n1*(m1*C1 + m2*C2) - m1*(n1*A1 + n2*A2))/(n1*m2-m1*n2) - return np.array([B1,B2]) - return np.array([hingPoint(self.refPts[i:i+2],self.refNormals[i:i+2]) for i in range(len(self.refPts)-1)]) - - def getBezier(self): - def bezier(R,H): - b = [] - for mu in np.linspace(0.0,1.0,self.nspace): - b.append(np.array(R[0]*np.ones_like(mu) + 2.0*mu*(H - R[0]) + mu**2*(R[0]+R[1] - 2.0*H))) - return b - return np.array([bezier(self.refPts[i:i+2],self.hingePts[i]) for i in range(len(self.refPts)-1)]) - -def VetgerCriterion(stress,lankford, rhoBi0, theta=0.0): - """0-pure shear; 1-uniaxial; 2-plane strain; 3-equi-biaxial""" - def getFourierParas(r): - # get the value after Fourier transformation - nset = len(r) - lmatrix = np.empty([nset,nset]) - theta = np.linspace(0.0,np.pi/2,nset) - for i,th in enumerate(theta): - lmatrix[i] = np.array([np.cos(2*j*th) for j in range(nset)]) - return np.linalg.solve(lmatrix, r) - - nps = len(stress) - if nps%4 != 0: - damask.util.croak('Warning: the number of stress points is uncorrect, stress points of %s are missing in set %i'%( - ['eq-biaxial, plane strain & uniaxial', 'eq-biaxial & plane strain','eq-biaxial'][nps%4-1],nps/4+1)) - else: - nset = nps/4 - strsSet = stress.reshape(nset,4,2) - refPts = np.empty([4,2]) - - fouriercoeffs = np.array([np.cos(2.0*i*theta) for i in range(nset)]) - for i in range(2): - refPts[3,i] = sum(strsSet[:,3,i])/nset - for j in range(3): - refPts[j,i] = np.dot(getFourierParas(strsSet[:,j,i]), fouriercoeffs) - - -def Tresca(eqStress=None, #not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None, #not needed/supported - dim=3, - Jac=False): - """ - Tresca yield criterion - - the fitted parameter is paras(sigma0) - """ - if not Jac: - lambdas = principalStresses(sigmas) - r = np.amax(np.array([abs(lambdas[2,:]-lambdas[1,:]),\ - abs(lambdas[1,:]-lambdas[0,:]),\ - abs(lambdas[0,:]-lambdas[2,:])]),0) - paras - return r.ravel() - else: - return -np.ones(len(sigmas)) - -def Cazacu_Barlat(eqStress=None, - paras=None, - sigmas=None, - mFix=None,#not needed/supported - criteria=None, - dim=3, #2D also possible - Jac=False): - """ - Cazacu-Barlat (CB) yield criterion - - the fitted parameters are: - a1,a2,a3,a6; b1,b2,b3,b4,b5,b10; c for plane stress - a1,a2,a3,a4,a5,a6; b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11; c: for general case - mFix is ignored - """ - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: - (a1,a2,a3,a4), (b1,b2,b3,b4,b5,b10), c = paras[0:4],paras[4:10],paras[10] - a5 = a6 = b6 = b7 = b8 = b9 = b11 = 0.0 - s33 = s23 = s31 = np.zeros_like(s11) - else: - (a1,a2,a3,a4,a5,a6), (b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11), c = paras[0:6],paras[6:17],paras[17] - - s1_2, s2_2, s3_2, s12_2, s23_2, s31_2 = np.array([s11,s22,s33,s12,s23,s31])**2 - s1_3, s2_3, s3_3, s123, s321 = s11*s1_2, s22*s2_2, s33*s3_2,s11*s22*s33, s12*s23*s31 - d12_2,d23_2,d31_2 = (s11-s22)**2, (s22-s33)**2, (s33-s11)**2 - - J20 = ( a1*d12_2 + a2*d23_2 + a3*d31_2 )/6.0 + a4*s12_2 + a5*s23_2 + a6*s31_2 - J30 = ( (b1 +b2 )*s1_3 + (b3 +b4 )*s2_3 + ( b1+b4-b2 + b1+b4-b3 )*s3_3 )/27.0- \ - ( (b1*s22+b2*s33)*s1_2 + (b3*s33+b4*s11)*s2_2 + ((b1+b4-b2)*s11 + (b1+b4-b3)*s22)*s3_2 )/9.0 + \ - ( (b1+b4)*s123/9.0 + b11*s321 )*2.0 - \ - ( ( 2.0*b9 *s22 - b8*s33 - (2.0*b9 -b8)*s11 )*s31_2 + - ( 2.0*b10*s33 - b5*s22 - (2.0*b10-b5)*s11 )*s12_2 + - ( (b6+b7)*s11 - b6*s22 - b7*s33 )*s23_2 - )/3.0 - f0 = J20**3 - c*J30**2 - r = f0**(1.0/6.0)*np.sqrt(3.0)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - drdf = r/f0/6.0 - dj2, dj3 = drdf*3.0*J20**2, -drdf*2.0*J30*c - jc = -drdf*J30**2 - - ja1,ja2,ja3 = dj2*d12_2/6.0, dj2*d23_2/6.0, dj2*d31_2/6.0 - ja4,ja5,ja6 = dj2*s12_2, dj2*s23_2, dj2*s31_2 - jb1 = dj3*( (s1_3 + 2.0*s3_3)/27.0 - s22*s1_2/9.0 - (s11+s22)*s3_2/9.0 + s123/4.5 ) - jb2 = dj3*( (s1_3 - s3_3)/27.0 - s33*s1_2/9.0 + s11 *s3_2/9.0 ) - jb3 = dj3*( (s2_3 - s3_3)/27.0 - s33*s2_2/9.0 + s22 *s3_2/9.0 ) - jb4 = dj3*( (s2_3 + 2.0*s3_3)/27.0 - s11*s2_2/9.0 - (s11+s22)*s3_2/9.0 + s123/4.5 ) - - jb5, jb10 = dj3*(s22 - s11)*s12_2/3.0, dj3*(s11 - s33)*s12_2/1.5 - jb6, jb7 = dj3*(s22 - s11)*s23_2/3.0, dj3*(s33 - s11)*s23_2/3.0 - jb8, jb9 = dj3*(s33 - s11)*s31_2/3.0, dj3*(s11 - s22)*s31_2/1.5 - jb11 = dj3*s321*2.0 - if dim == 2: - return np.vstack((ja1,ja2,ja3,ja4,jb1,jb2,jb3,jb4,jb5,jb10,jc)).T - else: - return np.vstack((ja1,ja2,ja3,ja4,ja5,ja6,jb1,jb2,jb3,jb4,jb5,jb6,jb7,jb8,jb9,jb10,jb11,jc)).T - -def Drucker(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None, - dim=3, - Jac=False): - """ - Drucker yield criterion - - the fitted parameters are - sigma0, C_D for Drucker(p=1); - sigma0, C_D, p for general Drucker - eqStress, mFix are invalid inputs - """ - if criteria == 'drucker': - sigma0, C_D= paras - p = 1.0 - else: - sigma0, C_D = paras[0:2] - if mFix[0]: p = mFix[1] - else: p = paras[-1] - I = invariant(sigmas) - J = np.zeros([3]) - J[1] = I[0]**2/3.0 - I[1] - J[2] = I[0]**3/13.5 - I[0]*I[1]/3.0 + I[2] - J2_3p = J[1]**(3.0*p) - J3_2p = J[2]**(2.0*p) - left = J2_3p - C_D*J3_2p - r = left**(1.0/(6.0*p))*3.0**0.5/sigma0 - - if not Jac: - return (r - 1.0).ravel() - else: - drdl = r/left/(6.0*p) - if criteria == 'drucker': - return np.vstack((-r/sigma0, -drdl*J3_2p)).T - else: - dldp = 3.0*J2_3p*math_ln(J[1]) - 2.0*C_D*J3_2p*math_ln(J[2]) - jp = drdl*dldp + r*math_ln(left)/(-6.0*p*p) - - if mFix[0]: return np.vstack((-r/sigma0, -drdl*J3_2p)).T - else: return np.vstack((-r/sigma0, -drdl*J3_2p, jp)).T - -def Hill1948(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, #not needed/supported - criteria=None,#not needed/supported - dim=3, - Jac=False): - """ - Hill 1948 yield criterion - - the fitted parameters are: - F, G, H, L, M, N for 3D - F, G, H, N for 2D - """ - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: # plane stress - jac = np.array([ s22**2, s11**2, (s11-s22)**2, 2.0*s12**2]) - else: # general case - jac = np.array([(s22-s33)**2,(s33-s11)**2,(s11-s22)**2, 2.0*s23**2,2.0*s31**2,2.0*s12**2]) - - if not Jac: - return (np.dot(paras,jac)/2.0-0.5).ravel() - else: - return jac.T - -def Hill1979(eqStress=None,#not needed/supported - paras=None, - sigmas=None, - mFix=None, - criteria=None,#not needed/supported - dim=3, - Jac=False): - """ - Hill 1979 yield criterion - - the fitted parameters are: f,g,h,a,b,c,m - """ - if mFix[0]: - m = mFix[1] - else: - m = paras[-1] - - coeff = paras[0:6] - s = principalStresses(sigmas) - diffs = np.array([s[1]-s[2], s[2]-s[0], s[0]-s[1],\ - 2.0*s[0]-s[1]-s[2], 2.0*s[1]-s[2]-s[0], 2.0*s[2]-s[0]-s[1]])**2 - - diffsm = diffs**(m/2.0) - left = np.dot(coeff,diffsm) - r = (0.5*left)**(1.0/m)/eqStress #left = base**mi - - if not Jac: - return (r-1.0).ravel() - else: - drdl, dldm = r/left/m, np.dot(coeff,diffsm*math_ln(diffs))*0.5 - jm = drdl*dldm + r*math_ln(0.5*left)*(-1.0/m/m) #/(-m**2) - - if mFix[0]: return np.vstack((drdl*diffsm)).T - else: return np.vstack((drdl*diffsm, jm)).T - -def Hosford(eqStress=None, - paras=None, - sigmas=None, - mFix=None, - criteria=None, - dim=3, - Jac=False): - """ - Hosford family criteria - - the fitted parameters are: - von Mises: sigma0 - Hershey: (1) sigma0, a, when a is not fixed; (2) sigma0, when a is fixed - general Hosford: (1) F,G,H, a, when a is not fixed; (2) F,G,H, when a is fixed - """ - if criteria == 'vonmises': - sigma0 = paras - coeff = np.ones(3) - a = 2.0 - elif criteria == 'hershey': - sigma0 = paras[0] - coeff = np.ones(3) - if mFix[0]: a = mFix[1] - else: a = paras[1] - else: - sigma0 = eqStress - coeff = paras[0:3] - if mFix[0]: a = mFix[1] - else: a = paras[3] - - s = principalStresses(sigmas) - diffs = np.array([s[1]-s[2], s[2]-s[0], s[0]-s[1]])**2 - diffsm = diffs**(a/2.0) - left = np.dot(coeff,diffsm) - r = (0.5*left)**(1.0/a)/sigma0 - - if not Jac: - return (r-1.0).ravel() - else: - if criteria == 'vonmises': # von Mises - return -r/sigma0 - else: - drdl, dlda = r/left/a, np.dot(coeff,diffsm*math_ln(diffs))*0.5 - ja = drdl*dlda + r*math_ln(0.5*left)*(-1.0/a/a) - if criteria == 'hershey': # Hershey - if mFix[0]: return -r/sigma0 - else: return np.vstack((-r/sigma0, ja)).T - else: # Anisotropic Hosford - if mFix[0]: return np.vstack((drdl*diffsm)).T - else: return np.vstack((drdl*diffsm, ja)).T - -def Barlat1989(eqStress=None, - paras=None, - sigmas=None, - mFix=None, - criteria=None, - dim=3, - Jac=False): - """ - Barlat-Lian 1989 yield criteria - - the fitted parameters are: - Anisotropic: a, h, p, m; m is optional - """ - a, h, p = paras[0:3] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - c = 2.0-a - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k1,k2 = 0.5*(s11 + h*s22), (0.25*(s11 - h*s22)**2 + (p*s12)**2)**0.5 - fs = np.array([ (k1+k2)**2, (k1-k2)**2, 4.0*k2**2 ]); fm = fs**(m/2.0) - left = np.dot(np.array([a,a,c]),fm) - r = (0.5*left)**(1.0/m)/eqStress - - if not Jac: - return (r-1.0).ravel() - else: - dk1dh = 0.5*s22 - dk2dh, dk2dp = 0.25*(s11-h*s22)*(-s22)/k2, p*s12**2/k2 - dlda, dldc = fm[0]+fm[1], fm[2] - fm1 = fs**(m/2.0-1.0)*m - dldk1, dldk2 = a*fm1[0]*(k1+k2)+a*fm1[1]*(k1-k2), a*fm1[0]*(k1+k2)-a*fm1[1]*(k1-k2)+c*fm1[2]*k2*4.0 - drdl, drdm = r/m/left, r*math_ln(0.5*left)*(-1.0/m/m) - dldm = np.dot(np.array([a,a,c]),fm*math_ln(fs))*0.5 - - ja,jc = drdl*dlda, drdl*dldc - jh,jp = drdl*(dldk1*dk1dh + dldk2*dk2dh), drdl*dldk2*dk2dp - jm = drdl*dldm + drdm - - if mFix[0]: return np.vstack((ja,jc,jh,jp)).T - else: return np.vstack((ja,jc,jh,jp,jm)).T - -def Barlat1991(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Barlat 1991 criteria - - the fitted parameters are: - Anisotropic: a, b, c, f, g, h, m for 3D - a, b, c, h, m for plane stress - m is optional - """ - if dim == 2: coeff = paras[0:4] # plane stress - else: coeff = paras[0:6] # general case - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - s11,s22,s33,s12,s23,s31 = sigmas - if dim == 2: - dXdx = np.array([s22,-s11,s11-s22,s12]) - A,B,C,H = np.array(coeff)[:,None]*dXdx; F=G=0.0 - else: - dXdx = np.array([s22-s33,s33-s11,s11-s22,s23,s31,s12]) - A,B,C,F,G,H = np.array(coeff)[:,None]*dXdx - - I2 = (F*F + G*G + H*H)/3.0+ ((A-C)**2+(C-B)**2+(B-A)**2)/54.0 - I3 = (C-B)*(A-C)*(B-A)/54.0 + F*G*H - ((C-B)*F*F + (A-C)*G*G + (B-A)*H*H)/6.0 - phi1 = np.arccos(I3/I2**1.5)/3.0 + np.pi/6.0; absc1 = 2.0*np.abs(np.cos(phi1)) - phi2 = phi1 + np.pi/3.0; absc2 = 2.0*np.abs(np.cos(phi2)) - phi3 = phi2 + np.pi/3.0; absc3 = 2.0*np.abs(np.cos(phi3)) - left = ( absc1**m + absc2**m + absc3**m ) - r = (0.5*left)**(1.0/m)*np.sqrt(3.0*I2)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - dfdl = r/left/m - jm = r*math_ln(0.5*left)*(-1.0/m/m) + dfdl*0.5*( - absc1**m*math_ln(absc1) + absc2**m*math_ln(absc2) + absc3**m*math_ln(absc3) ) - - da,db,dc = (2.0*A-B-C)/18.0, (2.0*B-C-A)/18.0, (2.0*C-A-B)/18.0 - if dim == 2: - dI2dx = np.array([da, db, dc, H])/1.5*dXdx - dI3dx = np.array([ da*(B-C) + (H**2-G**2)/2.0, - db*(C-A) + (F**2-H**2)/2.0, - dc*(A-B) + (G**2-F**2)/2.0, - (G*F + (A-B))*H ])/3.0*dXdx - else: - dI2dx = np.array([da, db, dc, F,G,H])/1.5*dXdx - dI3dx = np.array([ da*(B-C) + (H**2-G**2)/2.0, - db*(C-A) + (F**2-H**2)/2.0, - dc*(A-B) + (G**2-F**2)/2.0, - (H*G*3.0 + (B-C))*F, - (F*H*3.0 + (C-A))*G, - (G*F*3.0 + (A-B))*H ])/3.0*dXdx - darccos = -1.0/np.sqrt(1.0 - I3**2/I2**3) - - dfdcos = lambda phi : dfdl*m*(2.0*abs(np.cos(phi)))**(m-1.0)*np.sign(np.cos(phi))*(-np.sin(phi)/1.5) - - dfdthe= (dfdcos(phi1) + dfdcos(phi2) + dfdcos(phi3)) - dfdI2, dfdI3 = dfdthe*darccos*I3*(-1.5)*I2**(-2.5)+r/2.0/I2, dfdthe*darccos*I2**(-1.5) - - if mFix[0]: return np.vstack((dfdI2*dI2dx + dfdI3*dI3dx)).T - else: return np.vstack((dfdI2*dI2dx + dfdI3*dI3dx, jm)).T - -def BBC2000(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2000 yield criterion - - the fitted parameters are - d,e,f,g, b,c,a, k; k is optional - criteria are invalid input - """ - d,e,f,g, b,c,a= paras[0:7] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k2 = 2.0*k; k1 = k - 1.0 - M,N,P,Q,R = d+e, e+f, (d-e)/2.0, (e-f)/2.0, g**2 - Gamma = M*s11 + N*s22 - Psi = ( (P*s11 + Q*s22)**2 + s12**2*R )**0.5 - - l1, l2, l3 = b*Gamma + c*Psi, b*Gamma - c*Psi, 2.0*c*Psi - l1s,l2s,l3s = l1**2, l2**2, l3**2 - - left = a*l1s**k + a*l2s**k + (1-a)*l3s**k - r = left**(1.0/k2)/eqStress - if not Jac: - return (r - 1.0).ravel() - else: - drdl,drdk = r/left/k2, r*math_ln(left)*(-1.0/k2/k) - dldl1,dldl2,dldl3 = a*k2*(l1s**k1)*l1, a*k2*(l2s**k1)*l2, (1-a)*k2*(l3s**k1)*l3 - dldGama, dldPsi = (dldl1 + dldl2)*b, (dldl1 - dldl2 + 2.0*dldl3)*c - temp = (P*s11 + Q*s22)/Psi - dPsidP, dPsidQ, dPsidR = temp*s11, temp*s22, 0.5*s12**2/Psi - dlda = l1s**k + l2s**k - l3s**k - dldb = dldl1*Gamma + dldl2*Gamma - dldc = dldl1*Psi - dldl2*Psi + dldl3*2.0*Psi - dldk = a*math_ln(l1s)*l1s**k + a*math_ln(l2s)*l2s**k + (1-a)*math_ln(l3s)*l3s**k - - J = drdl*np.array([dldGama*s11+dldPsi*dPsidP*0.5, dldGama*(s11+s22)+dldPsi*(-dPsidP+dPsidQ)*0.5, #jd,je - dldGama*s22-dldPsi*dPsidQ*0.5, dldPsi*dPsidR*2.0*g, #jf,jg - dldb, dldc, dlda]) #jb,jc,ja - if mFix[0]: return np.vstack(J).T - else: return np.vstack((J, drdl*dldk + drdk)).T - - -def BBC2003(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2003 yield criterion - - the fitted parameters are - M,N,P,Q,R,S,T,a, k; k is optional - criteria are invalid input - """ - M,N,P,Q,R,S,T,a = paras[0:8] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11,s22,s12 = sigmas[0], sigmas[1], sigmas[3] - k2 = 2.0*k; k1 = k - 1.0 - Gamma = 0.5 * (s11 + M*s22) - Psi = ( 0.25*(N*s11 - P*s22)**2 + Q*Q*s12**2 )**0.5 - Lambda = ( 0.25*(R*s11 - S*s22)**2 + T*T*s12**2 )**0.5 - - l1, l2, l3 = Gamma + Psi, Gamma - Psi, 2.0*Lambda - l1s,l2s,l3s = l1**2, l2**2, l3**2 - left = a*l1s**k + a*l2s**k + (1-a)*l3s**k - r = left**(1.0/k2)/eqStress - if not Jac: - return (r - 1.0).ravel() - else: - drdl,drdk = r/left/k2, r*math_ln(left)*(-1.0/k2/k) - dldl1,dldl2,dldl3 = a*k2*(l1s**k1)*l1, a*k2*(l2s**k1)*l2, (1-a)*k2*(l3s**k1)*l3 - - dldGamma, dldPsi, dldLambda = dldl1+dldl2, dldl1-dldl2, 2.0*dldl3 - temp = 0.25/Psi*(N*s11 - P*s22) - dPsidN, dPsidP, dPsidQ = s11*temp, -s22*temp, Q*s12**2/Psi - temp = 0.25/Lambda*(R*s11 - S*s22) - dLambdadR, dLambdadS, dLambdadT = s11*temp, -s22*temp, T*s12**2/Psi - dldk = a*math_ln(l1s)*l1s**k + a*math_ln(l2s)*l2s**k + (1-a)*math_ln(l3s)*l3s**k - - J = drdl * np.array([dldGamma*s22*0.5, #jM - dldPsi*dPsidN, dldPsi*dPsidP, dldPsi*dPsidQ, #jN, jP, jQ - dldLambda*dLambdadR, dldLambda*dLambdadS, dldLambda*dLambdadT, #jR, jS, jT - l1s**k + l2s**k - l3s**k ]) #ja - - if mFix[0]: return np.vstack(J).T - else : return np.vstack((J, drdl*dldk+drdk)).T - -def BBC2005(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - BBC2005 yield criterion - - the fitted parameters are - a, b, L ,M, N, P, Q, R, k k are optional - criteria is invalid input - """ - a,b,L, M, N, P, Q, R = paras[0:8] - if mFix[0]: k = mFix[1] - else: k = paras[-1] - - s11 = sigmas[0]; s22 = sigmas[1]; s12 = sigmas[3] - k2 = 2.0*k - Gamma = L*s11 + M*s22 - Lambda = ( (N*s11 - P*s22)**2 + s12**2 )**0.5 - Psi = ( (Q*s11 - R*s22)**2 + s12**2 )**0.5 - - l1 = Lambda + Gamma; l2 = Lambda - Gamma; l3 = Lambda + Psi; l4 = Lambda - Psi - l1s = l1**2; l2s = l2**2; l3s = l3**2; l4s = l4**2 - left = a*l1s**k + a*l2s**k + b*l3s**k + b*l4s**k - sBar = left**(1.0/k2); r = sBar/eqStress - 1.0 - if not Jac: - return r.ravel() - else: - ln = lambda x : np.log(x + 1.0e-32) - expo = 0.5/k; k1 = k-1.0 - - dsBardl = expo*sBar/left/eqStress - dsBarde = sBar*ln(left); dedk = expo/(-k) - dldl1 = a*k*(l1s**k1)*(2.0*l1) - dldl2 = a*k*(l2s**k1)*(2.0*l2) - dldl3 = b*k*(l3s**k1)*(2.0*l3) - dldl4 = b*k*(l4s**k1)*(2.0*l4) - - dldLambda = dldl1 + dldl2 + dldl3 + dldl4 - dldGama = dldl1 - dldl2 - dldPsi = dldl3 - dldl4 - temp = (N*s11 - P*s22)/Lambda - dLambdadN = s11*temp; dLambdadP = -s22*temp - temp = (Q*s11 - R*s22)/Psi - dPsidQ = s11*temp; dPsidR = -s22*temp - dldk = a*ln(l1s)*l1s**k + a*ln(l2s)*l2s**k + b*ln(l3s)*l3s**k + b*ln(l4s)*l4s**k - - J = dsBardl * np.array( [ - l1s**k+l2s**k, l3s**k+l4s**k,dldGama*s11,dldGama*s22,dldLambda*dLambdadN, - dldLambda*dLambdadP, dldPsi*dPsidQ, dldPsi*dPsidR]) - - if mFix[0]: return np.vstack(J).T - else : return np.vstack(J, dldk+dsBarde*dedk).T - -def Yld2000(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Yld2000 yield criterion - - C: c11,c22,c66 c12=c21=1.0 JAC NOT PASS - D: d11,d12,d21,d22,d66 - """ - C,D = paras[0:3], paras[3:8] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - s11, s22, s12 = sigmas[0],sigmas[1],sigmas[3] - X = np.array([ 2.0*C[0]*s11-C[0]*s22, 2.0*C[1]*s22-C[1]*s11, 3.0*C[2]*s12 ])/3.0 # a1,a2,a7 - Y = np.array([ (8.0*D[2]-2.0*D[0]-2.0*D[3]+2.0*D[1])*s11 + (4.0*D[3]-4.0*D[1]-4.0*D[2]+ D[0])*s22, - (4.0*D[0]-4.0*D[2]-4.0*D[1]+ D[3])*s11 + (8.0*D[1]-2.0*D[3]-2.0*D[0]+2.0*D[2])*s22, - 9.0*D[4]*s12 ])/9.0 - - def priStrs(s): - temp = np.sqrt( (s[0]-s[1])**2 + 4.0*s[2]**2 ) - return 0.5*(s[0]+s[1] + temp), 0.5*(s[0]+s[1] - temp) - m2 = m/2.0; m21 = m2 - 1.0 - (X1,X2), (Y1,Y2) = priStrs(X), priStrs(Y) # Principal values of X, Y - phi1s, phi21s, phi22s = (X1-X2)**2, (2.0*Y2+Y1)**2, (2.0*Y1+Y2)**2 - phi1, phi21, phi22 = phi1s**m2, phi21s**m2, phi22s**m2 - left = phi1 + phi21 + phi22 - r = (0.5*left)**(1.0/m)/eqStress - - if not Jac: - return (r-1.0).ravel() - else: - drdl, drdm = r/m/left, r*math_ln(0.5*left)*(-1.0/m/m) #/(-m*m) - dldm = ( phi1*math_ln(phi1s) + phi21*math_ln(phi21s) + phi22*math_ln(phi22s) )*0.5 - zero = np.zeros_like(s11); num = len(s11) - def dPrincipalds(X): - """Derivative of principla with respect to stress""" - temp = 1.0/np.sqrt( (X[0]-X[1])**2 + 4.0*X[2]**2 ) - dP1dsi = 0.5*np.array([ 1.0+temp*(X[0]-X[1]), 1.0-temp*(X[0]-X[1]), temp*4.0*X[2]]) - dP2dsi = 0.5*np.array([ 1.0-temp*(X[0]-X[1]), 1.0+temp*(X[0]-X[1]), -temp*4.0*X[2]]) - return np.array([dP1dsi, dP2dsi]) - - dXdXi, dYdYi = dPrincipalds(X), dPrincipalds(Y) - dXidC = np.array([ [ 2.0*s11-s22, zero, zero ], #dX11dC - [ zero, 2.0*s22-s11, zero ], #dX22dC - [ zero, zero, 3.0*s12 ] ])/3.0 #dX12dC - dYidD = np.array([ [ -2.0*s11+ s22, 2.0*s11-4.0*s22, 8.0*s11-4.0*s22, -2.0*s11+4.0*s22, zero ], #dY11dD - [ 4.0*s11-2.0*s22, -4.0*s11+8.0*s22, -4.0*s11+2.0*s22, s11-2.0*s22, zero ], #dY22dD - [ zero, zero, zero, zero, 9.0*s12 ] ])/9.0 #dY12dD - - dXdC=np.array([np.dot(dXdXi[:,:,i], dXidC[:,:,i]).T for i in range(num)]).T - dYdD=np.array([np.dot(dYdYi[:,:,i], dYidD[:,:,i]).T for i in range(num)]).T - - dldX = m*np.array([ phi1s**m21*(X1-X2), phi1s**m21*(X2-X1)]) - dldY = m*np.array([phi21s**m21*(2.0*Y2+Y1) + 2.0*phi22s**m21*(2.0*Y1+Y2), \ - phi22s**m21*(2.0*Y1+Y2) + 2.0*phi21s**m21*(2.0*Y2+Y1) ]) - jC = drdl*np.array([np.dot(dldX[:,i], dXdC[:,:,i]) for i in range(num)]).T - jD = drdl*np.array([np.dot(dldY[:,i], dYdD[:,:,i]) for i in range(num)]).T - - jm = drdl*dldm + drdm - if mFix[0]: return np.vstack((jC,jD)).T - else: return np.vstack((jC,jD,jm)).T - -def Yld200418p(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Yld2004-18p yield criterion - - the fitted parameters are - C: c12,c21,c23,c32,c31,c13,c44,c55,c66; D: d12,d21,d23,d32,d31,d13,d44,d55,d66 for 3D - C: c12,c21,c23,c32,c31,c13,c44; D: d12,d21,d23,d32,d31,d13,d44 for 2D - and m, m are optional - criteria is ignored - """ - if dim == 2: C,D = np.append(paras[0:7],[0.0,0.0]), np.append(paras[7:14],[0.0,0.0]) - else: C,D = paras[0:9], paras[9:18] - if mFix[0]: m = mFix[1] - else: m = paras[-1] - - sv = (sigmas[0] + sigmas[1] + sigmas[2])/3.0 - sdev = np.vstack((sigmas[0:3]-sv,sigmas[3:6])) - ys = lambda sdev, C: np.array([-C[0]*sdev[1]-C[5]*sdev[2], -C[1]*sdev[0]-C[2]*sdev[2], - -C[4]*sdev[0]-C[3]*sdev[1], C[6]*sdev[3], C[7]*sdev[4], C[8]*sdev[5]]) - p,q = ys(sdev, C), ys(sdev, D) - pLambdas, qLambdas = principalStress(p), principalStress(q) # no sort - - m2 = m/2.0; x3 = range(3); num = len(sv) - PiQj = np.array([(pLambdas[i,:]-qLambdas[j,:]) for i in x3 for j in x3]) - QiPj = np.array([(qLambdas[i,:]-pLambdas[j,:]) for i in x3 for j in x3]).reshape(3,3,num) - PiQjs = PiQj**2 - left = np.sum(PiQjs**m2,axis=0) - r = (0.25*left)**(1.0/m)/eqStress - - if not Jac: - return (r - 1.0).ravel() - else: - drdl, drdm = r/m/left, r*math_ln(0.25*left)*(-1.0/m/m) - dldm = np.sum(PiQjs**m2*math_ln(PiQjs),axis=0)*0.5 - dPdc, dQdd = principalStrs_Der(p, sdev, dim), principalStrs_Der(q, sdev, dim) - PiQjs3d = ( PiQjs**(m2-1.0) ).reshape(3,3,num) - dldP = -m*np.array([np.diag(np.dot(PiQjs3d[:,:,i], QiPj [:,:,i])) for i in range(num)]).T - dldQ = m*np.array([np.diag(np.dot(QiPj [:,:,i], PiQjs3d[:,:,i])) for i in range(num)]).T - - jm = drdl*dldm + drdm - jc = drdl*np.sum([dldP[i]*dPdc[i] for i in x3],axis=0) - jd = drdl*np.sum([dldQ[i]*dQdd[i] for i in x3],axis=0) - - if mFix[0]: return np.vstack((jc,jd)).T - else: return np.vstack((jc,jd,jm)).T - -def KarafillisBoyce(eqStress, paras, sigmas, mFix, criteria, dim, Jac=False): - """ - Karafillis-Boyce - - the fitted parameters are - c11,c12,c13,c14,c15,c16,c,m for 3D - c11,c12,c13,c14,c,m for plane stress - 0 1 and self.dimen == 2: - return fitCriteria[self.name]['labels'][1] - else: - return fitCriteria[self.name]['labels'][0] - - def report_name(self): - return fitCriteria[self.name]['name'] - - def fit(self,stress): - global fitResults; fitErrors; fitResidual - if options.exponent > 0.0: nExponent = options.exponent - else: nExponent = 0 - nameCriterion = self.name.lower() - criteria = Criteria(nameCriterion,self.uniaxial,self.expo, self.dimen) - bounds = fitCriteria[nameCriterion]['bound'][dDim] # Default bounds, no bound - guess0 = Guess # Default initial guess, depends on bounds - - if fitResults == []: - initialguess = guess0 - else: - initialguess = np.array(fitResults[-1]) - - ydata = np.zeros(np.shape(stress)[1]) - try: - popt, pcov, infodict, errmsg, ierr = \ - leastsqBound (criteria.fun, initialguess, args=(ydata,stress), - bounds=bounds, Dfun=criteria.jac, full_output=True) - if ierr not in [1, 2, 3, 4]: - raise RuntimeError("Optimal parameters not found: "+errmsg) - else: - residual = criteria.fun(popt, ydata, stress) - fitResidual.append(np.linalg.norm(residual)/np.sqrt(len(residual))) - if (len(ydata) > len(initialguess)) and pcov is not None: - s_sq = (criteria.fun(popt, *(ydata,stress))**2).sum()/(len(ydata)-len(initialguess)) - pcov = pcov * s_sq - perr = np.sqrt(np.diag(pcov)) - fitResults.append(popt.tolist()) - fitErrors .append(perr.tolist()) - - popt = np.concatenate((np.array(popt), np.repeat(options.exponent,nExponent))) - perr = np.concatenate((np.array(perr), np.repeat(0.0,nExponent))) - - damask.util.croak('Needed {} function calls for fitting'.format(infodict['nfev'])) - except Exception as detail: - damask.util.croak(detail) - pass - return popt - -#--------------------------------------------------------------------------------------------------- -class myThread (threading.Thread): - """Runner""" - - def __init__(self, threadID): - threading.Thread.__init__(self) - self.threadID = threadID - def run(self): - semaphore.acquire() - conv=converged() - semaphore.release() - while not conv: - if options.criterion=='facet': - doSimForFacet(self.name) - else: - doSim(self.name) - semaphore.acquire() - conv=converged() - semaphore.release() - -def doSim(thread): - semaphore.acquire() - global myLoad - loadNo=loadcaseNo() - if not os.path.isfile('%s.load'%loadNo): - damask.util.croak('Generating load case for simulation %s (%s)'%(loadNo,thread)) - f=open('%s.load'%loadNo,'w') - f.write(myLoad.getLoadcase(loadNo)) - f.close() - semaphore.release() - else: semaphore.release() - -# if spectralOut does not exist, run simulation - semaphore.acquire() - if not os.path.isfile('%s_%i.spectralOut'%(options.geometry,loadNo)): - damask.util.croak('Starting simulation %i (%s)'%(loadNo,thread)) - semaphore.release() - damask.util.execute('DAMASK_spectral -g %s -l %i'%(options.geometry,loadNo)) - else: semaphore.release() - -# reading values from ASCII file - semaphore.acquire() - damask.util.croak('Reading values from simulation %i (%s)'%(loadNo,thread)) - semaphore.release() - refFile = '%s_%i.yield'%(options.geometry,loadNo) - yieldStress = np.empty((6),'d') - if not os.path.isfile(refFile): - validity = False - else: - validity = True - yieldData = np.loadtxt(refFile) - stress = yieldData[:3] - yieldStress = t33toSym6(stress) -# do the actual fitting procedure and write results to file - semaphore.acquire() - global stressAll - f=open(options.geometry+'_'+options.criterion+'_'+str(time.time())+'.txt','w') - f.write(' '.join([options.fitting]+myFit.report_labels())+'\n') - try: - if validity: - stressAll=np.append(stressAll, yieldStress/stressUnit) - f.write(' '.join(map(str,myFit.fit(stressAll.reshape(len(stressAll)//6,6).transpose())))+'\n') - except Exception: - damask.util.croak('Could not fit results of simulation (%s)'%thread) - semaphore.release() - return - damask.util.croak('\n') - semaphore.release() - -def doSimForFacet(thread): - semaphore.acquire() - global myLoad - loadNo=loadcaseNo() - if not os.path.isfile('%s.load'%loadNo): - damask.util.croak('Generating load case for simulation %s (%s)'%(loadNo,thread)) - f=open('%s.load'%loadNo,'w') - f.write(myLoad.getLoadcase(loadNo)) - f.close() - semaphore.release() - else: semaphore.release() - -# if spectralOut does not exist, run simulation - semaphore.acquire() - if not os.path.isfile('%s_%i.spectralOut'%(options.geometry,loadNo)): - damask.util.croak('Starting simulation %i (%s)'%(loadNo,thread)) - semaphore.release() - damask.util.execute('DAMASK_spectral -g %s -l %i'%(options.geometry,loadNo)) - else: semaphore.release() - -def loadcaseNo(): - global N_simulations - N_simulations+=1 - return N_simulations - -def converged(): - global N_simulations; fitResidual - - if options.criterion=='facet': - if N_simulations == options.numpoints: - return True - else: - return False - else: - if N_simulations < options.max: - if len(fitResidual) > 5 and N_simulations >= options.min: - residualList = np.array(fitResidual[len(fitResidual)-5:]) - if np.std(residualList)/np.max(residualList) < 0.05: - return True - return False - else: - return True - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ -Performs calculations with various loads on given geometry file and fits yield surface. - -""", version = scriptID) - -# maybe make an option to specifiy if 2D/3D fitting should be done? - -parser.add_option('-l','--load' , dest='load', type='float', nargs=3, - help='load: final strain; increments; time %default', metavar='float int float') -parser.add_option('-g','--geometry', dest='geometry', type='string', - help='name of the geometry file [%default]', metavar='string') -parser.add_option('-c','--criterion', dest='criterion', choices=fitCriteria.keys(), - help='criterion for stopping simulations [%default]', metavar='string') -parser.add_option('-f','--fitting', dest='fitting', choices=thresholdParameter, - help='yield criterion [%default]', metavar='string') -parser.add_option('-y','--yieldvalue', dest='yieldValue', type='float', - help='yield points %default', metavar='float') -parser.add_option('--min', dest='min', type='int', - help='minimum number of simulations [%default]', metavar='int') -parser.add_option('--max', dest='max', type='int', - help='maximum number of iterations [%default]', metavar='int') -parser.add_option('-t','--threads', dest='threads', type='int', - help='number of parallel executions [%default]', metavar='int') -parser.add_option('-b','--bound', dest='bounds', type='float', nargs=2, - help='yield points: start; end; count %default', metavar='float float') -parser.add_option('-d','--dimension', dest='dimension', type='choice', choices=['2','3'], - help='dimension of the virtual test [%default]', metavar='int') -parser.add_option('-e', '--exponent', dest='exponent', type='float', - help='exponent of non-quadratic criteria', metavar='int') -parser.add_option('-u', '--uniaxial', dest='eqStress', type='float', - help='Equivalent stress', metavar='float') -parser.add_option('--flag', dest='flag', type='string', - help='yield stop flag, totalStrain, plasticStrain or plasticWork', metavar='string') -parser.add_option('--numpoints', dest='numpoints', type='int', - help='number of yield points to fit facet potential [%default]', metavar='int') -parser.add_option('--order', dest='order', type='int', - help='order of facet potential [%default]', metavar='int') - -parser.set_defaults(min = 12, - max = 20, - threads = 4, - yieldValue = 0.002, - load = (0.010,100,100.0), - criterion = 'vonmises', - fitting = 'totalshear', - geometry = '20grains16x16x16', - bounds = None, - dimension = '3', - exponent = -1.0, - flag = 'totalStrain', - numpoints = 100, - order = 8 - ) - -options = parser.parse_args()[0] - -if options.threads < 1: - parser.error('invalid number of threads {}'.format(options.threads)) -if options.min < 0: - parser.error('invalid minimum number of simulations {}'.format(options.min)) -if options.max < options.min: - parser.error('invalid maximum number of simulations (below minimum)') - -for check in [options.geometry+'.geom','numerics.config','material.config']: - if not os.path.isfile(check): - damask.util.croak('"{}" file not found'.format(check)) - -options.dimension = int(options.dimension) - -stressUnit = 1.0e9 if options.criterion == 'hill1948' else 1.0e6 - - -if options.dimension not in fitCriteria[options.criterion]['dimen']: - parser.error('invalid dimension for selected criterion') - -if options.criterion not in ['vonmises','tresca','drucker','hill1948'] and options.eqStress is None: - parser.error('please specify an equivalent stress (e.g. fitting to von Mises)') - -# global variables -fitResults = [] -fitErrors = [] -fitResidual = [] -stressAll= np.zeros(0,'d').reshape(0,0) -N_simulations=0 -Guess = [] -threads=[] -semaphore=threading.Semaphore(1) -dDim = None -myLoad = None -myFit = None - -if options.criterion == 'facet': - run = runFit(options.exponent, options.eqStress, options.dimension, options.criterion) -else: - run = runFit(options.exponent, options.eqStress, options.dimension, options.criterion) - -damask.util.croak('Finished fitting to yield criteria') From d1fa2a14dc6fe63032c088378f66bdad2b1482ea Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:28:00 +0100 Subject: [PATCH 045/148] was used only for yield surface fitting --- python/damask/util.py | 257 ------------------------------------------ 1 file changed, 257 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index 63b9aed65..cf041f946 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -221,263 +221,6 @@ class return_message(): return srepr(self.message) -def leastsqBound(func, x0, args=(), bounds=None, Dfun=None, full_output=0, - col_deriv=0, ftol=1.49012e-8, xtol=1.49012e-8, - gtol=0.0, maxfev=0, epsfcn=None, factor=100, diag=None): - from scipy.optimize import _minpack - """ - Non-linear least square fitting (Levenberg-Marquardt method) with - bounded parameters. - the codes of transformation between int <-> ext refers to the work of - Jonathan J. Helmus: https://github.com/jjhelmus/leastsqbound-scipy - other codes refer to the source code of minpack.py: - - An internal parameter list is used to enforce contraints on the fitting - parameters. The transfomation is based on that of MINUIT package. - please see: F. James and M. Winkler. MINUIT User's Guide, 2004. - - bounds : list - (min, max) pairs for each parameter, use None for 'min' or 'max' - when there is no bound in that direction. - For example: if there are two parameters needed to be fitting, then - bounds is [(min1,max1), (min2,max2)] - - This function is based on 'leastsq' of minpack.py, the annotation of - other parameters can be found in 'least_squares.py'. - """ - - def _check_func(checker, argname, thefunc, x0, args, numinputs, - output_shape=None): - from numpy import shape - """The same as that of minpack.py""" - res = np.atleast_1d(thefunc(*((x0[:numinputs],) + args))) - if (output_shape is not None) and (shape(res) != output_shape): - if (output_shape[0] != 1): - if len(output_shape) > 1: - if output_shape[1] == 1: - return shape(res) - msg = "%s: there is a mismatch between the input and output " \ - "shape of the '%s' argument" % (checker, argname) - func_name = getattr(thefunc, '__name__', None) - if func_name: - msg += " '%s'." % func_name - else: - msg += "." - raise TypeError(msg) - if np.issubdtype(res.dtype, np.inexact): - dt = res.dtype - else: - dt = dtype(float) - return shape(res), dt - - def _int2extGrad(p_int, bounds): - """Calculate the gradients of transforming the internal (unconstrained) to external (constrained) parameter.""" - grad = np.empty_like(p_int) - for i, (x, bound) in enumerate(zip(p_int, bounds)): - lower, upper = bound - if lower is None and upper is None: # No constraints - grad[i] = 1.0 - elif upper is None: # only lower bound - grad[i] = x/np.sqrt(x*x + 1.0) - elif lower is None: # only upper bound - grad[i] = -x/np.sqrt(x*x + 1.0) - else: # lower and upper bounds - grad[i] = (upper - lower)*np.cos(x)/2.0 - return grad - - def _int2extFunc(bounds): - """Transform internal parameters into external parameters.""" - local = [_int2extLocal(b) for b in bounds] - - def _transform_i2e(p_int): - p_ext = np.empty_like(p_int) - p_ext[:] = [i(j) for i, j in zip(local, p_int)] - return p_ext - return _transform_i2e - - def _ext2intFunc(bounds): - """Transform external parameters into internal parameters.""" - local = [_ext2intLocal(b) for b in bounds] - - def _transform_e2i(p_ext): - p_int = np.empty_like(p_ext) - p_int[:] = [i(j) for i, j in zip(local, p_ext)] - return p_int - return _transform_e2i - - def _int2extLocal(bound): - """Transform a single internal parameter to an external parameter.""" - lower, upper = bound - if lower is None and upper is None: # no constraints - return lambda x: x - elif upper is None: # only lower bound - return lambda x: lower - 1.0 + np.sqrt(x*x + 1.0) - elif lower is None: # only upper bound - return lambda x: upper + 1.0 - np.sqrt(x*x + 1.0) - else: - return lambda x: lower + ((upper - lower)/2.0)*(np.sin(x) + 1.0) - - def _ext2intLocal(bound): - """Transform a single external parameter to an internal parameter.""" - lower, upper = bound - if lower is None and upper is None: # no constraints - return lambda x: x - elif upper is None: # only lower bound - return lambda x: np.sqrt((x - lower + 1.0)**2 - 1.0) - elif lower is None: # only upper bound - return lambda x: np.sqrt((x - upper - 1.0)**2 - 1.0) - else: - return lambda x: np.arcsin((2.0*(x - lower)/(upper - lower)) - 1.0) - - i2e = _int2extFunc(bounds) - e2i = _ext2intFunc(bounds) - - x0 = np.asarray(x0).flatten() - n = len(x0) - - if len(bounds) != n: - raise ValueError('the length of bounds is inconsistent with the number of parameters ') - - if not isinstance(args, tuple): - args = (args,) - - shape, dtype = _check_func('leastsq', 'func', func, x0, args, n) - m = shape[0] - - if n > m: - raise TypeError('Improper input: N=%s must not exceed M=%s' % (n, m)) - if epsfcn is None: - epsfcn = np.finfo(dtype).eps - - def funcWarp(x, *args): - return func(i2e(x), *args) - - xi0 = e2i(x0) - - if Dfun is None: - if maxfev == 0: - maxfev = 200*(n + 1) - retval = _minpack._lmdif(funcWarp, xi0, args, full_output, ftol, xtol, - gtol, maxfev, epsfcn, factor, diag) - else: - if col_deriv: - _check_func('leastsq', 'Dfun', Dfun, x0, args, n, (n, m)) - else: - _check_func('leastsq', 'Dfun', Dfun, x0, args, n, (m, n)) - if maxfev == 0: - maxfev = 100*(n + 1) - - def DfunWarp(x, *args): - return Dfun(i2e(x), *args) - - retval = _minpack._lmder(funcWarp, DfunWarp, xi0, args, full_output, col_deriv, - ftol, xtol, gtol, maxfev, factor, diag) - - errors = {0: ["Improper input parameters.", TypeError], - 1: ["Both actual and predicted relative reductions " - "in the sum of squares\n are at most %f" % ftol, None], - 2: ["The relative error between two consecutive " - "iterates is at most %f" % xtol, None], - 3: ["Both actual and predicted relative reductions in " - "the sum of squares\n are at most %f and the " - "relative error between two consecutive " - "iterates is at \n most %f" % (ftol, xtol), None], - 4: ["The cosine of the angle between func(x) and any " - "column of the\n Jacobian is at most %f in " - "absolute value" % gtol, None], - 5: ["Number of calls to function has reached " - "maxfev = %d." % maxfev, ValueError], - 6: ["ftol=%f is too small, no further reduction " - "in the sum of squares\n is possible.""" % ftol, - ValueError], - 7: ["xtol=%f is too small, no further improvement in " - "the approximate\n solution is possible." % xtol, - ValueError], - 8: ["gtol=%f is too small, func(x) is orthogonal to the " - "columns of\n the Jacobian to machine " - "precision." % gtol, ValueError], - 'unknown': ["Unknown error.", TypeError]} - - info = retval[-1] # The FORTRAN return value - - if info not in [1, 2, 3, 4] and not full_output: - if info in [5, 6, 7, 8]: - np.warnings.warn(errors[info][0], RuntimeWarning) - else: - try: - raise errors[info][1](errors[info][0]) - except KeyError: - raise errors['unknown'][1](errors['unknown'][0]) - - mesg = errors[info][0] - x = i2e(retval[0]) - - if full_output: - grad = _int2extGrad(retval[0], bounds) - retval[1]['fjac'] = (retval[1]['fjac'].T / np.take(grad, - retval[1]['ipvt'] - 1)).T - cov_x = None - if info in [1, 2, 3, 4]: - from numpy.dual import inv - from numpy.linalg import LinAlgError - perm = np.take(np.eye(n), retval[1]['ipvt'] - 1, 0) - r = np.triu(np.transpose(retval[1]['fjac'])[:n, :]) - R = np.dot(r, perm) - try: - cov_x = inv(np.dot(np.transpose(R), R)) - except LinAlgError as inverror: - print(inverror) - pass - return (x, cov_x) + retval[1:-1] + (mesg, info) - else: - return (x, info) - -def _general_function(params, ydata, xdata, function): - return function(xdata, *params) - ydata -def _weighted_general_function(params, ydata, xdata, function, weights): - return (function(xdata, *params) - ydata)*weights - -def curve_fit_bound(f, xdata, ydata, p0=None, sigma=None, bounds=None, **kw): - """Similar as 'curve_fit' in minpack.py.""" - if p0 is None: - # determine number of parameters by inspecting the function - import inspect - args, varargs, varkw, defaults = inspect.getargspec(f) - if len(args) < 2: - msg = "Unable to determine number of fit parameters." - raise ValueError(msg) - if 'self' in args: - p0 = [1.0] * (len(args)-2) - else: - p0 = [1.0] * (len(args)-1) - - if np.isscalar(p0): - p0 = np.array([p0]) - - args = (ydata, xdata, f) - if sigma is None: - func = _general_function - else: - func = _weighted_general_function - args += (1.0/np.asarray(sigma),) - - return_full = kw.pop('full_output', False) - res = leastsqBound(func, p0, args=args, bounds = bounds, full_output=True, **kw) - (popt, pcov, infodict, errmsg, ier) = res - - if ier not in [1, 2, 3, 4]: - msg = "Optimal parameters not found: " + errmsg - raise RuntimeError(msg) - - if (len(ydata) > len(p0)) and pcov is not None: - s_sq = (func(popt, *args)**2).sum()/(len(ydata)-len(p0)) - pcov = pcov * s_sq - else: - pcov = np.inf - - return (popt, pcov, infodict, errmsg, ier) if return_full else (popt, pcov) - - class ThreadPool: """Pool of threads consuming tasks from a queue.""" From 82c741d6bbfd5ebf2d0d82fb611c3d80a20110f5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:32:38 +0100 Subject: [PATCH 046/148] crystallite not needed anymore --- processing/pre/geom_fromDREAM3D.py | 1 - processing/pre/geom_fromOsteonGeometry.py | 3 --- processing/pre/geom_fromVoronoiTessellation.py | 1 - processing/pre/hybridIA_linODFsampling.py | 6 ------ processing/pre/patchFromReconstructedBoundaries.py | 4 ---- 5 files changed, 15 deletions(-) diff --git a/processing/pre/geom_fromDREAM3D.py b/processing/pre/geom_fromDREAM3D.py index 159793cd8..7d5b1442d 100755 --- a/processing/pre/geom_fromDREAM3D.py +++ b/processing/pre/geom_fromDREAM3D.py @@ -145,7 +145,6 @@ for name in filenames: config_header += [''] for i in range(np.nanmax(microstructure)): config_header += ['[{}{}]'.format(label,i+1), - 'crystallite 1', '(constituent)\tphase {}\ttexture {}\tfraction 1.0'.format(phase[i],i+1), ] diff --git a/processing/pre/geom_fromOsteonGeometry.py b/processing/pre/geom_fromOsteonGeometry.py index 499a8867f..627d92728 100755 --- a/processing/pre/geom_fromOsteonGeometry.py +++ b/processing/pre/geom_fromOsteonGeometry.py @@ -126,15 +126,12 @@ for i in range(3,np.max(microstructure)): config_header = ['', '[canal]', - 'crystallite 1', '(constituent)\tphase 1\ttexture 1\tfraction 1.0', '[interstitial]', - 'crystallite 1', '(constituent)\tphase 2\ttexture 2\tfraction 1.0' ] for i in range(3,np.max(microstructure)): config_header += ['[Point{}]'.format(i-2), - 'crystallite 1', '(constituent)\tphase 3\ttexture {}\tfraction 1.0'.format(i) ] diff --git a/processing/pre/geom_fromVoronoiTessellation.py b/processing/pre/geom_fromVoronoiTessellation.py index 28e215f85..d5ec43701 100755 --- a/processing/pre/geom_fromVoronoiTessellation.py +++ b/processing/pre/geom_fromVoronoiTessellation.py @@ -290,7 +290,6 @@ for name in filenames: config_header += [''] for ID in grainIDs: config_header += ['[Grain{}]'.format(ID), - 'crystallite 1', '(constituent)\tphase {}\ttexture {}\tfraction 1.0'.format(options.phase,ID) ] diff --git a/processing/pre/hybridIA_linODFsampling.py b/processing/pre/hybridIA_linODFsampling.py index cf1a473cf..caa747337 100755 --- a/processing/pre/hybridIA_linODFsampling.py +++ b/processing/pre/hybridIA_linODFsampling.py @@ -211,10 +211,6 @@ parser.add_option('-p','--phase', dest = 'phase', type = 'int', metavar = 'int', help = 'phase index to be used [%default]') -parser.add_option('--crystallite', - dest = 'crystallite', - type = 'int', metavar = 'int', - help = 'crystallite index to be used [%default]') parser.add_option('-r', '--rnd', dest = 'randomSeed', type = 'int', metavar = 'int', \ @@ -223,7 +219,6 @@ parser.set_defaults(randomSeed = None, number = 500, algorithm = 'IA', phase = 1, - crystallite = 1, ang = True, ) @@ -351,7 +346,6 @@ for name in filenames: for i,ID in enumerate(range(nSamples)): materialConfig += ['[Grain%s]'%(str(ID+1).zfill(formatwidth)), - 'crystallite %i'%options.crystallite, '(constituent) phase %i texture %s fraction 1.0'%(options.phase,str(ID+1).rjust(formatwidth)), ] diff --git a/processing/pre/patchFromReconstructedBoundaries.py b/processing/pre/patchFromReconstructedBoundaries.py index fabec0fdf..e9196916e 100755 --- a/processing/pre/patchFromReconstructedBoundaries.py +++ b/processing/pre/patchFromReconstructedBoundaries.py @@ -941,19 +941,15 @@ if any(output in options.output for output in ['spectral','mentat']): for i,grain in enumerate(rcData['grainMapping']): config+=['[grain{}]'.format(grain), - 'crystallite\t1', '(constituent)\tphase 1\ttexture {}\tfraction 1.0'.format(i+1)] if (options.xmargin > 0.0): config+=['[x-margin]', - 'crystallite\t1', '(constituent)\tphase 2\ttexture {}\tfraction 1.0\n'.format(len(rcData['grainMapping'])+1)] if (options.ymargin > 0.0): config+=['[y-margin]', - 'crystallite\t1', '(constituent)\tphase 2\ttexture {}\tfraction 1.0\n'.format(len(rcData['grainMapping'])+1)] if (options.xmargin > 0.0 and options.ymargin > 0.0): config+=['[xy-margin]', - 'crystallite\t1', '(constituent)\tphase 2\ttexture {}\tfraction 1.0\n'.format(len(rcData['grainMapping'])+1)] if (options.xmargin > 0.0 or options.ymargin > 0.0): From acc252ea5b8ebf316c75bf10cd291c67d12f9e44 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:38:15 +0100 Subject: [PATCH 047/148] thermal/damage constitutive (i.e. source) results are not tested --- src/constitutive.f90 | 89 +------------------------------------------- src/crystallite.f90 | 16 +------- 2 files changed, 3 insertions(+), 102 deletions(-) diff --git a/src/constitutive.f90 b/src/constitutive.f90 index 4067c026a..6ad5c76cc 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -50,7 +50,6 @@ module constitutive constitutive_SandItsTangents, & constitutive_collectDotState, & constitutive_collectDeltaState, & - constitutive_postResults, & constitutive_results contains @@ -101,51 +100,14 @@ subroutine constitutive_init if (any(phase_kinematics == KINEMATICS_slipplane_opening_ID)) call kinematics_slipplane_opening_init if (any(phase_kinematics == KINEMATICS_thermal_expansion_ID)) call kinematics_thermal_expansion_init - write(6,'(/,a)') ' <<<+- constitutive init -+>>>' + write(6,'(/,a)') ' <<<+- constitutive init -+>>>'; flush(6) mainProcess: if (worldrank == 0) then !-------------------------------------------------------------------------------------------------- ! write description file for constitutive output call IO_write_jobFile(FILEUNIT,'outputConstitutive') PhaseLoop: do ph = 1,material_Nphase - activePhase: if (any(material_phaseAt == ph)) then - write(FILEUNIT,'(/,a,/)') '['//trim(config_name_phase(ph))//']' - - SourceLoop: do s = 1, phase_Nsources(ph) - knownSource = .true. ! assume valid - sourceType: select case (phase_source(s,ph)) - case (SOURCE_damage_isoBrittle_ID) sourceType - ins = source_damage_isoBrittle_instance(ph) - outputName = SOURCE_damage_isoBrittle_label - thisOutput => source_damage_isoBrittle_output - thisSize => source_damage_isoBrittle_sizePostResult - case (SOURCE_damage_isoDuctile_ID) sourceType - ins = source_damage_isoDuctile_instance(ph) - outputName = SOURCE_damage_isoDuctile_label - thisOutput => source_damage_isoDuctile_output - thisSize => source_damage_isoDuctile_sizePostResult - case (SOURCE_damage_anisoBrittle_ID) sourceType - ins = source_damage_anisoBrittle_instance(ph) - outputName = SOURCE_damage_anisoBrittle_label - thisOutput => source_damage_anisoBrittle_output - thisSize => source_damage_anisoBrittle_sizePostResult - case (SOURCE_damage_anisoDuctile_ID) sourceType - ins = source_damage_anisoDuctile_instance(ph) - outputName = SOURCE_damage_anisoDuctile_label - thisOutput => source_damage_anisoDuctile_output - thisSize => source_damage_anisoDuctile_sizePostResult - case default sourceType - knownSource = .false. - end select sourceType - if (knownSource) then - write(FILEUNIT,'(a)') '(source)'//char(9)//trim(outputName) - OutputSourceLoop: do o = 1,size(thisOutput(:,ins)) - if(len_trim(thisOutput(o,ins)) > 0) & - write(FILEUNIT,'(a,i4)') trim(thisOutput(o,ins))//char(9),thisSize(o,ins) - enddo OutputSourceLoop - endif - enddo SourceLoop - endif activePhase + if (any(material_phaseAt == ph)) write(FILEUNIT,'(/,a,/)') '['//trim(config_name_phase(ph))//']' enddo PhaseLoop close(FILEUNIT) endif mainProcess @@ -169,8 +131,6 @@ subroutine constitutive_init plasticState(ph)%sizeDotState) constitutive_source_maxSizeDotState = max(constitutive_source_maxSizeDotState, & maxval(sourceState(ph)%p(:)%sizeDotState)) - constitutive_source_maxSizePostResults = max(constitutive_source_maxSizePostResults, & - maxval(sourceState(ph)%p(:)%sizePostResults)) enddo PhaseLoop2 @@ -639,51 +599,6 @@ subroutine constitutive_collectDeltaState(S, Fe, Fi, ipc, ip, el) end subroutine constitutive_collectDeltaState -!-------------------------------------------------------------------------------------------------- -!> @brief returns array of constitutive results -!-------------------------------------------------------------------------------------------------- -function constitutive_postResults(S, Fi, ipc, ip, el) - - integer, intent(in) :: & - ipc, & !< component-ID of integration point - ip, & !< integration point - el !< element - real(pReal), dimension(sum(sourceState(material_phaseAt(ipc,el))%p(:)%sizePostResults)) :: & - constitutive_postResults - real(pReal), intent(in), dimension(3,3) :: & - Fi !< intermediate deformation gradient - real(pReal), intent(in), dimension(3,3) :: & - S !< 2nd Piola Kirchhoff stress - integer :: & - startPos, endPos - integer :: & - i, of, instance !< counter in source loop - - constitutive_postResults = 0.0_pReal - - - endPos = 0 - - SourceLoop: do i = 1, phase_Nsources(material_phaseAt(ipc,el)) - startPos = endPos + 1 - endPos = endPos + sourceState(material_phaseAt(ipc,el))%p(i)%sizePostResults - of = material_phasememberAt(ipc,ip,el) - sourceType: select case (phase_source(i,material_phaseAt(ipc,el))) - case (SOURCE_damage_isoBrittle_ID) sourceType - constitutive_postResults(startPos:endPos) = source_damage_isoBrittle_postResults(material_phaseAt(ipc,el),of) - case (SOURCE_damage_isoDuctile_ID) sourceType - constitutive_postResults(startPos:endPos) = source_damage_isoDuctile_postResults(material_phaseAt(ipc,el),of) - case (SOURCE_damage_anisoBrittle_ID) sourceType - constitutive_postResults(startPos:endPos) = source_damage_anisoBrittle_postResults(material_phaseAt(ipc,el),of) - case (SOURCE_damage_anisoDuctile_ID) sourceType - constitutive_postResults(startPos:endPos) = source_damage_anisoDuctile_postResults(material_phaseAt(ipc,el),of) - end select sourceType - - enddo SourceLoop - -end function constitutive_postResults - - !-------------------------------------------------------------------------------------------------- !> @brief writes constitutive results to HDF5 output file !-------------------------------------------------------------------------------------------------- diff --git a/src/crystallite.f90 b/src/crystallite.f90 index 292241001..dffd4079a 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -742,23 +742,9 @@ function crystallite_postResults(ipc, ip, el) ip, & !< integration point index ipc !< grain index - real(pReal), dimension(1+ & - 1+sum(sourceState(material_phaseAt(ipc,el))%p(:)%sizePostResults)) :: & - crystallite_postResults - integer :: & - c - + real(pReal), dimension(2) :: crystallite_postResults crystallite_postResults = 0.0_pReal - crystallite_postResults(1) = 0.0_pReal ! header-like information (length) - c = 1 - - crystallite_postResults(c+1) = real(sum(sourceState(material_phaseAt(ipc,el))%p(:)%sizePostResults),pReal) ! size of constitutive results - c = c + 1 - if (size(crystallite_postResults)-c > 0) & - crystallite_postResults(c+1:size(crystallite_postResults)) = & - constitutive_postResults(crystallite_S(1:3,1:3,ipc,ip,el), crystallite_Fi(1:3,1:3,ipc,ip,el), & - ipc, ip, el) end function crystallite_postResults From 07ebd8d1b3e92efe14e76d7b67c475a503539360 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:48:37 +0100 Subject: [PATCH 048/148] only damage/thermal 'homogenization' postResults is currently needed --- src/crystallite.f90 | 18 ------------------ src/damage_nonlocal.f90 | 2 +- src/homogenization.f90 | 10 +++------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/crystallite.f90 b/src/crystallite.f90 index dffd4079a..4ae87b82e 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -108,7 +108,6 @@ module crystallite crystallite_stressTangent, & crystallite_orientations, & crystallite_push33ToRef, & - crystallite_postResults, & crystallite_results contains @@ -732,23 +731,6 @@ function crystallite_push33ToRef(ipc,ip,el, tensor33) end function crystallite_push33ToRef -!-------------------------------------------------------------------------------------------------- -!> @brief return results of particular grain -!-------------------------------------------------------------------------------------------------- -function crystallite_postResults(ipc, ip, el) - - integer, intent(in):: & - el, & !< element index - ip, & !< integration point index - ipc !< grain index - - real(pReal), dimension(2) :: crystallite_postResults - - crystallite_postResults = 0.0_pReal - -end function crystallite_postResults - - !-------------------------------------------------------------------------------------------------- !> @brief writes crystallite results to HDF5 output file !-------------------------------------------------------------------------------------------------- diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index 0a8a3c867..d37f68486 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -55,7 +55,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_nonlocal_init - integer :: maxNinstance,homog,instance,o,i + integer :: maxNinstance,homog,instance,i integer :: sizeState integer :: NofMyHomog, h integer(kind(undefined_ID)) :: & diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 0112f9cf5..c5250f9d2 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -583,7 +583,7 @@ end subroutine materialpoint_stressAndItsTangent !-------------------------------------------------------------------------------------------------- -!> @brief parallelized calculation of result array at material points +!> @brief calculation of result array at material points !-------------------------------------------------------------------------------------------------- subroutine materialpoint_postResults @@ -595,7 +595,6 @@ subroutine materialpoint_postResults i, & !< integration point number e !< element number - !$OMP PARALLEL DO PRIVATE(myNgrains,thePos,theSize) elementLooping: do e = FEsolving_execElem(1),FEsolving_execElem(2) myNgrains = homogenization_Ngrains(material_homogenizationAt(e)) IpLooping: do i = FEsolving_execIP(1,e),FEsolving_execIP(2,e) @@ -615,15 +614,12 @@ subroutine materialpoint_postResults thePos = thePos + 1 grainLooping :do g = 1,myNgrains - theSize = 1 + & - 1 + & - sum(sourceState(material_phaseAt(g,e))%p(:)%sizePostResults) - materialpoint_results(thePos+1:thePos+theSize,i,e) = crystallite_postResults(g,i,e) ! tell crystallite results + theSize = 2 + materialpoint_results(thePos+1:thePos+theSize,i,e) = 0.0_pReal thePos = thePos + theSize enddo grainLooping enddo IpLooping enddo elementLooping - !$OMP END PARALLEL DO end subroutine materialpoint_postResults From 1c180864022c5484813ced9b85725938441efc02 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 05:58:27 +0100 Subject: [PATCH 049/148] not needed anymore --- src/constitutive.f90 | 18 ++++-------------- src/homogenization.f90 | 3 +-- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/constitutive.f90 b/src/constitutive.f90 index 6ad5c76cc..2d0688467 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -37,7 +37,6 @@ module constitutive integer, public, protected :: & constitutive_plasticity_maxSizeDotState, & - constitutive_source_maxSizePostResults, & constitutive_source_maxSizeDotState public :: & @@ -60,17 +59,9 @@ contains !-------------------------------------------------------------------------------------------------- subroutine constitutive_init - integer, parameter :: FILEUNIT = 204 integer :: & - o, & !< counter in output loop ph, & !< counter in phase loop - s, & !< counter in source loop - ins !< instance of plasticity/source - - integer, dimension(:,:), pointer :: thisSize - character(len=64), dimension(:,:), pointer :: thisOutput - character(len=32) :: outputName !< name of output, intermediate fix until HDF5 output is ready - logical :: knownSource + s !< counter in source loop !-------------------------------------------------------------------------------------------------- ! initialized plasticity @@ -105,16 +96,15 @@ subroutine constitutive_init mainProcess: if (worldrank == 0) then !-------------------------------------------------------------------------------------------------- ! write description file for constitutive output - call IO_write_jobFile(FILEUNIT,'outputConstitutive') + call IO_write_jobFile(204,'outputConstitutive') PhaseLoop: do ph = 1,material_Nphase - if (any(material_phaseAt == ph)) write(FILEUNIT,'(/,a,/)') '['//trim(config_name_phase(ph))//']' + if (any(material_phaseAt == ph)) write(204,'(/,a,/)') '['//trim(config_name_phase(ph))//']' enddo PhaseLoop - close(FILEUNIT) + close(204) endif mainProcess constitutive_plasticity_maxSizeDotState = 0 constitutive_source_maxSizeDotState = 0 - constitutive_source_maxSizePostResults = 0 PhaseLoop2:do ph = 1,material_Nphase !-------------------------------------------------------------------------------------------------- diff --git a/src/homogenization.f90 b/src/homogenization.f90 index c5250f9d2..5be988ec3 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -262,8 +262,7 @@ subroutine homogenization_init materialpoint_sizeResults = 1 & ! grain count + 1 + thermal_maxSizePostResults & + damage_maxSizePostResults & - + homogenization_maxNgrains * ( 1 & ! crystallite size - + 1 + constitutive_source_maxSizePostResults) + + homogenization_maxNgrains * 2 ! obsolete header information allocate(materialpoint_results(materialpoint_sizeResults,discretization_nIP,discretization_nElem)) write(6,'(/,a)') ' <<<+- homogenization init -+>>>' From eef6ae5733a662da968bb05318c994b9e05221e7 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 06:12:14 +0100 Subject: [PATCH 050/148] shell scripts are deprecated --- .gitlab-ci.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e82561c5..a2d4fb6a2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -506,18 +506,6 @@ GridSolver: - master - release -Processing: - stage: createDocumentation - script: - - cd $DAMASKROOT/processing/pre - - $DAMASKROOT/PRIVATE/documenting/scriptHelpToWiki.py --debug *.py - - cd $DAMASKROOT/processing/post - - rm vtk2ang.py DAD*.py - - $DAMASKROOT/PRIVATE/documenting/scriptHelpToWiki.py --debug *.py - except: - - master - - release - ################################################################################################## backupData: stage: saveDocumentation @@ -528,7 +516,6 @@ backupData: - mv $TESTROOT/performance/time.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ - mv $TESTROOT/performance/memory.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ - mv $DAMASKROOT/PRIVATE/documenting/DAMASK_* $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ - - mv $DAMASKROOT/processing $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ only: - development From 5abe27ab60e81d47d01c35c141e8cc913b29e1ce Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Dec 2019 06:24:37 +0100 Subject: [PATCH 051/148] only locally used --- src/source_damage_anisoBrittle.f90 | 4 ++-- src/source_damage_isoBrittle.f90 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index 9997f81e5..a68f7b315 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -21,10 +21,10 @@ module source_damage_anisoBrittle source_damage_anisoBrittle_offset, & !< which source is my current source mechanism? source_damage_anisoBrittle_instance !< instance of source mechanism - integer, dimension(:,:), allocatable, target, public :: & + integer, dimension(:,:), allocatable :: & source_damage_anisoBrittle_sizePostResult !< size of each post result output - character(len=64), dimension(:,:), allocatable, target, public :: & + character(len=64), dimension(:,:), allocatable :: & source_damage_anisoBrittle_output !< name of each post result output integer, dimension(:,:), allocatable :: & diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index 89f5a038c..f6b99555f 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -18,9 +18,9 @@ module source_damage_isoBrittle integer, dimension(:), allocatable, public, protected :: & source_damage_isoBrittle_offset, & source_damage_isoBrittle_instance - integer, dimension(:,:), allocatable, target, public :: & + integer, dimension(:,:), allocatable :: & source_damage_isoBrittle_sizePostResult - character(len=64), dimension(:,:), allocatable, target, public :: & + character(len=64), dimension(:,:), allocatable :: & source_damage_isoBrittle_output enum, bind(c) From 4be7aa990c0c4afbe507e65fe3817febbd003e45 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 06:45:00 +0100 Subject: [PATCH 052/148] HDF5 results output for constitutive damage models --- src/source_damage_anisoBrittle.f90 | 52 +++++++++++---------------- src/source_damage_anisoDuctile.f90 | 56 ++++++++++++------------------ src/source_damage_isoBrittle.f90 | 54 ++++++++++++---------------- src/source_damage_isoDuctile.f90 | 55 ++++++++++++----------------- 4 files changed, 88 insertions(+), 129 deletions(-) diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index a68f7b315..5dc8b96af 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -13,6 +13,7 @@ module source_damage_anisoBrittle use discretization use config use lattice + use results implicit none private @@ -21,9 +22,6 @@ module source_damage_anisoBrittle source_damage_anisoBrittle_offset, & !< which source is my current source mechanism? source_damage_anisoBrittle_instance !< instance of source mechanism - integer, dimension(:,:), allocatable :: & - source_damage_anisoBrittle_sizePostResult !< size of each post result output - character(len=64), dimension(:,:), allocatable :: & source_damage_anisoBrittle_output !< name of each post result output @@ -61,7 +59,7 @@ module source_damage_anisoBrittle source_damage_anisoBrittle_init, & source_damage_anisoBrittle_dotState, & source_damage_anisobrittle_getRateAndItsTangent, & - source_damage_anisoBrittle_postResults + source_damage_anisoBrittle_results contains @@ -102,7 +100,6 @@ subroutine source_damage_anisoBrittle_init enddo enddo - allocate(source_damage_anisoBrittle_sizePostResult(maxval(phase_Noutput),Ninstance), source=0) allocate(source_damage_anisoBrittle_output(maxval(phase_Noutput),Ninstance)) source_damage_anisoBrittle_output = '' @@ -154,7 +151,6 @@ subroutine source_damage_anisoBrittle_init select case(outputs(i)) case ('anisobrittle_drivingforce') - source_damage_anisoBrittle_sizePostResult(i,source_damage_anisoBrittle_instance(p)) = 1 source_damage_anisoBrittle_output(i,source_damage_anisoBrittle_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] @@ -171,7 +167,6 @@ subroutine source_damage_anisoBrittle_init call material_allocateSourceState(phase,sourceOffset,NofMyPhase,1,1,0) - sourceState(phase)%p(sourceOffset)%sizePostResults = sum(source_damage_anisoBrittle_sizePostResult(:,instance)) sourceState(phase)%p(sourceOffset)%aTolState=param(instance)%aTol @@ -262,39 +257,32 @@ subroutine source_damage_anisobrittle_getRateAndItsTangent(localphiDot, dLocalph dLocalphiDot_dPhi = -sourceState(phase)%p(sourceOffset)%state(1,constituent) -end subroutine source_damage_anisobrittle_getRateAndItsTangent +end subroutine source_damage_anisoBrittle_getRateAndItsTangent !-------------------------------------------------------------------------------------------------- -!> @brief return array of local damage results +!> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- -function source_damage_anisoBrittle_postResults(phase, constituent) +subroutine source_damage_anisoBrittle_results(phase,group) - integer, intent(in) :: & - phase, & - constituent - - real(pReal), dimension(sum(source_damage_anisoBrittle_sizePostResult(:, & - source_damage_anisoBrittle_instance(phase)))) :: & - source_damage_anisoBrittle_postResults - - integer :: & - instance, sourceOffset, o, c - - instance = source_damage_anisoBrittle_instance(phase) + integer, intent(in) :: phase + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: sourceOffset, o, instance + + instance = source_damage_anisoBrittle_instance(phase) sourceOffset = source_damage_anisoBrittle_offset(phase) - c = 0 - - do o = 1,size(param(instance)%outputID) - select case(param(instance)%outputID(o)) + associate(prm => param(instance), stt => sourceState(phase)%p(sourceOffset)%state) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) case (damage_drivingforce_ID) - source_damage_anisoBrittle_postResults(c+1) = & - sourceState(phase)%p(sourceOffset)%state(1,constituent) - c = c + 1 - + call results_writeDataset(group,stt,'tbd','driving force','tbd') end select - enddo -end function source_damage_anisoBrittle_postResults + enddo outputsLoop + end associate +#endif + +end subroutine source_damage_anisoBrittle_results end module source_damage_anisoBrittle diff --git a/src/source_damage_anisoDuctile.f90 b/src/source_damage_anisoDuctile.f90 index 409466e48..caba26ef4 100644 --- a/src/source_damage_anisoDuctile.f90 +++ b/src/source_damage_anisoDuctile.f90 @@ -12,6 +12,7 @@ module source_damage_anisoDuctile use discretization use material use config + use results implicit none private @@ -20,9 +21,6 @@ module source_damage_anisoDuctile source_damage_anisoDuctile_offset, & !< which source is my current damage mechanism? source_damage_anisoDuctile_instance !< instance of damage source mechanism - integer, dimension(:,:), allocatable, target, public :: & - source_damage_anisoDuctile_sizePostResult !< size of each post result output - character(len=64), dimension(:,:), allocatable, target, public :: & source_damage_anisoDuctile_output !< name of each post result output @@ -54,7 +52,7 @@ module source_damage_anisoDuctile source_damage_anisoDuctile_init, & source_damage_anisoDuctile_dotState, & source_damage_anisoDuctile_getRateAndItsTangent, & - source_damage_anisoDuctile_postResults + source_damage_anisoDuctile_results contains @@ -96,7 +94,6 @@ subroutine source_damage_anisoDuctile_init enddo enddo - allocate(source_damage_anisoDuctile_sizePostResult(maxval(phase_Noutput),Ninstance),source=0) allocate(source_damage_anisoDuctile_output(maxval(phase_Noutput),Ninstance)) source_damage_anisoDuctile_output = '' @@ -139,7 +136,6 @@ subroutine source_damage_anisoDuctile_init select case(outputs(i)) case ('anisoductile_drivingforce') - source_damage_anisoDuctile_sizePostResult(i,source_damage_anisoDuctile_instance(p)) = 1 source_damage_anisoDuctile_output(i,source_damage_anisoDuctile_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] @@ -156,8 +152,7 @@ subroutine source_damage_anisoDuctile_init sourceOffset = source_damage_anisoDuctile_offset(phase) call material_allocateSourceState(phase,sourceOffset,NofMyPhase,1,1,0) - sourceState(phase)%p(sourceOffset)%sizePostResults = sum(source_damage_anisoDuctile_sizePostResult(:,instance)) - sourceState(phase)%p(sourceOffset)%aTolState=param(instance)%aTol + sourceState(phase)%p(sourceOffset)%aTolState=param(instance)%aTol enddo @@ -226,35 +221,28 @@ end subroutine source_damage_anisoDuctile_getRateAndItsTangent !-------------------------------------------------------------------------------------------------- -!> @brief return array of local damage results +!> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- -function source_damage_anisoDuctile_postResults(phase, constituent) +subroutine source_damage_anisoDuctile_results(phase,group) - integer, intent(in) :: & - phase, & - constituent - real(pReal), dimension(sum(source_damage_anisoDuctile_sizePostResult(:, & - source_damage_anisoDuctile_instance(phase)))) :: & - source_damage_anisoDuctile_postResults - - integer :: & - instance, sourceOffset, o, c - - instance = source_damage_anisoDuctile_instance(phase) + integer, intent(in) :: phase + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: sourceOffset, o, instance + + instance = source_damage_anisoDuctile_instance(phase) sourceOffset = source_damage_anisoDuctile_offset(phase) - - c = 0 - - do o = 1,size(param(instance)%outputID) - select case(param(instance)%outputID(o)) - case (damage_drivingforce_ID) - source_damage_anisoDuctile_postResults(c+1) = & - sourceState(phase)%p(sourceOffset)%state(1,constituent) - c = c + 1 - - end select - enddo -end function source_damage_anisoDuctile_postResults + associate(prm => param(instance), stt => sourceState(phase)%p(sourceOffset)%state) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) + case (damage_drivingforce_ID) + call results_writeDataset(group,stt,'tbd','driving force','tbd') + end select + enddo outputsLoop + end associate +#endif + +end subroutine source_damage_anisoDuctile_results end module source_damage_anisoDuctile diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index f6b99555f..e38c15682 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -12,14 +12,13 @@ module source_damage_isoBrittle use discretization use material use config + use results implicit none private integer, dimension(:), allocatable, public, protected :: & source_damage_isoBrittle_offset, & source_damage_isoBrittle_instance - integer, dimension(:,:), allocatable :: & - source_damage_isoBrittle_sizePostResult character(len=64), dimension(:,:), allocatable :: & source_damage_isoBrittle_output @@ -46,7 +45,7 @@ module source_damage_isoBrittle source_damage_isoBrittle_init, & source_damage_isoBrittle_deltaState, & source_damage_isoBrittle_getRateAndItsTangent, & - source_damage_isoBrittle_postResults + source_damage_isoBrittle_Results contains @@ -85,8 +84,7 @@ subroutine source_damage_isoBrittle_init source_damage_isoBrittle_offset(phase) = source enddo enddo - - allocate(source_damage_isoBrittle_sizePostResult(maxval(phase_Noutput),Ninstance),source=0) + allocate(source_damage_isoBrittle_output(maxval(phase_Noutput),Ninstance)) source_damage_isoBrittle_output = '' @@ -122,7 +120,6 @@ subroutine source_damage_isoBrittle_init select case(outputs(i)) case ('isobrittle_drivingforce') - source_damage_isoBrittle_sizePostResult(i,source_damage_isoBrittle_instance(p)) = 1 source_damage_isoBrittle_output(i,source_damage_isoBrittle_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] @@ -139,7 +136,6 @@ subroutine source_damage_isoBrittle_init sourceOffset = source_damage_isoBrittle_offset(phase) call material_allocateSourceState(phase,sourceOffset,NofMyPhase,1,1,1) - sourceState(phase)%p(sourceOffset)%sizePostResults = sum(source_damage_isoBrittle_sizePostResult(:,instance)) sourceState(phase)%p(sourceOffset)%aTolState=param(instance)%aTol enddo @@ -214,35 +210,31 @@ subroutine source_damage_isoBrittle_getRateAndItsTangent(localphiDot, dLocalphiD - sourceState(phase)%p(sourceOffset)%state(1,constituent) end subroutine source_damage_isoBrittle_getRateAndItsTangent - -!-------------------------------------------------------------------------------------------------- -!> @brief return array of local damage results -!-------------------------------------------------------------------------------------------------- -function source_damage_isoBrittle_postResults(phase, constituent) - integer, intent(in) :: & - phase, & - constituent - real(pReal), dimension(sum(source_damage_isoBrittle_sizePostResult(:, & - source_damage_isoBrittle_instance(phase)))) :: & - source_damage_isoBrittle_postResults - integer :: & - instance, sourceOffset, o, c - - instance = source_damage_isoBrittle_instance(phase) +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +subroutine source_damage_isoBrittle_results(phase,group) + + integer, intent(in) :: phase + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: sourceOffset, o, instance + + instance = source_damage_isoBrittle_instance(phase) sourceOffset = source_damage_isoBrittle_offset(phase) - c = 0 - - do o = 1,size(param(instance)%outputID) - select case(param(instance)%outputID(o)) + associate(prm => param(instance), stt => sourceState(phase)%p(sourceOffset)%state) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) case (damage_drivingforce_ID) - source_damage_isoBrittle_postResults(c+1) = sourceState(phase)%p(sourceOffset)%state(1,constituent) - c = c + 1 - + call results_writeDataset(group,stt,'tbd','driving force','tbd') end select - enddo -end function source_damage_isoBrittle_postResults + enddo outputsLoop + end associate +#endif + +end subroutine source_damage_isoBrittle_results end module source_damage_isoBrittle diff --git a/src/source_damage_isoDuctile.f90 b/src/source_damage_isoDuctile.f90 index 65930cd07..69b8e82bf 100644 --- a/src/source_damage_isoDuctile.f90 +++ b/src/source_damage_isoDuctile.f90 @@ -11,6 +11,7 @@ module source_damage_isoDuctile use discretization use material use config + use results implicit none private @@ -18,9 +19,6 @@ module source_damage_isoDuctile source_damage_isoDuctile_offset, & !< which source is my current damage mechanism? source_damage_isoDuctile_instance !< instance of damage source mechanism - integer, dimension(:,:), allocatable, target, public :: & - source_damage_isoDuctile_sizePostResult !< size of each post result output - character(len=64), dimension(:,:), allocatable, target, public :: & source_damage_isoDuctile_output !< name of each post result output @@ -46,7 +44,7 @@ module source_damage_isoDuctile source_damage_isoDuctile_init, & source_damage_isoDuctile_dotState, & source_damage_isoDuctile_getRateAndItsTangent, & - source_damage_isoDuctile_postResults + source_damage_isoDuctile_Results contains @@ -86,7 +84,6 @@ subroutine source_damage_isoDuctile_init enddo enddo - allocate(source_damage_isoDuctile_sizePostResult(maxval(phase_Noutput),Ninstance),source=0) allocate(source_damage_isoDuctile_output(maxval(phase_Noutput),Ninstance)) source_damage_isoDuctile_output = '' @@ -122,7 +119,6 @@ subroutine source_damage_isoDuctile_init select case(outputs(i)) case ('isoductile_drivingforce') - source_damage_isoDuctile_sizePostResult(i,source_damage_isoDuctile_instance(p)) = 1 source_damage_isoDuctile_output(i,source_damage_isoDuctile_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] @@ -138,9 +134,7 @@ subroutine source_damage_isoDuctile_init sourceOffset = source_damage_isoDuctile_offset(phase) call material_allocateSourceState(phase,sourceOffset,NofMyPhase,1,1,0) - sourceState(phase)%p(sourceOffset)%sizePostResults = sum(source_damage_isoDuctile_sizePostResult(:,instance)) sourceState(phase)%p(sourceOffset)%aTolState=param(instance)%aTol - enddo @@ -196,35 +190,32 @@ subroutine source_damage_isoDuctile_getRateAndItsTangent(localphiDot, dLocalphiD dLocalphiDot_dPhi = -sourceState(phase)%p(sourceOffset)%state(1,constituent) end subroutine source_damage_isoDuctile_getRateAndItsTangent - -!-------------------------------------------------------------------------------------------------- -!> @brief return array of local damage results -!-------------------------------------------------------------------------------------------------- -function source_damage_isoDuctile_postResults(phase, constituent) - integer, intent(in) :: & - phase, & - constituent - real(pReal), dimension(sum(source_damage_isoDuctile_sizePostResult(:, & - source_damage_isoDuctile_instance(phase)))) :: & - source_damage_isoDuctile_postResults - integer :: & - instance, sourceOffset, o, c +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +subroutine source_damage_isoDuctile_results(phase,group) + + integer, intent(in) :: phase + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: sourceOffset, o, instance - instance = source_damage_isoDuctile_instance(phase) - sourceOffset = source_damage_isoDuctile_offset(phase) + instance = source_damage_isoDuctile_instance(phase) + sourceOffset = source_damage_isoDuctile_offset(phase) - c = 0 + associate(prm => param(instance), stt => sourceState(phase)%p(sourceOffset)%state) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) + case (damage_drivingforce_ID) + call results_writeDataset(group,stt,'tbd','driving force','tbd') + end select + enddo outputsLoop + end associate +#endif - do o = 1,size(param(instance)%outputID) - select case(param(instance)%outputID(o)) - case (damage_drivingforce_ID) - source_damage_isoDuctile_postResults(c+1) = sourceState(phase)%p(sourceOffset)%state(1,constituent) - c = c + 1 +end subroutine source_damage_isoDuctile_results - end select - enddo -end function source_damage_isoDuctile_postResults end module source_damage_isoDuctile From 5681e661e2472f2692e067635c87913ac80bf937 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 07:14:39 +0100 Subject: [PATCH 053/148] DADF5-results replaces postResults --- src/damage_local.f90 | 31 +++++++++++++++++++++++++++++- src/damage_nonlocal.f90 | 32 ++++++++++++++++++++++++++++++- src/homogenization.f90 | 42 +++++++++++++++++++++++++---------------- 3 files changed, 87 insertions(+), 18 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index 74ad47c9b..5ce27c339 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -11,6 +11,7 @@ module damage_local use source_damage_isoDuctile use source_damage_anisoBrittle use source_damage_anisoDuctile + use results implicit none private @@ -42,7 +43,8 @@ module damage_local public :: & damage_local_init, & damage_local_updateState, & - damage_local_postResults + damage_local_postResults, & + damage_local_Results contains @@ -210,6 +212,33 @@ subroutine damage_local_getSourceAndItsTangent(phiDot, dPhiDot_dPhi, phi, ip, el end subroutine damage_local_getSourceAndItsTangent +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +module subroutine damage_local_results(homog,group) + + integer, intent(in) :: homog + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: o, instance + + instance = damage_typeInstance(homog) + associate(prm => param(instance)) + + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) + + case (damage_ID) + call results_writeDataset(group,damage(homog)%p,'phi',& + 'damage indicator','-') + end select + enddo outputsLoop + end associate +#endif + +end subroutine damage_local_results + + !-------------------------------------------------------------------------------------------------- !> @brief return array of damage results !-------------------------------------------------------------------------------------------------- diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index d37f68486..d7e1aa074 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -14,6 +14,7 @@ module damage_nonlocal use source_damage_isoDuctile use source_damage_anisoBrittle use source_damage_anisoDuctile + use results implicit none private @@ -45,7 +46,8 @@ module damage_nonlocal damage_nonlocal_getDiffusion33, & damage_nonlocal_getMobility, & damage_nonlocal_putNonLocalDamage, & - damage_nonlocal_postResults + damage_nonlocal_postResults, & + damage_nonlocal_Results contains @@ -246,6 +248,34 @@ subroutine damage_nonlocal_putNonLocalDamage(phi,ip,el) end subroutine damage_nonlocal_putNonLocalDamage + +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +module subroutine damage_nonlocal_results(homog,group) + + integer, intent(in) :: homog + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: o, instance + + instance = damage_typeInstance(homog) + associate(prm => param(instance)) + + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) + + case (damage_ID) + call results_writeDataset(group,damage(homog)%p,'phi',& + 'damage indicator','-') + end select + enddo outputsLoop + end associate +#endif + +end subroutine damage_nonlocal_results + + !-------------------------------------------------------------------------------------------------- !> @brief return array of damage results !-------------------------------------------------------------------------------------------------- diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 5be988ec3..842c5f4b6 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -23,7 +23,6 @@ module homogenization use damage_local use damage_nonlocal use results - use HDF5_utilities implicit none private @@ -790,33 +789,44 @@ subroutine homogenization_results material_homogenization_type => homogenization_type integer :: p - character(len=256) :: group + character(len=pStringLen) :: group_base,group !real(pReal), dimension(:,:,:), allocatable :: temp do p=1,size(config_name_homogenization) - group = trim('current/materialpoint')//'/'//trim(config_name_homogenization(p)) - call HDF5_closeGroup(results_addGroup(group)) - - group = trim(group)//'/mech' - - call HDF5_closeGroup(results_addGroup(group)) - select case(material_homogenization_type(p)) - case(HOMOGENIZATION_rgc_ID) - call mech_RGC_results(homogenization_typeInstance(p),group) - end select - - group = trim('current/materialpoint')//'/'//trim(config_name_homogenization(p))//'/generic' - call HDF5_closeGroup(results_addGroup(group)) + group_base = 'current/materialpoint/'//trim(config_name_homogenization(p)) + call results_closeGroup(results_addGroup(group_base)) + group = trim(group_base)//'/generic' + call results_closeGroup(results_addGroup(group)) !temp = reshape(materialpoint_F,[3,3,discretization_nIP*discretization_nElem]) !call results_writeDataset(group,temp,'F',& ! 'deformation gradient','1') !temp = reshape(materialpoint_P,[3,3,discretization_nIP*discretization_nElem]) !call results_writeDataset(group,temp,'P',& ! '1st Piola-Kirchoff stress','Pa') + + group = trim(group_base)//'/mech' + call results_closeGroup(results_addGroup(group)) + select case(material_homogenization_type(p)) + case(HOMOGENIZATION_rgc_ID) + call mech_RGC_results(homogenization_typeInstance(p),group) + end select + + group = trim(group_base)//'/damage' + call results_closeGroup(results_addGroup(group)) + select case(damage_type(p)) + case(DAMAGE_LOCAL_ID) + call damage_local_results(p,group) + case(DAMAGE_NONLOCAL_ID) + call damage_nonlocal_results(p,group) + end select + + group = trim(group_base)//'/thermal' + call results_closeGroup(results_addGroup(group)) + + enddo - enddo #endif end subroutine homogenization_results From ca06c1d3a294228d70235f3cfd7bd9e15360da60 Mon Sep 17 00:00:00 2001 From: "f.basile" Date: Tue, 10 Dec 2019 13:49:33 +0100 Subject: [PATCH 054/148] Fix bugs in relatedOperations for NW,GT and GTprime. --- python/damask/orientation.py | 6 +- python/tests/reference/Rotation/1_BCC.pdf | Bin 0 -> 44551 bytes python/tests/reference/Rotation/1_FCC.pdf | Bin 0 -> 44391 bytes python/tests/reference/Rotation/2_BCC.pdf | Bin 0 -> 43118 bytes python/tests/reference/Rotation/2_FCC.pdf | Bin 0 -> 43107 bytes .../tests/reference/Rotation/PoleFigures_OR.m | 99 ++++++++++++++++++ python/tests/reference/Rotation/bcc_GT.txt | 2 +- .../tests/reference/Rotation/bcc_GT_prime.txt | 2 +- python/tests/reference/Rotation/bcc_NW.txt | 2 +- python/tests/reference/Rotation/fcc_GT.txt | 2 +- .../tests/reference/Rotation/fcc_GT_prime.txt | 2 +- python/tests/reference/Rotation/fcc_NW.txt | 2 +- 12 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 python/tests/reference/Rotation/1_BCC.pdf create mode 100644 python/tests/reference/Rotation/1_FCC.pdf create mode 100644 python/tests/reference/Rotation/2_BCC.pdf create mode 100644 python/tests/reference/Rotation/2_FCC.pdf create mode 100644 python/tests/reference/Rotation/PoleFigures_OR.m diff --git a/python/damask/orientation.py b/python/damask/orientation.py index 65318f169..1b08d2937 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -875,7 +875,7 @@ class Lattice: [[ 17, 12, 5],[ 17, 7, 17]], [[ 5, 17, 12],[ 17, 17, 7]], [[ 12, -5,-17],[ 7,-17,-17]], - [[-17,-12, 5],[-17, 7, 17]]],dtype='float')} + [[-17,-12, 5],[-17,-7, 17]]],dtype='float')} # Greninger--Troiano' orientation relationship for fcc <-> bcc transformation # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 @@ -901,7 +901,7 @@ class Lattice: [[-17,-17, 7],[-17, -5, 12]], [[ 7,-17,-17],[ 12,-17, -5]], [[ 17, -7,-17],[ 5, -12,-17]], - [[ 17,-17, 7],[ 17, -5,-12]], + [[ 17,-17, -7],[ 17, -5,-12]], [[ -7, 17,-17],[-12, 17, -5]], [[-17, 7,-17],[ -5, 12,-17]], [[-17, 17, -7],[-17, 5,-12]]],dtype='float'), @@ -957,7 +957,7 @@ class Lattice: [[ 2, 1, -1],[ 0, -1, 1]], [[ -1, -2, -1],[ 0, -1, 1]], [[ -1, 1, 2],[ 0, -1, 1]], - [[ -1, 2, 1],[ 0, -1, 1]], + [[ 2, -1, 1],[ 0, -1, 1]], #It is wrong in the paper, but matrix is correct [[ -1, 2, 1],[ 0, -1, 1]], [[ -1, -1, -2],[ 0, -1, 1]]],dtype='float')} diff --git a/python/tests/reference/Rotation/1_BCC.pdf b/python/tests/reference/Rotation/1_BCC.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fe22f6a2e36d180c423a1902b1a7409f866ff324 GIT binary patch literal 44551 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%0ALAhtr$ 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 BBRK#7 literal 0 HcmV?d00001 diff --git a/python/tests/reference/Rotation/PoleFigures_OR.m b/python/tests/reference/Rotation/PoleFigures_OR.m new file mode 100644 index 000000000..fb2baa6a1 --- /dev/null +++ b/python/tests/reference/Rotation/PoleFigures_OR.m @@ -0,0 +1,99 @@ +%% Import Script for EBSD Data +% +% Use MTEX +clear ; clear all + +%% Specify Crystal and Specimen Symmetries + +% crystal symmetry +CS_bcc = {... + crystalSymmetry('m-3m', [2.8665 2.8665 2.8665], 'mineral', 'Iron-alpha', 'color', 'light blue'),... + crystalSymmetry('m-3m', [1 1 1], 'color', 'light blue')}; + +CS_fcc = {... + crystalSymmetry('m-3m', [3.662 3.662 3.662], 'mineral', 'Iron', 'color', 'light blue'),... + crystalSymmetry('m-3m', [1 1 1], 'color', 'light blue')}; + +% plotting convention +setMTEXpref('xAxisDirection','north'); +setMTEXpref('zAxisDirection','outOfPlane'); + +%% path to files +pname = 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed + +% which files to be imported +fname1 = [pname '\bcc_Bain.txt']; fname2 = [pname '\bcc_GT.txt']; fname3 = [pname '\bcc_GT_prime.txt']; +fname4 = [pname '\bcc_KS.txt']; fname5 = [pname '\bcc_NW.txt']; fname6 = [pname '\bcc_Pitsch.txt']; +fname7 = [pname '\fcc_Bain.txt']; fname8 = [pname '\fcc_GT.txt']; fname9 = [pname '\fcc_GT_prime.txt']; +fname10 = [pname '\fcc_KS.txt']; fname11 = [pname '\fcc_NW.txt']; fname12 = [pname '\fcc_Pitsch.txt']; + + +%% Import the Data + +% create an EBSD variable containing the data +ebsd1 = loadEBSD(fname1,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd2 = loadEBSD(fname2,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd3 = loadEBSD(fname3,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd4 = loadEBSD(fname4,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd5 = loadEBSD(fname5,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd6 = loadEBSD(fname6,CS_bcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + +ebsd7 = loadEBSD(fname7,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd8 = loadEBSD(fname8,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd9 = loadEBSD(fname9,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd10 = loadEBSD(fname10,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd11 = loadEBSD(fname11,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); +ebsd12 = loadEBSD(fname12,CS_fcc,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + +%% Plot Data 1stpart_bcc +h1 = [Miller(1,0,0,ebsd1.CS),Miller(1,1,0,ebsd1.CS),Miller(1,1,1,ebsd1.CS)]; % 3 pole figures +plotPDF(ebsd1.orientations,h1,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','r','DisplayName','BCC-Bain') +hold on +plotPDF(ebsd2.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','b','DisplayName','BCC-GT') +plotPDF(ebsd3.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','g','DisplayName','BCC-GT_Prime') +legend('show','location','southoutside') +cd 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed +orient('landscape') +print('-bestfit','1_BCC.pdf','-dpdf') + +%% Plot Data 2nd part_bcc +close +plotPDF(ebsd4.orientations,h1,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','w','DisplayName','BCC-KS') +hold on +plotPDF(ebsd5.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','m','DisplayName','BCC-NW') +plotPDF(ebsd6.orientations,h1,'MarkerSize',5,'MarkerColor','y','MarkerEdgeColor','w','DisplayName','BCC-Pitsch') +legend('show','location','southoutside') +print('-bestfit','2_BCC.pdf','-dpdf') + +%% Plot Data 1stpart_fcc +close +h2 = [Miller(1,0,0,ebsd7.CS),Miller(1,1,0,ebsd7.CS),Miller(1,1,1,ebsd7.CS)]; % 3 pole figures +plotPDF(ebsd7.orientations,h2,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','r','DisplayName','FCC-Bain') +hold on +plotPDF(ebsd8.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','b','DisplayName','FCC-GT') +plotPDF(ebsd9.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','g','DisplayName','FCC-GT_Prime') +legend('show','location','southoutside') +print('-bestfit','1_FCC.pdf','-dpdf') + +%% Plot Data 2nd part_bcc +close +plotPDF(ebsd10.orientations,h2,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','w','DisplayName','FCC-KS') +hold on +plotPDF(ebsd11.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','m','DisplayName','FCC-NW') +plotPDF(ebsd12.orientations,h2,'MarkerSize',5,'MarkerColor','y','MarkerEdgeColor','w','DisplayName','FCC-Pitsch') +legend('show','location','southoutside') +print('-bestfit','2_FCC.pdf','-dpdf') +close + diff --git a/python/tests/reference/Rotation/bcc_GT.txt b/python/tests/reference/Rotation/bcc_GT.txt index d1fe2e1c8..5d5102698 100644 --- a/python/tests/reference/Rotation/bcc_GT.txt +++ b/python/tests/reference/Rotation/bcc_GT.txt @@ -23,4 +23,4 @@ 352.1156357053931 43.82007387041961 14.074783631236542 1 21 77.82610341510008 43.397849654402556 273.4002228089796 1 22 193.60440567265297 9.976439066337806 123.24637065555939 1 23 -153.65751914298576 65.6559553854118 185.90444335627936 1 24 +172.11563570539317 43.82007387041961 194.07478363123653 1 24 diff --git a/python/tests/reference/Rotation/bcc_GT_prime.txt b/python/tests/reference/Rotation/bcc_GT_prime.txt index 42f32bcbb..e398d3139 100644 --- a/python/tests/reference/Rotation/bcc_GT_prime.txt +++ b/python/tests/reference/Rotation/bcc_GT_prime.txt @@ -20,7 +20,7 @@ 183.40022280897963 43.397849654402556 167.8261034151001 1 18 255.92521636876344 43.82007387041961 97.88436429460687 1 19 33.24637065555936 9.976439066337804 283.60440567265294 1 20 -26.291675350407385 65.60048732963618 354.34378938496315 1 21 +356.59977719102034 43.39784965440254 12.173896584899929 1 21 75.92521636876346 43.82007387041961 277.8843642946069 1 22 213.24637065555936 9.976439066337804 103.604405672653 1 23 176.59977719102034 43.397849654402556 192.17389658489986 1 24 diff --git a/python/tests/reference/Rotation/bcc_NW.txt b/python/tests/reference/Rotation/bcc_NW.txt index 76e7c6182..754c69bba 100644 --- a/python/tests/reference/Rotation/bcc_NW.txt +++ b/python/tests/reference/Rotation/bcc_NW.txt @@ -9,6 +9,6 @@ 134.58444405678858 83.13253115922213 96.91733794010702 1 7 225.41555594321142 83.13253115922213 173.082662059893 1 8 0.0 9.735610317245317 135.0 1 9 -260.40196970123213 45.81931182053556 283.6387072794765 1 10 +99.59803029876785 45.81931182053557 166.36129272052355 1 10 260.40196970123213 45.81931182053556 283.6387072794765 1 11 180.0 99.73561031724535 225.0 1 12 diff --git a/python/tests/reference/Rotation/fcc_GT.txt b/python/tests/reference/Rotation/fcc_GT.txt index b91a80c46..cefae431a 100644 --- a/python/tests/reference/Rotation/fcc_GT.txt +++ b/python/tests/reference/Rotation/fcc_GT.txt @@ -23,4 +23,4 @@ 165.92521636876344 43.82007387041961 187.88436429460683 1 21 266.59977719102034 43.39784965440254 102.17389658489992 1 22 56.75362934444064 9.976439066337804 346.395594327347 1 23 -354.0955566437206 65.6559553854118 26.342480857014277 1 24 +345.9252163687635 43.82007387041961 7.884364294606862 1 24 diff --git a/python/tests/reference/Rotation/fcc_GT_prime.txt b/python/tests/reference/Rotation/fcc_GT_prime.txt index 1d6f171c4..44a9b25ec 100644 --- a/python/tests/reference/Rotation/fcc_GT_prime.txt +++ b/python/tests/reference/Rotation/fcc_GT_prime.txt @@ -20,7 +20,7 @@ 12.173896584899929 43.39784965440254 356.59977719102034 1 18 82.11563570539313 43.82007387041961 284.0747836312365 1 19 256.395594327347 9.976439066337804 146.75362934444064 1 20 -185.65621061503683 65.60048732963617 153.70832464959264 1 21 +167.8261034151001 43.397849654402556 183.40022280897963 1 21 262.1156357053931 43.82007387041961 104.07478363123654 1 22 76.39559432734703 9.976439066337806 326.75362934444064 1 23 347.8261034151001 43.39784965440255 3.400222808979685 1 24 diff --git a/python/tests/reference/Rotation/fcc_NW.txt b/python/tests/reference/Rotation/fcc_NW.txt index e041ec0b0..cc9c95a05 100644 --- a/python/tests/reference/Rotation/fcc_NW.txt +++ b/python/tests/reference/Rotation/fcc_NW.txt @@ -9,6 +9,6 @@ 83.082662059893 83.13253115922213 45.415555943211444 1 7 6.917337940106983 83.13253115922211 314.5844440567886 1 8 45.0 9.73561031724532 180.0 1 9 -256.36129272052347 45.81931182053556 279.59803029876775 1 10 +13.638707279476469 45.81931182053557 80.40196970123216 1 10 256.36129272052347 45.81931182053556 279.59803029876775 1 11 315.0 99.73561031724536 0.0 1 12 From a162840ab6c0f2fecc6798b1c07d75c43f25d7d7 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 16:18:44 +0100 Subject: [PATCH 055/148] include some cleaning --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 524e86c11..a535b399a 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 524e86c117d816e3bd873eed7663e258a6f2e139 +Subproject commit a535b399a91ef1b117b88332b77bbba90dac83f2 From c46c18de6ffd59497c93c4d55830dbe5606920e3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 16:20:50 +0100 Subject: [PATCH 056/148] taking care of prospector complaints --- processing/pre/hybridIA_linODFsampling.py | 20 +++++++++---------- .../pre/patchFromReconstructedBoundaries.py | 12 ++++------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/processing/pre/hybridIA_linODFsampling.py b/processing/pre/hybridIA_linODFsampling.py index caa747337..80d82a458 100755 --- a/processing/pre/hybridIA_linODFsampling.py +++ b/processing/pre/hybridIA_linODFsampling.py @@ -19,7 +19,7 @@ def integerFactorization(i): return j def binAsBins(bin,intervals): - """Explode compound bin into 3D bins list""" + """Explode compound bin into 3D bins list.""" bins = [0]*3 bins[0] = (bin//(intervals[1] * intervals[2])) % intervals[0] bins[1] = (bin//intervals[2]) % intervals[1] @@ -27,17 +27,17 @@ def binAsBins(bin,intervals): return bins def binsAsBin(bins,intervals): - """Implode 3D bins into compound bin""" + """Implode 3D bins into compound bin.""" return (bins[0]*intervals[1] + bins[1])*intervals[2] + bins[2] def EulersAsBins(Eulers,intervals,deltas,center): - """Return list of Eulers translated into 3D bins list""" + """Return list of Eulers translated into 3D bins list.""" return [int((euler+(0.5-center)*delta)//delta)%interval \ for euler,delta,interval in zip(Eulers,deltas,intervals) \ ] def binAsEulers(bin,intervals,deltas,center): - """Compound bin number translated into list of Eulers""" + """Compound bin number translated into list of Eulers.""" Eulers = [0.0]*3 Eulers[2] = (bin%intervals[2] + center)*deltas[2] Eulers[1] = (bin//intervals[2]%intervals[1] + center)*deltas[1] @@ -45,7 +45,7 @@ def binAsEulers(bin,intervals,deltas,center): return Eulers def directInvRepetitions(probability,scale): - """Calculate number of samples drawn by direct inversion""" + """Calculate number of samples drawn by direct inversion.""" nDirectInv = 0 for bin in range(len(probability)): # loop over bins nDirectInv += int(round(probability[bin]*scale)) # calc repetition @@ -56,7 +56,7 @@ def directInvRepetitions(probability,scale): # ----- efficient algorithm --------- def directInversion (ODF,nSamples): - """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians)""" + """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians).""" nOptSamples = max(ODF['nNonZero'],nSamples) # random subsampling if too little samples requested nInvSamples = 0 @@ -118,7 +118,7 @@ def directInversion (ODF,nSamples): # ----- trial and error algorithms --------- def MonteCarloEulers (ODF,nSamples): - """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians)""" + """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians).""" countMC = 0 maxdV_V = max(ODF['dV_V']) orientations = np.zeros((nSamples,3),'f') @@ -141,7 +141,7 @@ def MonteCarloEulers (ODF,nSamples): def MonteCarloBins (ODF,nSamples): - """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians)""" + """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians).""" countMC = 0 maxdV_V = max(ODF['dV_V']) orientations = np.zeros((nSamples,3),'f') @@ -163,7 +163,7 @@ def MonteCarloBins (ODF,nSamples): def TothVanHoutteSTAT (ODF,nSamples): - """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians)""" + """ODF contains 'dV_V' (normalized to 1), 'center', 'intervals', 'limits' (in radians).""" orientations = np.zeros((nSamples,3),'f') reconstructedODF = np.zeros(ODF['nBins'],'f') unitInc = 1.0/nSamples @@ -235,7 +235,7 @@ if filenames == []: filenames = [None] for name in filenames: try: table = damask.ASCIItable(name = name, buffered = False, readonly=True) - except: + except IOError: continue damask.util.report(scriptName,name) diff --git a/processing/pre/patchFromReconstructedBoundaries.py b/processing/pre/patchFromReconstructedBoundaries.py index e9196916e..b710fb2cb 100755 --- a/processing/pre/patchFromReconstructedBoundaries.py +++ b/processing/pre/patchFromReconstructedBoundaries.py @@ -78,13 +78,11 @@ def rcbOrientationParser(content,idcolumn): damask.util.croak('You might not have chosen the correct column for the grain IDs! '+ 'Please check the "--id" option.') raise - except: - raise return grains def rcbParser(content,M,size,tolerance,idcolumn,segmentcolumn): - """Parser for TSL-OIM reconstructed boundary files""" + """Parser for TSL-OIM reconstructed boundary files.""" # find bounding box boxX = [1.*sys.maxint,-1.*sys.maxint] boxY = [1.*sys.maxint,-1.*sys.maxint] @@ -99,8 +97,6 @@ def rcbParser(content,M,size,tolerance,idcolumn,segmentcolumn): damask.util.croak('You might not have chosen the correct column for the segment end points! '+ 'Please check the "--segment" option.') raise - except: - raise (x[0],y[0]) = (M[0]*x[0]+M[1]*y[0],M[2]*x[0]+M[3]*y[0]) # apply transformation to coordinates (x[1],y[1]) = (M[0]*x[1]+M[1]*y[1],M[2]*x[1]+M[3]*y[1]) # to get rcb --> Euler system boxX[0] = min(boxX[0],x[0],x[1]) @@ -728,7 +724,7 @@ def image(name,imgsize,marginX,marginY,rcData): # ------------------------- def inside(x,y,points): - """Tests whether point(x,y) is within polygon described by points""" + """Tests whether point(x,y) is within polygon described by points.""" inside = False npoints=len(points) (x1,y1) = points[npoints-1] # start with last point of points @@ -750,7 +746,7 @@ def inside(x,y,points): # ------------------------- def fftbuild(rcData,height,xframe,yframe,grid,extrusion): - """Build array of grain numbers""" + """Build array of grain numbers.""" maxX = -1.*sys.maxint maxY = -1.*sys.maxint for line in rcData['point']: # find data range @@ -883,7 +879,7 @@ try: boundaryFile = open(args[0]) boundarySegments = boundaryFile.readlines() boundaryFile.close() -except: +except IOError: damask.util.croak('unable to read boundary file "{}".'.format(args[0])) raise From ac35759c2fab4710d1831e72c49f7d27df773bac Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 17:25:51 +0100 Subject: [PATCH 057/148] not a module subroutine --- src/damage_local.f90 | 2 +- src/damage_nonlocal.f90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index 5ce27c339..a57bb5c65 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -215,7 +215,7 @@ end subroutine damage_local_getSourceAndItsTangent !-------------------------------------------------------------------------------------------------- !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- -module subroutine damage_local_results(homog,group) +subroutine damage_local_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index d7e1aa074..a5e211783 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -252,7 +252,7 @@ end subroutine damage_nonlocal_putNonLocalDamage !-------------------------------------------------------------------------------------------------- !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- -module subroutine damage_nonlocal_results(homog,group) +subroutine damage_nonlocal_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group From 74707b1b00be08413175f0573d4075b11da551bd Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 17:36:25 +0100 Subject: [PATCH 058/148] hybridIA test needs update (no crystallite) --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index a535b399a..be7872952 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit a535b399a91ef1b117b88332b77bbba90dac83f2 +Subproject commit be78729525144accdbcda97e9abc625558af89cb From db91803b807ef016c18cf43460adb85b868f3369 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 17:52:37 +0100 Subject: [PATCH 059/148] cleaning --- PRIVATE | 2 +- src/damage_local.f90 | 38 -------------------------------- src/damage_nonlocal.f90 | 40 --------------------------------- src/homogenization.f90 | 49 ++--------------------------------------- 4 files changed, 3 insertions(+), 126 deletions(-) diff --git a/PRIVATE b/PRIVATE index be7872952..952238b95 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit be78729525144accdbcda97e9abc625558af89cb +Subproject commit 952238b951a3d0c1c79df52530681724d3dead2e diff --git a/src/damage_local.f90 b/src/damage_local.f90 index a57bb5c65..aa9292f49 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -16,8 +16,6 @@ module damage_local implicit none private - integer, dimension(:,:), allocatable, target, public :: & - damage_local_sizePostResult character(len=64), dimension(:,:), allocatable, target, public :: & damage_local_output integer, dimension(:), allocatable, target, public :: & @@ -43,7 +41,6 @@ module damage_local public :: & damage_local_init, & damage_local_updateState, & - damage_local_postResults, & damage_local_Results contains @@ -68,7 +65,6 @@ subroutine damage_local_init maxNinstance = count(damage_type == DAMAGE_local_ID) if (maxNinstance == 0) return - allocate(damage_local_sizePostResult (maxval(homogenization_Noutput),maxNinstance),source=0) allocate(damage_local_output (maxval(homogenization_Noutput),maxNinstance)) damage_local_output = '' allocate(damage_local_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) @@ -92,7 +88,6 @@ subroutine damage_local_init case ('damage') damage_local_output(i,damage_typeInstance(h)) = outputs(i) damage_local_Noutput(instance) = damage_local_Noutput(instance) + 1 - damage_local_sizePostResult(i,damage_typeInstance(h)) = 1 prm%outputID = [prm%outputID , damage_ID] end select @@ -108,7 +103,6 @@ subroutine damage_local_init ! allocate state arrays sizeState = 1 damageState(homog)%sizeState = sizeState - damageState(homog)%sizePostResults = sum(damage_local_sizePostResult(:,instance)) allocate(damageState(homog)%state0 (sizeState,NofMyHomog), source=damage_initialPhi(homog)) allocate(damageState(homog)%subState0(sizeState,NofMyHomog), source=damage_initialPhi(homog)) allocate(damageState(homog)%state (sizeState,NofMyHomog), source=damage_initialPhi(homog)) @@ -239,36 +233,4 @@ subroutine damage_local_results(homog,group) end subroutine damage_local_results -!-------------------------------------------------------------------------------------------------- -!> @brief return array of damage results -!-------------------------------------------------------------------------------------------------- -function damage_local_postResults(ip,el) - - integer, intent(in) :: & - ip, & !< integration point - el !< element - real(pReal), dimension(sum(damage_local_sizePostResult(:,damage_typeInstance(material_homogenizationAt(el))))) :: & - damage_local_postResults - - integer :: instance, homog, offset, o, c - - homog = material_homogenizationAt(el) - offset = damageMapping(homog)%p(ip,el) - instance = damage_typeInstance(homog) - associate(prm => param(instance)) - c = 0 - - outputsLoop: do o = 1,size(prm%outputID) - select case(prm%outputID(o)) - - case (damage_ID) - damage_local_postResults(c+1) = damage(homog)%p(offset) - c = c + 1 - end select - enddo outputsLoop - - end associate - -end function damage_local_postResults - end module damage_local diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index a5e211783..855fa0ea5 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -19,8 +19,6 @@ module damage_nonlocal implicit none private - integer, dimension(:,:), allocatable, target, public :: & - damage_nonlocal_sizePostResult character(len=64), dimension(:,:), allocatable, target, public :: & damage_nonlocal_output integer, dimension(:), allocatable, target, public :: & @@ -46,7 +44,6 @@ module damage_nonlocal damage_nonlocal_getDiffusion33, & damage_nonlocal_getMobility, & damage_nonlocal_putNonLocalDamage, & - damage_nonlocal_postResults, & damage_nonlocal_Results contains @@ -71,7 +68,6 @@ subroutine damage_nonlocal_init maxNinstance = count(damage_type == DAMAGE_nonlocal_ID) if (maxNinstance == 0) return - allocate(damage_nonlocal_sizePostResult (maxval(homogenization_Noutput),maxNinstance),source=0) allocate(damage_nonlocal_output (maxval(homogenization_Noutput),maxNinstance)) damage_nonlocal_output = '' allocate(damage_nonlocal_Noutput (maxNinstance), source=0) @@ -94,7 +90,6 @@ subroutine damage_nonlocal_init case ('damage') damage_nonlocal_output(i,damage_typeInstance(h)) = outputs(i) damage_nonlocal_Noutput(instance) = damage_nonlocal_Noutput(instance) + 1 - damage_nonlocal_sizePostResult(i,damage_typeInstance(h)) = 1 prm%outputID = [prm%outputID , damage_ID] end select @@ -109,7 +104,6 @@ subroutine damage_nonlocal_init ! allocate state arrays sizeState = 1 damageState(homog)%sizeState = sizeState - damageState(homog)%sizePostResults = sum(damage_nonlocal_sizePostResult(:,instance)) allocate(damageState(homog)%state0 (sizeState,NofMyHomog), source=damage_initialPhi(homog)) allocate(damageState(homog)%subState0(sizeState,NofMyHomog), source=damage_initialPhi(homog)) allocate(damageState(homog)%state (sizeState,NofMyHomog), source=damage_initialPhi(homog)) @@ -248,7 +242,6 @@ subroutine damage_nonlocal_putNonLocalDamage(phi,ip,el) end subroutine damage_nonlocal_putNonLocalDamage - !-------------------------------------------------------------------------------------------------- !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- @@ -275,37 +268,4 @@ subroutine damage_nonlocal_results(homog,group) end subroutine damage_nonlocal_results - -!-------------------------------------------------------------------------------------------------- -!> @brief return array of damage results -!-------------------------------------------------------------------------------------------------- -function damage_nonlocal_postResults(ip,el) - - integer, intent(in) :: & - ip, & !< integration point - el !< element - real(pReal), dimension(sum(damage_nonlocal_sizePostResult(:,damage_typeInstance(material_homogenizationAt(el))))) :: & - damage_nonlocal_postResults - - integer :: & - instance, homog, offset, o, c - - homog = material_homogenizationAt(el) - offset = damageMapping(homog)%p(ip,el) - instance = damage_typeInstance(homog) - associate(prm => param(instance)) - c = 0 - - outputsLoop: do o = 1,size(prm%outputID) - select case(prm%outputID(o)) - - case (damage_ID) - damage_nonlocal_postResults(c+1) = damage(homog)%p(offset) - c = c + 1 - end select - enddo outputsLoop - - end associate -end function damage_nonlocal_postResults - end module damage_nonlocal diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 842c5f4b6..5fa723eda 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -39,8 +39,7 @@ module homogenization materialpoint_results !< results array of material point integer, public, protected :: & materialpoint_sizeResults, & - thermal_maxSizePostResults, & - damage_maxSizePostResults + thermal_maxSizePostResults real(pReal), dimension(:,:,:,:), allocatable :: & materialpoint_subF0, & !< def grad of IP at beginning of homogenization increment @@ -196,35 +195,6 @@ subroutine homogenization_init endif endif - i = damage_typeInstance(p) ! which instance of this damage type - valid = .true. ! assume valid - select case(damage_type(p)) ! split per damage type - case (DAMAGE_none_ID) - outputName = DAMAGE_none_label - thisNoutput => null() - thisOutput => null() - thisSize => null() - case (DAMAGE_local_ID) - outputName = DAMAGE_local_label - thisNoutput => damage_local_Noutput - thisOutput => damage_local_output - thisSize => damage_local_sizePostResult - case (DAMAGE_nonlocal_ID) - outputName = DAMAGE_nonlocal_label - thisNoutput => damage_nonlocal_Noutput - thisOutput => damage_nonlocal_output - thisSize => damage_nonlocal_sizePostResult - case default - valid = .false. - end select - if (valid) then - write(FILEUNIT,'(a)') '(damage)'//char(9)//trim(outputName) - if (damage_type(p) /= DAMAGE_none_ID) then - do e = 1,thisNoutput(i) - write(FILEUNIT,'(a,i4)') trim(thisOutput(e,i))//char(9),thisSize(e,i) - enddo - endif - endif endif enddo close(FILEUNIT) @@ -252,15 +222,12 @@ subroutine homogenization_init !-------------------------------------------------------------------------------------------------- ! allocate and initialize global state and postresutls variables thermal_maxSizePostResults = 0 - damage_maxSizePostResults = 0 do p = 1,size(config_homogenization) thermal_maxSizePostResults = max(thermal_maxSizePostResults, thermalState(p)%sizePostResults) - damage_maxSizePostResults = max(damage_maxSizePostResults, damageState (p)%sizePostResults) enddo materialpoint_sizeResults = 1 & ! grain count + 1 + thermal_maxSizePostResults & - + damage_maxSizePostResults & + homogenization_maxNgrains * 2 ! obsolete header information allocate(materialpoint_results(materialpoint_sizeResults,discretization_nIP,discretization_nElem)) @@ -742,8 +709,7 @@ function postResults(ip,el) integer, intent(in) :: & ip, & !< integration point el !< element number - real(pReal), dimension( thermalState (material_homogenizationAt(el))%sizePostResults & - + damageState (material_homogenizationAt(el))%sizePostResults) :: & + real(pReal), dimension( thermalState (material_homogenizationAt(el))%sizePostResults) :: & postResults integer :: & startPos, endPos ,& @@ -766,17 +732,6 @@ function postResults(ip,el) end select chosenThermal - startPos = endPos + 1 - endPos = endPos + damageState(material_homogenizationAt(el))%sizePostResults - chosenDamage: select case (damage_type(material_homogenizationAt(el))) - - case (DAMAGE_local_ID) chosenDamage - postResults(startPos:endPos) = damage_local_postResults(ip, el) - case (DAMAGE_nonlocal_ID) chosenDamage - postResults(startPos:endPos) = damage_nonlocal_postResults(ip, el) - - end select chosenDamage - end function postResults From 4c1281b4fc417e3042c501e2e0c36a25ec1fe742 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 20:04:03 +0100 Subject: [PATCH 060/148] rename changed order in shapes dict. This resulted in wrong column names when writing to ASCII file two fixes (one would be enough): 1) keep order (build new directory) 2) write in order of labels in pandas dataframe, not in order in shapes dict --- python/damask/table.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/damask/table.py b/python/damask/table.py index 6181fdb1f..56af8b622 100644 --- a/python/damask/table.py +++ b/python/damask/table.py @@ -203,7 +203,7 @@ class Table(): '' if info is None else ': {}'.format(info), )) - self.shapes[label_new] = self.shapes.pop(label_old) + self.shapes = {(label if label is not label_old else label_new):self.shapes[label] for label in self.shapes} def sort_by(self,labels,ascending=True): @@ -234,8 +234,9 @@ class Table(): Filename or file for reading. """ + seen = set() labels = [] - for l in self.shapes: + for l in [x for x in self.data.columns if not (x in seen or seen.add(x))]: if(self.shapes[l] == (1,)): labels.append('{}'.format(l)) elif(len(self.shapes[l]) == 1): From e142d00d02a4229ceee3203e270ecdc16360902f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 20:05:24 +0100 Subject: [PATCH 061/148] test now sensible to wrong rename --- python/tests/test_Table.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/python/tests/test_Table.py b/python/tests/test_Table.py index a0dc31975..2046d3803 100644 --- a/python/tests/test_Table.py +++ b/python/tests/test_Table.py @@ -65,11 +65,13 @@ class TestTable: default.add('nine',d,'random data') assert np.allclose(d,default.get('nine')) - def test_rename_equivalent(self,default): - v = default.get('v') - default.rename('v','u') - u = default.get('u') - assert np.all(v == u) + def test_rename_equivalent(self): + x = np.random.random((5,13)) + t = Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['random test data']) + s = t.get('s') + t.rename('s','u') + u = t.get('u') + assert np.all(s == u) def test_rename_gone(self,default): default.rename('v','V') From 8fb8e9be6ea2a3fff9c2d037df9a11f831943d86 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 20:25:19 +0100 Subject: [PATCH 062/148] write temperature to DADF5 --- src/homogenization.f90 | 7 +++++++ src/thermal_adiabatic.f90 | 26 ++++++++++++++++++++++++++ src/thermal_conduction.f90 | 27 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 5fa723eda..b014bebb5 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -779,6 +779,13 @@ subroutine homogenization_results group = trim(group_base)//'/thermal' call results_closeGroup(results_addGroup(group)) + select case(thermal_type(p)) + case(THERMAL_ADIABATIC_ID) + call thermal_adiabatic_results(p,group) + case(THERMAL_CONDUCTION_ID) + call thermal_conduction_results(p,group) + end select + enddo diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 2aa69bec5..5c30f280b 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -7,6 +7,7 @@ module thermal_adiabatic use config use numerics use material + use results use source_thermal_dissipation use source_thermal_externalheat use crystallite @@ -37,6 +38,7 @@ module thermal_adiabatic thermal_adiabatic_getSourceAndItsTangent, & thermal_adiabatic_getSpecificHeat, & thermal_adiabatic_getMassDensity, & + thermal_adiabatic_results, & thermal_adiabatic_postResults contains @@ -251,6 +253,30 @@ function thermal_adiabatic_getMassDensity(ip,el) end function thermal_adiabatic_getMassDensity +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +subroutine thermal_adiabatic_results(homog,group) + + integer, intent(in) :: homog + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: o, instance + + instance = thermal_typeInstance(homog) + + outputsLoop: do o = 1,thermal_adiabatic_Noutput(instance) + select case(thermal_adiabatic_outputID(o,instance)) + + case (temperature_ID) + call results_writeDataset(group,temperature(homog)%p,'T',& + 'temperature','K') + end select + enddo outputsLoop +#endif + +end subroutine thermal_adiabatic_results + !-------------------------------------------------------------------------------------------------- !> @brief return array of thermal results !-------------------------------------------------------------------------------------------------- diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index e513d709f..5507d8a7d 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -7,6 +7,7 @@ module thermal_conduction use material use config use lattice + use results use crystallite use source_thermal_dissipation use source_thermal_externalheat @@ -37,6 +38,7 @@ module thermal_conduction thermal_conduction_getSpecificHeat, & thermal_conduction_getMassDensity, & thermal_conduction_putTemperatureAndItsRate, & + thermal_conduction_results, & thermal_conduction_postResults contains @@ -263,6 +265,31 @@ subroutine thermal_conduction_putTemperatureAndItsRate(T,Tdot,ip,el) end subroutine thermal_conduction_putTemperatureAndItsRate + +!-------------------------------------------------------------------------------------------------- +!> @brief writes results to HDF5 output file +!-------------------------------------------------------------------------------------------------- +subroutine thermal_conduction_results(homog,group) + + integer, intent(in) :: homog + character(len=*), intent(in) :: group +#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: o, instance + + instance = thermal_typeInstance(homog) + + outputsLoop: do o = 1,thermal_conduction_Noutput(instance) + select case(thermal_conduction_outputID(o,instance)) + + case (temperature_ID) + call results_writeDataset(group,temperature(homog)%p,'T',& + 'temperature','K') + end select + enddo outputsLoop +#endif + +end subroutine thermal_conduction_results + !-------------------------------------------------------------------------------------------------- !> @brief return array of thermal results From e3b316bcaeee886f644bae31c19d4dc30d89ca64 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 10 Dec 2019 20:33:44 +0100 Subject: [PATCH 063/148] all tests without spectral Out --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 952238b95..4c20d48ec 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 952238b951a3d0c1c79df52530681724d3dead2e +Subproject commit 4c20d48ec1df8230dde85afbc3e7b5efca8cd314 From 9b67ead62f4b7795499d6ae6f7b64ddd9e9c56ad Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 00:10:02 +0100 Subject: [PATCH 064/148] removed postResults completely --- src/CPFEM2.f90 | 1 - src/DAMASK_abaqus.f | 5 +- src/constitutive.f90 | 11 --- src/crystallite.f90 | 8 -- src/grid/DAMASK_grid.f90 | 78 +------------------ src/homogenization.f90 | 150 +------------------------------------ src/prec.f90 | 3 +- src/thermal_adiabatic.f90 | 37 +-------- src/thermal_conduction.f90 | 37 +-------- 9 files changed, 8 insertions(+), 322 deletions(-) diff --git a/src/CPFEM2.f90 b/src/CPFEM2.f90 index 7123602f8..ace3e51f4 100644 --- a/src/CPFEM2.f90 +++ b/src/CPFEM2.f90 @@ -65,7 +65,6 @@ subroutine CPFEM_initAll call constitutive_init call crystallite_init call homogenization_init - call materialpoint_postResults call CPFEM_init end subroutine CPFEM_initAll diff --git a/src/DAMASK_abaqus.f b/src/DAMASK_abaqus.f index e2c56a06e..0f663dde3 100644 --- a/src/DAMASK_abaqus.f +++ b/src/DAMASK_abaqus.f @@ -143,9 +143,6 @@ subroutine UMAT(STRESS,STATEV,DDSDDE,SSE,SPD,SCD,& outdatedByNewInc, & outdatedFFN1, & lastStep - use homogenization, only: & - materialpoint_sizeResults, & - materialpoint_results implicit none integer(pInt), intent(in) :: & @@ -332,7 +329,7 @@ subroutine UMAT(STRESS,STATEV,DDSDDE,SSE,SPD,SCD,& ddsdde(6,:) = ddsdde_h(5,:) end if - statev = materialpoint_results(1:min(nstatv,materialpoint_sizeResults),npt,mesh_FEasCP('elem', noel)) + statev = 0 if (terminallyIll) pnewdt = 0.5_pReal ! force cutback directly ? !$ call omp_set_num_threads(defaultNumThreadsInt) ! reset number of threads to stored default value diff --git a/src/constitutive.f90 b/src/constitutive.f90 index 2d0688467..1fd833f29 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -93,16 +93,6 @@ subroutine constitutive_init write(6,'(/,a)') ' <<<+- constitutive init -+>>>'; flush(6) - mainProcess: if (worldrank == 0) then -!-------------------------------------------------------------------------------------------------- -! write description file for constitutive output - call IO_write_jobFile(204,'outputConstitutive') - PhaseLoop: do ph = 1,material_Nphase - if (any(material_phaseAt == ph)) write(204,'(/,a,/)') '['//trim(config_name_phase(ph))//']' - enddo PhaseLoop - close(204) - endif mainProcess - constitutive_plasticity_maxSizeDotState = 0 constitutive_source_maxSizeDotState = 0 @@ -123,7 +113,6 @@ subroutine constitutive_init maxval(sourceState(ph)%p(:)%sizeDotState)) enddo PhaseLoop2 - end subroutine constitutive_init diff --git a/src/crystallite.f90 b/src/crystallite.f90 index 4ae87b82e..9b7768e4a 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -118,7 +118,6 @@ contains !-------------------------------------------------------------------------------------------------- subroutine crystallite_init - integer, parameter :: FILEUNIT=434 logical, dimension(:,:), allocatable :: devNull integer :: & c, & !< counter in integration point component loop @@ -232,13 +231,6 @@ subroutine crystallite_init #endif enddo -!-------------------------------------------------------------------------------------------------- -! write description file for crystallite output - if (worldrank == 0) then - call IO_write_jobFile(FILEUNIT,'outputCrystallite') - write(FILEUNIT,'(/,a,/)') '[not supported anymore]' - close(FILEUNIT) - endif call config_deallocate('material.config/phase') !-------------------------------------------------------------------------------------------------- diff --git a/src/grid/DAMASK_grid.f90 b/src/grid/DAMASK_grid.f90 index 51c97456b..d2a86d769 100644 --- a/src/grid/DAMASK_grid.f90 +++ b/src/grid/DAMASK_grid.f90 @@ -15,11 +15,7 @@ program DAMASK_spectral use config use debug use math - use mesh_grid use CPFEM2 - use FEsolving - use numerics - use homogenization use material use spectral_utilities use grid_mech_spectral_basic @@ -80,12 +76,6 @@ program DAMASK_spectral type(tLoadCase), allocatable, dimension(:) :: loadCases !< array of all load cases type(tLoadCase) :: newLoadCase type(tSolutionState), allocatable, dimension(:) :: solres - integer(MPI_OFFSET_KIND) :: fileOffset - integer(MPI_OFFSET_KIND), dimension(:), allocatable :: outputSize - integer, parameter :: maxByteOut = 2147483647-4096 !< limit of one file output write https://trac.mpich.org/projects/mpich/ticket/1742 - integer, parameter :: maxRealOut = maxByteOut/pReal - integer(pLongInt), dimension(2) :: outputIndex - PetscErrorCode :: ierr procedure(grid_mech_spectral_basic_init), pointer :: & mech_init procedure(grid_mech_spectral_basic_forward), pointer :: & @@ -280,10 +270,8 @@ program DAMASK_spectral enddo if (any(newLoadCase%stress%maskLogical .eqv. & newLoadCase%deformation%maskLogical)) errorID = 831 ! exclusive or masking only - if (any(newLoadCase%stress%maskLogical .and. & - transpose(newLoadCase%stress%maskLogical) .and. & - reshape([ .false.,.true.,.true.,.true.,.false.,.true.,.true.,.true.,.false.],[ 3,3]))) & - errorID = 838 ! no rotation is allowed by stress BC + if (any(newLoadCase%stress%maskLogical .and. transpose(newLoadCase%stress%maskLogical) & + .and. (math_I3<1))) errorID = 838 ! no rotation is allowed by stress BC write(6,'(2x,a)') 'stress / GPa:' do i = 1, 3; do j = 1, 3 if(newLoadCase%stress%maskLogical(i,j)) then @@ -335,26 +323,10 @@ program DAMASK_spectral ! write header of output file if (worldrank == 0) then writeHeader: if (interface_restartInc < 1) then - open(newunit=fileUnit,file=trim(getSolverJobName())//& - '.spectralOut',form='UNFORMATTED',status='REPLACE') - write(fileUnit) 'load:', trim(loadCaseFile) ! ... and write header - write(fileUnit) 'workingdir:', 'n/a' - write(fileUnit) 'geometry:', trim(geometryFile) - write(fileUnit) 'grid:', grid - write(fileUnit) 'size:', geomSize - write(fileUnit) 'materialpoint_sizeResults:', materialpoint_sizeResults - write(fileUnit) 'loadcases:', size(loadCases) - write(fileUnit) 'frequencies:', loadCases%outputfrequency ! one entry per LoadCase - write(fileUnit) 'times:', loadCases%time ! one entry per LoadCase - write(fileUnit) 'logscales:', loadCases%logscale - write(fileUnit) 'increments:', loadCases%incs ! one entry per LoadCase - write(fileUnit) 'startingIncrement:', interface_restartInc ! start with writing out the previous inc - write(fileUnit) 'eoh' - close(fileUnit) ! end of header open(newunit=statUnit,file=trim(getSolverJobName())//'.sta',form='FORMATTED',status='REPLACE') write(statUnit,'(a)') 'Increment Time CutbackLevel Converged IterationsNeeded' ! statistics file if (iand(debug_level(debug_spectral),debug_levelBasic) /= 0) & - write(6,'(/,a)') ' header of result and statistics file written out' + write(6,'(/,a)') ' header of statistics file written out' flush(6) else writeHeader open(newunit=statUnit,file=trim(getSolverJobName())//& @@ -362,40 +334,11 @@ program DAMASK_spectral endif writeHeader endif -!-------------------------------------------------------------------------------------------------- -! prepare MPI parallel out (including opening of file) - allocate(outputSize(worldsize), source = 0_MPI_OFFSET_KIND) - outputSize(worldrank+1) = size(materialpoint_results,kind=MPI_OFFSET_KIND)*int(pReal,MPI_OFFSET_KIND) - call MPI_allreduce(MPI_IN_PLACE,outputSize,worldsize,MPI_LONG,MPI_SUM,PETSC_COMM_WORLD,ierr) ! get total output size over each process - if (ierr /= 0) call IO_error(error_ID=894, ext_msg='MPI_allreduce') - call MPI_file_open(PETSC_COMM_WORLD, trim(getSolverJobName())//'.spectralOut', & - MPI_MODE_WRONLY + MPI_MODE_APPEND, & - MPI_INFO_NULL, & - fileUnit, & - ierr) - if (ierr /= 0) call IO_error(error_ID=894, ext_msg='MPI_file_open') - call MPI_file_get_position(fileUnit,fileOffset,ierr) ! get offset from header - if (ierr /= 0) call IO_error(error_ID=894, ext_msg='MPI_file_get_position') - fileOffset = fileOffset + sum(outputSize(1:worldrank)) ! offset of my process in file (header + processes before me) - call MPI_file_seek (fileUnit,fileOffset,MPI_SEEK_SET,ierr) - if (ierr /= 0) call IO_error(error_ID=894, ext_msg='MPI_file_seek') - writeUndeformed: if (interface_restartInc < 1) then write(6,'(1/,a)') ' ... writing initial configuration to file ........................' call CPFEM_results(0,0.0_pReal) - do i = 1, size(materialpoint_results,3)/(maxByteOut/(materialpoint_sizeResults*pReal))+1 ! slice the output of my process in chunks not exceeding the limit for one output - outputIndex = int([(i-1)*((maxRealOut)/materialpoint_sizeResults)+1, & - min(i*((maxRealOut)/materialpoint_sizeResults),size(materialpoint_results,3))],pLongInt) - call MPI_file_write(fileUnit,reshape(materialpoint_results(:,:,outputIndex(1):outputIndex(2)), & - [(outputIndex(2)-outputIndex(1)+1)*int(materialpoint_sizeResults,pLongInt)]), & - int((outputIndex(2)-outputIndex(1)+1)*int(materialpoint_sizeResults,pLongInt)), & - MPI_DOUBLE, MPI_STATUS_IGNORE, ierr) - if (ierr /= 0) call IO_error(error_ID=894, ext_msg='MPI_file_write') - enddo - fileOffset = fileOffset + sum(outputSize) ! forward to current file position endif writeUndeformed - loadCaseLooping: do currentLoadCase = 1, size(loadCases) time0 = time ! load case start time guess = loadCases(currentLoadCase)%followFormerTrajectory ! change of load case? homogeneous guess for the first inc @@ -526,7 +469,6 @@ program DAMASK_spectral write(6,'(/,a)') ' cutting back ' else ! no more options to continue call IO_warning(850) - call MPI_File_close(fileUnit,ierr) close(statUnit) call quit(0) ! quit endif @@ -546,19 +488,6 @@ program DAMASK_spectral if (mod(inc,loadCases(currentLoadCase)%outputFrequency) == 0) then ! at output frequency write(6,'(1/,a)') ' ... writing results to file ......................................' flush(6) - call materialpoint_postResults() - call MPI_File_seek (fileUnit,fileOffset,MPI_SEEK_SET,ierr) - if (ierr /= 0) call IO_error(894, ext_msg='MPI_file_seek') - do i=1, size(materialpoint_results,3)/(maxByteOut/(materialpoint_sizeResults*pReal))+1 ! slice the output of my process in chunks not exceeding the limit for one output - outputIndex=int([(i-1)*((maxRealOut)/materialpoint_sizeResults)+1, & - min(i*((maxRealOut)/materialpoint_sizeResults),size(materialpoint_results,3))],pLongInt) - call MPI_file_write(fileUnit,reshape(materialpoint_results(:,:,outputIndex(1):outputIndex(2)),& - [(outputIndex(2)-outputIndex(1)+1)*int(materialpoint_sizeResults,pLongInt)]), & - int((outputIndex(2)-outputIndex(1)+1)*int(materialpoint_sizeResults,pLongInt)),& - MPI_DOUBLE, MPI_STATUS_IGNORE, ierr) - if(ierr /=0) call IO_error(894, ext_msg='MPI_file_write') - enddo - fileOffset = fileOffset + sum(outputSize) ! forward to current file position call CPFEM_results(totalIncsCounter,time) endif if (mod(inc,loadCases(currentLoadCase)%restartFrequency) == 0) then @@ -575,7 +504,6 @@ program DAMASK_spectral !-------------------------------------------------------------------------------------------------- ! report summary of whole calculation write(6,'(/,a)') ' ###########################################################################' - call MPI_file_close(fileUnit,ierr) close(statUnit) call quit(0) ! no complains ;) diff --git a/src/homogenization.f90 b/src/homogenization.f90 index b014bebb5..a3a9be2f0 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -35,11 +35,6 @@ module homogenization materialpoint_P !< first P--K stress of IP real(pReal), dimension(:,:,:,:,:,:), allocatable, public :: & materialpoint_dPdF !< tangent of first P--K stress at IP - real(pReal), dimension(:,:,:), allocatable, public :: & - materialpoint_results !< results array of material point - integer, public, protected :: & - materialpoint_sizeResults, & - thermal_maxSizePostResults real(pReal), dimension(:,:,:,:), allocatable :: & materialpoint_subF0, & !< def grad of IP at beginning of homogenization increment @@ -124,7 +119,6 @@ module homogenization public :: & homogenization_init, & materialpoint_stressAndItsTangent, & - materialpoint_postResults, & homogenization_results contains @@ -135,14 +129,6 @@ contains !-------------------------------------------------------------------------------------------------- subroutine homogenization_init - integer, parameter :: FILEUNIT = 200 - integer :: e,i,p - integer, dimension(:,:), pointer :: thisSize - integer, dimension(:) , pointer :: thisNoutput - character(len=64), dimension(:,:), pointer :: thisOutput - character(len=32) :: outputName !< name of output, intermediate fix until HDF5 output is ready - logical :: valid - if (any(homogenization_type == HOMOGENIZATION_NONE_ID)) call mech_none_init if (any(homogenization_type == HOMOGENIZATION_ISOSTRAIN_ID)) call mech_isostrain_init if (any(homogenization_type == HOMOGENIZATION_RGC_ID)) call mech_RGC_init @@ -155,51 +141,6 @@ subroutine homogenization_init if (any(damage_type == DAMAGE_local_ID)) call damage_local_init if (any(damage_type == DAMAGE_nonlocal_ID)) call damage_nonlocal_init -!-------------------------------------------------------------------------------------------------- -! write description file for homogenization output - mainProcess: if (worldrank == 0) then - call IO_write_jobFile(FILEUNIT,'outputHomogenization') - do p = 1,size(config_homogenization) - if (any(material_homogenizationAt == p)) then - write(FILEUNIT,'(/,a,/)') '['//trim(config_name_homogenization(p))//']' - write(FILEUNIT,'(a)') '(type) n/a' - write(FILEUNIT,'(a,i4)') '(ngrains)'//char(9),homogenization_Ngrains(p) - - i = thermal_typeInstance(p) ! which instance of this thermal type - valid = .true. ! assume valid - select case(thermal_type(p)) ! split per thermal type - case (THERMAL_isothermal_ID) - outputName = THERMAL_isothermal_label - thisNoutput => null() - thisOutput => null() - thisSize => null() - case (THERMAL_adiabatic_ID) - outputName = THERMAL_adiabatic_label - thisNoutput => thermal_adiabatic_Noutput - thisOutput => thermal_adiabatic_output - thisSize => thermal_adiabatic_sizePostResult - case (THERMAL_conduction_ID) - outputName = THERMAL_conduction_label - thisNoutput => thermal_conduction_Noutput - thisOutput => thermal_conduction_output - thisSize => thermal_conduction_sizePostResult - case default - valid = .false. - end select - if (valid) then - write(FILEUNIT,'(a)') '(thermal)'//char(9)//trim(outputName) - if (thermal_type(p) /= THERMAL_isothermal_ID) then - do e = 1,thisNoutput(i) - write(FILEUNIT,'(a,i4)') trim(thisOutput(e,i))//char(9),thisSize(e,i) - enddo - endif - endif - - endif - enddo - close(FILEUNIT) - endif mainProcess - call config_deallocate('material.config/homogenization') !-------------------------------------------------------------------------------------------------- @@ -219,19 +160,7 @@ subroutine homogenization_init allocate(materialpoint_converged(discretization_nIP,discretization_nElem), source=.true.) allocate(materialpoint_doneAndHappy(2,discretization_nIP,discretization_nElem), source=.true.) -!-------------------------------------------------------------------------------------------------- -! allocate and initialize global state and postresutls variables - thermal_maxSizePostResults = 0 - do p = 1,size(config_homogenization) - thermal_maxSizePostResults = max(thermal_maxSizePostResults, thermalState(p)%sizePostResults) - enddo - - materialpoint_sizeResults = 1 & ! grain count - + 1 + thermal_maxSizePostResults & - + homogenization_maxNgrains * 2 ! obsolete header information - allocate(materialpoint_results(materialpoint_sizeResults,discretization_nIP,discretization_nElem)) - - write(6,'(/,a)') ' <<<+- homogenization init -+>>>' + write(6,'(/,a)') ' <<<+- homogenization init -+>>>'; flush(6) if (iand(debug_level(debug_homogenization), debug_levelBasic) /= 0) then write(6,'(a32,1x,7(i8,1x))') 'materialpoint_dPdF: ', shape(materialpoint_dPdF) @@ -547,48 +476,6 @@ subroutine materialpoint_stressAndItsTangent(updateJaco,dt) end subroutine materialpoint_stressAndItsTangent -!-------------------------------------------------------------------------------------------------- -!> @brief calculation of result array at material points -!-------------------------------------------------------------------------------------------------- -subroutine materialpoint_postResults - - integer :: & - thePos, & - theSize, & - myNgrains, & - g, & !< grain number - i, & !< integration point number - e !< element number - - elementLooping: do e = FEsolving_execElem(1),FEsolving_execElem(2) - myNgrains = homogenization_Ngrains(material_homogenizationAt(e)) - IpLooping: do i = FEsolving_execIP(1,e),FEsolving_execIP(2,e) - thePos = 0 - - theSize = thermalState (material_homogenizationAt(e))%sizePostResults & - + damageState (material_homogenizationAt(e))%sizePostResults - materialpoint_results(thePos+1,i,e) = real(theSize,pReal) ! tell size of homogenization results - thePos = thePos + 1 - - if (theSize > 0) then ! any homogenization results to mention? - materialpoint_results(thePos+1:thePos+theSize,i,e) = postResults(i,e) - thePos = thePos + theSize - endif - - materialpoint_results(thePos+1,i,e) = real(myNgrains,pReal) ! tell number of grains at materialpoint - thePos = thePos + 1 - - grainLooping :do g = 1,myNgrains - theSize = 2 - materialpoint_results(thePos+1:thePos+theSize,i,e) = 0.0_pReal - thePos = thePos + theSize - enddo grainLooping - enddo IpLooping - enddo elementLooping - -end subroutine materialpoint_postResults - - !-------------------------------------------------------------------------------------------------- !> @brief partition material point def grad onto constituents !-------------------------------------------------------------------------------------------------- @@ -700,41 +587,6 @@ subroutine averageStressAndItsTangent(ip,el) end subroutine averageStressAndItsTangent -!-------------------------------------------------------------------------------------------------- -!> @brief return array of homogenization results for post file inclusion. call only, -!> if homogenization_sizePostResults(i,e) > 0 !! -!-------------------------------------------------------------------------------------------------- -function postResults(ip,el) - - integer, intent(in) :: & - ip, & !< integration point - el !< element number - real(pReal), dimension( thermalState (material_homogenizationAt(el))%sizePostResults) :: & - postResults - integer :: & - startPos, endPos ,& - homog - - - postResults = 0.0_pReal - startPos = 1 - endPos = thermalState(material_homogenizationAt(el))%sizePostResults - chosenThermal: select case (thermal_type(material_homogenizationAt(el))) - - case (THERMAL_adiabatic_ID) chosenThermal - homog = material_homogenizationAt(el) - postResults(startPos:endPos) = & - thermal_adiabatic_postResults(homog,thermal_typeInstance(homog),thermalMapping(homog)%p(ip,el)) - case (THERMAL_conduction_ID) chosenThermal - homog = material_homogenizationAt(el) - postResults(startPos:endPos) = & - thermal_conduction_postResults(homog,thermal_typeInstance(homog),thermalMapping(homog)%p(ip,el)) - - end select chosenThermal - -end function postResults - - !-------------------------------------------------------------------------------------------------- !> @brief writes homogenization results to HDF5 output file !-------------------------------------------------------------------------------------------------- diff --git a/src/prec.f90 b/src/prec.f90 index b6d5d4fdf..8fd2495ce 100644 --- a/src/prec.f90 +++ b/src/prec.f90 @@ -42,8 +42,7 @@ module prec sizeState = 0, & !< size of state sizeDotState = 0, & !< size of dot state, i.e. state(1:sizeDot) follows time evolution by dotState rates offsetDeltaState = 0, & !< index offset of delta state - sizeDeltaState = 0, & !< size of delta state, i.e. state(offset+1:offset+sizeDelta) follows time evolution by deltaState increments - sizePostResults = 0 !< size of output data + sizeDeltaState = 0 !< size of delta state, i.e. state(offset+1:offset+sizeDelta) follows time evolution by deltaState increments real(pReal), pointer, dimension(:), contiguous :: & atolState real(pReal), pointer, dimension(:,:), contiguous :: & ! a pointer is needed here because we might point to state/doState. However, they will never point to something, but are rather allocated and, hence, contiguous diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 5c30f280b..36dd2316b 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -16,8 +16,6 @@ module thermal_adiabatic implicit none private - integer, dimension(:,:), allocatable, target, public :: & - thermal_adiabatic_sizePostResult !< size of each post result output character(len=64), dimension(:,:), allocatable, target, public :: & thermal_adiabatic_output !< name of each post result output @@ -38,8 +36,7 @@ module thermal_adiabatic thermal_adiabatic_getSourceAndItsTangent, & thermal_adiabatic_getSpecificHeat, & thermal_adiabatic_getMassDensity, & - thermal_adiabatic_results, & - thermal_adiabatic_postResults + thermal_adiabatic_results contains @@ -59,7 +56,6 @@ subroutine thermal_adiabatic_init maxNinstance = count(thermal_type == THERMAL_adiabatic_ID) if (maxNinstance == 0) return - allocate(thermal_adiabatic_sizePostResult (maxval(homogenization_Noutput),maxNinstance),source=0) allocate(thermal_adiabatic_output (maxval(homogenization_Noutput),maxNinstance)) thermal_adiabatic_output = '' allocate(thermal_adiabatic_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) @@ -77,14 +73,12 @@ subroutine thermal_adiabatic_init thermal_adiabatic_Noutput(instance) = thermal_adiabatic_Noutput(instance) + 1 thermal_adiabatic_outputID(thermal_adiabatic_Noutput(instance),instance) = temperature_ID thermal_adiabatic_output(thermal_adiabatic_Noutput(instance),instance) = outputs(i) - thermal_adiabatic_sizePostResult(thermal_adiabatic_Noutput(instance),instance) = 1 end select enddo ! allocate state arrays sizeState = 1 thermalState(section)%sizeState = sizeState - thermalState(section)%sizePostResults = sum(thermal_adiabatic_sizePostResult(:,instance)) allocate(thermalState(section)%state0 (sizeState,NofMyHomog), source=thermal_initialT(section)) allocate(thermalState(section)%subState0(sizeState,NofMyHomog), source=thermal_initialT(section)) allocate(thermalState(section)%state (sizeState,NofMyHomog), source=thermal_initialT(section)) @@ -277,33 +271,4 @@ subroutine thermal_adiabatic_results(homog,group) end subroutine thermal_adiabatic_results -!-------------------------------------------------------------------------------------------------- -!> @brief return array of thermal results -!-------------------------------------------------------------------------------------------------- -function thermal_adiabatic_postResults(homog,instance,of) result(postResults) - - integer, intent(in) :: & - homog, & - instance, & - of - - real(pReal), dimension(sum(thermal_adiabatic_sizePostResult(:,instance))) :: & - postResults - - integer :: & - o, c - - c = 0 - - do o = 1,thermal_adiabatic_Noutput(instance) - select case(thermal_adiabatic_outputID(o,instance)) - - case (temperature_ID) - postResults(c+1) = temperature(homog)%p(of) - c = c + 1 - end select - enddo - -end function thermal_adiabatic_postResults - end module thermal_adiabatic diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 5507d8a7d..ed25fccde 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -15,8 +15,6 @@ module thermal_conduction implicit none private - integer, dimension(:,:), allocatable, target, public :: & - thermal_conduction_sizePostResult !< size of each post result output character(len=64), dimension(:,:), allocatable, target, public :: & thermal_conduction_output !< name of each post result output @@ -38,8 +36,7 @@ module thermal_conduction thermal_conduction_getSpecificHeat, & thermal_conduction_getMassDensity, & thermal_conduction_putTemperatureAndItsRate, & - thermal_conduction_results, & - thermal_conduction_postResults + thermal_conduction_results contains @@ -62,7 +59,6 @@ subroutine thermal_conduction_init maxNinstance = count(thermal_type == THERMAL_conduction_ID) if (maxNinstance == 0) return - allocate(thermal_conduction_sizePostResult (maxval(homogenization_Noutput),maxNinstance),source=0) allocate(thermal_conduction_output (maxval(homogenization_Noutput),maxNinstance)) thermal_conduction_output = '' allocate(thermal_conduction_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) @@ -80,7 +76,6 @@ subroutine thermal_conduction_init thermal_conduction_Noutput(instance) = thermal_conduction_Noutput(instance) + 1 thermal_conduction_outputID(thermal_conduction_Noutput(instance),instance) = temperature_ID thermal_conduction_output(thermal_conduction_Noutput(instance),instance) = outputs(i) - thermal_conduction_sizePostResult(thermal_conduction_Noutput(instance),instance) = 1 end select enddo @@ -88,7 +83,6 @@ subroutine thermal_conduction_init ! allocate state arrays sizeState = 0 thermalState(section)%sizeState = sizeState - thermalState(section)%sizePostResults = sum(thermal_conduction_sizePostResult(:,instance)) allocate(thermalState(section)%state0 (sizeState,NofMyHomog)) allocate(thermalState(section)%subState0(sizeState,NofMyHomog)) allocate(thermalState(section)%state (sizeState,NofMyHomog)) @@ -290,33 +284,4 @@ subroutine thermal_conduction_results(homog,group) end subroutine thermal_conduction_results - -!-------------------------------------------------------------------------------------------------- -!> @brief return array of thermal results -!-------------------------------------------------------------------------------------------------- -function thermal_conduction_postResults(homog,instance,of) result(postResults) - - integer, intent(in) :: & - homog, & - instance, & - of - - real(pReal), dimension(sum(thermal_conduction_sizePostResult(:,instance))) :: & - postResults - - integer :: & - o, c - - c = 0 - do o = 1,thermal_conduction_Noutput(instance) - select case(thermal_conduction_outputID(o,instance)) - - case (temperature_ID) - postResults(c+1) = temperature(homog)%p(of) - c = c + 1 - end select - enddo - -end function thermal_conduction_postResults - end module thermal_conduction From b3639387f0b89a87159d2d09de6db326c350e6e6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 08:10:45 +0100 Subject: [PATCH 065/148] including fixed tests for restart --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 4c20d48ec..84eb17697 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 4c20d48ec1df8230dde85afbc3e7b5efca8cd314 +Subproject commit 84eb176974a97026f518fc3f2f4d63a1336280b3 From 0b04843c91efb68d59196f8fcee36b4c512236f8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 08:11:00 +0100 Subject: [PATCH 066/148] pandas is needed for Table class --- python/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/setup.py b/python/setup.py index 515401c59..def343ec1 100644 --- a/python/setup.py +++ b/python/setup.py @@ -15,6 +15,7 @@ setuptools.setup( packages=setuptools.find_packages(), include_package_data=True, install_requires = [ + "pandas", "scipy", "h5py", "vtk" From 5e4e53b9f9cfce4d0b4775d51588891079de0d5d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 08:17:23 +0100 Subject: [PATCH 067/148] including one more test that works without spectralOut --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 84eb17697..283c7567f 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 84eb176974a97026f518fc3f2f4d63a1336280b3 +Subproject commit 283c7567f1e4f6f1893cb5b5efdbbda1682f7533 From 562d216fa934f185a8205f7791e546b52d1303bb Mon Sep 17 00:00:00 2001 From: "f.basile" Date: Wed, 11 Dec 2019 09:23:12 +0100 Subject: [PATCH 068/148] bcc is passive rotation and fcc is active rotation --- .../tests/reference/Rotation/PoleFigures_OR.m | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/tests/reference/Rotation/PoleFigures_OR.m b/python/tests/reference/Rotation/PoleFigures_OR.m index fb2baa6a1..7a6d750ad 100644 --- a/python/tests/reference/Rotation/PoleFigures_OR.m +++ b/python/tests/reference/Rotation/PoleFigures_OR.m @@ -1,7 +1,7 @@ %% Import Script for EBSD Data -% -% Use MTEX -clear ; clear all +% +% Start MTEX first in Matlab +clear ; clear all ; close; %% Specify Crystal and Specimen Symmetries @@ -19,7 +19,7 @@ setMTEXpref('xAxisDirection','north'); setMTEXpref('zAxisDirection','outOfPlane'); %% path to files -pname = 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed +pname = 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed to hwere you have DAMASK % which files to be imported fname1 = [pname '\bcc_Bain.txt']; fname2 = [pname '\bcc_GT.txt']; fname3 = [pname '\bcc_GT_prime.txt']; @@ -45,17 +45,17 @@ ebsd6 = loadEBSD(fname6,CS_bcc,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); ebsd7 = loadEBSD(fname7,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); ebsd8 = loadEBSD(fname8,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); ebsd9 = loadEBSD(fname9,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); ebsd10 = loadEBSD(fname10,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); ebsd11 = loadEBSD(fname11,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); ebsd12 = loadEBSD(fname12,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); %% Plot Data 1stpart_bcc h1 = [Miller(1,0,0,ebsd1.CS),Miller(1,1,0,ebsd1.CS),Miller(1,1,1,ebsd1.CS)]; % 3 pole figures From 37e52fd81f16861b45b96d50349d3aa1f1d29a3d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 22 Oct 2019 23:31:27 +0200 Subject: [PATCH 069/148] polishing --- python/damask/orientation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index 1b08d2937..a63444155 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -1126,10 +1126,9 @@ class Orientation: return (Orientation(r,self.lattice), i,j, k == 1) if symmetries else r # disorientation ... # ... own sym, other sym, # self-->other: True, self<--other: False - - def inFZ(self): return self.lattice.symmetry.inFZ(self.rotation.asRodrigues(vector=True)) + def equivalentOrientations(self,members=[]): """List of orientations which are symmetrically equivalent.""" @@ -1145,6 +1144,7 @@ class Orientation: """List of orientations related by the given orientation relationship.""" r = self.lattice.relationOperations(model) return [self.__class__(o*self.rotation,r['lattice']) for o in r['rotations']] + def reduced(self): """Transform orientation to fall into fundamental zone according to symmetry.""" @@ -1152,7 +1152,8 @@ class Orientation: if self.lattice.symmetry.inFZ(me.rotation.asRodrigues(vector=True)): break return self.__class__(me.rotation,self.lattice) - + + def inversePole(self, axis, proper = False, From bd12ef83b92a6cc8722099d366945da7f6ce6f1e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 17:38:31 +0100 Subject: [PATCH 070/148] parametrizing --- .../tests/reference/Rotation/PoleFigures_OR.m | 116 +++++------------- 1 file changed, 29 insertions(+), 87 deletions(-) diff --git a/python/tests/reference/Rotation/PoleFigures_OR.m b/python/tests/reference/Rotation/PoleFigures_OR.m index 7a6d750ad..a17bdd831 100644 --- a/python/tests/reference/Rotation/PoleFigures_OR.m +++ b/python/tests/reference/Rotation/PoleFigures_OR.m @@ -1,99 +1,41 @@ -%% Import Script for EBSD Data -% % Start MTEX first in Matlab -clear ; clear all ; close; -%% Specify Crystal and Specimen Symmetries +tmp = matlab.desktop.editor.getActive; +cd(fileparts(tmp.Filename)); -% crystal symmetry -CS_bcc = {... - crystalSymmetry('m-3m', [2.8665 2.8665 2.8665], 'mineral', 'Iron-alpha', 'color', 'light blue'),... - crystalSymmetry('m-3m', [1 1 1], 'color', 'light blue')}; -CS_fcc = {... - crystalSymmetry('m-3m', [3.662 3.662 3.662], 'mineral', 'Iron', 'color', 'light blue'),... - crystalSymmetry('m-3m', [1 1 1], 'color', 'light blue')}; +%% Specify Crystal +symmetry = {crystalSymmetry('m-3m', [1 1 1], 'mineral', 'Iron', 'color', 'light blue')} % plotting convention setMTEXpref('xAxisDirection','north'); setMTEXpref('zAxisDirection','outOfPlane'); -%% path to files -pname = 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed to hwere you have DAMASK -% which files to be imported -fname1 = [pname '\bcc_Bain.txt']; fname2 = [pname '\bcc_GT.txt']; fname3 = [pname '\bcc_GT_prime.txt']; -fname4 = [pname '\bcc_KS.txt']; fname5 = [pname '\bcc_NW.txt']; fname6 = [pname '\bcc_Pitsch.txt']; -fname7 = [pname '\fcc_Bain.txt']; fname8 = [pname '\fcc_GT.txt']; fname9 = [pname '\fcc_GT_prime.txt']; -fname10 = [pname '\fcc_KS.txt']; fname11 = [pname '\fcc_NW.txt']; fname12 = [pname '\fcc_Pitsch.txt']; +lattice_types = {'BCC','FCC'}; +models = {'Bain','GT','GT_prime','KS','NW','Pitsch'}; +rotation = containers.Map; +rotation('BCC') = 'Passive Rotation'; +rotation('FCC') = 'Active Rotation'; -%% Import the Data - -% create an EBSD variable containing the data -ebsd1 = loadEBSD(fname1,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); -ebsd2 = loadEBSD(fname2,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); -ebsd3 = loadEBSD(fname3,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); -ebsd4 = loadEBSD(fname4,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); -ebsd5 = loadEBSD(fname5,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); -ebsd6 = loadEBSD(fname6,CS_bcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Passive Rotation'); - -ebsd7 = loadEBSD(fname7,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); -ebsd8 = loadEBSD(fname8,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); -ebsd9 = loadEBSD(fname9,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); -ebsd10 = loadEBSD(fname10,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); -ebsd11 = loadEBSD(fname11,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); -ebsd12 = loadEBSD(fname12,CS_fcc,'interface','generic',... - 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', 'Active Rotation'); - -%% Plot Data 1stpart_bcc -h1 = [Miller(1,0,0,ebsd1.CS),Miller(1,1,0,ebsd1.CS),Miller(1,1,1,ebsd1.CS)]; % 3 pole figures -plotPDF(ebsd1.orientations,h1,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','r','DisplayName','BCC-Bain') -hold on -plotPDF(ebsd2.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','b','DisplayName','BCC-GT') -plotPDF(ebsd3.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','g','DisplayName','BCC-GT_Prime') -legend('show','location','southoutside') -cd 'L:\f.gallardo\DAMASK\python\tests\reference\Rotation'; % has to be changed -orient('landscape') -print('-bestfit','1_BCC.pdf','-dpdf') - -%% Plot Data 2nd part_bcc -close -plotPDF(ebsd4.orientations,h1,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','w','DisplayName','BCC-KS') -hold on -plotPDF(ebsd5.orientations,h1,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','m','DisplayName','BCC-NW') -plotPDF(ebsd6.orientations,h1,'MarkerSize',5,'MarkerColor','y','MarkerEdgeColor','w','DisplayName','BCC-Pitsch') -legend('show','location','southoutside') -print('-bestfit','2_BCC.pdf','-dpdf') - -%% Plot Data 1stpart_fcc -close -h2 = [Miller(1,0,0,ebsd7.CS),Miller(1,1,0,ebsd7.CS),Miller(1,1,1,ebsd7.CS)]; % 3 pole figures -plotPDF(ebsd7.orientations,h2,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','r','DisplayName','FCC-Bain') -hold on -plotPDF(ebsd8.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','b','DisplayName','FCC-GT') -plotPDF(ebsd9.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','g','DisplayName','FCC-GT_Prime') -legend('show','location','southoutside') -print('-bestfit','1_FCC.pdf','-dpdf') - -%% Plot Data 2nd part_bcc -close -plotPDF(ebsd10.orientations,h2,'MarkerSize',5,'MarkerColor','r','MarkerEdgeColor','w','DisplayName','FCC-KS') -hold on -plotPDF(ebsd11.orientations,h2,'MarkerSize',5,'MarkerColor','w','MarkerEdgeColor','m','DisplayName','FCC-NW') -plotPDF(ebsd12.orientations,h2,'MarkerSize',5,'MarkerColor','y','MarkerEdgeColor','w','DisplayName','FCC-Pitsch') -legend('show','location','southoutside') -print('-bestfit','2_FCC.pdf','-dpdf') -close - +for lattice = lattice_types + for p = 0:length(models)/3-1 + p + EBSD_data = {loadEBSD(strcat(lattice,'_',models{p*3+1},'.txt'),symmetry,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), + loadEBSD(strcat(lattice,'_',models{p*3+2},'.txt'),symmetry,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), + loadEBSD(strcat(lattice,'_',models{p*3+3},'.txt'),symmetry,'interface','generic',... + 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice)))} + h = [Miller(1,0,0,symmetry{1}),Miller(1,1,0,symmetry{1}),Miller(1,1,1,symmetry{1})]; % 3 pole figures + plotPDF(EBSD_data{1}.orientations,h,'MarkerSize',5,'MarkerColor','r','DisplayName',models{p*3+1}) + hold on + plotPDF(EBSD_data{2}.orientations,h,'MarkerSize',5,'MarkerColor','b','DisplayName',models{p*3+2}) + plotPDF(EBSD_data{3}.orientations,h,'MarkerSize',5,'MarkerColor','g','DisplayName',models{p*3+3}) + legend('show','location','southoutside') + orient('landscape') + print('-bestfit',strcat(int2str(p+1),'_',char(lattice),'.pdf'),'-dpdf') + close + end +end \ No newline at end of file From fcbe4ee5a2950cbafd7e851db81d833b6647b82f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 18:08:51 +0100 Subject: [PATCH 071/148] [skip ci] polishing --- python/tests/reference/Rotation/PoleFigures_OR.m | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/python/tests/reference/Rotation/PoleFigures_OR.m b/python/tests/reference/Rotation/PoleFigures_OR.m index a17bdd831..74e1365b7 100644 --- a/python/tests/reference/Rotation/PoleFigures_OR.m +++ b/python/tests/reference/Rotation/PoleFigures_OR.m @@ -3,8 +3,6 @@ tmp = matlab.desktop.editor.getActive; cd(fileparts(tmp.Filename)); - -%% Specify Crystal symmetry = {crystalSymmetry('m-3m', [1 1 1], 'mineral', 'Iron', 'color', 'light blue')} % plotting convention @@ -21,14 +19,13 @@ rotation('FCC') = 'Active Rotation'; for lattice = lattice_types for p = 0:length(models)/3-1 - p EBSD_data = {loadEBSD(strcat(lattice,'_',models{p*3+1},'.txt'),symmetry,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), loadEBSD(strcat(lattice,'_',models{p*3+2},'.txt'),symmetry,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), loadEBSD(strcat(lattice,'_',models{p*3+3},'.txt'),symmetry,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice)))} - h = [Miller(1,0,0,symmetry{1}),Miller(1,1,0,symmetry{1}),Miller(1,1,1,symmetry{1})]; % 3 pole figures + h = [Miller(1,0,0,symmetry{1}),Miller(1,1,0,symmetry{1}),Miller(1,1,1,symmetry{1})]; % 3 pole figures plotPDF(EBSD_data{1}.orientations,h,'MarkerSize',5,'MarkerColor','r','DisplayName',models{p*3+1}) hold on plotPDF(EBSD_data{2}.orientations,h,'MarkerSize',5,'MarkerColor','b','DisplayName',models{p*3+2}) @@ -38,4 +35,4 @@ for lattice = lattice_types print('-bestfit',strcat(int2str(p+1),'_',char(lattice),'.pdf'),'-dpdf') close end -end \ No newline at end of file +end From a0a99c526b285965e2647047bcd29f413802fdf2 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 19:24:29 +0100 Subject: [PATCH 072/148] some remaining IntOut format strings --- PRIVATE | 2 +- src/IO.f90 | 23 +------------------- src/grid/grid_mech_FEM.f90 | 3 +-- src/grid/grid_mech_spectral_basic.f90 | 3 +-- src/grid/grid_mech_spectral_polarisation.f90 | 3 +-- 5 files changed, 5 insertions(+), 29 deletions(-) diff --git a/PRIVATE b/PRIVATE index 283c7567f..f60209558 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 283c7567f1e4f6f1893cb5b5efdbbda1682f7533 +Subproject commit f602095584e1d8c4f96e5746d6a199bd3bc029c3 diff --git a/src/IO.f90 b/src/IO.f90 index 8973860c2..c121cc65e 100644 --- a/src/IO.f90 +++ b/src/IO.f90 @@ -32,8 +32,7 @@ module IO IO_intValue, & IO_lc, & IO_error, & - IO_warning, & - IO_intOut + IO_warning #if defined(Marc4DAMASK) || defined(Abaqus) public :: & IO_open_inputFile, & @@ -542,26 +541,6 @@ pure function IO_lc(string) end function IO_lc -!-------------------------------------------------------------------------------------------------- -!> @brief returns format string for integer values without leading zeros -!> @details deprecated, use '(i0)' format specifier -!-------------------------------------------------------------------------------------------------- -pure function IO_intOut(intToPrint) - - integer, intent(in) :: intToPrint - character(len=41) :: IO_intOut - integer :: N_digits - character(len=19) :: width ! maximum digits for 64 bit integer - character(len=20) :: min_width ! longer for negative values - - N_digits = 1 + int(log10(real(max(abs(intToPrint),1)))) - write(width, '(I19.19)') N_digits - write(min_width, '(I20.20)') N_digits + merge(1,0,intToPrint < 0) - IO_intOut = 'I'//trim(min_width)//'.'//trim(width) - -end function IO_intOut - - !-------------------------------------------------------------------------------------------------- !> @brief write error statements to standard out and terminate the Marc/spectral run with exit #9xxx !> in ABAQUS either time step is reduced or execution terminated diff --git a/src/grid/grid_mech_FEM.f90 b/src/grid/grid_mech_FEM.f90 index f6074fee9..a34d880f7 100644 --- a/src/grid/grid_mech_FEM.f90 +++ b/src/grid/grid_mech_FEM.f90 @@ -476,8 +476,7 @@ subroutine formResidual(da_local,x_local, & ! begin of new iteration newIteration: if (totalIter <= PETScIter) then totalIter = totalIter + 1 - write(6,'(1x,a,3(a,'//IO_intOut(itmax)//'))') & - trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter+1, '≤', itmax + write(6,'(1x,a,3(a,i0))') trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter+1, '≤', itmax if (iand(debug_level(debug_spectral),debug_spectralRotation) /= 0) & write(6,'(/,a,/,3(3(f12.7,1x)/))',advance='no') & ' deformation gradient aim (lab) =', transpose(params%rotation_BC%rotTensor2(F_aim,active=.true.)) diff --git a/src/grid/grid_mech_spectral_basic.f90 b/src/grid/grid_mech_spectral_basic.f90 index fb69427e3..f05f9bc93 100644 --- a/src/grid/grid_mech_spectral_basic.f90 +++ b/src/grid/grid_mech_spectral_basic.f90 @@ -440,8 +440,7 @@ subroutine formResidual(in, F, & ! begin of new iteration newIteration: if (totalIter <= PETScIter) then totalIter = totalIter + 1 - write(6,'(1x,a,3(a,'//IO_intOut(itmax)//'))') & - trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter, '≤', itmax + write(6,'(1x,a,3(a,i0))') trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter, '≤', itmax if (iand(debug_level(debug_spectral),debug_spectralRotation) /= 0) & write(6,'(/,a,/,3(3(f12.7,1x)/))',advance='no') & ' deformation gradient aim (lab) =', transpose(params%rotation_BC%rotTensor2(F_aim,active=.true.)) diff --git a/src/grid/grid_mech_spectral_polarisation.f90 b/src/grid/grid_mech_spectral_polarisation.f90 index ed2e0e1a9..33c3e4e72 100644 --- a/src/grid/grid_mech_spectral_polarisation.f90 +++ b/src/grid/grid_mech_spectral_polarisation.f90 @@ -509,8 +509,7 @@ subroutine formResidual(in, FandF_tau, & ! begin of new iteration newIteration: if (totalIter <= PETScIter) then totalIter = totalIter + 1 - write(6,'(1x,a,3(a,'//IO_intOut(itmax)//'))') & - trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter, '≤', itmax + write(6,'(1x,a,3(a,i0))') trim(incInfo), ' @ Iteration ', itmin, '≤',totalIter, '≤', itmax if (iand(debug_level(debug_spectral),debug_spectralRotation) /= 0) & write(6,'(/,a,/,3(3(f12.7,1x)/))',advance='no') & ' deformation gradient aim (lab) =', transpose(params%rotation_BC%rotTensor2(F_aim,active=.true.)) From 1d7010778e446d2d95aa441560da31a0e48eedc9 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 19:43:23 +0100 Subject: [PATCH 073/148] [skip ci] silence annoying warnings --- python/damask/dadf5.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index beced188d..278a4d89d 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -360,7 +360,7 @@ class DADF5(): f[k] path.append(k) except KeyError as e: - print('unable to locate geometry dataset: {}'.format(str(e))) + pass for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']): for oo in self.iter_visible(o): for pp in self.iter_visible(p): @@ -369,7 +369,7 @@ class DADF5(): f[k] path.append(k) except KeyError as e: - print('unable to locate {} dataset: {}'.format(o,str(e))) + pass return path From 674d800ceafff78f10d361034128d9e0c9524967 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 11 Dec 2019 20:52:45 +0100 Subject: [PATCH 074/148] fix for test needed --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index f60209558..a4a216604 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit f602095584e1d8c4f96e5746d6a199bd3bc029c3 +Subproject commit a4a216604ca07f6391c209fa75b593c8e8a887e5 From 4276ef4764e07d01094896a51ce6e5ba7b382c06 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 12 Dec 2019 23:11:26 +0100 Subject: [PATCH 075/148] only stack size matters (for Intel Compiler) --- env/DAMASK.csh | 1 - env/DAMASK.sh | 3 --- env/DAMASK.zsh | 3 --- 3 files changed, 7 deletions(-) diff --git a/env/DAMASK.csh b/env/DAMASK.csh index d223d885a..d3b4474b2 100644 --- a/env/DAMASK.csh +++ b/env/DAMASK.csh @@ -26,7 +26,6 @@ endif # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap # http://superuser.com/questions/220059/what-parameters-has-ulimit -limit datasize unlimited # maximum heap size (kB) limit stacksize unlimited # maximum stack size (kB) endif if ( `limit | grep memoryuse` != "" ) then diff --git a/env/DAMASK.sh b/env/DAMASK.sh index 1b4bea86a..a6d7b2667 100644 --- a/env/DAMASK.sh +++ b/env/DAMASK.sh @@ -48,10 +48,7 @@ PROCESSING=$(type -p postResults || true 2>/dev/null) # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap # http://superuser.com/questions/220059/what-parameters-has-ulimit -ulimit -d unlimited 2>/dev/null # maximum heap size (kB) ulimit -s unlimited 2>/dev/null # maximum stack size (kB) -ulimit -v unlimited 2>/dev/null # maximum virtual memory size -ulimit -m unlimited 2>/dev/null # maximum physical memory size # disable output in case of scp if [ ! -z "$PS1" ]; then diff --git a/env/DAMASK.zsh b/env/DAMASK.zsh index 5449007f9..42021191f 100644 --- a/env/DAMASK.zsh +++ b/env/DAMASK.zsh @@ -40,10 +40,7 @@ PROCESSING=$(which postResults || true 2>/dev/null) # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap # http://superuser.com/questions/220059/what-parameters-has-ulimit -ulimit -d unlimited 2>/dev/null # maximum heap size (kB) ulimit -s unlimited 2>/dev/null # maximum stack size (kB) -ulimit -v unlimited 2>/dev/null # maximum virtual memory size -ulimit -m unlimited 2>/dev/null # maximum physical memory size # disable output in case of scp if [ ! -z "$PS1" ]; then From ee56b82c8a97de781d01a428c27efbe6cc421b7c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 12 Dec 2019 23:22:37 +0100 Subject: [PATCH 076/148] avoid spaces --- src/grid/DAMASK_grid.f90 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/grid/DAMASK_grid.f90 b/src/grid/DAMASK_grid.f90 index 7fe8d82c4..60caf8947 100644 --- a/src/grid/DAMASK_grid.f90 +++ b/src/grid/DAMASK_grid.f90 @@ -257,7 +257,7 @@ program DAMASK_spectral reportAndCheck: if (worldrank == 0) then write (loadcase_string, '(i6)' ) currentLoadCase - write(6,'(/,1x,a,i6)') 'load case: ', currentLoadCase + write(6,'(/,1x,a,i0)') 'load case: ', currentLoadCase if (.not. newLoadCase%followFormerTrajectory) write(6,'(2x,a)') 'drop guessing along trajectory' if (newLoadCase%deformation%myType == 'l') then do j = 1, 3 @@ -300,14 +300,14 @@ program DAMASK_spectral write(6,'(2x,a,/,3(3(3x,f12.7,1x)/))',advance='no') 'rotation of loadframe:',& transpose(newLoadCase%rot%asMatrix()) if (newLoadCase%time < 0.0_pReal) errorID = 834 ! negative time increment - write(6,'(2x,a,f12.6)') 'time: ', newLoadCase%time + write(6,'(2x,a,f0.3)') 'time: ', newLoadCase%time if (newLoadCase%incs < 1) errorID = 835 ! non-positive incs count - write(6,'(2x,a,i5)') 'increments: ', newLoadCase%incs + write(6,'(2x,a,i0)') 'increments: ', newLoadCase%incs if (newLoadCase%outputfrequency < 1) errorID = 836 ! non-positive result frequency - write(6,'(2x,a,i5)') 'output frequency: ', newLoadCase%outputfrequency + write(6,'(2x,a,i0)') 'output frequency: ', newLoadCase%outputfrequency if (newLoadCase%restartfrequency < 1) errorID = 839 ! non-positive restart frequency if (newLoadCase%restartfrequency < huge(0)) & - write(6,'(2x,a,i5)') 'restart frequency: ', newLoadCase%restartfrequency + write(6,'(2x,a,i0)') 'restart frequency: ', newLoadCase%restartfrequency if (errorID > 0) call IO_error(error_ID = errorID, ext_msg = loadcase_string) ! exit with error message endif reportAndCheck loadCases = [loadCases,newLoadCase] ! load case is ok, append it From 63fc1253982e36a6e92688082f8493d9ff7c2c8f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 13 Dec 2019 09:08:48 +0100 Subject: [PATCH 077/148] include origin do DADF5 requires updat of file version --- python/damask/dadf5.py | 7 +++++-- src/CPFEM2.f90 | 2 +- src/mesh_grid.f90 | 40 ++++++++++++++++++++++++++++++---------- src/results.f90 | 2 +- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index beced188d..959d222df 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -37,7 +37,7 @@ class DADF5(): self.version_major = f.attrs['DADF5-major'] self.version_minor = f.attrs['DADF5-minor'] - if self.version_major != 0 or not 2 <= self.version_minor <= 4: + if self.version_major != 0 or not 2 <= self.version_minor <= 5: raise TypeError('Unsupported DADF5 version {} '.format(f.attrs['DADF5-version'])) self.structured = 'grid' in f['geometry'].attrs.keys() @@ -45,6 +45,9 @@ class DADF5(): if self.structured: self.grid = f['geometry'].attrs['grid'] self.size = f['geometry'].attrs['size'] + if self.version_major == 0 and self.version_minor >= 5: + self.origin = f['geometry'].attrs['origin'] + r=re.compile('inc[0-9]+') increments_unsorted = {int(i[3:]):i for i in f.keys() if r.match(i)} @@ -830,7 +833,7 @@ class DADF5(): N_not_calculated = len(todo) while N_not_calculated > 0: result = results.get() - with h5py.File(self.fname,'a') as f: # write to file + with h5py.File(self.fname,'a') as f: # write to file dataset_out = f[result['group']].create_dataset(result['label'],data=result['data']) for k in result['meta'].keys(): dataset_out.attrs[k] = result['meta'][k].encode() diff --git a/src/CPFEM2.f90 b/src/CPFEM2.f90 index 9edb61d33..6406e9b30 100644 --- a/src/CPFEM2.f90 +++ b/src/CPFEM2.f90 @@ -14,10 +14,10 @@ module CPFEM2 use material use lattice use IO - use HDF5 use DAMASK_interface use results use discretization + use HDF5 use HDF5_utilities use homogenization use constitutive diff --git a/src/mesh_grid.f90 b/src/mesh_grid.f90 index 2b337f047..d10ffef8a 100644 --- a/src/mesh_grid.f90 +++ b/src/mesh_grid.f90 @@ -27,9 +27,8 @@ module mesh_grid integer, public, protected :: & grid3, & !< (local) grid in 3rd direction grid3Offset !< (local) grid offset in 3rd direction - real(pReal), dimension(3), public, protected :: & - geomSize + geomSize !< (global) physical size real(pReal), public, protected :: & size3, & !< (local) size in 3rd direction size3offset !< (local) size offset in 3rd direction @@ -49,7 +48,8 @@ subroutine mesh_init(ip,el) include 'fftw3-mpi.f03' real(pReal), dimension(3) :: & - mySize !< domain size of this process + mySize, & !< domain size of this process + origin !< (global) distance to origin integer, dimension(3) :: & myGrid !< domain grid of this process @@ -61,9 +61,9 @@ subroutine mesh_init(ip,el) integer(C_INTPTR_T) :: & devNull, z, z_offset - write(6,'(/,a)') ' <<<+- mesh_grid init -+>>>' + write(6,'(/,a)') ' <<<+- mesh_grid init -+>>>'; flush(6) - call readGeom(grid,geomSize,microstructureAt,homogenizationAt) + call readGeom(grid,geomSize,origin,microstructureAt,homogenizationAt) !-------------------------------------------------------------------------------------------------- ! grid solver specific quantities @@ -104,8 +104,9 @@ subroutine mesh_init(ip,el) ! store geometry information for post processing call results_openJobFile call results_closeGroup(results_addGroup('geometry')) - call results_addAttribute('grid',grid,'geometry') - call results_addAttribute('size',geomSize,'geometry') + call results_addAttribute('grid', grid, 'geometry') + call results_addAttribute('size', geomSize,'geometry') + call results_addAttribute('origin',origin, 'geometry') call results_closeJobFile !-------------------------------------------------------------------------------------------------- @@ -129,10 +130,13 @@ end subroutine mesh_init !> @details important variables have an implicit "save" attribute. Therefore, this function is ! supposed to be called only once! !-------------------------------------------------------------------------------------------------- -subroutine readGeom(grid,geomSize,microstructure,homogenization) +subroutine readGeom(grid,geomSize,origin,microstructure,homogenization) - integer, dimension(3), intent(out) :: grid ! grid (for all processes!) - real(pReal), dimension(3), intent(out) :: geomSize ! size (for all processes!) + integer, dimension(3), intent(out) :: & + grid ! grid (for all processes!) + real(pReal), dimension(3), intent(out) :: & + geomSize, & ! size (for all processes!) + origin ! origin (for all processes!) integer, dimension(:), intent(out), allocatable :: & microstructure, & homogenization @@ -181,6 +185,7 @@ subroutine readGeom(grid,geomSize,microstructure,homogenization) !-------------------------------------------------------------------------------------------------- ! read and interprete header + origin = 0.0_pReal l = 0 do while (l < headerLength .and. startPos < len(rawData)) endPos = startPos + index(rawData(startPos:),new_line('')) - 1 @@ -221,8 +226,23 @@ subroutine readGeom(grid,geomSize,microstructure,homogenization) enddo endif + case ('origin') + if (chunkPos(1) > 6) then + do j = 2,6,2 + select case (IO_lc(IO_stringValue(line,chunkPos,j))) + case('x') + origin(1) = IO_floatValue(line,chunkPos,j+1) + case('y') + origin(2) = IO_floatValue(line,chunkPos,j+1) + case('z') + origin(3) = IO_floatValue(line,chunkPos,j+1) + end select + enddo + endif + case ('homogenization') if (chunkPos(1) > 1) h = IO_intValue(line,chunkPos,2) + end select enddo diff --git a/src/results.f90 b/src/results.f90 index 8fb7e134d..a7037a454 100644 --- a/src/results.f90 +++ b/src/results.f90 @@ -70,7 +70,7 @@ subroutine results_init resultsFile = HDF5_openFile(trim(getSolverJobName())//'.hdf5','w',.true.) call HDF5_addAttribute(resultsFile,'DADF5_version_major',0) - call HDF5_addAttribute(resultsFile,'DADF5_version_minor',4) + call HDF5_addAttribute(resultsFile,'DADF5_version_minor',5) call HDF5_addAttribute(resultsFile,'DAMASK_version',DAMASKVERSION) call get_command(commandLine) call HDF5_addAttribute(resultsFile,'call',trim(commandLine)) From 186605610d69e13fd735f3646fde5764cb7a79bd Mon Sep 17 00:00:00 2001 From: Vitesh Shah Date: Fri, 13 Dec 2019 09:53:47 +0100 Subject: [PATCH 078/148] No phase name for generic datasets --- processing/post/DADF5_vtk_cells.py | 5 ++++- processing/post/DADF5_vtk_points.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/processing/post/DADF5_vtk_cells.py b/processing/post/DADF5_vtk_cells.py index b8875f4e9..9e8585773 100755 --- a/processing/post/DADF5_vtk_cells.py +++ b/processing/post/DADF5_vtk_cells.py @@ -2,6 +2,7 @@ import os import argparse +import re import h5py import numpy as np @@ -89,10 +90,12 @@ for filename in options.filenames: x = results.get_dataset_location(label) if len(x) == 0: continue + ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name array = results.read_dataset(x,0) shape = [array.shape[0],np.product(array.shape[1:])] vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) + dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset + vtk_data[-1].SetName(dset_name) grid.GetCellData().AddArray(vtk_data[-1]) results.set_visible('constituents', False) diff --git a/processing/post/DADF5_vtk_points.py b/processing/post/DADF5_vtk_points.py index 9265cc3a0..908474336 100755 --- a/processing/post/DADF5_vtk_points.py +++ b/processing/post/DADF5_vtk_points.py @@ -2,6 +2,7 @@ import os import argparse +import re import numpy as np import vtk @@ -76,10 +77,12 @@ for filename in options.filenames: x = results.get_dataset_location(label) if len(x) == 0: continue + ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name array = results.read_dataset(x,0) shape = [array.shape[0],np.product(array.shape[1:])] vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) + dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset + vtk_data[-1].SetName(dset_name) Polydata.GetCellData().AddArray(vtk_data[-1]) results.set_visible('constituents', False) From b14c15fd9ee6720d79677ed30d4558e3928269a5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 13 Dec 2019 12:15:45 +0100 Subject: [PATCH 079/148] directly output DADF5 to vtk from python --- python/damask/dadf5.py | 120 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index beced188d..1142d02b4 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -1,7 +1,10 @@ from queue import Queue import re import glob +import os +import vtk +from vtk.util import numpy_support import h5py import numpy as np @@ -841,3 +844,120 @@ class DADF5(): N_added +=1 pool.wait_completion() + + + def to_vtk(self,labels): + """ + Export to vtk cell data. + + Parameters + ---------- + labels : list of str + Labels of the datasets to be exported. + + """ + if self.structured: + + coordArray = [vtk.vtkDoubleArray(),vtk.vtkDoubleArray(),vtk.vtkDoubleArray()] + for dim in [0,1,2]: + for c in np.linspace(0,self.size[dim],1+self.grid[dim]): + coordArray[dim].InsertNextValue(c) + + grid = vtk.vtkRectilinearGrid() + grid.SetDimensions(*(self.grid+1)) + grid.SetXCoordinates(coordArray[0]) + grid.SetYCoordinates(coordArray[1]) + grid.SetZCoordinates(coordArray[2]) + + else: + + nodes = vtk.vtkPoints() + with h5py.File(self.fname) as f: + nodes.SetData(numpy_support.numpy_to_vtk(f['/geometry/x_n'][()],deep=True)) + + grid = vtk.vtkUnstructuredGrid() + grid.SetPoints(nodes) + grid.Allocate(f['/geometry/T_c'].shape[0]) + for i in f['/geometry/T_c']: + grid.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) # not for all elements! + + N_digits = int(np.floor(np.log10(int(self.increments[-1][3:]))))+1 + + for i,inc in enumerate(self.iter_visible('increments')): + vtk_data = [] + + materialpoints_backup = self.visible['materialpoints'].copy() + self.set_visible('materialpoints',False) + for label in labels: + for p in self.iter_visible('con_physics'): + if p != 'generic': + for c in self.iter_visible('constituents'): + x = self.get_dataset_location(label) + if len(x) == 0: + continue + array = self.read_dataset(x,0) + shape = [array.shape[0],np.product(array.shape[1:])] + vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), + deep=True,array_type= vtk.VTK_DOUBLE)) + vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: hard coded 1! + grid.GetCellData().AddArray(vtk_data[-1]) + else: + x = self.get_dataset_location(label) + if len(x) == 0: + continue + array = self.read_dataset(x,0) + shape = [array.shape[0],np.product(array.shape[1:])] + vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), + deep=True,array_type= vtk.VTK_DOUBLE)) + ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name + dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset + vtk_data[-1].SetName(dset_name) + grid.GetCellData().AddArray(vtk_data[-1]) + self.set_visible('materialpoints',materialpoints_backup) + + constituents_backup = self.visible['constituents'].copy() + self.set_visible('constituents',False) + for label in labels: + for p in self.iter_visible('mat_physics'): + if p != 'generic': + for m in self.iter_visible('materialpoints'): + x = self.get_dataset_location(label) + if len(x) == 0: + continue + array = self.read_dataset(x,0) + shape = [array.shape[0],np.product(array.shape[1:])] + vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), + deep=True,array_type= vtk.VTK_DOUBLE)) + vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: why 1_? + grid.GetCellData().AddArray(vtk_data[-1]) + else: + x = self.get_dataset_location(label) + if len(x) == 0: + continue + array = self.read_dataset(x,0) + shape = [array.shape[0],np.product(array.shape[1:])] + vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), + deep=True,array_type= vtk.VTK_DOUBLE)) + vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) + grid.GetCellData().AddArray(vtk_data[-1]) + self.set_visible('constituents',constituents_backup) + + writer = vtk.vtkXMLRectilinearGridWriter() if self.structured else \ + vtk.vtkXMLUnstructuredGridWriter() + + x = self.get_dataset_location('u_n') + vtk_data.append(numpy_support.numpy_to_vtk(num_array=self.read_dataset(x,0), + deep=True,array_type=vtk.VTK_DOUBLE)) + vtk_data[-1].SetName('u') + grid.GetPointData().AddArray(vtk_data[-1]) + + file_out = '{}_inc{}.{}'.format(os.path.splitext(os.path.basename(self.fname))[0], + inc[3:].zfill(N_digits), + writer.GetDefaultFileExtension()) + + writer.SetCompressorTypeToZLib() + writer.SetDataModeToBinary() + writer.SetFileName(file_out) + writer.SetInputData(grid) + + writer.Write() From 0c30f6b1dfc52cc399a08bf479e239f29abf36ff Mon Sep 17 00:00:00 2001 From: "f.basile" Date: Fri, 13 Dec 2019 12:52:33 +0100 Subject: [PATCH 080/148] Polishing Mtex script for plotting pole figures. --- python/tests/reference/Rotation/1_BCC.pdf | Bin 44551 -> 43263 bytes python/tests/reference/Rotation/1_FCC.pdf | Bin 44391 -> 43263 bytes python/tests/reference/Rotation/2_BCC.pdf | Bin 43118 -> 43017 bytes python/tests/reference/Rotation/2_FCC.pdf | Bin 43107 -> 43017 bytes .../tests/reference/Rotation/PoleFigures_OR.m | 10 +++++----- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/tests/reference/Rotation/1_BCC.pdf b/python/tests/reference/Rotation/1_BCC.pdf index fe22f6a2e36d180c423a1902b1a7409f866ff324..599b892105907e96d19f52b7d927dafda0500d7c 100644 GIT binary patch literal 43263 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 AKL7v# literal 44391 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 literal 43107 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%0ALAhtr$ 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 BBRK#7 diff --git a/python/tests/reference/Rotation/PoleFigures_OR.m b/python/tests/reference/Rotation/PoleFigures_OR.m index 74e1365b7..ede2a0e0c 100644 --- a/python/tests/reference/Rotation/PoleFigures_OR.m +++ b/python/tests/reference/Rotation/PoleFigures_OR.m @@ -19,7 +19,7 @@ rotation('FCC') = 'Active Rotation'; for lattice = lattice_types for p = 0:length(models)/3-1 - EBSD_data = {loadEBSD(strcat(lattice,'_',models{p*3+1},'.txt'),symmetry,'interface','generic',... + EBSD_data = {loadEBSD(strcat (lattice,'_',models{p*3+1},'.txt'),symmetry,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), loadEBSD(strcat(lattice,'_',models{p*3+2},'.txt'),symmetry,'interface','generic',... 'ColumnNames', { 'phi1' 'Phi' 'phi2' 'x' 'y'}, 'Bunge', rotation(char(lattice))), @@ -28,11 +28,11 @@ for lattice = lattice_types h = [Miller(1,0,0,symmetry{1}),Miller(1,1,0,symmetry{1}),Miller(1,1,1,symmetry{1})]; % 3 pole figures plotPDF(EBSD_data{1}.orientations,h,'MarkerSize',5,'MarkerColor','r','DisplayName',models{p*3+1}) hold on - plotPDF(EBSD_data{2}.orientations,h,'MarkerSize',5,'MarkerColor','b','DisplayName',models{p*3+2}) - plotPDF(EBSD_data{3}.orientations,h,'MarkerSize',5,'MarkerColor','g','DisplayName',models{p*3+3}) - legend('show','location','southoutside') + plotPDF(EBSD_data{2}.orientations,h,'MarkerSize',4,'MarkerColor','b','DisplayName',models{p*3+2}) + plotPDF(EBSD_data{3}.orientations,h,'MarkerSize',3,'MarkerColor','g','DisplayName',models{p*3+3}) + legend('show','location','southoutside','Interpreter', 'none') orient('landscape') print('-bestfit',strcat(int2str(p+1),'_',char(lattice),'.pdf'),'-dpdf') close end -end +end \ No newline at end of file From b662ca0acf72d4a6eac8c8587ff518d1edbe0704 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 13 Dec 2019 13:23:13 +0100 Subject: [PATCH 081/148] do not modify pdf files --- .gitattributes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 8d1f26a78..7a5c5bde5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,8 +3,8 @@ # always use LF, even if the files are edited on windows, they need to be compiled/used on unix * text eol=lf -# Denote all files that are truly binary and should not be modified. +# Denote all files that are binary and should not be modified. *.png binary *.jpg binary -*.cae binary *.hdf5 binary +*.pdf binary From 57a69ad4c93ce54740097bd421e574e806d442c8 Mon Sep 17 00:00:00 2001 From: "f.basile" Date: Fri, 13 Dec 2019 13:32:16 +0100 Subject: [PATCH 082/148] Reupload PDF pole figures. --- python/tests/reference/Rotation/1_BCC.pdf | Bin 43263 -> 43264 bytes python/tests/reference/Rotation/1_FCC.pdf | Bin 43263 -> 43264 bytes python/tests/reference/Rotation/2_BCC.pdf | Bin 43017 -> 43017 bytes python/tests/reference/Rotation/2_FCC.pdf | Bin 43017 -> 43017 bytes .../tests/reference/Rotation/PoleFigures_OR.m | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/reference/Rotation/1_BCC.pdf b/python/tests/reference/Rotation/1_BCC.pdf index 599b892105907e96d19f52b7d927dafda0500d7c..445f1d250fea234be7680d874b149e52bed42851 100644 GIT binary patch delta 97 zcmex=k*VPl(}YS^V`BqjlZ|y8{EWPt+xYosIvct;S-Kgx8oC;oTNoIb7`d4lyBQc5 X7?~TInmL)78`>$@5K=Pv#tL-+B;Xlw delta 95 zcmZp;#Pt6n(}YS^BNJmIIU1W=nwuD!n3qFW+rYHj&6=Ft|rE& Q29|aTHiVQ+ezaU20EpWdssI20 delta 90 zcmeCYz|?txX+kBdk%_U9*~U7><<3Tq#^#nr&gO~2CmK)&d!$3j!v!yrp~6OE^bB^rY6Q_KtVGD PCp!fjLP{n-TCNTNkZl;Z delta 90 zcmeCYz|?txX+kBdk%_Uf(Z)K(<<2ISrk0MT2Bwxy#x72dhDMgAj*f Date: Fri, 13 Dec 2019 14:20:18 +0100 Subject: [PATCH 083/148] Point based vtk file in DADF5 class --- python/damask/dadf5.py | 115 +++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index 1142d02b4..1f4af8de3 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -846,41 +846,58 @@ class DADF5(): pool.wait_completion() - def to_vtk(self,labels): + def to_vtk(self,labels,mode='Cell'): """ - Export to vtk cell data. + Export to vtk cell/point data. Parameters ---------- labels : list of str Labels of the datasets to be exported. + mode : str + Export in cell format or point format. + Default value is 'Cell' """ - if self.structured: + if mode=='Cell': - coordArray = [vtk.vtkDoubleArray(),vtk.vtkDoubleArray(),vtk.vtkDoubleArray()] - for dim in [0,1,2]: - for c in np.linspace(0,self.size[dim],1+self.grid[dim]): - coordArray[dim].InsertNextValue(c) - - grid = vtk.vtkRectilinearGrid() - grid.SetDimensions(*(self.grid+1)) - grid.SetXCoordinates(coordArray[0]) - grid.SetYCoordinates(coordArray[1]) - grid.SetZCoordinates(coordArray[2]) - - else: - - nodes = vtk.vtkPoints() - with h5py.File(self.fname) as f: - nodes.SetData(numpy_support.numpy_to_vtk(f['/geometry/x_n'][()],deep=True)) + if self.structured: + + coordArray = [vtk.vtkDoubleArray(),vtk.vtkDoubleArray(),vtk.vtkDoubleArray()] + for dim in [0,1,2]: + for c in np.linspace(0,self.size[dim],1+self.grid[dim]): + coordArray[dim].InsertNextValue(c) + + grid = vtk.vtkRectilinearGrid() + grid.SetDimensions(*(self.grid+1)) + grid.SetXCoordinates(coordArray[0]) + grid.SetYCoordinates(coordArray[1]) + grid.SetZCoordinates(coordArray[2]) + + else: - grid = vtk.vtkUnstructuredGrid() - grid.SetPoints(nodes) - grid.Allocate(f['/geometry/T_c'].shape[0]) - for i in f['/geometry/T_c']: - grid.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) # not for all elements! - + nodes = vtk.vtkPoints() + with h5py.File(self.fname) as f: + nodes.SetData(numpy_support.numpy_to_vtk(f['/geometry/x_n'][()],deep=True)) + + grid = vtk.vtkUnstructuredGrid() + grid.SetPoints(nodes) + grid.Allocate(f['/geometry/T_c'].shape[0]) + for i in f['/geometry/T_c']: + grid.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) # not for all elements! + else: + Points = vtk.vtkPoints() + Vertices = vtk.vtkCellArray() + for c in self.cell_coordinates(): + pointID = Points.InsertNextPoint(c) + Vertices.InsertNextCell(1) + Vertices.InsertCellPoint(pointID) + + Polydata = vtk.vtkPolyData() + Polydata.SetPoints(Points) + Polydata.SetVerts(Vertices) + Polydata.Modified() + N_digits = int(np.floor(np.log10(int(self.increments[-1][3:]))))+1 for i,inc in enumerate(self.iter_visible('increments')): @@ -900,7 +917,10 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: hard coded 1! - grid.GetCellData().AddArray(vtk_data[-1]) + if mode=='Cell': + grid.GetCellData().AddArray(vtk_data[-1]) + else: + Polydata.GetCellData().AddArray(vtk_data[-1]) else: x = self.get_dataset_location(label) if len(x) == 0: @@ -912,9 +932,12 @@ class DADF5(): ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset vtk_data[-1].SetName(dset_name) - grid.GetCellData().AddArray(vtk_data[-1]) + if mode=='Cell': + grid.GetCellData().AddArray(vtk_data[-1]) + else: + Polydata.GetCellData().AddArray(vtk_data[-1]) self.set_visible('materialpoints',materialpoints_backup) - + constituents_backup = self.visible['constituents'].copy() self.set_visible('constituents',False) for label in labels: @@ -929,7 +952,10 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: why 1_? - grid.GetCellData().AddArray(vtk_data[-1]) + if mode=='Cell': + grid.GetCellData().AddArray(vtk_data[-1]) + else: + Polydata.GetCellData().AddArray(vtk_data[-1]) else: x = self.get_dataset_location(label) if len(x) == 0: @@ -939,17 +965,23 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - grid.GetCellData().AddArray(vtk_data[-1]) + if mode=='Cell': + grid.GetCellData().AddArray(vtk_data[-1]) + else: + Polydata.GetCellData().AddArray(vtk_data[-1]) self.set_visible('constituents',constituents_backup) - - writer = vtk.vtkXMLRectilinearGridWriter() if self.structured else \ - vtk.vtkXMLUnstructuredGridWriter() + + if mode=='Cell': + writer = vtk.vtkXMLRectilinearGridWriter() if self.structured else \ + vtk.vtkXMLUnstructuredGridWriter() + x = self.get_dataset_location('u_n') + vtk_data.append(numpy_support.numpy_to_vtk(num_array=self.read_dataset(x,0), + deep=True,array_type=vtk.VTK_DOUBLE)) + vtk_data[-1].SetName('u') + grid.GetPointData().AddArray(vtk_data[-1]) + else: + writer = vtk.vtkXMLPolyDataWriter() - x = self.get_dataset_location('u_n') - vtk_data.append(numpy_support.numpy_to_vtk(num_array=self.read_dataset(x,0), - deep=True,array_type=vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('u') - grid.GetPointData().AddArray(vtk_data[-1]) file_out = '{}_inc{}.{}'.format(os.path.splitext(os.path.basename(self.fname))[0], inc[3:].zfill(N_digits), @@ -958,6 +990,9 @@ class DADF5(): writer.SetCompressorTypeToZLib() writer.SetDataModeToBinary() writer.SetFileName(file_out) - writer.SetInputData(grid) - + if mode=='Cell': + writer.SetInputData(grid) + else: + writer.SetInputData(Polydata) + writer.Write() From e5448fc381b8524cfeaf7ea89e8a6423d6b62f80 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 13 Dec 2019 14:36:52 +0100 Subject: [PATCH 084/148] avoid code duplication --- python/damask/dadf5.py | 63 +++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index 1f4af8de3..0f32b4b5e 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -854,9 +854,9 @@ class DADF5(): ---------- labels : list of str Labels of the datasets to be exported. - mode : str + mode : str, either 'Cell' or 'Point' Export in cell format or point format. - Default value is 'Cell' + Default value is 'Cell'. """ if mode=='Cell': @@ -868,11 +868,11 @@ class DADF5(): for c in np.linspace(0,self.size[dim],1+self.grid[dim]): coordArray[dim].InsertNextValue(c) - grid = vtk.vtkRectilinearGrid() - grid.SetDimensions(*(self.grid+1)) - grid.SetXCoordinates(coordArray[0]) - grid.SetYCoordinates(coordArray[1]) - grid.SetZCoordinates(coordArray[2]) + vtk_geom = vtk.vtkRectilinearGrid() + vtk_geom.SetDimensions(*(self.grid+1)) + vtk_geom.SetXCoordinates(coordArray[0]) + vtk_geom.SetYCoordinates(coordArray[1]) + vtk_geom.SetZCoordinates(coordArray[2]) else: @@ -880,12 +880,12 @@ class DADF5(): with h5py.File(self.fname) as f: nodes.SetData(numpy_support.numpy_to_vtk(f['/geometry/x_n'][()],deep=True)) - grid = vtk.vtkUnstructuredGrid() - grid.SetPoints(nodes) - grid.Allocate(f['/geometry/T_c'].shape[0]) + vtk_geom = vtk.vtkUnstructuredGrid() + vtk_geom.SetPoints(nodes) + vtk_geom.Allocate(f['/geometry/T_c'].shape[0]) for i in f['/geometry/T_c']: - grid.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) # not for all elements! - else: + vtk_geom.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) # not for all elements! + elif mode == 'Point': Points = vtk.vtkPoints() Vertices = vtk.vtkCellArray() for c in self.cell_coordinates(): @@ -893,10 +893,10 @@ class DADF5(): Vertices.InsertNextCell(1) Vertices.InsertCellPoint(pointID) - Polydata = vtk.vtkPolyData() - Polydata.SetPoints(Points) - Polydata.SetVerts(Vertices) - Polydata.Modified() + vtk_geom = vtk.vtkPolyData() + vtk_geom.SetPoints(Points) + vtk_geom.SetVerts(Vertices) + vtk_geom.Modified() N_digits = int(np.floor(np.log10(int(self.increments[-1][3:]))))+1 @@ -917,10 +917,8 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: hard coded 1! - if mode=='Cell': - grid.GetCellData().AddArray(vtk_data[-1]) - else: - Polydata.GetCellData().AddArray(vtk_data[-1]) + vtk_geom.GetCellData().AddArray(vtk_data[-1]) + else: x = self.get_dataset_location(label) if len(x) == 0: @@ -932,10 +930,8 @@ class DADF5(): ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset vtk_data[-1].SetName(dset_name) - if mode=='Cell': - grid.GetCellData().AddArray(vtk_data[-1]) - else: - Polydata.GetCellData().AddArray(vtk_data[-1]) + vtk_geom.GetCellData().AddArray(vtk_data[-1]) + self.set_visible('materialpoints',materialpoints_backup) constituents_backup = self.visible['constituents'].copy() @@ -952,10 +948,7 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) #ToDo: why 1_? - if mode=='Cell': - grid.GetCellData().AddArray(vtk_data[-1]) - else: - Polydata.GetCellData().AddArray(vtk_data[-1]) + vtk_geom.GetCellData().AddArray(vtk_data[-1]) else: x = self.get_dataset_location(label) if len(x) == 0: @@ -965,10 +958,7 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - if mode=='Cell': - grid.GetCellData().AddArray(vtk_data[-1]) - else: - Polydata.GetCellData().AddArray(vtk_data[-1]) + vtk_geom.GetCellData().AddArray(vtk_data[-1]) self.set_visible('constituents',constituents_backup) if mode=='Cell': @@ -978,8 +968,8 @@ class DADF5(): vtk_data.append(numpy_support.numpy_to_vtk(num_array=self.read_dataset(x,0), deep=True,array_type=vtk.VTK_DOUBLE)) vtk_data[-1].SetName('u') - grid.GetPointData().AddArray(vtk_data[-1]) - else: + vtk_geom.GetPointData().AddArray(vtk_data[-1]) + elif mode == 'Point': writer = vtk.vtkXMLPolyDataWriter() @@ -990,9 +980,6 @@ class DADF5(): writer.SetCompressorTypeToZLib() writer.SetDataModeToBinary() writer.SetFileName(file_out) - if mode=='Cell': - writer.SetInputData(grid) - else: - writer.SetInputData(Polydata) + writer.SetInputData(vtk_geom) writer.Write() From 5b376712ef80091a618b384cf723a8b02a927e8b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 13 Dec 2019 14:39:10 +0100 Subject: [PATCH 085/148] bugfix: wrong coordinates --- python/damask/dadf5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index 0f32b4b5e..367d15f36 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -436,7 +436,7 @@ class DADF5(): np.linspace(delta[1],self.size[1]-delta[1],self.grid[1]), np.linspace(delta[0],self.size[0]-delta[0],self.grid[0]), ) - return np.concatenate((x[:,:,:,None],y[:,:,:,None],y[:,:,:,None]),axis = 3).reshape([np.product(self.grid),3]) + return np.concatenate((x[:,:,:,None],y[:,:,:,None],z[:,:,:,None]),axis = 3).reshape([np.product(self.grid),3]) else: with h5py.File(self.fname,'r') as f: return f['geometry/x_c'][()] From 2fb5ac652b0d9fa5d319a78c773bc052341be4d5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 14 Dec 2019 14:02:16 +0100 Subject: [PATCH 086/148] use new keyword keywords should follow the symbols in formulas, not their description: "T" for temperature, not "temperature" "F" for deformation gradient, not "defgrad" --- PRIVATE | 2 +- examples/ConfigFiles/Homogenization_Thermal_Conduction.config | 2 +- examples/SpectralMethod/EshelbyInclusion/material.config | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PRIVATE b/PRIVATE index a4a216604..62842dec1 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit a4a216604ca07f6391c209fa75b593c8e8a887e5 +Subproject commit 62842dec152d30213cc586852b0825ad264fb56b diff --git a/examples/ConfigFiles/Homogenization_Thermal_Conduction.config b/examples/ConfigFiles/Homogenization_Thermal_Conduction.config index 48ad9ddc6..36fc7ea6e 100644 --- a/examples/ConfigFiles/Homogenization_Thermal_Conduction.config +++ b/examples/ConfigFiles/Homogenization_Thermal_Conduction.config @@ -1,3 +1,3 @@ thermal conduction -initialT 300.0 +t0 270.0 (output) temperature diff --git a/examples/SpectralMethod/EshelbyInclusion/material.config b/examples/SpectralMethod/EshelbyInclusion/material.config index e002584b0..d1ea80964 100644 --- a/examples/SpectralMethod/EshelbyInclusion/material.config +++ b/examples/SpectralMethod/EshelbyInclusion/material.config @@ -6,7 +6,7 @@ mech none # isostrain 1 grain thermal adiabatic # thermal strain (stress) induced mass transport -initialT 300.0 +t0 330.0 (output) temperature #-------------------# From 898352e2220b4edcea864cc40a34095ad821d8f9 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 14 Dec 2019 16:57:23 +0100 Subject: [PATCH 087/148] [skip ci] updated version information after successful test of v2.0.3-1255-ga6da8fdd --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1439ed27c..dd4016408 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1237-g5a2053cd +v2.0.3-1255-ga6da8fdd From 4469f2f8f0712202d8175a6e1ea3ceb3b05073e4 Mon Sep 17 00:00:00 2001 From: Vitesh Shah Date: Wed, 18 Dec 2019 10:14:19 +0100 Subject: [PATCH 088/148] More general regex --- processing/post/DADF5_vtk_cells.py | 2 +- processing/post/DADF5_vtk_points.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processing/post/DADF5_vtk_cells.py b/processing/post/DADF5_vtk_cells.py index 9e8585773..b3afe939d 100755 --- a/processing/post/DADF5_vtk_cells.py +++ b/processing/post/DADF5_vtk_cells.py @@ -90,7 +90,7 @@ for filename in options.filenames: x = results.get_dataset_location(label) if len(x) == 0: continue - ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name + ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') #looking for phase name in dataset name array = results.read_dataset(x,0) shape = [array.shape[0],np.product(array.shape[1:])] vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) diff --git a/processing/post/DADF5_vtk_points.py b/processing/post/DADF5_vtk_points.py index 908474336..925a73a5c 100755 --- a/processing/post/DADF5_vtk_points.py +++ b/processing/post/DADF5_vtk_points.py @@ -77,7 +77,7 @@ for filename in options.filenames: x = results.get_dataset_location(label) if len(x) == 0: continue - ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name + ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') #looking for phase name in dataset name array = results.read_dataset(x,0) shape = [array.shape[0],np.product(array.shape[1:])] vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) From 3c42368f6681313a6efcd55afe9b145f1a5166a8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 18 Dec 2019 11:29:13 +0100 Subject: [PATCH 089/148] also use newer regex --- python/damask/dadf5.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/dadf5.py b/python/damask/dadf5.py index 367d15f36..2b86cb5f0 100644 --- a/python/damask/dadf5.py +++ b/python/damask/dadf5.py @@ -833,7 +833,7 @@ class DADF5(): N_not_calculated = len(todo) while N_not_calculated > 0: result = results.get() - with h5py.File(self.fname,'a') as f: # write to file + with h5py.File(self.fname,'a') as f: # write to file dataset_out = f[result['group']].create_dataset(result['label'],data=result['data']) for k in result['meta'].keys(): dataset_out.attrs[k] = result['meta'][k].encode() @@ -927,8 +927,8 @@ class DADF5(): shape = [array.shape[0],np.product(array.shape[1:])] vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape), deep=True,array_type= vtk.VTK_DOUBLE)) - ph_name = re.compile(r'(\/[1-9])_([A-Z][a-z]*)_(([a-z]*)|([A-Z]*))') #looking for phase name in dataset name - dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset + ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') # identify phase name + dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) # removing phase name vtk_data[-1].SetName(dset_name) vtk_geom.GetCellData().AddArray(vtk_data[-1]) From 4b6388fbb29e4257860ff4595ef3aaf821f8106c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 18 Dec 2019 20:05:51 +0100 Subject: [PATCH 090/148] always use HDF5 output --- src/CPFEM.f90 | 4 ---- src/HDF5_utilities.f90 | 4 ---- src/constitutive.f90 | 6 +++--- src/crystallite.f90 | 4 ++-- src/discretization.f90 | 4 ++-- src/homogenization.f90 | 4 ++-- src/homogenization_mech_RGC.f90 | 6 ------ src/material.f90 | 2 -- src/mesh_marc.f90 | 2 -- src/results.f90 | 2 -- 10 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/CPFEM.f90 b/src/CPFEM.f90 index 9bf8c547c..3a7f35633 100644 --- a/src/CPFEM.f90 +++ b/src/CPFEM.f90 @@ -87,10 +87,8 @@ subroutine CPFEM_initAll(el,ip) call math_init call rotations_init call FE_init -#ifdef DAMASK_HDF5 call HDF5_utilities_init call results_init -#endif call mesh_init(ip, el) call lattice_init call material_init @@ -374,7 +372,6 @@ subroutine CPFEM_results(inc,time) integer(pInt), intent(in) :: inc real(pReal), intent(in) :: time -#ifdef DAMASK_HDF5 call results_openJobFile call results_addIncrement(inc,time) call constitutive_results @@ -382,7 +379,6 @@ subroutine CPFEM_results(inc,time) call homogenization_results call results_removeLink('current') ! ToDo: put this into closeJobFile call results_closeJobFile -#endif end subroutine CPFEM_results diff --git a/src/HDF5_utilities.f90 b/src/HDF5_utilities.f90 index e4819431e..c88afbf7d 100644 --- a/src/HDF5_utilities.f90 +++ b/src/HDF5_utilities.f90 @@ -5,9 +5,7 @@ !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH !-------------------------------------------------------------------------------------------------- module HDF5_utilities -#if defined(PETSc) || defined(DAMASK_HDF5) use HDF5 -#endif #ifdef PETSc use PETSC #endif @@ -20,7 +18,6 @@ module HDF5_utilities implicit none public -#if defined(PETSc) || defined(DAMASK_HDF5) !-------------------------------------------------------------------------------------------------- !> @brief reads integer or float data of defined shape from file ! ToDo: order of arguments wrong !> @details for parallel IO, all dimension except for the last need to match @@ -1928,6 +1925,5 @@ subroutine finalize_write(plist_id, dset_id, filespace_id, memspace_id) if (hdferr < 0) call IO_error(1,ext_msg='finalize_write: h5sclose_f/memspace_id') end subroutine finalize_write -#endif end module HDF5_Utilities diff --git a/src/constitutive.f90 b/src/constitutive.f90 index 4067c026a..f6e0054af 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -688,7 +688,7 @@ end function constitutive_postResults !> @brief writes constitutive results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine constitutive_results -#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: p character(len=256) :: group do p=1,size(config_name_phase) @@ -719,8 +719,8 @@ subroutine constitutive_results call plastic_nonlocal_results(phase_plasticityInstance(p),group) end select - enddo -#endif + enddo + end subroutine constitutive_results end module constitutive diff --git a/src/crystallite.f90 b/src/crystallite.f90 index 292241001..e6e1473c4 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -767,7 +767,7 @@ end function crystallite_postResults !> @brief writes crystallite results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine crystallite_results -#if defined(PETSc) || defined(DAMASK_HDF5) + integer :: p,o real(pReal), allocatable, dimension(:,:,:) :: selected_tensors type(rotation), allocatable, dimension(:) :: selected_rotations @@ -888,7 +888,7 @@ subroutine crystallite_results enddo end function select_rotations -#endif + end subroutine crystallite_results diff --git a/src/discretization.f90 b/src/discretization.f90 index 873148666..5f9d3f521 100644 --- a/src/discretization.f90 +++ b/src/discretization.f90 @@ -78,7 +78,7 @@ end subroutine discretization_init !> @brief write the displacements !-------------------------------------------------------------------------------------------------- subroutine discretization_results -#if defined(PETSc) || defined(DAMASK_HDF5) + real(pReal), dimension(:,:), allocatable :: u call results_closeGroup(results_addGroup(trim('current/geometry'))) @@ -90,7 +90,7 @@ subroutine discretization_results u = discretization_IPcoords & - discretization_IPcoords0 call results_writeDataset('current/geometry',u,'u_c','cell center displacements','m') -#endif + end subroutine discretization_results diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 0112f9cf5..6c64f0fb5 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -790,7 +790,7 @@ end function postResults !> @brief writes homogenization results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine homogenization_results -#if defined(PETSc) || defined(DAMASK_HDF5) + use material, only: & material_homogenization_type => homogenization_type @@ -822,7 +822,7 @@ subroutine homogenization_results ! '1st Piola-Kirchoff stress','Pa') enddo -#endif + end subroutine homogenization_results end module homogenization diff --git a/src/homogenization_mech_RGC.f90 b/src/homogenization_mech_RGC.f90 index 61a1997cd..23e99c8c5 100644 --- a/src/homogenization_mech_RGC.f90 +++ b/src/homogenization_mech_RGC.f90 @@ -928,7 +928,6 @@ end subroutine mech_RGC_averageStressAndItsTangent !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- module subroutine mech_RGC_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*), intent(in) :: group @@ -962,11 +961,6 @@ module subroutine mech_RGC_results(instance,group) enddo outputsLoop end associate -#else - integer, intent(in) :: instance - character(len=*), intent(in) :: group -#endif - end subroutine mech_RGC_results diff --git a/src/material.f90 b/src/material.f90 index 8aeab5dec..9ebd00397 100644 --- a/src/material.f90 +++ b/src/material.f90 @@ -354,12 +354,10 @@ subroutine material_init call config_deallocate('material.config/microstructure') call config_deallocate('material.config/texture') -#if defined(PETSc) || defined(DAMASK_HDF5) call results_openJobFile call results_mapping_constituent(material_phaseAt,material_phaseMemberAt,config_name_phase) call results_mapping_materialpoint(material_homogenizationAt,material_homogenizationMemberAt,config_name_homogenization) call results_closeJobFile -#endif !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/mesh_marc.f90 b/src/mesh_marc.f90 index f640baa72..aef15e6bf 100644 --- a/src/mesh_marc.f90 +++ b/src/mesh_marc.f90 @@ -150,7 +150,6 @@ subroutine writeGeometry(elemType, & real(pReal), dimension(:,:), allocatable :: & coordinates_temp -#if defined(DAMASK_HDF5) call results_openJobFile call HDF5_closeGroup(results_addGroup('geometry')) @@ -171,7 +170,6 @@ subroutine writeGeometry(elemType, & 'coordinates of the material points','m') call results_closeJobFile -#endif end subroutine writeGeometry diff --git a/src/results.f90 b/src/results.f90 index a7037a454..d38e629ec 100644 --- a/src/results.f90 +++ b/src/results.f90 @@ -16,7 +16,6 @@ module results implicit none private -#if defined(PETSc) || defined(DAMASK_HDF5) integer(HID_T) :: resultsFile interface results_writeDataset @@ -978,5 +977,4 @@ end subroutine results_mapping_materialpoint !end subroutine HDF5_mappingCells -#endif end module results From 7ccf83637938077cc655d00686ad81496e048a7d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 18 Dec 2019 20:06:44 +0100 Subject: [PATCH 091/148] not needed for python-based workflow --- processing/post/DADF5_vtk_cells.py | 147 ---------------------------- processing/post/DADF5_vtk_points.py | 127 ------------------------ 2 files changed, 274 deletions(-) delete mode 100755 processing/post/DADF5_vtk_cells.py delete mode 100755 processing/post/DADF5_vtk_points.py diff --git a/processing/post/DADF5_vtk_cells.py b/processing/post/DADF5_vtk_cells.py deleted file mode 100755 index b3afe939d..000000000 --- a/processing/post/DADF5_vtk_cells.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 - -import os -import argparse -import re - -import h5py -import numpy as np -import vtk -from vtk.util import numpy_support - -import damask - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- -parser = argparse.ArgumentParser() - -#ToDo: We need to decide on a way of handling arguments of variable lentght -#https://stackoverflow.com/questions/15459997/passing-integer-lists-to-python - -#parser.add_argument('--version', action='version', version='%(prog)s {}'.format(scriptID)) -parser.add_argument('filenames', nargs='+', - help='DADF5 files') -parser.add_argument('-d','--dir', dest='dir',default='postProc',metavar='string', - help='name of subdirectory relative to the location of the DADF5 file to hold output') -parser.add_argument('--mat', nargs='+', - help='labels for materialpoint',dest='mat') -parser.add_argument('--con', nargs='+', - help='labels for constituent',dest='con') - -options = parser.parse_args() - -if options.mat is None: options.mat=[] -if options.con is None: options.con=[] - -# --- loop over input files ------------------------------------------------------------------------ - -for filename in options.filenames: - results = damask.DADF5(filename) - - if results.structured: # for grid solvers use rectilinear grid - grid = vtk.vtkRectilinearGrid() - coordArray = [vtk.vtkDoubleArray(), - vtk.vtkDoubleArray(), - vtk.vtkDoubleArray(), - ] - - grid.SetDimensions(*(results.grid+1)) - for dim in [0,1,2]: - for c in np.linspace(0,results.size[dim],1+results.grid[dim]): - coordArray[dim].InsertNextValue(c) - - grid.SetXCoordinates(coordArray[0]) - grid.SetYCoordinates(coordArray[1]) - grid.SetZCoordinates(coordArray[2]) - else: - nodes = vtk.vtkPoints() - with h5py.File(filename) as f: - nodes.SetData(numpy_support.numpy_to_vtk(f['/geometry/x_n'][()],deep=True)) - grid = vtk.vtkUnstructuredGrid() - grid.SetPoints(nodes) - grid.Allocate(f['/geometry/T_c'].shape[0]) - for i in f['/geometry/T_c']: - grid.InsertNextCell(vtk.VTK_HEXAHEDRON,8,i-1) - - N_digits = int(np.floor(np.log10(int(results.increments[-1][3:]))))+1 - for i,inc in enumerate(results.iter_visible('increments')): - print('Output step {}/{}'.format(i+1,len(results.increments))) - vtk_data = [] - - results.set_visible('materialpoints',False) - results.set_visible('constituents', True) - for label in options.con: - for p in results.iter_visible('con_physics'): - if p != 'generic': - for c in results.iter_visible('constituents'): - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - grid.GetCellData().AddArray(vtk_data[-1]) - else: - x = results.get_dataset_location(label) - if len(x) == 0: - continue - ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') #looking for phase name in dataset name - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset - vtk_data[-1].SetName(dset_name) - grid.GetCellData().AddArray(vtk_data[-1]) - - results.set_visible('constituents', False) - results.set_visible('materialpoints',True) - for label in options.mat: - for p in results.iter_visible('mat_physics'): - if p != 'generic': - for m in results.iter_visible('materialpoints'): - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - grid.GetCellData().AddArray(vtk_data[-1]) - else: - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - grid.GetCellData().AddArray(vtk_data[-1]) - - writer = vtk.vtkXMLRectilinearGridWriter() if results.structured else \ - vtk.vtkXMLUnstructuredGridWriter() - - results.set_visible('constituents', False) - results.set_visible('materialpoints',False) - x = results.get_dataset_location('u_n') - vtk_data.append(numpy_support.numpy_to_vtk(num_array=results.read_dataset(x,0),deep=True,array_type=vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('u') - grid.GetPointData().AddArray(vtk_data[-1]) - - dirname = os.path.abspath(os.path.join(os.path.dirname(filename),options.dir)) - if not os.path.isdir(dirname): - os.mkdir(dirname,0o755) - file_out = '{}_inc{}.{}'.format(os.path.splitext(os.path.split(filename)[-1])[0], - inc[3:].zfill(N_digits), - writer.GetDefaultFileExtension()) - - writer.SetCompressorTypeToZLib() - writer.SetDataModeToBinary() - writer.SetFileName(os.path.join(dirname,file_out)) - writer.SetInputData(grid) - - writer.Write() diff --git a/processing/post/DADF5_vtk_points.py b/processing/post/DADF5_vtk_points.py deleted file mode 100755 index 925a73a5c..000000000 --- a/processing/post/DADF5_vtk_points.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 - -import os -import argparse -import re - -import numpy as np -import vtk -from vtk.util import numpy_support - -import damask - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- -parser = argparse.ArgumentParser() - -#ToDo: We need to decide on a way of handling arguments of variable lentght -#https://stackoverflow.com/questions/15459997/passing-integer-lists-to-python - -#parser.add_argument('--version', action='version', version='%(prog)s {}'.format(scriptID)) -parser.add_argument('filenames', nargs='+', - help='DADF5 files') -parser.add_argument('-d','--dir', dest='dir',default='postProc',metavar='string', - help='name of subdirectory relative to the location of the DADF5 file to hold output') -parser.add_argument('--mat', nargs='+', - help='labels for materialpoint',dest='mat') -parser.add_argument('--con', nargs='+', - help='labels for constituent',dest='con') - -options = parser.parse_args() - -if options.mat is None: options.mat=[] -if options.con is None: options.con=[] - -# --- loop over input files ------------------------------------------------------------------------ - -for filename in options.filenames: - results = damask.DADF5(filename) - - Points = vtk.vtkPoints() - Vertices = vtk.vtkCellArray() - for c in results.cell_coordinates(): - pointID = Points.InsertNextPoint(c) - Vertices.InsertNextCell(1) - Vertices.InsertCellPoint(pointID) - - Polydata = vtk.vtkPolyData() - Polydata.SetPoints(Points) - Polydata.SetVerts(Vertices) - Polydata.Modified() - - N_digits = int(np.floor(np.log10(int(results.increments[-1][3:]))))+1 - for i,inc in enumerate(results.iter_visible('increments')): - print('Output step {}/{}'.format(i+1,len(results.increments))) - vtk_data = [] - - results.set_visible('materialpoints',False) - results.set_visible('constituents', True) - for label in options.con: - - for p in results.iter_visible('con_physics'): - if p != 'generic': - for c in results.iter_visible('constituents'): - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - Polydata.GetCellData().AddArray(vtk_data[-1]) - else: - x = results.get_dataset_location(label) - if len(x) == 0: - continue - ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') #looking for phase name in dataset name - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) #removing phase name from generic dataset - vtk_data[-1].SetName(dset_name) - Polydata.GetCellData().AddArray(vtk_data[-1]) - - results.set_visible('constituents', False) - results.set_visible('materialpoints',True) - for label in options.mat: - for p in results.iter_visible('mat_physics'): - if p != 'generic': - for m in results.iter_visible('materialpoints'): - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - Polydata.GetCellData().AddArray(vtk_data[-1]) - else: - x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0) - shape = [array.shape[0],np.product(array.shape[1:])] - vtk_data.append(numpy_support.numpy_to_vtk(num_array=array.reshape(shape),deep=True,array_type= vtk.VTK_DOUBLE)) - vtk_data[-1].SetName('1_'+x[0].split('/',1)[1]) - Polydata.GetCellData().AddArray(vtk_data[-1]) - - writer = vtk.vtkXMLPolyDataWriter() - - - dirname = os.path.abspath(os.path.join(os.path.dirname(filename),options.dir)) - if not os.path.isdir(dirname): - os.mkdir(dirname,0o755) - file_out = '{}_inc{}.{}'.format(os.path.splitext(os.path.split(filename)[-1])[0], - inc[3:].zfill(N_digits), - writer.GetDefaultFileExtension()) - - writer.SetCompressorTypeToZLib() - writer.SetDataModeToBinary() - writer.SetFileName(os.path.join(dirname,file_out)) - writer.SetInputData(Polydata) - - writer.Write() From 9d7248f8c49c62727194c33d66b7637a858ebcff Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 18 Dec 2019 22:10:38 +0100 Subject: [PATCH 092/148] [skip ci] updated version information after successful test of v2.0.3-1264-g80b559dc --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dd4016408..005c93723 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1255-ga6da8fdd +v2.0.3-1264-g80b559dc From 98e606d6f0c4e061548c49c894fdc09b02db71b7 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 19 Dec 2019 00:00:00 +0100 Subject: [PATCH 093/148] correct type --- src/mesh_grid.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh_grid.f90 b/src/mesh_grid.f90 index d10ffef8a..3d839e1c9 100644 --- a/src/mesh_grid.f90 +++ b/src/mesh_grid.f90 @@ -367,7 +367,7 @@ pure function cellEdgeNormal(nElems) integer, intent(in) :: nElems - real, dimension(3,6,1,nElems) :: cellEdgeNormal + real(pReal), dimension(3,6,1,nElems) :: cellEdgeNormal cellEdgeNormal(1:3,1,1,:) = spread([+1.0_pReal, 0.0_pReal, 0.0_pReal],2,nElems) cellEdgeNormal(1:3,2,1,:) = spread([-1.0_pReal, 0.0_pReal, 0.0_pReal],2,nElems) From 101cc83cee9b536a094be710101bb5a179b13058 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 19 Dec 2019 00:00:09 +0100 Subject: [PATCH 094/148] copy and paste error --- cmake/Compiler-PGI.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/Compiler-PGI.cmake b/cmake/Compiler-PGI.cmake index bca76f648..c18679417 100644 --- a/cmake/Compiler-PGI.cmake +++ b/cmake/Compiler-PGI.cmake @@ -1,7 +1,6 @@ ################################################################################################### # PGI Compiler ################################################################################################### -elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "PGI") if (OPTIMIZATION STREQUAL "OFF") set (OPTIMIZATION_FLAGS "-O0" ) From f0ad07580290dbac7a43f8e0f33205aafd2fbe7d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 19 Dec 2019 00:01:44 +0100 Subject: [PATCH 095/148] fixed indentation --- src/homogenization.f90 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 6c64f0fb5..4380ee5aa 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -107,12 +107,12 @@ module homogenization P,& !< partitioned stresses F,& !< partitioned deformation gradients F0 !< partitioned initial deformation gradients - real(pReal), dimension(:,:,:,:,:), intent(in) :: dPdF !< partitioned stiffnesses - real(pReal), dimension(3,3), intent(in) :: avgF !< average F - real(pReal), intent(in) :: dt !< time increment - integer, intent(in) :: & - ip, & !< integration point number - el !< element number + real(pReal), dimension(:,:,:,:,:), intent(in) :: dPdF !< partitioned stiffnesses + real(pReal), dimension(3,3), intent(in) :: avgF !< average F + real(pReal), intent(in) :: dt !< time increment + integer, intent(in) :: & + ip, & !< integration point number + el !< element number end function mech_RGC_updateState From befd4e2adf4dad2aa193b4ac60e411f485600cc5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 19 Dec 2019 07:49:53 +0100 Subject: [PATCH 096/148] avoid imports --- src/homogenization.f90 | 7 +++---- src/mesh_marc.f90 | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 4380ee5aa..9580336d3 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -23,7 +23,6 @@ module homogenization use damage_local use damage_nonlocal use results - use HDF5_utilities implicit none private @@ -801,18 +800,18 @@ subroutine homogenization_results do p=1,size(config_name_homogenization) group = trim('current/materialpoint')//'/'//trim(config_name_homogenization(p)) - call HDF5_closeGroup(results_addGroup(group)) + call results_closeGroup(results_addGroup(group)) group = trim(group)//'/mech' - call HDF5_closeGroup(results_addGroup(group)) + call results_closeGroup(results_addGroup(group)) select case(material_homogenization_type(p)) case(HOMOGENIZATION_rgc_ID) call mech_RGC_results(homogenization_typeInstance(p),group) end select group = trim('current/materialpoint')//'/'//trim(config_name_homogenization(p))//'/generic' - call HDF5_closeGroup(results_addGroup(group)) + call results_closeGroup(results_addGroup(group)) !temp = reshape(materialpoint_F,[3,3,discretization_nIP*discretization_nElem]) !call results_writeDataset(group,temp,'F',& diff --git a/src/mesh_marc.f90 b/src/mesh_marc.f90 index aef15e6bf..cebe844e7 100644 --- a/src/mesh_marc.f90 +++ b/src/mesh_marc.f90 @@ -18,7 +18,6 @@ module mesh use element use discretization use geometry_plastic_nonlocal - use HDF5_utilities use results implicit none @@ -151,7 +150,7 @@ subroutine writeGeometry(elemType, & coordinates_temp call results_openJobFile - call HDF5_closeGroup(results_addGroup('geometry')) + call results_closeGroup(results_addGroup('geometry')) connectivity_temp = connectivity_elem call results_writeDataset('geometry',connectivity_temp,'T_e',& From 6114f5c325d593bbea0001fc38d1cf34a039a2ff Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 19 Dec 2019 18:04:24 +0100 Subject: [PATCH 097/148] inplace option does not exist anymore --- processing/pre/seeds_check.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/processing/pre/seeds_check.sh b/processing/pre/seeds_check.sh index 025c9eb90..502a19024 100755 --- a/processing/pre/seeds_check.sh +++ b/processing/pre/seeds_check.sh @@ -6,7 +6,6 @@ do vtk_addPointCloudData $seeds \ --data microstructure,weight \ - --inplace \ --vtk ${seeds%.*}.vtp \ done From 0fdc880e2cd51f693984f3195b4040762ac71229 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 20 Dec 2019 14:10:21 +0100 Subject: [PATCH 098/148] [skip ci] updated version information after successful test of v2.0.3-1294-g034367fa --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 005c93723..287da9b11 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1264-g80b559dc +v2.0.3-1294-g034367fa From d12842a4418c1c52c4dfb9f14a384171cb8df618 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:13:13 +0100 Subject: [PATCH 099/148] only stack size matters Intel compiler requires large stack size, otherwise DAMASK crashes with segmentation fault. Other limits are not important (checked runtime) --- env/DAMASK.csh | 12 ++---------- env/DAMASK.sh | 6 +++--- env/DAMASK.zsh | 7 +++---- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/env/DAMASK.csh b/env/DAMASK.csh index d3b4474b2..b6ccca869 100644 --- a/env/DAMASK.csh +++ b/env/DAMASK.csh @@ -12,7 +12,6 @@ cd $DAMASK_ROOT >/dev/null set BRANCH = `git branch 2>/dev/null| grep -E '^\* ')` cd - >/dev/null -# if DAMASK_BIN is present set path = ($DAMASK_ROOT/bin $path) set SOLVER=`which DAMASK_spectral` @@ -21,19 +20,12 @@ if ( "x$DAMASK_NUM_THREADS" == "x" ) then set DAMASK_NUM_THREADS=1 endif -# currently, there is no information that unlimited causes problems +# currently, there is no information that unlimited stack size causes problems # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap # http://superuser.com/questions/220059/what-parameters-has-ulimit limit stacksize unlimited # maximum stack size (kB) -endif -if ( `limit | grep memoryuse` != "" ) then - limit memoryuse unlimited # maximum physical memory size -endif -if ( `limit | grep vmemoryuse` != "" ) then - limit vmemoryuse unlimited # maximum virtual memory size -endif # disable output in case of scp if ( $?prompt ) then @@ -44,7 +36,7 @@ if ( $?prompt ) then echo echo Using environment with ... echo "DAMASK $DAMASK_ROOT $BRANCH" - echo "Spectral Solver $SOLVER" + echo "Grid Solver $SOLVER" echo "Post Processing $PROCESSING" if ( $?PETSC_DIR) then echo "PETSc location $PETSC_DIR" diff --git a/env/DAMASK.sh b/env/DAMASK.sh index a6d7b2667..56696a0e8 100644 --- a/env/DAMASK.sh +++ b/env/DAMASK.sh @@ -43,7 +43,7 @@ PROCESSING=$(type -p postResults || true 2>/dev/null) [ "x$DAMASK_NUM_THREADS" == "x" ] && DAMASK_NUM_THREADS=1 -# currently, there is no information that unlimited causes problems +# currently, there is no information that unlimited stack size causes problems # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap @@ -59,7 +59,7 @@ if [ ! -z "$PS1" ]; then echo echo Using environment with ... echo "DAMASK $DAMASK_ROOT $BRANCH" - echo "Spectral Solver $SOLVER" + echo "Grid Solver $SOLVER" echo "Post Processing $PROCESSING" if [ "x$PETSC_DIR" != "x" ]; then echo -n "PETSc location " @@ -93,7 +93,7 @@ fi export DAMASK_NUM_THREADS export PYTHONPATH=$DAMASK_ROOT/python:$PYTHONPATH -for var in BASE STAT SOLVER PROCESSING FREE DAMASK_BIN BRANCH; do +for var in BASE STAT SOLVER PROCESSING BRANCH; do unset "${var}" done for var in DAMASK MSC; do diff --git a/env/DAMASK.zsh b/env/DAMASK.zsh index 42021191f..8ac97fe18 100644 --- a/env/DAMASK.zsh +++ b/env/DAMASK.zsh @@ -24,7 +24,6 @@ unset -f set # add BRANCH if DAMASK_ROOT is a git repository cd $DAMASK_ROOT >/dev/null; BRANCH=$(git branch 2>/dev/null| grep -E '^\* '); cd - >/dev/null -# add DAMASK_BIN if present PATH=${DAMASK_ROOT}/bin:$PATH SOLVER=$(which DAMASK_spectral || true 2>/dev/null) @@ -35,7 +34,7 @@ PROCESSING=$(which postResults || true 2>/dev/null) [[ "x$DAMASK_NUM_THREADS" == "x" ]] && DAMASK_NUM_THREADS=1 -# currently, there is no information that unlimited causes problems +# currently, there is no information that unlimited stack size causes problems # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap @@ -51,7 +50,7 @@ if [ ! -z "$PS1" ]; then echo echo "Using environment with ..." echo "DAMASK $DAMASK_ROOT $BRANCH" - echo "Spectral Solver $SOLVER" + echo "Grid Solver $SOLVER" echo "Post Processing $PROCESSING" if [ "x$PETSC_DIR" != "x" ]; then echo -n "PETSc location " @@ -87,7 +86,7 @@ fi export DAMASK_NUM_THREADS export PYTHONPATH=$DAMASK_ROOT/python:$PYTHONPATH -for var in BASE STAT SOLVER PROCESSING FREE DAMASK_BIN BRANCH; do +for var in SOLVER PROCESSING BRANCH; do unset "${var}" done for var in DAMASK MSC; do From 089e71deba23882900d95b065818dfbf443fa234 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:29:01 +0100 Subject: [PATCH 100/148] does not work (even after removing the extra bracket) --- env/DAMASK.csh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/env/DAMASK.csh b/env/DAMASK.csh index b6ccca869..1b16e444b 100644 --- a/env/DAMASK.csh +++ b/env/DAMASK.csh @@ -7,11 +7,6 @@ set DAMASK_ROOT=`python -c "import os,sys; print(os.path.realpath(os.path.expand source $DAMASK_ROOT/CONFIG -# add BRANCH if DAMASK_ROOT is a git repository -cd $DAMASK_ROOT >/dev/null -set BRANCH = `git branch 2>/dev/null| grep -E '^\* ')` -cd - >/dev/null - set path = ($DAMASK_ROOT/bin $path) set SOLVER=`which DAMASK_spectral` @@ -35,7 +30,7 @@ if ( $?prompt ) then echo https://damask.mpie.de echo echo Using environment with ... - echo "DAMASK $DAMASK_ROOT $BRANCH" + echo "DAMASK $DAMASK_ROOT" echo "Grid Solver $SOLVER" echo "Post Processing $PROCESSING" if ( $?PETSC_DIR) then From b10e862be82978b5002ac825fd2592ced8a0ef6c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:34:40 +0100 Subject: [PATCH 101/148] the compiler can do the counting --- src/IO.f90 | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/IO.f90 b/src/IO.f90 index c121cc65e..cffcb6471 100644 --- a/src/IO.f90 +++ b/src/IO.f90 @@ -11,9 +11,9 @@ module IO implicit none private - character(len=5), parameter, public :: & + character(len=*), parameter, public :: & IO_EOF = '#EOF#' !< end of file string - character(len=207), parameter, private :: & + character(len=*), parameter, private :: & IO_DIVIDER = '───────────────────'//& '───────────────────'//& '───────────────────'//& @@ -401,7 +401,7 @@ function IO_stringValue(string,chunkPos,myChunk,silent) character(len=:), allocatable :: IO_stringValue logical, optional,intent(in) :: silent !< switch to trigger verbosity - character(len=16), parameter :: MYNAME = 'IO_stringValue: ' + character(len=*), parameter :: MYNAME = 'IO_stringValue: ' logical :: warn @@ -429,8 +429,8 @@ real(pReal) function IO_floatValue (string,chunkPos,myChunk) integer, dimension(:), intent(in) :: chunkPos !< positions of start and end of each tag/chunk in given string integer, intent(in) :: myChunk !< position number of desired chunk character(len=*), intent(in) :: string !< raw input with known start and end of each chunk - character(len=15), parameter :: MYNAME = 'IO_floatValue: ' - character(len=17), parameter :: VALIDCHARACTERS = '0123456789eEdD.+-' + character(len=*), parameter :: MYNAME = 'IO_floatValue: ' + character(len=*), parameter :: VALIDCHARACTERS = '0123456789eEdD.+-' IO_floatValue = 0.0_pReal @@ -453,8 +453,8 @@ integer function IO_intValue(string,chunkPos,myChunk) character(len=*), intent(in) :: string !< raw input with known start and end of each chunk integer, intent(in) :: myChunk !< position number of desired chunk integer, dimension(:), intent(in) :: chunkPos !< positions of start and end of each tag/chunk in given string - character(len=13), parameter :: MYNAME = 'IO_intValue: ' - character(len=12), parameter :: VALIDCHARACTERS = '0123456789+-' + character(len=*), parameter :: MYNAME = 'IO_intValue: ' + character(len=*), parameter :: VALIDCHARACTERS = '0123456789+-' IO_intValue = 0 @@ -477,9 +477,9 @@ real(pReal) function IO_fixedNoEFloatValue (string,ends,myChunk) character(len=*), intent(in) :: string !< raw input with known ends of each chunk integer, intent(in) :: myChunk !< position number of desired chunk integer, dimension(:), intent(in) :: ends !< positions of end of each tag/chunk in given string - character(len=22), parameter :: MYNAME = 'IO_fixedNoEFloatValue ' - character(len=13), parameter :: VALIDBASE = '0123456789.+-' - character(len=12), parameter :: VALIDEXP = '0123456789+-' + character(len=*), parameter :: MYNAME = 'IO_fixedNoEFloatValue ' + character(len=*), parameter :: VALIDBASE = '0123456789.+-' + character(len=*), parameter :: VALIDEXP = '0123456789+-' real(pReal) :: base integer :: expon @@ -509,8 +509,8 @@ integer function IO_fixedIntValue(string,ends,myChunk) character(len=*), intent(in) :: string !< raw input with known ends of each chunk integer, intent(in) :: myChunk !< position number of desired chunk integer, dimension(:), intent(in) :: ends !< positions of end of each tag/chunk in given string - character(len=20), parameter :: MYNAME = 'IO_fixedIntValue: ' - character(len=12), parameter :: VALIDCHARACTERS = '0123456789+-' + character(len=*), parameter :: MYNAME = 'IO_fixedIntValue: ' + character(len=*), parameter :: VALIDCHARACTERS = '0123456789+-' IO_fixedIntValue = IO_verifyIntValue(trim(adjustl(string(ends(myChunk)+1:ends(myChunk+1)))),& VALIDCHARACTERS,MYNAME) From 41650cdd118d0fd4523f0f22b64b8f28aba6a516 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:38:17 +0100 Subject: [PATCH 102/148] use standard string length --- src/HDF5_utilities.f90 | 32 ++++++++++++++++---------------- src/constitutive.f90 | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/HDF5_utilities.f90 b/src/HDF5_utilities.f90 index c88afbf7d..02e575d98 100644 --- a/src/HDF5_utilities.f90 +++ b/src/HDF5_utilities.f90 @@ -276,8 +276,8 @@ logical function HDF5_objectExists(loc_id,path) integer(HID_T), intent(in) :: loc_id character(len=*), intent(in), optional :: path - integer :: hdferr - character(len=256) :: p + integer :: hdferr + character(len=pStringLen) :: p if (present(path)) then p = trim(path) @@ -305,10 +305,10 @@ subroutine HDF5_addAttribute_str(loc_id,attrLabel,attrValue,path) character(len=*), intent(in) :: attrLabel, attrValue character(len=*), intent(in), optional :: path - integer :: hdferr - integer(HID_T) :: attr_id, space_id, type_id - logical :: attrExists - character(len=256) :: p + integer :: hdferr + integer(HID_T) :: attr_id, space_id, type_id + logical :: attrExists + character(len=pStringLen) :: p if (present(path)) then p = trim(path) @@ -352,10 +352,10 @@ subroutine HDF5_addAttribute_int(loc_id,attrLabel,attrValue,path) integer, intent(in) :: attrValue character(len=*), intent(in), optional :: path - integer :: hdferr - integer(HID_T) :: attr_id, space_id - logical :: attrExists - character(len=256) :: p + integer :: hdferr + integer(HID_T) :: attr_id, space_id + logical :: attrExists + character(len=pStringLen) :: p if (present(path)) then p = trim(path) @@ -393,10 +393,10 @@ subroutine HDF5_addAttribute_real(loc_id,attrLabel,attrValue,path) real(pReal), intent(in) :: attrValue character(len=*), intent(in), optional :: path - integer :: hdferr - integer(HID_T) :: attr_id, space_id - logical :: attrExists - character(len=256) :: p + integer :: hdferr + integer(HID_T) :: attr_id, space_id + logical :: attrExists + character(len=pStringLen) :: p if (present(path)) then p = trim(path) @@ -438,7 +438,7 @@ subroutine HDF5_addAttribute_int_array(loc_id,attrLabel,attrValue,path) integer(HID_T) :: attr_id, space_id integer(HSIZE_T),dimension(1) :: array_size logical :: attrExists - character(len=256) :: p + character(len=pStringLen) :: p if (present(path)) then p = trim(path) @@ -482,7 +482,7 @@ subroutine HDF5_addAttribute_real_array(loc_id,attrLabel,attrValue,path) integer(HID_T) :: attr_id, space_id integer(HSIZE_T),dimension(1) :: array_size logical :: attrExists - character(len=256) :: p + character(len=pStringLen) :: p if (present(path)) then p = trim(path) diff --git a/src/constitutive.f90 b/src/constitutive.f90 index de262efe1..977c80337 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -584,7 +584,7 @@ end subroutine constitutive_collectDeltaState subroutine constitutive_results integer :: p - character(len=256) :: group + character(len=pStringLen) :: group do p=1,size(config_name_phase) group = trim('current/constituent')//'/'//trim(config_name_phase(p)) call HDF5_closeGroup(results_addGroup(group)) From 19a45d9c2b68a6a658cec16c890098cea4b552b8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:55:42 +0100 Subject: [PATCH 103/148] cleaning of source modules: - offset and instance are only used locally - output names are needed only temporarly - HDF5 is always enabled --- src/source_damage_anisoBrittle.f90 | 15 +++------------ src/source_damage_anisoDuctile.f90 | 15 ++------------- src/source_damage_isoBrittle.f90 | 14 +++----------- src/source_damage_isoDuctile.f90 | 16 +++------------- src/source_thermal_dissipation.f90 | 7 +++---- src/source_thermal_externalheat.f90 | 6 +++--- 6 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index 5dc8b96af..2211ffdd2 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -18,12 +18,9 @@ module source_damage_anisoBrittle implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_damage_anisoBrittle_offset, & !< which source is my current source mechanism? source_damage_anisoBrittle_instance !< instance of source mechanism - - character(len=64), dimension(:,:), allocatable :: & - source_damage_anisoBrittle_output !< name of each post result output integer, dimension(:,:), allocatable :: & source_damage_anisoBrittle_Ncleavage !< number of cleavage systems per family @@ -82,7 +79,7 @@ subroutine source_damage_anisoBrittle_init character(len=65536), dimension(:), allocatable :: & outputs - write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISOBRITTLE_LABEL//' init -+>>>' + write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISOBRITTLE_LABEL//' init -+>>>'; flush(6) Ninstance = count(phase_source == SOURCE_damage_anisoBrittle_ID) if (Ninstance == 0) return @@ -100,9 +97,6 @@ subroutine source_damage_anisoBrittle_init enddo enddo - allocate(source_damage_anisoBrittle_output(maxval(phase_Noutput),Ninstance)) - source_damage_anisoBrittle_output = '' - allocate(source_damage_anisoBrittle_Ncleavage(lattice_maxNcleavageFamily,Ninstance), source=0) allocate(param(Ninstance)) @@ -151,7 +145,6 @@ subroutine source_damage_anisoBrittle_init select case(outputs(i)) case ('anisobrittle_drivingforce') - source_damage_anisoBrittle_output(i,source_damage_anisoBrittle_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] end select @@ -266,8 +259,7 @@ end subroutine source_damage_anisoBrittle_getRateAndItsTangent subroutine source_damage_anisoBrittle_results(phase,group) integer, intent(in) :: phase - character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) + character(len=*), intent(in) :: group integer :: sourceOffset, o, instance instance = source_damage_anisoBrittle_instance(phase) @@ -281,7 +273,6 @@ subroutine source_damage_anisoBrittle_results(phase,group) end select enddo outputsLoop end associate -#endif end subroutine source_damage_anisoBrittle_results diff --git a/src/source_damage_anisoDuctile.f90 b/src/source_damage_anisoDuctile.f90 index caba26ef4..b6a4942c1 100644 --- a/src/source_damage_anisoDuctile.f90 +++ b/src/source_damage_anisoDuctile.f90 @@ -17,14 +17,10 @@ module source_damage_anisoDuctile implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_damage_anisoDuctile_offset, & !< which source is my current damage mechanism? source_damage_anisoDuctile_instance !< instance of damage source mechanism - character(len=64), dimension(:,:), allocatable, target, public :: & - source_damage_anisoDuctile_output !< name of each post result output - - enum, bind(c) enumerator :: undefined_ID, & damage_drivingforce_ID @@ -76,7 +72,7 @@ subroutine source_damage_anisoDuctile_init character(len=65536), dimension(:), allocatable :: & outputs - write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISODUCTILE_LABEL//' init -+>>>' + write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISODUCTILE_LABEL//' init -+>>>'; flush(6) Ninstance = count(phase_source == SOURCE_damage_anisoDuctile_ID) if (Ninstance == 0) return @@ -93,10 +89,6 @@ subroutine source_damage_anisoDuctile_init source_damage_anisoDuctile_offset(phase) = source enddo enddo - - allocate(source_damage_anisoDuctile_output(maxval(phase_Noutput),Ninstance)) - source_damage_anisoDuctile_output = '' - allocate(param(Ninstance)) @@ -136,7 +128,6 @@ subroutine source_damage_anisoDuctile_init select case(outputs(i)) case ('anisoductile_drivingforce') - source_damage_anisoDuctile_output(i,source_damage_anisoDuctile_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] end select @@ -227,7 +218,6 @@ subroutine source_damage_anisoDuctile_results(phase,group) integer, intent(in) :: phase character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) integer :: sourceOffset, o, instance instance = source_damage_anisoDuctile_instance(phase) @@ -241,7 +231,6 @@ subroutine source_damage_anisoDuctile_results(phase,group) end select enddo outputsLoop end associate -#endif end subroutine source_damage_anisoDuctile_results diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index e38c15682..e10177502 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -16,11 +16,9 @@ module source_damage_isoBrittle implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_damage_isoBrittle_offset, & source_damage_isoBrittle_instance - character(len=64), dimension(:,:), allocatable :: & - source_damage_isoBrittle_output enum, bind(c) enumerator :: & @@ -67,7 +65,7 @@ subroutine source_damage_isoBrittle_init character(len=65536), dimension(:), allocatable :: & outputs - write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ISOBRITTLE_LABEL//' init -+>>>' + write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ISOBRITTLE_LABEL//' init -+>>>'; flush(6) Ninstance = count(phase_source == SOURCE_damage_isoBrittle_ID) if (Ninstance == 0) return @@ -84,9 +82,6 @@ subroutine source_damage_isoBrittle_init source_damage_isoBrittle_offset(phase) = source enddo enddo - - allocate(source_damage_isoBrittle_output(maxval(phase_Noutput),Ninstance)) - source_damage_isoBrittle_output = '' allocate(param(Ninstance)) @@ -120,7 +115,6 @@ subroutine source_damage_isoBrittle_init select case(outputs(i)) case ('isobrittle_drivingforce') - source_damage_isoBrittle_output(i,source_damage_isoBrittle_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] end select @@ -218,8 +212,7 @@ end subroutine source_damage_isoBrittle_getRateAndItsTangent subroutine source_damage_isoBrittle_results(phase,group) integer, intent(in) :: phase - character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) + character(len=*), intent(in) :: group integer :: sourceOffset, o, instance instance = source_damage_isoBrittle_instance(phase) @@ -233,7 +226,6 @@ subroutine source_damage_isoBrittle_results(phase,group) end select enddo outputsLoop end associate -#endif end subroutine source_damage_isoBrittle_results diff --git a/src/source_damage_isoDuctile.f90 b/src/source_damage_isoDuctile.f90 index 69b8e82bf..fca804c84 100644 --- a/src/source_damage_isoDuctile.f90 +++ b/src/source_damage_isoDuctile.f90 @@ -15,18 +15,14 @@ module source_damage_isoDuctile implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_damage_isoDuctile_offset, & !< which source is my current damage mechanism? source_damage_isoDuctile_instance !< instance of damage source mechanism - character(len=64), dimension(:,:), allocatable, target, public :: & - source_damage_isoDuctile_output !< name of each post result output - - enum, bind(c) enumerator :: undefined_ID, & damage_drivingforce_ID - end enum !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11 ToDo + end enum !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ToDo type, private :: tParameters !< container type for internal constitutive parameters real(pReal) :: & @@ -83,9 +79,6 @@ subroutine source_damage_isoDuctile_init source_damage_isoDuctile_offset(phase) = source enddo enddo - - allocate(source_damage_isoDuctile_output(maxval(phase_Noutput),Ninstance)) - source_damage_isoDuctile_output = '' allocate(param(Ninstance)) @@ -119,7 +112,6 @@ subroutine source_damage_isoDuctile_init select case(outputs(i)) case ('isoductile_drivingforce') - source_damage_isoDuctile_output(i,source_damage_isoDuctile_instance(p)) = outputs(i) prm%outputID = [prm%outputID, damage_drivingforce_ID] end select @@ -198,8 +190,7 @@ end subroutine source_damage_isoDuctile_getRateAndItsTangent subroutine source_damage_isoDuctile_results(phase,group) integer, intent(in) :: phase - character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) + character(len=*), intent(in) :: group integer :: sourceOffset, o, instance instance = source_damage_isoDuctile_instance(phase) @@ -213,7 +204,6 @@ subroutine source_damage_isoDuctile_results(phase,group) end select enddo outputsLoop end associate -#endif end subroutine source_damage_isoDuctile_results diff --git a/src/source_thermal_dissipation.f90 b/src/source_thermal_dissipation.f90 index 0d16a9171..e13742a90 100644 --- a/src/source_thermal_dissipation.f90 +++ b/src/source_thermal_dissipation.f90 @@ -14,7 +14,7 @@ module source_thermal_dissipation implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_thermal_dissipation_offset, & !< which source is my current thermal dissipation mechanism? source_thermal_dissipation_instance !< instance of thermal dissipation source mechanism @@ -39,10 +39,9 @@ contains !-------------------------------------------------------------------------------------------------- subroutine source_thermal_dissipation_init - integer :: Ninstance,instance,source,sourceOffset - integer :: NofMyPhase,p + integer :: Ninstance,instance,source,sourceOffset,NofMyPhase,p - write(6,'(/,a)') ' <<<+- source_'//SOURCE_thermal_dissipation_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- source_'//SOURCE_thermal_dissipation_label//' init -+>>>'; flush(6) Ninstance = count(phase_source == SOURCE_thermal_dissipation_ID) diff --git a/src/source_thermal_externalheat.f90 b/src/source_thermal_externalheat.f90 index 00e6da6bf..7ae37f037 100644 --- a/src/source_thermal_externalheat.f90 +++ b/src/source_thermal_externalheat.f90 @@ -14,7 +14,7 @@ module source_thermal_externalheat implicit none private - integer, dimension(:), allocatable, public, protected :: & + integer, dimension(:), allocatable :: & source_thermal_externalheat_offset, & !< which source is my current thermal dissipation mechanism? source_thermal_externalheat_instance !< instance of thermal dissipation source mechanism @@ -43,9 +43,9 @@ contains !-------------------------------------------------------------------------------------------------- subroutine source_thermal_externalheat_init - integer :: maxNinstance,instance,source,sourceOffset,NofMyPhase,p + integer :: maxNinstance,instance,source,sourceOffset,NofMyPhase,p - write(6,'(/,a)') ' <<<+- source_'//SOURCE_thermal_externalheat_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- source_'//SOURCE_thermal_externalheat_label//' init -+>>>'; flush(6) maxNinstance = count(phase_source == SOURCE_thermal_externalheat_ID) From e8e3af000ada1439b8d77150ecdf81989495c121 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 07:59:09 +0100 Subject: [PATCH 104/148] indentation was not corrected after split per compiler --- cmake/Compiler-GNU.cmake | 210 ++++++++++++++++++------------------- cmake/Compiler-Intel.cmake | 180 +++++++++++++++---------------- cmake/Compiler-PGI.cmake | 24 ++--- 3 files changed, 207 insertions(+), 207 deletions(-) diff --git a/cmake/Compiler-GNU.cmake b/cmake/Compiler-GNU.cmake index 008c0c90e..6a9973bc6 100644 --- a/cmake/Compiler-GNU.cmake +++ b/cmake/Compiler-GNU.cmake @@ -2,129 +2,129 @@ # GNU Compiler ################################################################################################### - if (OPENMP) - set (OPENMP_FLAGS "-fopenmp") - endif () +if (OPENMP) + set (OPENMP_FLAGS "-fopenmp") +endif () - if (OPTIMIZATION STREQUAL "OFF") - set (OPTIMIZATION_FLAGS "-O0" ) - elseif (OPTIMIZATION STREQUAL "DEFENSIVE") - set (OPTIMIZATION_FLAGS "-O2") - elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") - set (OPTIMIZATION_FLAGS "-O3 -ffast-math -funroll-loops -ftree-vectorize") - endif () +if (OPTIMIZATION STREQUAL "OFF") + set (OPTIMIZATION_FLAGS "-O0" ) +elseif (OPTIMIZATION STREQUAL "DEFENSIVE") + set (OPTIMIZATION_FLAGS "-O2") +elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") + set (OPTIMIZATION_FLAGS "-O3 -ffast-math -funroll-loops -ftree-vectorize") +endif () - set (STANDARD_CHECK "-std=f2008ts -pedantic-errors" ) - set (LINKER_FLAGS "${LINKER_FLAGS} -Wl") - # options parsed directly to the linker - set (LINKER_FLAGS "${LINKER_FLAGS},-undefined,dynamic_lookup" ) - # ensure to link against dynamic libraries +set (STANDARD_CHECK "-std=f2008ts -pedantic-errors" ) +set (LINKER_FLAGS "${LINKER_FLAGS} -Wl") +# options parsed directly to the linker +set (LINKER_FLAGS "${LINKER_FLAGS},-undefined,dynamic_lookup" ) +# ensure to link against dynamic libraries #------------------------------------------------------------------------------------------------ # Fine tuning compilation options - set (COMPILE_FLAGS "${COMPILE_FLAGS} -xf95-cpp-input") - # preprocessor +set (COMPILE_FLAGS "${COMPILE_FLAGS} -xf95-cpp-input") +# preprocessor - set (COMPILE_FLAGS "${COMPILE_FLAGS} -ffree-line-length-132") - # restrict line length to the standard 132 characters (lattice.f90 require more characters) +set (COMPILE_FLAGS "${COMPILE_FLAGS} -ffree-line-length-132") +# restrict line length to the standard 132 characters (lattice.f90 require more characters) - set (COMPILE_FLAGS "${COMPILE_FLAGS} -fimplicit-none") - # assume "implicit none" even if not present in source +set (COMPILE_FLAGS "${COMPILE_FLAGS} -fimplicit-none") +# assume "implicit none" even if not present in source - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wall") - # sets the following Fortran options: - # -Waliasing: warn about possible aliasing of dummy arguments. Specifically, it warns if the same actual argument is associated with a dummy argument with "INTENT(IN)" and a dummy argument with "INTENT(OUT)" in a call with an explicit interface. - # -Wampersand: checks if a character expression is continued proberly by an ampersand at the end of the line and at the beginning of the new line - # -Warray-bounds: checks if array reference is out of bounds at compile time. use -fcheck-bounds to also check during runtime - # -Wconversion: warn about implicit conversions between different type - # -Wsurprising: warn when "suspicious" code constructs are encountered. While technically legal these usually indicate that an error has been made. - # -Wc-binding-type: - # -Wintrinsics-std: only standard intrisics are available, e.g. "call flush(6)" will cause an error - # -Wno-tabs: do not allow tabs in source - # -Wintrinsic-shadow: warn if a user-defined procedure or module procedure has the same name as an intrinsic - # -Wline-truncation: - # -Wtarget-lifetime: - # -Wreal-q-constant: warn about real-literal-constants with 'q' exponent-letter - # -Wunused: a number of unused-xxx warnings - # and sets the general (non-Fortran options) options: - # -Waddress - # -Warray-bounds (only with -O2) - # -Wc++11-compat - # -Wchar-subscripts - # -Wcomment - # -Wformat - # -Wmaybe-uninitialized - # -Wnonnull - # -Wparentheses - # -Wpointer-sign - # -Wreorder - # -Wreturn-type - # -Wsequence-point - # -Wstrict-aliasing - # -Wstrict-overflow=1 - # -Wswitch - # -Wtrigraphs - # -Wuninitialized - # -Wunknown-pragmas - # -Wunused-function - # -Wunused-label - # -Wunused-value - # -Wunused-variable - # -Wvolatile-register-var +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wall") +# sets the following Fortran options: +# -Waliasing: warn about possible aliasing of dummy arguments. Specifically, it warns if the same actual argument is associated with a dummy argument with "INTENT(IN)" and a dummy argument with "INTENT(OUT)" in a call with an explicit interface. +# -Wampersand: checks if a character expression is continued proberly by an ampersand at the end of the line and at the beginning of the new line +# -Warray-bounds: checks if array reference is out of bounds at compile time. use -fcheck-bounds to also check during runtime +# -Wconversion: warn about implicit conversions between different type +# -Wsurprising: warn when "suspicious" code constructs are encountered. While technically legal these usually indicate that an error has been made. +# -Wc-binding-type: +# -Wintrinsics-std: only standard intrisics are available, e.g. "call flush(6)" will cause an error +# -Wno-tabs: do not allow tabs in source +# -Wintrinsic-shadow: warn if a user-defined procedure or module procedure has the same name as an intrinsic +# -Wline-truncation: +# -Wtarget-lifetime: +# -Wreal-q-constant: warn about real-literal-constants with 'q' exponent-letter +# -Wunused: a number of unused-xxx warnings +# and sets the general (non-Fortran options) options: +# -Waddress +# -Warray-bounds (only with -O2) +# -Wc++11-compat +# -Wchar-subscripts +# -Wcomment +# -Wformat +# -Wmaybe-uninitialized +# -Wnonnull +# -Wparentheses +# -Wpointer-sign +# -Wreorder +# -Wreturn-type +# -Wsequence-point +# -Wstrict-aliasing +# -Wstrict-overflow=1 +# -Wswitch +# -Wtrigraphs +# -Wuninitialized +# -Wunknown-pragmas +# -Wunused-function +# -Wunused-label +# -Wunused-value +# -Wunused-variable +# -Wvolatile-register-var - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wextra") - # sets the following Fortran options: - # -Wunuses-parameter: - # -Wcompare-reals: - # and sets the general (non-Fortran options) options: - # -Wclobbered - # -Wempty-body - # -Wignored-qualifiers - # -Wmissing-field-initializers - # -Woverride-init - # -Wsign-compare - # -Wtype-limits - # -Wuninitialized - # -Wunused-but-set-parameter (only with -Wunused or -Wall) - # -Wno-globals +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wextra") +# sets the following Fortran options: +# -Wunuses-parameter: +# -Wcompare-reals: +# and sets the general (non-Fortran options) options: +# -Wclobbered +# -Wempty-body +# -Wignored-qualifiers +# -Wmissing-field-initializers +# -Woverride-init +# -Wsign-compare +# -Wtype-limits +# -Wuninitialized +# -Wunused-but-set-parameter (only with -Wunused or -Wall) +# -Wno-globals - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wcharacter-truncation") - # warn if character expressions (strings) are truncated +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wcharacter-truncation") +# warn if character expressions (strings) are truncated - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wunderflow") - # produce a warning when numerical constant expressions are encountered, which yield an UNDERFLOW during compilation +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wunderflow") +# produce a warning when numerical constant expressions are encountered, which yield an UNDERFLOW during compilation - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wsuggest-attribute=pure") - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wsuggest-attribute=noreturn") - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion-extra") - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wimplicit-procedure") - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-unused-parameter") - set (COMPILE_FLAGS "${COMPILE_FLAGS} -ffpe-summary=all") - # print summary of floating point exeptions (invalid,zero,overflow,underflow,inexact,denormal) +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wsuggest-attribute=pure") +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wsuggest-attribute=noreturn") +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion-extra") +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wimplicit-procedure") +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-unused-parameter") +set (COMPILE_FLAGS "${COMPILE_FLAGS} -ffpe-summary=all") +# print summary of floating point exeptions (invalid,zero,overflow,underflow,inexact,denormal) - # Additional options - # -Warray-temporarieswarnings: because we have many temporary arrays (performance issue?): - # -Wimplicit-interface: no interfaces for lapack/MPI routines - # -Wunsafe-loop-optimizations: warn if the loop cannot be optimized due to nontrivial assumptions. +# Additional options +# -Warray-temporarieswarnings: because we have many temporary arrays (performance issue?): +# -Wimplicit-interface: no interfaces for lapack/MPI routines +# -Wunsafe-loop-optimizations: warn if the loop cannot be optimized due to nontrivial assumptions. #------------------------------------------------------------------------------------------------ # Runtime debugging - set (DEBUG_FLAGS "${DEBUG_FLAGS} -ffpe-trap=invalid,zero,overflow") - # stop execution if floating point exception is detected (NaN is silent) +set (DEBUG_FLAGS "${DEBUG_FLAGS} -ffpe-trap=invalid,zero,overflow") +# stop execution if floating point exception is detected (NaN is silent) - set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") - # Generate symbolic debugging information in the object file +set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") +# Generate symbolic debugging information in the object file - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fbacktrace") - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fdump-core") - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fcheck=all") - # checks for (array-temps,bounds,do,mem,pointer,recursion) +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fbacktrace") +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fdump-core") +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fcheck=all") +# checks for (array-temps,bounds,do,mem,pointer,recursion) - # Additional options - # -ffpe-trap=precision,denormal,underflow +# Additional options +# -ffpe-trap=precision,denormal,underflow #------------------------------------------------------------------------------------------------ # precision settings - set (PRECISION_FLAGS "${PRECISION_FLAGS} -fdefault-real-8") - # set precision to 8 bytes for standard real (=8 for pReal). Will set size of double to 16 bytes as long as -fdefault-double-8 is not set - set (PRECISION_FLAGS "${PRECISION_FLAGS} -fdefault-double-8") - # set precision to 8 bytes for double real, would be 16 bytes if -fdefault-real-8 is used +set (PRECISION_FLAGS "${PRECISION_FLAGS} -fdefault-real-8") +# set precision to 8 bytes for standard real (=8 for pReal). Will set size of double to 16 bytes as long as -fdefault-double-8 is not set +set (PRECISION_FLAGS "${PRECISION_FLAGS} -fdefault-double-8") +# set precision to 8 bytes for double real, would be 16 bytes if -fdefault-real-8 is used diff --git a/cmake/Compiler-Intel.cmake b/cmake/Compiler-Intel.cmake index 60ed46cbc..1a2c2c455 100644 --- a/cmake/Compiler-Intel.cmake +++ b/cmake/Compiler-Intel.cmake @@ -1,116 +1,116 @@ ################################################################################################### # Intel Compiler ################################################################################################### - if (OPENMP) - set (OPENMP_FLAGS "-qopenmp -parallel") - endif () +if (OPENMP) + set (OPENMP_FLAGS "-qopenmp -parallel") +endif () - if (OPTIMIZATION STREQUAL "OFF") - set (OPTIMIZATION_FLAGS "-O0 -no-ip") - elseif (OPTIMIZATION STREQUAL "DEFENSIVE") - set (OPTIMIZATION_FLAGS "-O2") - elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") - set (OPTIMIZATION_FLAGS "-ipo -O3 -no-prec-div -fp-model fast=2 -xHost") - # -fast = -ipo, -O3, -no-prec-div, -static, -fp-model fast=2, and -xHost" - endif () +if (OPTIMIZATION STREQUAL "OFF") + set (OPTIMIZATION_FLAGS "-O0 -no-ip") +elseif (OPTIMIZATION STREQUAL "DEFENSIVE") + set (OPTIMIZATION_FLAGS "-O2") +elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") + set (OPTIMIZATION_FLAGS "-ipo -O3 -no-prec-div -fp-model fast=2 -xHost") + # -fast = -ipo, -O3, -no-prec-div, -static, -fp-model fast=2, and -xHost" +endif () - # -assume std_mod_proc_name (included in -standard-semantics) causes problems if other modules - # (PETSc, HDF5) are not compiled with this option (https://software.intel.com/en-us/forums/intel-fortran-compiler-for-linux-and-mac-os-x/topic/62172) - set (STANDARD_CHECK "-stand f15 -standard-semantics -assume nostd_mod_proc_name") - set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel") - # Link against shared Intel libraries instead of static ones +# -assume std_mod_proc_name (included in -standard-semantics) causes problems if other modules +# (PETSc, HDF5) are not compiled with this option (https://software.intel.com/en-us/forums/intel-fortran-compiler-for-linux-and-mac-os-x/topic/62172) +set (STANDARD_CHECK "-stand f15 -standard-semantics -assume nostd_mod_proc_name") +set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel") +# Link against shared Intel libraries instead of static ones #------------------------------------------------------------------------------------------------ # Fine tuning compilation options - set (COMPILE_FLAGS "${COMPILE_FLAGS} -fpp") - # preprocessor +set (COMPILE_FLAGS "${COMPILE_FLAGS} -fpp") +# preprocessor - set (COMPILE_FLAGS "${COMPILE_FLAGS} -ftz") - # flush underflow to zero, automatically set if -O[1,2,3] +set (COMPILE_FLAGS "${COMPILE_FLAGS} -ftz") +# flush underflow to zero, automatically set if -O[1,2,3] - set (COMPILE_FLAGS "${COMPILE_FLAGS} -diag-disable") - # disables warnings ... - set (COMPILE_FLAGS "${COMPILE_FLAGS} 5268") - # ... the text exceeds right hand column allowed on the line (we have only comments there) - set (COMPILE_FLAGS "${COMPILE_FLAGS},7624") - # ... about deprecated forall (has nice syntax and most likely a performance advantage) +set (COMPILE_FLAGS "${COMPILE_FLAGS} -diag-disable") +# disables warnings ... +set (COMPILE_FLAGS "${COMPILE_FLAGS} 5268") +# ... the text exceeds right hand column allowed on the line (we have only comments there) +set (COMPILE_FLAGS "${COMPILE_FLAGS},7624") +# ... about deprecated forall (has nice syntax and most likely a performance advantage) - set (COMPILE_FLAGS "${COMPILE_FLAGS} -warn") - # enables warnings ... - set (COMPILE_FLAGS "${COMPILE_FLAGS} declarations") - # ... any undeclared names (alternative name: -implicitnone) - set (COMPILE_FLAGS "${COMPILE_FLAGS},general") - # ... warning messages and informational messages are issued by the compiler - set (COMPILE_FLAGS "${COMPILE_FLAGS},usage") - # ... questionable programming practices - set (COMPILE_FLAGS "${COMPILE_FLAGS},interfaces") - # ... checks the interfaces of all SUBROUTINEs called and FUNCTIONs invoked in your compilation against an external set of interface blocks - set (COMPILE_FLAGS "${COMPILE_FLAGS},ignore_loc") - # ... %LOC is stripped from an actual argument - set (COMPILE_FLAGS "${COMPILE_FLAGS},alignments") - # ... data that is not naturally aligned - set (COMPILE_FLAGS "${COMPILE_FLAGS},unused") - # ... declared variables that are never used +set (COMPILE_FLAGS "${COMPILE_FLAGS} -warn") +# enables warnings ... +set (COMPILE_FLAGS "${COMPILE_FLAGS} declarations") +# ... any undeclared names (alternative name: -implicitnone) +set (COMPILE_FLAGS "${COMPILE_FLAGS},general") +# ... warning messages and informational messages are issued by the compiler +set (COMPILE_FLAGS "${COMPILE_FLAGS},usage") +# ... questionable programming practices +set (COMPILE_FLAGS "${COMPILE_FLAGS},interfaces") +# ... checks the interfaces of all SUBROUTINEs called and FUNCTIONs invoked in your compilation against an external set of interface blocks +set (COMPILE_FLAGS "${COMPILE_FLAGS},ignore_loc") +# ... %LOC is stripped from an actual argument +set (COMPILE_FLAGS "${COMPILE_FLAGS},alignments") +# ... data that is not naturally aligned +set (COMPILE_FLAGS "${COMPILE_FLAGS},unused") +# ... declared variables that are never used - # Additional options - # -warn: enables warnings, where - # truncated_source: Determines whether warnings occur when source exceeds the maximum column width in fixed-format files. - # (too many warnings because we have comments beyond character 132) - # uncalled: Determines whether warnings occur when a statement function is never called - # all: - # -name as_is: case sensitive Fortran! +# Additional options +# -warn: enables warnings, where +# truncated_source: Determines whether warnings occur when source exceeds the maximum column width in fixed-format files. +# (too many warnings because we have comments beyond character 132) +# uncalled: Determines whether warnings occur when a statement function is never called +# all: +# -name as_is: case sensitive Fortran! #------------------------------------------------------------------------------------------------ # Runtime debugging - set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") - # Generate symbolic debugging information in the object file +set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") +# Generate symbolic debugging information in the object file - set (DEBUG_FLAGS "${DEBUG_FLAGS} -traceback") - # Generate extra information in the object file to provide source file traceback information when a severe error occurs at run time +set (DEBUG_FLAGS "${DEBUG_FLAGS} -traceback") +# Generate extra information in the object file to provide source file traceback information when a severe error occurs at run time - set (DEBUG_FLAGS "${DEBUG_FLAGS} -gen-interfaces") - # Generate an interface block for each routine. http://software.intel.com/en-us/blogs/2012/01/05/doctor-fortran-gets-explicit-again/ +set (DEBUG_FLAGS "${DEBUG_FLAGS} -gen-interfaces") +# Generate an interface block for each routine. http://software.intel.com/en-us/blogs/2012/01/05/doctor-fortran-gets-explicit-again/ - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fp-stack-check") - # Generate extra code after every function call to ensure that the floating-point (FP) stack is in the expected state +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fp-stack-check") +# Generate extra code after every function call to ensure that the floating-point (FP) stack is in the expected state - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fp-model strict") - # Trap uninitalized variables +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fp-model strict") +# Trap uninitalized variables - set (DEBUG_FLAGS "${DEBUG_FLAGS} -check" ) - # Checks at runtime ... - set (DEBUG_FLAGS "${DEBUG_FLAGS} bounds") - # ... if an array index is too small (<1) or too large! - set (DEBUG_FLAGS "${DEBUG_FLAGS},format") - # ... for the data type of an item being formatted for output. - set (DEBUG_FLAGS "${DEBUG_FLAGS},output_conversion") - # ... for the fit of data items within a designated format descriptor field. - set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers") - # ... for certain disassociated or uninitialized pointers or unallocated allocatable objects. - set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit") - # ... for uninitialized variables. - set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv") - # ... initializes stack local variables to an unusual value to aid error detection - set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0") - # ... capture all floating-point exceptions, sets -ftz automatically +set (DEBUG_FLAGS "${DEBUG_FLAGS} -check" ) +# Checks at runtime ... +set (DEBUG_FLAGS "${DEBUG_FLAGS} bounds") +# ... if an array index is too small (<1) or too large! +set (DEBUG_FLAGS "${DEBUG_FLAGS},format") +# ... for the data type of an item being formatted for output. +set (DEBUG_FLAGS "${DEBUG_FLAGS},output_conversion") +# ... for the fit of data items within a designated format descriptor field. +set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers") +# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects. +set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit") +# ... for uninitialized variables. +set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv") +# ... initializes stack local variables to an unusual value to aid error detection +set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0") +# ... capture all floating-point exceptions, sets -ftz automatically - set (DEBUG_FLAGS "${DEBUG_FLAGS} -warn") - # enables warnings ... - set (DEBUG_FLAGS "${DEBUG_FLAGS} errors") - # ... warnings are changed to errors - set (DEBUG_FLAGS "${DEBUG_FLAGS},stderrors") - # ... warnings about Fortran standard violations are changed to errors +set (DEBUG_FLAGS "${DEBUG_FLAGS} -warn") +# enables warnings ... +set (DEBUG_FLAGS "${DEBUG_FLAGS} errors") +# ... warnings are changed to errors +set (DEBUG_FLAGS "${DEBUG_FLAGS},stderrors") +# ... warnings about Fortran standard violations are changed to errors - set (DEBUG_FLAGS "${DEBUG_FLAGS} -debug-parameters all") - # generate debug information for parameters +set (DEBUG_FLAGS "${DEBUG_FLAGS} -debug-parameters all") +# generate debug information for parameters - # Additional options - # -heap-arrays: Should not be done for OpenMP, but set "ulimit -s unlimited" on shell. Probably it helps also to unlimit other limits - # -check: Checks at runtime, where - # arg_temp_created: will cause a lot of warnings because we create a bunch of temporary arrays (performance?) - # stack: +# Additional options +# -heap-arrays: Should not be done for OpenMP, but set "ulimit -s unlimited" on shell. Probably it helps also to unlimit other limits +# -check: Checks at runtime, where +# arg_temp_created: will cause a lot of warnings because we create a bunch of temporary arrays (performance?) +# stack: #------------------------------------------------------------------------------------------------ # precision settings - set (PRECISION_FLAGS "${PRECISION_FLAGS} -real-size 64") - # set precision for standard real to 32 | 64 | 128 (= 4 | 8 | 16 bytes, type pReal is always 8 bytes) +set (PRECISION_FLAGS "${PRECISION_FLAGS} -real-size 64") +# set precision for standard real to 32 | 64 | 128 (= 4 | 8 | 16 bytes, type pReal is always 8 bytes) diff --git a/cmake/Compiler-PGI.cmake b/cmake/Compiler-PGI.cmake index c18679417..39d9b092f 100644 --- a/cmake/Compiler-PGI.cmake +++ b/cmake/Compiler-PGI.cmake @@ -2,23 +2,23 @@ # PGI Compiler ################################################################################################### - if (OPTIMIZATION STREQUAL "OFF") - set (OPTIMIZATION_FLAGS "-O0" ) - elseif (OPTIMIZATION STREQUAL "DEFENSIVE") - set (OPTIMIZATION_FLAGS "-O2") - elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") - set (OPTIMIZATION_FLAGS "-O3") - endif () +if (OPTIMIZATION STREQUAL "OFF") + set (OPTIMIZATION_FLAGS "-O0" ) +elseif (OPTIMIZATION STREQUAL "DEFENSIVE") + set (OPTIMIZATION_FLAGS "-O2") +elseif (OPTIMIZATION STREQUAL "AGGRESSIVE") + set (OPTIMIZATION_FLAGS "-O3") +endif () #------------------------------------------------------------------------------------------------ # Fine tuning compilation options - set (COMPILE_FLAGS "${COMPILE_FLAGS} -Mpreprocess") - # preprocessor +set (COMPILE_FLAGS "${COMPILE_FLAGS} -Mpreprocess") +# preprocessor - set (STANDARD_CHECK "-Mallocatable=03") +set (STANDARD_CHECK "-Mallocatable=03") #------------------------------------------------------------------------------------------------ # Runtime debugging - set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") - # Includes debugging information in the object module; sets the optimization level to zero unless a -⁠O option is present on the command line +set (DEBUG_FLAGS "${DEBUG_FLAGS} -g") +# Includes debugging information in the object module; sets the optimization level to zero unless a -⁠O option is present on the command line From 6425c37f2d8e4f24078819f18a2a3287866e8fc0 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:07:37 +0100 Subject: [PATCH 105/148] use default string length 256 characters are more than enough for string values and keys --- src/list.f90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/list.f90 b/src/list.f90 index 79eafc964..689227545 100644 --- a/src/list.f90 +++ b/src/list.f90 @@ -261,7 +261,7 @@ end function getInt !! error unless default is given. If raw is true, the the complete string is returned, otherwise !! the individual chunks are returned !-------------------------------------------------------------------------------------------------- -character(len=65536) function getString(this,key,defaultVal,raw) +character(len=pStringLen) function getString(this,key,defaultVal,raw) class(tPartitionedStringList), target, intent(in) :: this character(len=*), intent(in) :: key @@ -400,13 +400,13 @@ end function getInts !-------------------------------------------------------------------------------------------------- function getStrings(this,key,defaultVal,raw) - character(len=65536),dimension(:), allocatable :: getStrings + character(len=pStringLen),dimension(:), allocatable :: getStrings class(tPartitionedStringList),target, intent(in) :: this character(len=*), intent(in) :: key character(len=*), dimension(:), intent(in), optional :: defaultVal logical, intent(in), optional :: raw type(tPartitionedStringList), pointer :: item - character(len=65536) :: str + character(len=pStringLen) :: str integer :: i logical :: found, & whole, & From af6973adf628cca3f1396c2a11a4f39af837b111 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:10:23 +0100 Subject: [PATCH 106/148] HDF5 out is always on --- src/damage_local.f90 | 2 -- src/damage_nonlocal.f90 | 2 -- src/geometry_plastic_nonlocal.f90 | 2 -- src/plastic_disloUCLA.f90 | 6 ------ src/plastic_dislotwin.f90 | 6 ------ src/plastic_isotropic.f90 | 5 ----- src/plastic_kinematichardening.f90 | 5 ----- src/plastic_nonlocal.f90 | 8 +------- src/plastic_phenopowerlaw.f90 | 6 ------ src/thermal_adiabatic.f90 | 2 -- src/thermal_conduction.f90 | 5 ++--- 11 files changed, 3 insertions(+), 46 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index aa9292f49..fdb6c2206 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -213,7 +213,6 @@ subroutine damage_local_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) integer :: o, instance instance = damage_typeInstance(homog) @@ -228,7 +227,6 @@ subroutine damage_local_results(homog,group) end select enddo outputsLoop end associate -#endif end subroutine damage_local_results diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index 855fa0ea5..bd5a0b2e6 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -249,7 +249,6 @@ subroutine damage_nonlocal_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) integer :: o, instance instance = damage_typeInstance(homog) @@ -264,7 +263,6 @@ subroutine damage_nonlocal_results(homog,group) end select enddo outputsLoop end associate -#endif end subroutine damage_nonlocal_results diff --git a/src/geometry_plastic_nonlocal.f90 b/src/geometry_plastic_nonlocal.f90 index 408306b2b..b69ab2eff 100644 --- a/src/geometry_plastic_nonlocal.f90 +++ b/src/geometry_plastic_nonlocal.f90 @@ -122,7 +122,6 @@ subroutine geometry_plastic_nonlocal_results integer, dimension(:), allocatable :: shp -#if defined(PETSc) || defined(DAMASK_HDF5) call results_openJobFile writeVolume: block @@ -151,7 +150,6 @@ subroutine geometry_plastic_nonlocal_results call results_closeJobFile -#endif end subroutine geometry_plastic_nonlocal_results diff --git a/src/plastic_disloUCLA.f90 b/src/plastic_disloUCLA.f90 index 29ef0fcc9..e64c7ae58 100644 --- a/src/plastic_disloUCLA.f90 +++ b/src/plastic_disloUCLA.f90 @@ -463,7 +463,6 @@ end subroutine plastic_disloUCLA_dependentState !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_disloUCLA_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*), intent(in) :: group @@ -491,11 +490,6 @@ subroutine plastic_disloUCLA_results(instance,group) end select enddo outputsLoop end associate - -#else - integer, intent(in) :: instance - character(len=*), intent(in) :: group -#endif end subroutine plastic_disloUCLA_results diff --git a/src/plastic_dislotwin.f90 b/src/plastic_dislotwin.f90 index 1f731a891..454ed42b1 100644 --- a/src/plastic_dislotwin.f90 +++ b/src/plastic_dislotwin.f90 @@ -926,7 +926,6 @@ end subroutine plastic_dislotwin_dependentState !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_dislotwin_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*) :: group @@ -969,11 +968,6 @@ subroutine plastic_dislotwin_results(instance,group) end select enddo outputsLoop end associate - -#else - integer, intent(in) :: instance - character(len=*) :: group -#endif end subroutine plastic_dislotwin_results diff --git a/src/plastic_isotropic.f90 b/src/plastic_isotropic.f90 index 9beb2262b..b86fff04a 100644 --- a/src/plastic_isotropic.f90 +++ b/src/plastic_isotropic.f90 @@ -373,7 +373,6 @@ end subroutine plastic_isotropic_dotState !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_isotropic_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*), intent(in) :: group @@ -388,10 +387,6 @@ subroutine plastic_isotropic_results(instance,group) end select enddo outputsLoop end associate -#else - integer, intent(in) :: instance - character(len=*) :: group -#endif end subroutine plastic_isotropic_results diff --git a/src/plastic_kinematichardening.f90 b/src/plastic_kinematichardening.f90 index 2a3dc4640..569051602 100644 --- a/src/plastic_kinematichardening.f90 +++ b/src/plastic_kinematichardening.f90 @@ -437,7 +437,6 @@ end subroutine plastic_kinehardening_deltaState !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_kinehardening_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*) :: group @@ -470,10 +469,6 @@ subroutine plastic_kinehardening_results(instance,group) end select enddo outputsLoop end associate -#else - integer, intent(in) :: instance - character(len=*) :: group -#endif end subroutine plastic_kinehardening_results diff --git a/src/plastic_nonlocal.f90 b/src/plastic_nonlocal.f90 index 5375aba49..f65fb2193 100644 --- a/src/plastic_nonlocal.f90 +++ b/src/plastic_nonlocal.f90 @@ -12,6 +12,7 @@ module plastic_nonlocal use material use lattice use rotations + use results use config use lattice use discretization @@ -1974,9 +1975,6 @@ end function getRho !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_nonlocal_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) - use results, only: & - results_writeDataset integer, intent(in) :: instance character(len=*) :: group @@ -2039,10 +2037,6 @@ subroutine plastic_nonlocal_results(instance,group) end select enddo outputsLoop end associate -#else - integer, intent(in) :: instance - character(len=*) :: group -#endif end subroutine plastic_nonlocal_results diff --git a/src/plastic_phenopowerlaw.f90 b/src/plastic_phenopowerlaw.f90 index b8f5c8306..ef5bd36ef 100644 --- a/src/plastic_phenopowerlaw.f90 +++ b/src/plastic_phenopowerlaw.f90 @@ -464,7 +464,6 @@ end subroutine plastic_phenopowerlaw_dotState !> @brief writes results to HDF5 output file !-------------------------------------------------------------------------------------------------- subroutine plastic_phenopowerlaw_results(instance,group) -#if defined(PETSc) || defined(DAMASK_HDF5) integer, intent(in) :: instance character(len=*), intent(in) :: group @@ -492,11 +491,6 @@ subroutine plastic_phenopowerlaw_results(instance,group) end select enddo outputsLoop end associate - -#else - integer, intent(in) :: instance - character(len=*), intent(in) :: group -#endif end subroutine plastic_phenopowerlaw_results diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 36dd2316b..7f2007195 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -254,7 +254,6 @@ subroutine thermal_adiabatic_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) integer :: o, instance instance = thermal_typeInstance(homog) @@ -267,7 +266,6 @@ subroutine thermal_adiabatic_results(homog,group) 'temperature','K') end select enddo outputsLoop -#endif end subroutine thermal_adiabatic_results diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index ed25fccde..153ca12eb 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -209,7 +209,8 @@ function thermal_conduction_getSpecificHeat(ip,el) thermal_conduction_getSpecificHeat/real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) end function thermal_conduction_getSpecificHeat - + + !-------------------------------------------------------------------------------------------------- !> @brief returns homogenized mass density !-------------------------------------------------------------------------------------------------- @@ -267,7 +268,6 @@ subroutine thermal_conduction_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group -#if defined(PETSc) || defined(DAMASK_HDF5) integer :: o, instance instance = thermal_typeInstance(homog) @@ -280,7 +280,6 @@ subroutine thermal_conduction_results(homog,group) 'temperature','K') end select enddo outputsLoop -#endif end subroutine thermal_conduction_results From 4dc5dac831a02bcc39481d3ebd26af376a3e1216 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:20:50 +0100 Subject: [PATCH 107/148] was only needed for postResults --- src/damage_local.f90 | 14 ++------------ src/damage_nonlocal.f90 | 16 ++-------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index fdb6c2206..93ec5ec7f 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -15,11 +15,6 @@ module damage_local implicit none private - - character(len=64), dimension(:,:), allocatable, target, public :: & - damage_local_output - integer, dimension(:), allocatable, target, public :: & - damage_local_Noutput enum, bind(c) enumerator :: & @@ -60,15 +55,12 @@ subroutine damage_local_init character(len=65536), dimension(:), allocatable :: & outputs - write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_local_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_local_label//' init -+>>>'; flush(6) maxNinstance = count(damage_type == DAMAGE_local_ID) if (maxNinstance == 0) return - allocate(damage_local_output (maxval(homogenization_Noutput),maxNinstance)) - damage_local_output = '' allocate(damage_local_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) - allocate(damage_local_Noutput (maxNinstance), source=0) allocate(param(maxNinstance)) @@ -86,9 +78,7 @@ subroutine damage_local_init select case(outputs(i)) case ('damage') - damage_local_output(i,damage_typeInstance(h)) = outputs(i) - damage_local_Noutput(instance) = damage_local_Noutput(instance) + 1 - prm%outputID = [prm%outputID , damage_ID] + prm%outputID = [prm%outputID , damage_ID] end select enddo diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index bd5a0b2e6..8365cbca5 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -18,11 +18,6 @@ module damage_nonlocal implicit none private - - character(len=64), dimension(:,:), allocatable, target, public :: & - damage_nonlocal_output - integer, dimension(:), allocatable, target, public :: & - damage_nonlocal_Noutput enum, bind(c) enumerator :: & @@ -63,14 +58,10 @@ subroutine damage_nonlocal_init character(len=65536), dimension(:), allocatable :: & outputs - write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_nonlocal_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_nonlocal_label//' init -+>>>'; flush(6) maxNinstance = count(damage_type == DAMAGE_nonlocal_ID) if (maxNinstance == 0) return - - allocate(damage_nonlocal_output (maxval(homogenization_Noutput),maxNinstance)) - damage_nonlocal_output = '' - allocate(damage_nonlocal_Noutput (maxNinstance), source=0) allocate(param(maxNinstance)) @@ -86,11 +77,8 @@ subroutine damage_nonlocal_init do i=1, size(outputs) outputID = undefined_ID select case(outputs(i)) - case ('damage') - damage_nonlocal_output(i,damage_typeInstance(h)) = outputs(i) - damage_nonlocal_Noutput(instance) = damage_nonlocal_Noutput(instance) + 1 - prm%outputID = [prm%outputID , damage_ID] + prm%outputID = [prm%outputID , damage_ID] end select enddo From 91ad5092f47d20b2d133ea5c6462bcd640a44003 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:31:19 +0100 Subject: [PATCH 108/148] preparing removal of old output data --- src/thermal_adiabatic.f90 | 19 +++++++++++++++++-- src/thermal_conduction.f90 | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 7f2007195..8d1af8fd0 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -26,6 +26,15 @@ module thermal_adiabatic enumerator :: undefined_ID, & temperature_ID end enum + + type :: tParameters + integer(kind(undefined_ID)), dimension(:), allocatable :: & + outputID + end type tParameters + + type(tparameters), dimension(:), allocatable :: & + param + integer(kind(undefined_ID)), dimension(:,:), allocatable :: & thermal_adiabatic_outputID !< ID of each post result output @@ -51,11 +60,13 @@ subroutine thermal_adiabatic_init character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs - write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_ADIABATIC_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_ADIABATIC_label//' init -+>>>'; flush(6) maxNinstance = count(thermal_type == THERMAL_adiabatic_ID) if (maxNinstance == 0) return + allocate(param(maxNinstance)) + allocate(thermal_adiabatic_output (maxval(homogenization_Noutput),maxNinstance)) thermal_adiabatic_output = '' allocate(thermal_adiabatic_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) @@ -64,6 +75,9 @@ subroutine thermal_adiabatic_init initializeInstances: do section = 1, size(thermal_type) if (thermal_type(section) /= THERMAL_adiabatic_ID) cycle + associate(prm => param(thermal_typeInstance(section)), & + config => config_homogenization(section)) + NofMyHomog=count(material_homogenizationAt==section) instance = thermal_typeInstance(section) outputs = config_homogenization(section)%getStrings('(output)',defaultVal=emptyStringArray) @@ -89,7 +103,8 @@ subroutine thermal_adiabatic_init temperature(section)%p => thermalState(section)%state(1,:) deallocate(temperatureRate(section)%p) allocate (temperatureRate(section)%p(NofMyHomog), source=0.0_pReal) - + + end associate enddo initializeInstances end subroutine thermal_adiabatic_init diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 153ca12eb..095a9a85c 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -25,6 +25,15 @@ module thermal_conduction enumerator :: undefined_ID, & temperature_ID end enum + + type :: tParameters + integer(kind(undefined_ID)), dimension(:), allocatable :: & + outputID + end type tParameters + + type(tparameters), dimension(:), allocatable :: & + param + integer(kind(undefined_ID)), dimension(:,:), allocatable, private :: & thermal_conduction_outputID !< ID of each post result output @@ -54,11 +63,13 @@ subroutine thermal_conduction_init character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs - write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_CONDUCTION_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_CONDUCTION_label//' init -+>>>'; flush(6) maxNinstance = count(thermal_type == THERMAL_conduction_ID) if (maxNinstance == 0) return + allocate(param(maxNinstance)) + allocate(thermal_conduction_output (maxval(homogenization_Noutput),maxNinstance)) thermal_conduction_output = '' allocate(thermal_conduction_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) @@ -67,6 +78,9 @@ subroutine thermal_conduction_init initializeInstances: do section = 1, size(thermal_type) if (thermal_type(section) /= THERMAL_conduction_ID) cycle + associate(prm => param(thermal_typeInstance(section)), & + config => config_homogenization(section)) + NofMyHomog=count(material_homogenizationAt==section) instance = thermal_typeInstance(section) outputs = config_homogenization(section)%getStrings('(output)',defaultVal=emptyStringArray) @@ -93,7 +107,8 @@ subroutine thermal_conduction_init allocate (temperature (section)%p(NofMyHomog), source=thermal_initialT(section)) deallocate(temperatureRate(section)%p) allocate (temperatureRate(section)%p(NofMyHomog), source=0.0_pReal) - + + end associate enddo initializeInstances end subroutine thermal_conduction_init From 7baf4e7f53ca8906caaf08ccd2efd227a2a610b9 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:36:42 +0100 Subject: [PATCH 109/148] cleaning --- src/damage_local.f90 | 8 +++----- src/damage_nonlocal.f90 | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index 93ec5ec7f..b96ea8ca8 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -46,7 +46,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_local_init - integer :: maxNinstance,homog,instance,i + integer :: maxNinstance,homog,i integer :: sizeState integer :: NofMyHomog, h integer(kind(undefined_ID)) :: & @@ -87,7 +87,6 @@ subroutine damage_local_init homog = h NofMyHomog = count(material_homogenizationAt == homog) - instance = damage_typeInstance(homog) ! allocate state arrays @@ -203,10 +202,9 @@ subroutine damage_local_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group - integer :: o, instance + integer :: o - instance = damage_typeInstance(homog) - associate(prm => param(instance)) + associate(prm => param(damage_typeInstance(homog))) outputsLoop: do o = 1,size(prm%outputID) select case(prm%outputID(o)) diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index 8365cbca5..db9242235 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -49,7 +49,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_nonlocal_init - integer :: maxNinstance,homog,instance,i + integer :: maxNinstance,homog,i integer :: sizeState integer :: NofMyHomog, h integer(kind(undefined_ID)) :: & @@ -70,7 +70,6 @@ subroutine damage_nonlocal_init associate(prm => param(damage_typeInstance(h)), & config => config_homogenization(h)) - instance = damage_typeInstance(h) outputs = config%getStrings('(output)',defaultVal=emptyStringArray) allocate(prm%outputID(0)) @@ -86,7 +85,6 @@ subroutine damage_nonlocal_init homog = h NofMyHomog = count(material_homogenizationAt == homog) - instance = damage_typeInstance(homog) ! allocate state arrays @@ -237,10 +235,9 @@ subroutine damage_nonlocal_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group - integer :: o, instance + integer :: o - instance = damage_typeInstance(homog) - associate(prm => param(instance)) + associate(prm => param(damage_typeInstance(homog))) outputsLoop: do o = 1,size(prm%outputID) select case(prm%outputID(o)) From 6678770c43ac10b29611e464d2dccd999a8aa7ad Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:43:36 +0100 Subject: [PATCH 110/148] public variables not needed anymore --- src/thermal_adiabatic.f90 | 34 +++++++++------------------------- src/thermal_conduction.f90 | 36 ++++++++++-------------------------- 2 files changed, 19 insertions(+), 51 deletions(-) diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 8d1af8fd0..a32d6941d 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -15,13 +15,7 @@ module thermal_adiabatic implicit none private - - character(len=64), dimension(:,:), allocatable, target, public :: & - thermal_adiabatic_output !< name of each post result output - - integer, dimension(:), allocatable, target, public :: & - thermal_adiabatic_Noutput !< number of outputs per instance of this thermal model - + enum, bind(c) enumerator :: undefined_ID, & temperature_ID @@ -34,10 +28,6 @@ module thermal_adiabatic type(tparameters), dimension(:), allocatable :: & param - - integer(kind(undefined_ID)), dimension(:,:), allocatable :: & - thermal_adiabatic_outputID !< ID of each post result output - public :: & thermal_adiabatic_init, & @@ -67,12 +57,6 @@ subroutine thermal_adiabatic_init allocate(param(maxNinstance)) - allocate(thermal_adiabatic_output (maxval(homogenization_Noutput),maxNinstance)) - thermal_adiabatic_output = '' - allocate(thermal_adiabatic_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) - allocate(thermal_adiabatic_Noutput (maxNinstance), source=0) - - initializeInstances: do section = 1, size(thermal_type) if (thermal_type(section) /= THERMAL_adiabatic_ID) cycle associate(prm => param(thermal_typeInstance(section)), & @@ -80,13 +64,12 @@ subroutine thermal_adiabatic_init NofMyHomog=count(material_homogenizationAt==section) instance = thermal_typeInstance(section) - outputs = config_homogenization(section)%getStrings('(output)',defaultVal=emptyStringArray) + outputs = config%getStrings('(output)',defaultVal=emptyStringArray) + allocate(prm%outputID(0)) do i=1, size(outputs) select case(outputs(i)) case('temperature') - thermal_adiabatic_Noutput(instance) = thermal_adiabatic_Noutput(instance) + 1 - thermal_adiabatic_outputID(thermal_adiabatic_Noutput(instance),instance) = temperature_ID - thermal_adiabatic_output(thermal_adiabatic_Noutput(instance),instance) = outputs(i) + prm%outputID = [prm%outputID, temperature_ID] end select enddo @@ -269,18 +252,19 @@ subroutine thermal_adiabatic_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group - integer :: o, instance + integer :: o - instance = thermal_typeInstance(homog) + associate(prm => param(damage_typeInstance(homog))) - outputsLoop: do o = 1,thermal_adiabatic_Noutput(instance) - select case(thermal_adiabatic_outputID(o,instance)) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) case (temperature_ID) call results_writeDataset(group,temperature(homog)%p,'T',& 'temperature','K') end select enddo outputsLoop + end associate end subroutine thermal_adiabatic_results diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 095a9a85c..57703c325 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -14,13 +14,7 @@ module thermal_conduction implicit none private - - character(len=64), dimension(:,:), allocatable, target, public :: & - thermal_conduction_output !< name of each post result output - - integer, dimension(:), allocatable, target, public :: & - thermal_conduction_Noutput !< number of outputs per instance of this damage - + enum, bind(c) enumerator :: undefined_ID, & temperature_ID @@ -32,11 +26,7 @@ module thermal_conduction end type tParameters type(tparameters), dimension(:), allocatable :: & - param - - integer(kind(undefined_ID)), dimension(:,:), allocatable, private :: & - thermal_conduction_outputID !< ID of each post result output - + param public :: & thermal_conduction_init, & @@ -70,12 +60,6 @@ subroutine thermal_conduction_init allocate(param(maxNinstance)) - allocate(thermal_conduction_output (maxval(homogenization_Noutput),maxNinstance)) - thermal_conduction_output = '' - allocate(thermal_conduction_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) - allocate(thermal_conduction_Noutput (maxNinstance), source=0) - - initializeInstances: do section = 1, size(thermal_type) if (thermal_type(section) /= THERMAL_conduction_ID) cycle associate(prm => param(thermal_typeInstance(section)), & @@ -83,13 +67,12 @@ subroutine thermal_conduction_init NofMyHomog=count(material_homogenizationAt==section) instance = thermal_typeInstance(section) - outputs = config_homogenization(section)%getStrings('(output)',defaultVal=emptyStringArray) + outputs = config%getStrings('(output)',defaultVal=emptyStringArray) + allocate(prm%outputID(0)) do i=1, size(outputs) select case(outputs(i)) case('temperature') - thermal_conduction_Noutput(instance) = thermal_conduction_Noutput(instance) + 1 - thermal_conduction_outputID(thermal_conduction_Noutput(instance),instance) = temperature_ID - thermal_conduction_output(thermal_conduction_Noutput(instance),instance) = outputs(i) + prm%outputID = [prm%outputID, temperature_ID] end select enddo @@ -283,18 +266,19 @@ subroutine thermal_conduction_results(homog,group) integer, intent(in) :: homog character(len=*), intent(in) :: group - integer :: o, instance + integer :: o - instance = thermal_typeInstance(homog) + associate(prm => param(damage_typeInstance(homog))) - outputsLoop: do o = 1,thermal_conduction_Noutput(instance) - select case(thermal_conduction_outputID(o,instance)) + outputsLoop: do o = 1,size(prm%outputID) + select case(prm%outputID(o)) case (temperature_ID) call results_writeDataset(group,temperature(homog)%p,'T',& 'temperature','K') end select enddo outputsLoop + end associate end subroutine thermal_conduction_results From ac182ef536fa834071c9bef720ea17932fbb1333 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 10:55:11 +0100 Subject: [PATCH 111/148] less variable, same style --- src/damage_local.f90 | 60 ++++++++++++-------------------------- src/damage_none.f90 | 29 +++++++++--------- src/damage_nonlocal.f90 | 56 +++++++++++++---------------------- src/thermal_adiabatic.f90 | 47 ++++++++++++++--------------- src/thermal_conduction.f90 | 55 ++++++++++++++++------------------ src/thermal_isothermal.f90 | 33 ++++++++++----------- 6 files changed, 115 insertions(+), 165 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index b96ea8ca8..34aef6c9d 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -5,8 +5,8 @@ module damage_local use prec use material - use numerics use config + use numerics use source_damage_isoBrittle use source_damage_isoDuctile use source_damage_anisoBrittle @@ -22,9 +22,6 @@ module damage_local damage_ID end enum - integer(kind(undefined_ID)), dimension(:,:), allocatable :: & - damage_local_outputID !< ID of each post result output - type :: tParameters integer(kind(undefined_ID)), dimension(:), allocatable :: & outputID @@ -46,61 +43,42 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_local_init - integer :: maxNinstance,homog,i - integer :: sizeState - integer :: NofMyHomog, h - integer(kind(undefined_ID)) :: & - outputID + integer :: maxNinstance,o,NofMyHomog,h character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - character(len=65536), dimension(:), allocatable :: & - outputs + character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_local_label//' init -+>>>'; flush(6) maxNinstance = count(damage_type == DAMAGE_local_ID) if (maxNinstance == 0) return - allocate(damage_local_outputID (maxval(homogenization_Noutput),maxNinstance),source=undefined_ID) - allocate(param(maxNinstance)) do h = 1, size(damage_type) if (damage_type(h) /= DAMAGE_LOCAL_ID) cycle - associate(prm => param(damage_typeInstance(h)), & - config => config_homogenization(h)) + associate(prm => param(damage_typeInstance(h)),config => config_homogenization(h)) - outputs = config%getStrings('(output)',defaultVal=emptyStringArray) allocate(prm%outputID(0)) - do i=1, size(outputs) - outputID = undefined_ID - select case(outputs(i)) - - case ('damage') - prm%outputID = [prm%outputID , damage_ID] - end select - + do o=1, size(outputs) + select case(outputs(o)) + case ('damage') + prm%outputID = [prm%outputID , damage_ID] + end select enddo - - homog = h - - NofMyHomog = count(material_homogenizationAt == homog) - - -! allocate state arrays - sizeState = 1 - damageState(homog)%sizeState = sizeState - allocate(damageState(homog)%state0 (sizeState,NofMyHomog), source=damage_initialPhi(homog)) - allocate(damageState(homog)%subState0(sizeState,NofMyHomog), source=damage_initialPhi(homog)) - allocate(damageState(homog)%state (sizeState,NofMyHomog), source=damage_initialPhi(homog)) + NofMyHomog = count(material_homogenizationAt == h) + damageState(h)%sizeState = 1 + allocate(damageState(h)%state0 (1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%subState0(1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%state (1,NofMyHomog), source=damage_initialPhi(h)) - nullify(damageMapping(homog)%p) - damageMapping(homog)%p => mappingHomogenization(1,:,:) - deallocate(damage(homog)%p) - damage(homog)%p => damageState(homog)%state(1,:) - + nullify(damageMapping(h)%p) + damageMapping(h)%p => mappingHomogenization(1,:,:) + deallocate(damage(h)%p) + damage(h)%p => damageState(h)%state(1,:) + end associate enddo diff --git a/src/damage_none.f90 b/src/damage_none.f90 index 62d2cc0eb..d3b1b73c5 100644 --- a/src/damage_none.f90 +++ b/src/damage_none.f90 @@ -19,26 +19,23 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_none_init - integer :: & - homog, & - NofMyHomog + integer :: h,NofMyHomog - write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_NONE_LABEL//' init -+>>>' + write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_NONE_LABEL//' init -+>>>'; flush(6) - initializeInstances: do homog = 1, size(config_homogenization) + do h = 1, size(config_homogenization) + if (damage_type(h) /= DAMAGE_NONE_ID) cycle + + NofMyHomog = count(material_homogenizationAt == h) + damageState(h)%sizeState = 0 + allocate(damageState(h)%state0 (0,NofMyHomog)) + allocate(damageState(h)%subState0(0,NofMyHomog)) + allocate(damageState(h)%state (0,NofMyHomog)) - myhomog: if (damage_type(homog) == DAMAGE_NONE_ID) then - NofMyHomog = count(material_homogenizationAt == homog) - damageState(homog)%sizeState = 0 - allocate(damageState(homog)%state0 (0,NofMyHomog)) - allocate(damageState(homog)%subState0(0,NofMyHomog)) - allocate(damageState(homog)%state (0,NofMyHomog)) + deallocate(damage(h)%p) + allocate (damage(h)%p(1), source=damage_initialPhi(h)) - deallocate(damage(homog)%p) - allocate (damage(homog)%p(1), source=damage_initialPhi(homog)) - - endif myhomog - enddo initializeInstances + enddo end subroutine damage_none_init diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index db9242235..18a456f34 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -1,13 +1,12 @@ !-------------------------------------------------------------------------------------------------- !> @author Pratheek Shanthraj, Max-Planck-Institut für Eisenforschung GmbH !> @brief material subroutine for non-locally evolving damage field -!> @details to be done !-------------------------------------------------------------------------------------------------- module damage_nonlocal use prec use material - use numerics use config + use numerics use crystallite use lattice use source_damage_isoBrittle @@ -49,14 +48,9 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_nonlocal_init - integer :: maxNinstance,homog,i - integer :: sizeState - integer :: NofMyHomog, h - integer(kind(undefined_ID)) :: & - outputID - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - character(len=65536), dimension(:), allocatable :: & - outputs + integer :: maxNinstance,o,NofMyHomog,h + character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] + character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_nonlocal_label//' init -+>>>'; flush(6) @@ -67,40 +61,32 @@ subroutine damage_nonlocal_init do h = 1, size(damage_type) if (damage_type(h) /= DAMAGE_NONLOCAL_ID) cycle - associate(prm => param(damage_typeInstance(h)), & - config => config_homogenization(h)) + associate(prm => param(damage_typeInstance(h)),config => config_homogenization(h)) outputs = config%getStrings('(output)',defaultVal=emptyStringArray) allocate(prm%outputID(0)) - do i=1, size(outputs) - outputID = undefined_ID - select case(outputs(i)) - case ('damage') - prm%outputID = [prm%outputID , damage_ID] - end select - + do o=1, size(outputs) + select case(outputs(o)) + case ('damage') + prm%outputID = [prm%outputID, damage_ID] + end select enddo - homog = h + NofMyHomog = count(material_homogenizationAt == h) + damageState(h)%sizeState = 1 + allocate(damageState(h)%state0 (1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%subState0(1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%state (1,NofMyHomog), source=damage_initialPhi(h)) - NofMyHomog = count(material_homogenizationAt == homog) - - -! allocate state arrays - sizeState = 1 - damageState(homog)%sizeState = sizeState - allocate(damageState(homog)%state0 (sizeState,NofMyHomog), source=damage_initialPhi(homog)) - allocate(damageState(homog)%subState0(sizeState,NofMyHomog), source=damage_initialPhi(homog)) - allocate(damageState(homog)%state (sizeState,NofMyHomog), source=damage_initialPhi(homog)) - - nullify(damageMapping(homog)%p) - damageMapping(homog)%p => mappingHomogenization(1,:,:) - deallocate(damage(homog)%p) - damage(homog)%p => damageState(homog)%state(1,:) - + nullify(damageMapping(h)%p) + damageMapping(h)%p => mappingHomogenization(1,:,:) + deallocate(damage(h)%p) + damage(h)%p => damageState(h)%state(1,:) + end associate enddo + end subroutine damage_nonlocal_init diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index a32d6941d..3bbc5b613 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -46,8 +46,8 @@ contains !-------------------------------------------------------------------------------------------------- subroutine thermal_adiabatic_init - integer :: maxNinstance,section,instance,i,sizeState,NofMyHomog - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] + integer :: maxNinstance,o,h,NofMyHomog + character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_ADIABATIC_label//' init -+>>>'; flush(6) @@ -57,38 +57,35 @@ subroutine thermal_adiabatic_init allocate(param(maxNinstance)) - initializeInstances: do section = 1, size(thermal_type) - if (thermal_type(section) /= THERMAL_adiabatic_ID) cycle - associate(prm => param(thermal_typeInstance(section)), & - config => config_homogenization(section)) + do h = 1, size(thermal_type) + if (thermal_type(h) /= THERMAL_adiabatic_ID) cycle + associate(prm => param(thermal_typeInstance(h)),config => config_homogenization(h)) - NofMyHomog=count(material_homogenizationAt==section) - instance = thermal_typeInstance(section) outputs = config%getStrings('(output)',defaultVal=emptyStringArray) allocate(prm%outputID(0)) - do i=1, size(outputs) - select case(outputs(i)) + + do o=1, size(outputs) + select case(outputs(o)) case('temperature') - prm%outputID = [prm%outputID, temperature_ID] + prm%outputID = [prm%outputID, temperature_ID] end select enddo + + NofMyHomog=count(material_homogenizationAt==h) + thermalState(h)%sizeState = 1 + allocate(thermalState(h)%state0 (1,NofMyHomog), source=thermal_initialT(h)) + allocate(thermalState(h)%subState0(1,NofMyHomog), source=thermal_initialT(h)) + allocate(thermalState(h)%state (1,NofMyHomog), source=thermal_initialT(h)) - ! allocate state arrays - sizeState = 1 - thermalState(section)%sizeState = sizeState - allocate(thermalState(section)%state0 (sizeState,NofMyHomog), source=thermal_initialT(section)) - allocate(thermalState(section)%subState0(sizeState,NofMyHomog), source=thermal_initialT(section)) - allocate(thermalState(section)%state (sizeState,NofMyHomog), source=thermal_initialT(section)) - - nullify(thermalMapping(section)%p) - thermalMapping(section)%p => mappingHomogenization(1,:,:) - deallocate(temperature(section)%p) - temperature(section)%p => thermalState(section)%state(1,:) - deallocate(temperatureRate(section)%p) - allocate (temperatureRate(section)%p(NofMyHomog), source=0.0_pReal) + nullify(thermalMapping(h)%p) + thermalMapping(h)%p => mappingHomogenization(1,:,:) + deallocate(temperature(h)%p) + temperature(h)%p => thermalState(h)%state(1,:) + deallocate(temperatureRate(h)%p) + allocate (temperatureRate(h)%p(NofMyHomog), source=0.0_pReal) end associate - enddo initializeInstances + enddo end subroutine thermal_adiabatic_init diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 57703c325..27adc39aa 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -16,8 +16,9 @@ module thermal_conduction private enum, bind(c) - enumerator :: undefined_ID, & - temperature_ID + enumerator :: & + undefined_ID, & + temperature_ID end enum type :: tParameters @@ -47,10 +48,8 @@ contains subroutine thermal_conduction_init - integer :: maxNinstance,section,instance,i - integer :: sizeState - integer :: NofMyHomog - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] + integer :: maxNinstance,o,NofMyHomog,h + character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_CONDUCTION_label//' init -+>>>'; flush(6) @@ -60,39 +59,35 @@ subroutine thermal_conduction_init allocate(param(maxNinstance)) - initializeInstances: do section = 1, size(thermal_type) - if (thermal_type(section) /= THERMAL_conduction_ID) cycle - associate(prm => param(thermal_typeInstance(section)), & - config => config_homogenization(section)) + do h = 1, size(thermal_type) + if (thermal_type(h) /= THERMAL_conduction_ID) cycle + associate(prm => param(thermal_typeInstance(h)),config => config_homogenization(h)) - NofMyHomog=count(material_homogenizationAt==section) - instance = thermal_typeInstance(section) outputs = config%getStrings('(output)',defaultVal=emptyStringArray) allocate(prm%outputID(0)) - do i=1, size(outputs) - select case(outputs(i)) + + do o=1, size(outputs) + select case(outputs(o)) case('temperature') - prm%outputID = [prm%outputID, temperature_ID] + prm%outputID = [prm%outputID, temperature_ID] end select enddo + + NofMyHomog=count(material_homogenizationAt==h) + thermalState(h)%sizeState = 0 + allocate(thermalState(h)%state0 (0,NofMyHomog)) + allocate(thermalState(h)%subState0(0,NofMyHomog)) + allocate(thermalState(h)%state (0,NofMyHomog)) - - ! allocate state arrays - sizeState = 0 - thermalState(section)%sizeState = sizeState - allocate(thermalState(section)%state0 (sizeState,NofMyHomog)) - allocate(thermalState(section)%subState0(sizeState,NofMyHomog)) - allocate(thermalState(section)%state (sizeState,NofMyHomog)) - - nullify(thermalMapping(section)%p) - thermalMapping(section)%p => mappingHomogenization(1,:,:) - deallocate(temperature (section)%p) - allocate (temperature (section)%p(NofMyHomog), source=thermal_initialT(section)) - deallocate(temperatureRate(section)%p) - allocate (temperatureRate(section)%p(NofMyHomog), source=0.0_pReal) + nullify(thermalMapping(h)%p) + thermalMapping(h)%p => mappingHomogenization(1,:,:) + deallocate(temperature (h)%p) + allocate (temperature (h)%p(NofMyHomog), source=thermal_initialT(h)) + deallocate(temperatureRate(h)%p) + allocate (temperatureRate(h)%p(NofMyHomog), source=0.0_pReal) end associate - enddo initializeInstances + enddo end subroutine thermal_conduction_init diff --git a/src/thermal_isothermal.f90 b/src/thermal_isothermal.f90 index f06239944..3cafc6402 100644 --- a/src/thermal_isothermal.f90 +++ b/src/thermal_isothermal.f90 @@ -3,7 +3,6 @@ !> @brief material subroutine for isothermal temperature field !-------------------------------------------------------------------------------------------------- module thermal_isothermal - use prec use config use material @@ -20,27 +19,25 @@ contains !-------------------------------------------------------------------------------------------------- subroutine thermal_isothermal_init - integer :: & - homog, & - NofMyHomog + integer :: h,NofMyHomog - write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_isothermal_label//' init -+>>>' + write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_isothermal_label//' init -+>>>'; flush(6) - initializeInstances: do homog = 1, material_Nhomogenization - - if (thermal_type(homog) /= THERMAL_isothermal_ID) cycle - NofMyHomog = count(material_homogenizationAt == homog) - thermalState(homog)%sizeState = 0 - allocate(thermalState(homog)%state0 (0,NofMyHomog), source=0.0_pReal) - allocate(thermalState(homog)%subState0(0,NofMyHomog), source=0.0_pReal) - allocate(thermalState(homog)%state (0,NofMyHomog), source=0.0_pReal) + do h = 1, size(config_homogenization) + if (thermal_type(h) /= THERMAL_isothermal_ID) cycle + + NofMyHomog = count(material_homogenizationAt == h) + thermalState(h)%sizeState = 0 + allocate(thermalState(h)%state0 (0,NofMyHomog)) + allocate(thermalState(h)%subState0(0,NofMyHomog)) + allocate(thermalState(h)%state (0,NofMyHomog)) - deallocate(temperature (homog)%p) - allocate (temperature (homog)%p(1), source=thermal_initialT(homog)) - deallocate(temperatureRate(homog)%p) - allocate (temperatureRate(homog)%p(1), source=0.0_pReal) + deallocate(temperature (h)%p) + allocate (temperature (h)%p(1), source=thermal_initialT(h)) + deallocate(temperatureRate(h)%p) + allocate (temperatureRate(h)%p(1)) - enddo initializeInstances + enddo end subroutine thermal_isothermal_init From 83cf0623185eb2b3f77872bcef25a7cd34de3922 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 11:19:33 +0100 Subject: [PATCH 112/148] better define only once --- src/damage_local.f90 | 1 - src/damage_nonlocal.f90 | 1 - src/homogenization_mech_RGC.f90 | 2 -- src/plastic_disloUCLA.f90 | 4 ---- src/plastic_dislotwin.f90 | 4 ---- src/plastic_isotropic.f90 | 2 -- src/plastic_kinematichardening.f90 | 4 ---- src/plastic_nonlocal.f90 | 4 ---- src/plastic_phenopowerlaw.f90 | 4 ---- src/prec.f90 | 4 ++++ src/source_damage_anisoBrittle.f90 | 2 -- src/source_damage_anisoDuctile.f90 | 2 -- src/source_damage_isoBrittle.f90 | 1 - src/source_damage_isoDuctile.f90 | 1 - src/thermal_adiabatic.f90 | 1 - src/thermal_conduction.f90 | 1 - 16 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/damage_local.f90 b/src/damage_local.f90 index 34aef6c9d..0874b5aee 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -44,7 +44,6 @@ contains subroutine damage_local_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_local_label//' init -+>>>'; flush(6) diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index 18a456f34..47355a479 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -49,7 +49,6 @@ contains subroutine damage_nonlocal_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_nonlocal_label//' init -+>>>'; flush(6) diff --git a/src/homogenization_mech_RGC.f90 b/src/homogenization_mech_RGC.f90 index 23e99c8c5..d2e12c072 100644 --- a/src/homogenization_mech_RGC.f90 +++ b/src/homogenization_mech_RGC.f90 @@ -74,8 +74,6 @@ module subroutine mech_RGC_init NofMyHomog, & sizeState, nIntFaceTot - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/plastic_disloUCLA.f90 b/src/plastic_disloUCLA.f90 index e64c7ae58..8610b6bc2 100644 --- a/src/plastic_disloUCLA.f90 +++ b/src/plastic_disloUCLA.f90 @@ -121,10 +121,6 @@ subroutine plastic_disloUCLA_init() sizeState, sizeDotState, & startIndex, endIndex - integer, dimension(0), parameter :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter :: emptyRealArray = [real(pReal)::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/plastic_dislotwin.f90 b/src/plastic_dislotwin.f90 index 454ed42b1..8c19c7d83 100644 --- a/src/plastic_dislotwin.f90 +++ b/src/plastic_dislotwin.f90 @@ -180,10 +180,6 @@ subroutine plastic_dislotwin_init sizeState, sizeDotState, & startIndex, endIndex - integer, dimension(0), parameter :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter :: emptyRealArray = [real(pReal)::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/plastic_isotropic.f90 b/src/plastic_isotropic.f90 index b86fff04a..38166df4a 100644 --- a/src/plastic_isotropic.f90 +++ b/src/plastic_isotropic.f90 @@ -85,8 +85,6 @@ subroutine plastic_isotropic_init NipcMyPhase, & sizeState, sizeDotState - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/plastic_kinematichardening.f90 b/src/plastic_kinematichardening.f90 index 569051602..9b0c41041 100644 --- a/src/plastic_kinematichardening.f90 +++ b/src/plastic_kinematichardening.f90 @@ -103,10 +103,6 @@ subroutine plastic_kinehardening_init sizeState, sizeDeltaState, sizeDotState, & startIndex, endIndex - integer, dimension(0), parameter :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter :: emptyRealArray = [real(pReal)::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/plastic_nonlocal.f90 b/src/plastic_nonlocal.f90 index f65fb2193..ac914104c 100644 --- a/src/plastic_nonlocal.f90 +++ b/src/plastic_nonlocal.f90 @@ -225,10 +225,6 @@ contains !-------------------------------------------------------------------------------------------------- subroutine plastic_nonlocal_init - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer, dimension(0), parameter :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter :: emptyRealArray = [real(pReal)::] - integer :: & sizeState, sizeDotState,sizeDependentState, sizeDeltaState, & maxNinstances, & diff --git a/src/plastic_phenopowerlaw.f90 b/src/plastic_phenopowerlaw.f90 index ef5bd36ef..f5c430558 100644 --- a/src/plastic_phenopowerlaw.f90 +++ b/src/plastic_phenopowerlaw.f90 @@ -113,10 +113,6 @@ subroutine plastic_phenopowerlaw_init sizeState, sizeDotState, & startIndex, endIndex - integer, dimension(0), parameter :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter :: emptyRealArray = [real(pReal)::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] - integer(kind(undefined_ID)) :: & outputID diff --git a/src/prec.f90 b/src/prec.f90 index 8fd2495ce..710400a82 100644 --- a/src/prec.f90 +++ b/src/prec.f90 @@ -79,6 +79,10 @@ module prec real(pReal), private, parameter :: PREAL_EPSILON = epsilon(0.0_pReal) !< minimum positive number such that 1.0 + EPSILON /= 1.0. real(pReal), private, parameter :: PREAL_MIN = tiny(0.0_pReal) !< smallest normalized floating point number + integer, dimension(0), parameter, public :: emptyIntArray = [integer::] + real(pReal), dimension(0), parameter, public :: emptyRealArray = [real(pReal)::] + character(len=65536), dimension(0), parameter, public :: emptyStringArray = [character(len=65536)::] + private :: & unitTest diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index 2211ffdd2..240e3ae48 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -69,8 +69,6 @@ subroutine source_damage_anisoBrittle_init integer :: Ninstance,phase,instance,source,sourceOffset integer :: NofMyPhase,p ,i - integer, dimension(0), parameter :: emptyIntArray = [integer::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] integer(kind(undefined_ID)) :: & outputID diff --git a/src/source_damage_anisoDuctile.f90 b/src/source_damage_anisoDuctile.f90 index b6a4942c1..6101eb214 100644 --- a/src/source_damage_anisoDuctile.f90 +++ b/src/source_damage_anisoDuctile.f90 @@ -62,8 +62,6 @@ subroutine source_damage_anisoDuctile_init integer :: Ninstance,phase,instance,source,sourceOffset integer :: NofMyPhase,p ,i - integer, dimension(0), parameter :: emptyIntArray = [integer::] - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] integer(kind(undefined_ID)) :: & outputID diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index e10177502..609b7a7e0 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -56,7 +56,6 @@ subroutine source_damage_isoBrittle_init integer :: Ninstance,phase,instance,source,sourceOffset integer :: NofMyPhase,p,i - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] integer(kind(undefined_ID)) :: & outputID diff --git a/src/source_damage_isoDuctile.f90 b/src/source_damage_isoDuctile.f90 index fca804c84..9212e771f 100644 --- a/src/source_damage_isoDuctile.f90 +++ b/src/source_damage_isoDuctile.f90 @@ -53,7 +53,6 @@ subroutine source_damage_isoDuctile_init integer :: Ninstance,phase,instance,source,sourceOffset integer :: NofMyPhase,p,i - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] integer(kind(undefined_ID)) :: & outputID diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 3bbc5b613..985c0fffb 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -47,7 +47,6 @@ contains subroutine thermal_adiabatic_init integer :: maxNinstance,o,h,NofMyHomog - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_ADIABATIC_label//' init -+>>>'; flush(6) diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 27adc39aa..7b09864cf 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -49,7 +49,6 @@ subroutine thermal_conduction_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(0), parameter :: emptyStringArray = [character(len=65536)::] character(len=65536), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_CONDUCTION_label//' init -+>>>'; flush(6) From 747a340599d7f0b12dbc57ee5f28c92fe9d00aad Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 11:46:12 +0100 Subject: [PATCH 113/148] unified string length --- src/material.f90 | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/material.f90 b/src/material.f90 index 9ebd00397..b8f7e9baf 100644 --- a/src/material.f90 +++ b/src/material.f90 @@ -391,8 +391,8 @@ end subroutine material_init !-------------------------------------------------------------------------------------------------- subroutine material_parseHomogenization - integer :: h - character(len=65536) :: tag + integer :: h + character(len=pStringLen) :: tag allocate(homogenization_type(size(config_homogenization)), source=HOMOGENIZATION_undefined_ID) allocate(thermal_type(size(config_homogenization)), source=THERMAL_isothermal_ID) @@ -482,11 +482,11 @@ end subroutine material_parseHomogenization !-------------------------------------------------------------------------------------------------- subroutine material_parseMicrostructure - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & strings integer, allocatable, dimension(:) :: chunkPos integer :: e, m, c, i - character(len=65536) :: & + character(len=pStringLen) :: & tag allocate(microstructure_Nconstituents(size(config_microstructure)), source=0) @@ -540,7 +540,7 @@ end subroutine material_parseMicrostructure subroutine material_parsePhase integer :: sourceCtr, kinematicsCtr, stiffDegradationCtr, p - character(len=65536), dimension(:), allocatable :: str + character(len=pStringLen), dimension(:), allocatable :: str allocate(phase_elasticity(size(config_phase)),source=ELASTICITY_undefined_ID) @@ -594,9 +594,9 @@ subroutine material_parsePhase #if defined(__GFORTRAN__) || defined(__PGI) str = ['GfortranBug86277'] str = config_phase(p)%getStrings('(source)',defaultVal=str) - if (str(1) == 'GfortranBug86277') str = [character(len=65536)::] + if (str(1) == 'GfortranBug86277') str = [character(len=pStringLen)::] #else - str = config_phase(p)%getStrings('(source)',defaultVal=[character(len=65536)::]) + str = config_phase(p)%getStrings('(source)',defaultVal=[character(len=pStringLen)::]) #endif do sourceCtr = 1, size(str) select case (trim(str(sourceCtr))) @@ -618,9 +618,9 @@ subroutine material_parsePhase #if defined(__GFORTRAN__) || defined(__PGI) str = ['GfortranBug86277'] str = config_phase(p)%getStrings('(kinematics)',defaultVal=str) - if (str(1) == 'GfortranBug86277') str = [character(len=65536)::] + if (str(1) == 'GfortranBug86277') str = [character(len=pStringLen)::] #else - str = config_phase(p)%getStrings('(kinematics)',defaultVal=[character(len=65536)::]) + str = config_phase(p)%getStrings('(kinematics)',defaultVal=[character(len=pStringLen)::]) #endif do kinematicsCtr = 1, size(str) select case (trim(str(kinematicsCtr))) @@ -635,9 +635,9 @@ subroutine material_parsePhase #if defined(__GFORTRAN__) || defined(__PGI) str = ['GfortranBug86277'] str = config_phase(p)%getStrings('(stiffness_degradation)',defaultVal=str) - if (str(1) == 'GfortranBug86277') str = [character(len=65536)::] + if (str(1) == 'GfortranBug86277') str = [character(len=pStringLen)::] #else - str = config_phase(p)%getStrings('(stiffness_degradation)',defaultVal=[character(len=65536)::]) + str = config_phase(p)%getStrings('(stiffness_degradation)',defaultVal=[character(len=pStringLen)::]) #endif do stiffDegradationCtr = 1, size(str) select case (trim(str(stiffDegradationCtr))) @@ -663,8 +663,8 @@ end subroutine material_parsePhase !-------------------------------------------------------------------------------------------------- subroutine material_parseTexture - integer :: j, t - character(len=65536), dimension(:), allocatable :: strings ! Values for given key in material config + integer :: j,t + character(len=pStringLen), dimension(:), allocatable :: strings ! Values for given key in material config integer, dimension(:), allocatable :: chunkPos real(pReal), dimension(3,3) :: transformation ! maps texture to microstructure coordinate system real(pReal), dimension(3) :: Eulers ! Euler angles in degrees from file @@ -700,29 +700,27 @@ subroutine material_parseTexture do j = 1, 3 ! look for "x", "y", and "z" entries select case (strings(j)) case('x', '+x') - transformation(j,1:3) = [ 1.0_pReal, 0.0_pReal, 0.0_pReal] ! original axis is now +x-axis + transformation(j,1:3) = [ 1.0_pReal, 0.0_pReal, 0.0_pReal] ! original axis is now +x-axis case('-x') - transformation(j,1:3) = [-1.0_pReal, 0.0_pReal, 0.0_pReal] ! original axis is now -x-axis + transformation(j,1:3) = [-1.0_pReal, 0.0_pReal, 0.0_pReal] ! original axis is now -x-axis case('y', '+y') - transformation(j,1:3) = [ 0.0_pReal, 1.0_pReal, 0.0_pReal] ! original axis is now +y-axis + transformation(j,1:3) = [ 0.0_pReal, 1.0_pReal, 0.0_pReal] ! original axis is now +y-axis case('-y') - transformation(j,1:3) = [ 0.0_pReal,-1.0_pReal, 0.0_pReal] ! original axis is now -y-axis + transformation(j,1:3) = [ 0.0_pReal,-1.0_pReal, 0.0_pReal] ! original axis is now -y-axis case('z', '+z') - transformation(j,1:3) = [ 0.0_pReal, 0.0_pReal, 1.0_pReal] ! original axis is now +z-axis + transformation(j,1:3) = [ 0.0_pReal, 0.0_pReal, 1.0_pReal] ! original axis is now +z-axis case('-z') - transformation(j,1:3) = [ 0.0_pReal, 0.0_pReal,-1.0_pReal] ! original axis is now -z-axis + transformation(j,1:3) = [ 0.0_pReal, 0.0_pReal,-1.0_pReal] ! original axis is now -z-axis case default call IO_error(157,t) end select enddo - if(dNeq(math_det33(transformation),1.0_pReal)) call IO_error(157,t) call transformation_%fromMatrix(transformation) texture_orientation(t) = texture_orientation(t) * transformation_ endif enddo - end subroutine material_parseTexture From 27483bafbc2daf993e8a61912856502a1a083799 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 11:52:33 +0100 Subject: [PATCH 114/148] non-existing value evaluates to 0 or 1 --- src/future.f90 | 2 +- src/rotations.f90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future.f90 b/src/future.f90 index 354a522e4..b7eb3fec9 100644 --- a/src/future.f90 +++ b/src/future.f90 @@ -10,7 +10,7 @@ module future contains -#if defined(__GFORTRAN__) && __GNUC__<9 || __INTEL_COMPILER<1800 +#if defined(__GFORTRAN__) && __GNUC__<9 || defined(__INTEL_COMPILER) && INTEL_COMPILER<1800 !-------------------------------------------------------------------------------------------------- !> @brief substitute for the findloc intrinsic (only for integer, dimension(:) at the moment) !-------------------------------------------------------------------------------------------------- diff --git a/src/rotations.f90 b/src/rotations.f90 index a4a0bac88..5deb02a20 100644 --- a/src/rotations.f90 +++ b/src/rotations.f90 @@ -596,7 +596,7 @@ function om2ax(om) result(ax) else call dgeev('N','V',3,om_,3,Wr,Wi,devNull,3,VR,3,work,size(work,1),ierr) if (ierr /= 0) call IO_error(0,ext_msg='Error in om2ax: DGEEV return not zero') -#if defined(__GFORTRAN__) && __GNUC__ < 9 || __INTEL_COMPILER < 1800 +#if defined(__GFORTRAN__) && __GNUC__<9 || defined(__INTEL_COMPILER) && INTEL_COMPILER<1800 i = maxloc(merge(1,0,cEq(cmplx(Wr,Wi,pReal),cmplx(1.0_pReal,0.0_pReal,pReal),tol=1.0e-14_pReal)),dim=1) #else i = findloc(cEq(cmplx(Wr,Wi,pReal),cmplx(1.0_pReal,0.0_pReal,pReal),tol=1.0e-14_pReal),.true.,dim=1) !find eigenvalue (1,0) From 127678e2e140f0e6f0e01fa9684ef10fb4fc28ae Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 12:28:24 +0100 Subject: [PATCH 115/148] use default string length --- src/plastic_nonlocal.f90 | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/plastic_nonlocal.f90 b/src/plastic_nonlocal.f90 index ac914104c..3977f1fa7 100644 --- a/src/plastic_nonlocal.f90 +++ b/src/plastic_nonlocal.f90 @@ -28,9 +28,6 @@ module plastic_nonlocal real(pReal), parameter :: & KB = 1.38e-23_pReal !< Physical parameter, Boltzmann constant in J/Kelvin - character(len=64), dimension(:,:), allocatable :: & - plastic_nonlocal_output !< name of each post result output - ! storage order of dislocation types integer, dimension(8), parameter :: & sgl = [1,2,3,4,5,6,7,8] !< signed (single) @@ -202,9 +199,6 @@ module plastic_nonlocal type(tNonlocalMicrostructure), dimension(:), allocatable :: microstructure - integer(kind(undefined_ID)), dimension(:,:), allocatable :: & - plastic_nonlocal_outputID !< ID of each post result output - public :: & plastic_nonlocal_init, & plastic_nonlocal_dependentState, & @@ -237,10 +231,10 @@ subroutine plastic_nonlocal_init integer(kind(undefined_ID)) :: & outputID - character(len=512) :: & + character(len=pStringLen) :: & extmsg = '', & structure - character(len=65536), dimension(:), allocatable :: outputs + character(len=pStringLen), dimension(:), allocatable :: outputs integer :: NofMyPhase write(6,'(/,a)') ' <<<+- constitutive_'//PLASTICITY_NONLOCAL_label//' init -+>>>' @@ -261,9 +255,6 @@ subroutine plastic_nonlocal_init allocate(deltaState(maxNinstances)) allocate(microstructure(maxNinstances)) - allocate(plastic_nonlocal_output(maxval(phase_Noutput), maxNinstances)) - plastic_nonlocal_output = '' - allocate(plastic_nonlocal_outputID(maxval(phase_Noutput), maxNinstances), source=undefined_ID) allocate(totalNslip(maxNinstances), source=0) @@ -489,7 +480,6 @@ subroutine plastic_nonlocal_init end select if (outputID /= undefined_ID) then - plastic_nonlocal_output(i,phase_plasticityInstance(p)) = outputs(i) prm%outputID = [prm%outputID , outputID] endif @@ -511,8 +501,8 @@ subroutine plastic_nonlocal_init 'maxDipoleHeightEdge ','maxDipoleHeightScrew' ]) * prm%totalNslip !< other dependent state variables that are not updated by microstructure sizeDeltaState = sizeDotState - call material_allocatePlasticState(p,NofMyPhase,sizeState,sizeDotState,sizeDeltaState, & - prm%totalNslip,0,0) + call material_allocatePlasticState(p,NofMyPhase,sizeState,sizeDotState,sizeDeltaState) + plasticState(p)%nonlocal = .true. plasticState(p)%offsetDeltaState = 0 ! ToDo: state structure does not follow convention From 1037aa98d3358ab2787b26c7e019c7cbdb171e24 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 12:28:41 +0100 Subject: [PATCH 116/148] DADF5 is now the only output --- .gitlab-ci.yml | 5 ----- CONFIG | 4 ---- .../mods_MarcMentat/2018.1/Marc_tools/include_linux64 | 11 +++-------- .../mods_MarcMentat/2018/Marc_tools/include_linux64 | 11 +++-------- .../mods_MarcMentat/2019/Marc_tools/include_linux64 | 8 +++----- 5 files changed, 9 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 36d723db4..7958db9b8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -391,7 +391,6 @@ Marc_compileIfort: stage: compileMarc script: - module load $IntelMarc $HDF5Marc $MSC - - export DAMASK_HDF5=ON - Marc_compileIfort/test.py except: - master @@ -402,7 +401,6 @@ Hex_elastic: stage: marc script: - module load $IntelMarc $HDF5Marc $MSC - - export DAMASK_HDF5=ON - Hex_elastic/test.py except: - master @@ -412,7 +410,6 @@ CubicFCC_elastic: stage: marc script: - module load $IntelMarc $HDF5Marc $MSC - - export DAMASK_HDF5=ON - CubicFCC_elastic/test.py except: - master @@ -422,7 +419,6 @@ CubicBCC_elastic: stage: marc script: - module load $IntelMarc $HDF5Marc $MSC - - export DAMASK_HDF5=ON - CubicBCC_elastic/test.py except: - master @@ -432,7 +428,6 @@ J2_plasticBehavior: stage: marc script: - module load $IntelMarc $HDF5Marc $MSC - - export DAMASK_HDF5=ON - J2_plasticBehavior/test.py except: - master diff --git a/CONFIG b/CONFIG index 53e87b647..8da4d5b96 100644 --- a/CONFIG +++ b/CONFIG @@ -1,11 +1,7 @@ # "set"-syntax needed only for tcsh (but works with bash and zsh) -# DAMASK_ROOT will be expanded - set DAMASK_NUM_THREADS = 4 set MSC_ROOT = /opt/msc set MARC_VERSION = 2019 set ABAQUS_VERSION = 2019 - -set DAMASK_HDF5 = ON diff --git a/installation/mods_MarcMentat/2018.1/Marc_tools/include_linux64 b/installation/mods_MarcMentat/2018.1/Marc_tools/include_linux64 index 10a796e47..8adabaff1 100644 --- a/installation/mods_MarcMentat/2018.1/Marc_tools/include_linux64 +++ b/installation/mods_MarcMentat/2018.1/Marc_tools/include_linux64 @@ -99,14 +99,9 @@ else fi # DAMASK uses the HDF5 compiler wrapper around the Intel compiler -if test "$DAMASK_HDF5" = "ON";then - H5FC="$(h5fc -shlib -show)" - HDF5_LIB=${H5FC//ifort/} - FCOMP="$H5FC -DDAMASK_HDF5" - echo $FCOMP -else - FCOMP=ifort -fi +H5FC="$(h5fc -shlib -show)" +HDF5_LIB=${H5FC//ifort/} +FCOMP="$H5FC -DDAMASK_HDF5" # AEM if test "$MARCDLLOUTDIR" = ""; then diff --git a/installation/mods_MarcMentat/2018/Marc_tools/include_linux64 b/installation/mods_MarcMentat/2018/Marc_tools/include_linux64 index 694dccee3..c99313a30 100644 --- a/installation/mods_MarcMentat/2018/Marc_tools/include_linux64 +++ b/installation/mods_MarcMentat/2018/Marc_tools/include_linux64 @@ -99,14 +99,9 @@ else fi # DAMASK uses the HDF5 compiler wrapper around the Intel compiler -if test "$DAMASK_HDF5" = "ON";then - H5FC="$(h5fc -shlib -show)" - HDF5_LIB=${H5FC//ifort/} - FCOMP="$H5FC -DDAMASK_HDF5" - echo $FCOMP -else - FCOMP=ifort -fi +H5FC="$(h5fc -shlib -show)" +HDF5_LIB=${H5FC//ifort/} +FCOMP="$H5FC -DDAMASK_HDF5" # AEM if test "$MARCDLLOUTDIR" = ""; then diff --git a/installation/mods_MarcMentat/2019/Marc_tools/include_linux64 b/installation/mods_MarcMentat/2019/Marc_tools/include_linux64 index 6d630bd1d..2dba03961 100644 --- a/installation/mods_MarcMentat/2019/Marc_tools/include_linux64 +++ b/installation/mods_MarcMentat/2019/Marc_tools/include_linux64 @@ -100,11 +100,9 @@ else fi # DAMASK uses the HDF5 compiler wrapper around the Intel compiler -if test "$DAMASK_HDF5" = "ON";then - H5FC="$(h5fc -shlib -show)" - HDF5_LIB=${H5FC//ifort/} - FCOMP="$H5FC -DDAMASK_HDF5" -fi +H5FC="$(h5fc -shlib -show)" +HDF5_LIB=${H5FC//ifort/} +FCOMP="$H5FC -DDAMASK_HDF5" # AEM if test "$MARCDLLOUTDIR" = ""; then From 34af10fac1eacf1a8075f5fdcedc2c77b2348266 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 12:37:02 +0100 Subject: [PATCH 117/148] using default string length --- src/IO.f90 | 24 ++++++++++++------------ src/crystallite.f90 | 2 +- src/damage_local.f90 | 2 +- src/damage_nonlocal.f90 | 2 +- src/grid/DAMASK_grid.f90 | 2 +- src/homogenization_mech_RGC.f90 | 2 +- src/homogenization_mech_isostrain.f90 | 2 +- src/lattice.f90 | 2 +- src/mesh/DAMASK_FEM.f90 | 2 +- src/prec.f90 | 6 +++--- src/source_damage_anisoBrittle.f90 | 2 +- src/source_damage_anisoDuctile.f90 | 2 +- src/source_damage_isoBrittle.f90 | 2 +- src/source_damage_isoDuctile.f90 | 2 +- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/IO.f90 b/src/IO.f90 index cffcb6471..c07809d51 100644 --- a/src/IO.f90 +++ b/src/IO.f90 @@ -243,12 +243,12 @@ subroutine IO_open_inputFile(fileUnit) integer, allocatable, dimension(:) :: chunkPos - character(len=65536) :: line,fname + character(len=pStringLen :: line,fname logical :: createSuccess,fexist do - read(unit2,'(A65536)',END=220) line + read(unit2,'(A256)',END=220) line chunkPos = IO_stringPos(line) if (IO_lc(IO_StringValue(line,chunkPos,1))=='*include') then @@ -884,7 +884,7 @@ end subroutine IO_warning !-------------------------------------------------------------------------------------------------- function IO_read(fileUnit) result(line) - integer, intent(in) :: fileUnit !< file unit + integer, intent(in) :: fileUnit !< file unit character(len=pStringLen) :: line @@ -924,7 +924,7 @@ integer function IO_countDataLines(fileUnit) integer, allocatable, dimension(:) :: chunkPos - character(len=65536) :: line, & + character(len=pStringLen) :: line, & tmp IO_countDataLines = 0 @@ -956,7 +956,7 @@ integer function IO_countNumericalDataLines(fileUnit) integer, allocatable, dimension(:) :: chunkPos - character(len=65536) :: line, & + character(len=pStringLen) :: line, & tmp IO_countNumericalDataLines = 0 @@ -991,7 +991,7 @@ integer function IO_countContinuousIntValues(fileUnit) integer :: l,c #endif integer, allocatable, dimension(:) :: chunkPos - character(len=65536) :: line + character(len=pString) :: line IO_countContinuousIntValues = 0 line = '' @@ -1048,21 +1048,21 @@ function IO_continuousIntValues(fileUnit,maxN,lookupName,lookupMap,lookupMaxN) integer, intent(in) :: fileUnit, & lookupMaxN integer, dimension(:,:), intent(in) :: lookupMap - character(len=64), dimension(:), intent(in) :: lookupName + character(len=*), dimension(:), intent(in) :: lookupName integer :: i,first,last #ifdef Abaqus integer :: j,l,c #endif integer, allocatable, dimension(:) :: chunkPos - character(len=65536) line - logical rangeGeneration + character(len=pStringLen) :: line + logical :: rangeGeneration IO_continuousIntValues = 0 rangeGeneration = .false. #if defined(Marc4DAMASK) do - read(fileUnit,'(A65536)',end=100) line + read(fileUnit,'(A256)',end=100) line chunkPos = IO_stringPos(line) if (chunkPos(1) < 1) then ! empty line exit @@ -1103,14 +1103,14 @@ function IO_continuousIntValues(fileUnit,maxN,lookupName,lookupMap,lookupMaxN) !-------------------------------------------------------------------------------------------------- ! check if the element values in the elset are auto generated backspace(fileUnit) - read(fileUnit,'(A65536)',end=100) line + read(fileUnit,'(A256)',end=100) line chunkPos = IO_stringPos(line) do i = 1,chunkPos(1) if (IO_lc(IO_stringValue(line,chunkPos,i)) == 'generate') rangeGeneration = .true. enddo do l = 1,c - read(fileUnit,'(A65536)',end=100) line + read(fileUnit,'(A256)',end=100) line chunkPos = IO_stringPos(line) if (verify(IO_stringValue(line,chunkPos,1),'0123456789') > 0) then ! a non-int, i.e. set names follow on this line do i = 1,chunkPos(1) ! loop over set names in line diff --git a/src/crystallite.f90 b/src/crystallite.f90 index 84d5dd17d..d33e774e9 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -77,7 +77,7 @@ module crystallite crystallite_localPlasticity !< indicates this grain to have purely local constitutive law type :: tOutput !< new requested output (per phase) - character(len=65536), allocatable, dimension(:) :: & + character(len=pStringLen), allocatable, dimension(:) :: & label end type tOutput type(tOutput), allocatable, dimension(:) :: output_constituent diff --git a/src/damage_local.f90 b/src/damage_local.f90 index 0874b5aee..6cb45a391 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -44,7 +44,7 @@ contains subroutine damage_local_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(:), allocatable :: outputs + character(len=pStringLen), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_local_label//' init -+>>>'; flush(6) diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index 47355a479..17bdecaca 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -49,7 +49,7 @@ contains subroutine damage_nonlocal_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(:), allocatable :: outputs + character(len=pStringLen), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- damage_'//DAMAGE_nonlocal_label//' init -+>>>'; flush(6) diff --git a/src/grid/DAMASK_grid.f90 b/src/grid/DAMASK_grid.f90 index b17e490ea..b324a5afc 100644 --- a/src/grid/DAMASK_grid.f90 +++ b/src/grid/DAMASK_grid.f90 @@ -36,7 +36,7 @@ program DAMASK_spectral N_t = 0, & !< # of time indicators found in load case file N_n = 0, & !< # of increment specifiers found in load case file N_def = 0 !< # of rate of deformation specifiers found in load case file - character(len=65536) :: & + character(len=pStringLen) :: & line !-------------------------------------------------------------------------------------------------- diff --git a/src/homogenization_mech_RGC.f90 b/src/homogenization_mech_RGC.f90 index d2e12c072..c493c4190 100644 --- a/src/homogenization_mech_RGC.f90 +++ b/src/homogenization_mech_RGC.f90 @@ -77,7 +77,7 @@ module subroutine mech_RGC_init integer(kind(undefined_ID)) :: & outputID - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- homogenization_'//HOMOGENIZATION_RGC_label//' init -+>>>' diff --git a/src/homogenization_mech_isostrain.f90 b/src/homogenization_mech_isostrain.f90 index cdc078925..9345d1eda 100644 --- a/src/homogenization_mech_isostrain.f90 +++ b/src/homogenization_mech_isostrain.f90 @@ -33,7 +33,7 @@ module subroutine mech_isostrain_init Ninstance, & h, & NofMyHomog - character(len=65536) :: & + character(len=pStringLen) :: & tag = '' write(6,'(/,a)') ' <<<+- homogenization_'//HOMOGENIZATION_ISOSTRAIN_label//' init -+>>>' diff --git a/src/lattice.f90 b/src/lattice.f90 index fada61392..025a1f8a5 100644 --- a/src/lattice.f90 +++ b/src/lattice.f90 @@ -492,7 +492,7 @@ contains subroutine lattice_init integer :: Nphases - character(len=65536) :: & + character(len=pStringLen) :: & tag = '' integer :: i,p real(pReal), dimension(:), allocatable :: & diff --git a/src/mesh/DAMASK_FEM.f90 b/src/mesh/DAMASK_FEM.f90 index cd0bcacb1..9b9b95b91 100644 --- a/src/mesh/DAMASK_FEM.f90 +++ b/src/mesh/DAMASK_FEM.f90 @@ -27,7 +27,7 @@ program DAMASK_FEM integer, allocatable, dimension(:) :: chunkPos ! this is longer than needed for geometry parsing integer :: & N_def = 0 !< # of rate of deformation specifiers found in load case file - character(len=65536) :: & + character(len=pStringLen) :: & line !-------------------------------------------------------------------------------------------------- diff --git a/src/prec.f90 b/src/prec.f90 index 710400a82..2f0f20a00 100644 --- a/src/prec.f90 +++ b/src/prec.f90 @@ -79,9 +79,9 @@ module prec real(pReal), private, parameter :: PREAL_EPSILON = epsilon(0.0_pReal) !< minimum positive number such that 1.0 + EPSILON /= 1.0. real(pReal), private, parameter :: PREAL_MIN = tiny(0.0_pReal) !< smallest normalized floating point number - integer, dimension(0), parameter, public :: emptyIntArray = [integer::] - real(pReal), dimension(0), parameter, public :: emptyRealArray = [real(pReal)::] - character(len=65536), dimension(0), parameter, public :: emptyStringArray = [character(len=65536)::] + integer, dimension(0), parameter, public :: emptyIntArray = [integer::] + real(pReal), dimension(0), parameter, public :: emptyRealArray = [real(pReal)::] + character(len=pStringLen), dimension(0), parameter, public :: emptyStringArray = [character(len=pStringLen)::] private :: & unitTest diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index 240e3ae48..e5ed05799 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -74,7 +74,7 @@ subroutine source_damage_anisoBrittle_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISOBRITTLE_LABEL//' init -+>>>'; flush(6) diff --git a/src/source_damage_anisoDuctile.f90 b/src/source_damage_anisoDuctile.f90 index 6101eb214..fef897914 100644 --- a/src/source_damage_anisoDuctile.f90 +++ b/src/source_damage_anisoDuctile.f90 @@ -67,7 +67,7 @@ subroutine source_damage_anisoDuctile_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ANISODUCTILE_LABEL//' init -+>>>'; flush(6) diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index 609b7a7e0..53c0b77a7 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -61,7 +61,7 @@ subroutine source_damage_isoBrittle_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ISOBRITTLE_LABEL//' init -+>>>'; flush(6) diff --git a/src/source_damage_isoDuctile.f90 b/src/source_damage_isoDuctile.f90 index 9212e771f..6ee588d0c 100644 --- a/src/source_damage_isoDuctile.f90 +++ b/src/source_damage_isoDuctile.f90 @@ -58,7 +58,7 @@ subroutine source_damage_isoDuctile_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- source_'//SOURCE_DAMAGE_ISODUCTILE_LABEL//' init -+>>>' From 0d975e70233640d6d79971265628e999614171e2 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 12:40:02 +0100 Subject: [PATCH 118/148] polishing - default string length - Nslip/Ntwin/Ntrans not stored in state anymore --- src/IO.f90 | 2 +- src/material.f90 | 8 ++------ src/plastic_disloUCLA.f90 | 5 ++--- src/plastic_dislotwin.f90 | 5 ++--- src/plastic_isotropic.f90 | 5 ++--- src/plastic_kinematichardening.f90 | 5 ++--- src/plastic_none.f90 | 4 ++-- src/plastic_phenopowerlaw.f90 | 5 ++--- src/thermal_adiabatic.f90 | 2 +- src/thermal_conduction.f90 | 2 +- 10 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/IO.f90 b/src/IO.f90 index c07809d51..8926e1e21 100644 --- a/src/IO.f90 +++ b/src/IO.f90 @@ -991,7 +991,7 @@ integer function IO_countContinuousIntValues(fileUnit) integer :: l,c #endif integer, allocatable, dimension(:) :: chunkPos - character(len=pString) :: line + character(len=pStringLen) :: line IO_countContinuousIntValues = 0 line = '' diff --git a/src/material.f90 b/src/material.f90 index b8f7e9baf..a4494ed6e 100644 --- a/src/material.f90 +++ b/src/material.f90 @@ -728,18 +728,14 @@ end subroutine material_parseTexture !> @brief allocates the plastic state of a phase !-------------------------------------------------------------------------------------------------- subroutine material_allocatePlasticState(phase,NofMyPhase,& - sizeState,sizeDotState,sizeDeltaState,& - Nslip,Ntwin,Ntrans) + sizeState,sizeDotState,sizeDeltaState) integer, intent(in) :: & phase, & NofMyPhase, & sizeState, & sizeDotState, & - sizeDeltaState, & - Nslip, & - Ntwin, & - Ntrans + sizeDeltaState plasticState(phase)%sizeState = sizeState plasticState(phase)%sizeDotState = sizeDotState diff --git a/src/plastic_disloUCLA.f90 b/src/plastic_disloUCLA.f90 index 8610b6bc2..d1291d853 100644 --- a/src/plastic_disloUCLA.f90 +++ b/src/plastic_disloUCLA.f90 @@ -126,7 +126,7 @@ subroutine plastic_disloUCLA_init() character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- plastic_'//PLASTICITY_DISLOUCLA_label//' init -+>>>' @@ -286,8 +286,7 @@ subroutine plastic_disloUCLA_init() sizeDotState = size(['rho_mob ','rho_dip ','gamma_sl']) * prm%sum_N_sl sizeState = sizeDotState - call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0, & - prm%sum_N_sl,0,0) + call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and aTolState diff --git a/src/plastic_dislotwin.f90 b/src/plastic_dislotwin.f90 index 8c19c7d83..ae89dcc1c 100644 --- a/src/plastic_dislotwin.f90 +++ b/src/plastic_dislotwin.f90 @@ -185,7 +185,7 @@ subroutine plastic_dislotwin_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- constitutive_'//PLASTICITY_DISLOTWIN_label//' init -+>>>' @@ -506,8 +506,7 @@ subroutine plastic_dislotwin_init + size(['f_tr']) * prm%sum_N_tr sizeState = sizeDotState - call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0, & - prm%sum_N_sl,prm%sum_N_tw,prm%sum_N_tr) + call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and aTolState diff --git a/src/plastic_isotropic.f90 b/src/plastic_isotropic.f90 index 38166df4a..96d70be4a 100644 --- a/src/plastic_isotropic.f90 +++ b/src/plastic_isotropic.f90 @@ -90,7 +90,7 @@ subroutine plastic_isotropic_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- plastic_'//PLASTICITY_ISOTROPIC_label//' init -+>>>' @@ -179,8 +179,7 @@ subroutine plastic_isotropic_init sizeDotState = size(['xi ','accumulated_shear']) sizeState = sizeDotState - call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0, & - 1,0,0) + call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and aTolState diff --git a/src/plastic_kinematichardening.f90 b/src/plastic_kinematichardening.f90 index 9b0c41041..721073746 100644 --- a/src/plastic_kinematichardening.f90 +++ b/src/plastic_kinematichardening.f90 @@ -108,7 +108,7 @@ subroutine plastic_kinehardening_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- plastic_'//PLASTICITY_KINEHARDENING_label//' init -+>>>' @@ -245,8 +245,7 @@ subroutine plastic_kinehardening_init sizeDeltaState = size(['sense ', 'chi0 ', 'gamma0' ]) * prm%totalNslip sizeState = sizeDotState + sizeDeltaState - call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,sizeDeltaState, & - prm%totalNslip,0,0) + call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,sizeDeltaState) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and aTolState diff --git a/src/plastic_none.f90 b/src/plastic_none.f90 index a4979bb2c..f77b19c09 100644 --- a/src/plastic_none.f90 +++ b/src/plastic_none.f90 @@ -38,8 +38,8 @@ subroutine plastic_none_init if (phase_plasticity(p) /= PLASTICITY_NONE_ID) cycle NipcMyPhase = count(material_phaseAt == p) * discretization_nIP - call material_allocatePlasticState(p,NipcMyPhase,0,0,0, & - 0,0,0) + call material_allocatePlasticState(p,NipcMyPhase,0,0,0) + enddo end subroutine plastic_none_init diff --git a/src/plastic_phenopowerlaw.f90 b/src/plastic_phenopowerlaw.f90 index f5c430558..a8e459f63 100644 --- a/src/plastic_phenopowerlaw.f90 +++ b/src/plastic_phenopowerlaw.f90 @@ -118,7 +118,7 @@ subroutine plastic_phenopowerlaw_init character(len=pStringLen) :: & extmsg = '' - character(len=65536), dimension(:), allocatable :: & + character(len=pStringLen), dimension(:), allocatable :: & outputs write(6,'(/,a)') ' <<<+- plastic_'//PLASTICITY_PHENOPOWERLAW_label//' init -+>>>' @@ -304,8 +304,7 @@ subroutine plastic_phenopowerlaw_init + size(['tau_twin ','gamma_twin']) * prm%totalNtwin sizeState = sizeDotState - call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0, & - prm%totalNslip,prm%totalNtwin,0) + call material_allocatePlasticState(p,NipcMyPhase,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and aTolState diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 985c0fffb..d96604e59 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -47,7 +47,7 @@ contains subroutine thermal_adiabatic_init integer :: maxNinstance,o,h,NofMyHomog - character(len=65536), dimension(:), allocatable :: outputs + character(len=pStringLen), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_ADIABATIC_label//' init -+>>>'; flush(6) diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 7b09864cf..537b0366b 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -49,7 +49,7 @@ subroutine thermal_conduction_init integer :: maxNinstance,o,NofMyHomog,h - character(len=65536), dimension(:), allocatable :: outputs + character(len=pStringLen), dimension(:), allocatable :: outputs write(6,'(/,a)') ' <<<+- thermal_'//THERMAL_CONDUCTION_label//' init -+>>>'; flush(6) From dd318c8d1d3af3af6fd5a4c8c6d313499045d0c0 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 15:12:01 +0100 Subject: [PATCH 119/148] Table class replaces ASCIItable class --- processing/post/addCompatibilityMismatch.py | 111 +++----------------- processing/post/addIPFcolor.py | 55 +++------- processing/post/addNorm.py | 71 +++---------- processing/post/addPole.py | 57 +++------- 4 files changed, 60 insertions(+), 234 deletions(-) diff --git a/processing/post/addCompatibilityMismatch.py b/processing/post/addCompatibilityMismatch.py index 7556cb863..e4b6d940d 100755 --- a/processing/post/addCompatibilityMismatch.py +++ b/processing/post/addCompatibilityMismatch.py @@ -2,10 +2,10 @@ import os import math +import sys from optparse import OptionParser import numpy as np -import scipy.ndimage import damask @@ -13,26 +13,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -#-------------------------------------------------------------------------------------------------- -def cell2node(cellData,grid): - - nodeData = 0.0 - datalen = np.array(cellData.shape[3:]).prod() - - for i in range(datalen): - node = scipy.ndimage.convolve(cellData.reshape(tuple(grid[::-1])+(datalen,))[...,i], - np.ones((2,2,2))/8., # 2x2x2 neighborhood of cells - mode = 'wrap', - origin = -1, # offset to have cell origin as center - ) # now averaged at cell origins - node = np.append(node,node[np.newaxis,0,:,:,...],axis=0) # wrap along z - node = np.append(node,node[:,0,np.newaxis,:,...],axis=1) # wrap along y - node = np.append(node,node[:,:,0,np.newaxis,...],axis=2) # wrap along x - - nodeData = node[...,np.newaxis] if i==0 else np.concatenate((nodeData,node[...,np.newaxis]),axis=-1) - - return nodeData - #-------------------------------------------------------------------------------------------------- def deformationAvgFFT(F,grid,size,nodal=False,transformed=False): """Calculate average cell center (or nodal) deformation for deformation gradient field specified in each grid cell""" @@ -82,7 +62,7 @@ def displacementFluctFFT(F,grid,size,nodal=False,transformed=False): displacement = np.fft.irfftn(displacement_fourier,grid[::-1],axes=(0,1,2)) - return cell2node(displacement,grid) if nodal else displacement + return damask.grid_filters.cell_2_node(displacement) if nodal else displacement def volTetrahedron(coords): @@ -241,92 +221,33 @@ parser.set_defaults(pos = 'pos', ) (options,filenames) = parser.parse_args() - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] + for name in filenames: - try: - table = damask.ASCIItable(name = name, - buffered = False) - except: continue damask.util.report(scriptName,name) - -# ------------------------------------------ read header ------------------------------------------ - - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - if table.label_dimension(options.defgrad) != 9: - errors.append('deformation gradient "{}" is not a 3x3 tensor.'.format(options.defgrad)) + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) - coordDim = table.label_dimension(options.pos) - if not 3 >= coordDim >= 1: - errors.append('coordinates "{}" need to have one, two, or three dimensions.'.format(options.pos)) - elif coordDim < 3: - remarks.append('appending {} dimension{} to coordinates "{}"...'.format(3-coordDim, - 's' if coordDim < 2 else '', - options.pos)) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss=True) - continue - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray([options.defgrad,options.pos]) - table.data_rewind() - - if table.data[:,9:].shape[1] < 3: - table.data = np.hstack((table.data, - np.zeros((table.data.shape[0], - 3-table.data[:,9:].shape[1]),dtype='f'))) # fill coords up to 3D with zeros - - grid,size = damask.util.coordGridAndSize(table.data[:,9:12]) N = grid.prod() - if N != len(table.data): errors.append('data count {} does not match grid {}x{}x{}.'.format(N,*grid)) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# -----------------------------process data and assemble header ------------------------------------- - - F_fourier = np.fft.rfftn(table.data[:,:9].reshape(grid[2],grid[1],grid[0],3,3),axes=(0,1,2)) # perform transform only once... + F_fourier = np.fft.rfftn(table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),axes=(0,1,2)) # perform transform only once... nodes = displacementFluctFFT(F_fourier,grid,size,True,transformed=True)\ + deformationAvgFFT (F_fourier,grid,size,True,transformed=True) if options.shape: - table.labels_append(['shapeMismatch({})'.format(options.defgrad)]) centres = displacementFluctFFT(F_fourier,grid,size,False,transformed=True)\ + deformationAvgFFT (F_fourier,grid,size,False,transformed=True) - + shapeMismatch = shapeMismatch( size,table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),nodes,centres) + table.add('shapeMismatch(({}))'.format(options.defgrad), + shapeMismatch.reshape((-1,1)), + scriptID+' '+' '.join(sys.argv[1:])) + if options.volume: - table.labels_append(['volMismatch({})'.format(options.defgrad)]) + volumeMismatch = volumeMismatch(size,table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),nodes) + table.add('volMismatch(({}))'.format(options.defgrad), + volumeMismatch.reshape((-1,1)), + scriptID+' '+' '.join(sys.argv[1:])) - table.head_write() - if options.shape: - shapeMismatch = shapeMismatch( size,table.data[:,:9].reshape(grid[2],grid[1],grid[0],3,3),nodes,centres) - if options.volume: - volumeMismatch = volumeMismatch(size,table.data[:,:9].reshape(grid[2],grid[1],grid[0],3,3),nodes) - -# ------------------------------------------ output data ------------------------------------------- - for i in range(grid[2]): - for j in range(grid[1]): - for k in range(grid[0]): - table.data_read() - if options.shape: table.data_append(shapeMismatch[i,j,k]) - if options.volume: table.data_append(volumeMismatch[i,j,k]) - table.data_write() - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addIPFcolor.py b/processing/post/addIPFcolor.py index 0149dd078..5114b6a91 100755 --- a/processing/post/addIPFcolor.py +++ b/processing/post/addIPFcolor.py @@ -43,54 +43,25 @@ parser.set_defaults(pole = (0.0,0.0,1.0), ) (options, filenames) = parser.parse_args() +if filenames == []: filenames = [None] # damask.Orientation requires Bravais lattice, but we are only interested in symmetry -symmetry2lattice={'cubic':'bcc','hexagonal':'hex','tetragonal':'bct'} +symmetry2lattice={'cubic':'fcc','hexagonal':'hex','tetragonal':'bct'} lattice = symmetry2lattice[options.symmetry] pole = np.array(options.pole) pole /= np.linalg.norm(pole) -# --- loop over input files ------------------------------------------------------------------------ - -if filenames == []: filenames = [None] - for name in filenames: - try: - table = damask.ASCIItable(name = name, - buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ - - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - if not table.label_dimension(options.quaternion) == 4: - damask.util.croak('input {} does not have dimension 4.'.format(options.quaternion)) - table.close(dismiss = True) # close ASCIItable and remove empty file - continue - - column = table.label_index(options.quaternion) - -# ------------------------------------------ assemble header --------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.labels_append(['{}_IPF_{:g}{:g}{:g}_{sym}'.format(i+1,*options.pole,sym = options.symmetry.lower()) for i in range(3)]) - table.head_write() - -# ------------------------------------------ process data ------------------------------------------ - - outputAlive = True - while outputAlive and table.data_read(): # read next data line of ASCII table - o = damask.Orientation(np.array(list(map(float,table.data[column:column+4]))), - lattice = lattice).reduced() - - table.data_append(o.IPFcolor(pole)) - outputAlive = table.data_write() # output processed line - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + orientation = table.get(options.quaternion) + color = np.empty((orientation.shape[0],3)) + for i,o in enumerate(orientation): + color[i] = damask.Orientation(o,lattice = lattice).IPFcolor(pole) + + table.add('IPF_{:g}{:g}{:g}_{sym}'.format(*options.pole,sym = options.symmetry.lower()), + color, + scriptID+' '+' '.join(sys.argv[1:])) + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addNorm.py b/processing/post/addNorm.py index c8c0b05bf..e58947de0 100755 --- a/processing/post/addNorm.py +++ b/processing/post/addNorm.py @@ -42,7 +42,7 @@ parser.add_option('-n','--norm', type = 'choice', choices = normChoices, metavar='string', help = 'type of element-wise p-norm [frobenius] {%s}'%(','.join(map(str,normChoices)))) parser.add_option('-l','--label', - dest = 'label', + dest = 'labels', action = 'extend', metavar = '', help = 'heading of column(s) to calculate norm of') @@ -50,62 +50,25 @@ parser.set_defaults(norm = 'frobenius', ) (options,filenames) = parser.parse_args() - -if options.norm.lower() not in normChoices: - parser.error('invalid norm ({}) specified.'.format(options.norm)) -if options.label is None: - parser.error('no data column specified.') - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] +if options.norm.lower() not in normChoices: + parser.error('invalid norm ({}) specified.'.format(options.norm)) +if options.labels is None: + parser.error('no data column specified.') + for name in filenames: - try: - table = damask.ASCIItable(name = name, - buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + for label in options.labels: + data = table.get(label) + data_norm = np.empty((data.shape[0],1)) + for i,d in enumerate(data): + data_norm[i] = norm(options.norm.capitalize(),d) - table.head_read() + table.add('norm{}({})'.format(options.norm.capitalize(),label), + data_norm, + scriptID+' '+' '.join(sys.argv[1:])) -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - columns = [] - dims = [] - - for what in options.label: - dim = table.label_dimension(what) - if dim < 0: remarks.append('column {} not found...'.format(what)) - else: - dims.append(dim) - columns.append(table.label_index(what)) - table.labels_append('norm{}({})'.format(options.norm.capitalize(),what)) # extend ASCII header with new labels - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.head_write() - -# ------------------------------------------ process data ------------------------------------------ - - outputAlive = True - while outputAlive and table.data_read(): # read next data line of ASCII table - for column,dim in zip(columns,dims): - table.data_append(norm(options.norm.capitalize(), - map(float,table.data[column:column+dim]))) - outputAlive = table.data_write() # output processed line - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addPole.py b/processing/post/addPole.py index c8b83b106..9f4982ff5 100755 --- a/processing/post/addPole.py +++ b/processing/post/addPole.py @@ -42,52 +42,23 @@ parser.set_defaults(pole = (1.0,0.0,0.0), ) (options, filenames) = parser.parse_args() +if filenames == []: filenames = [None] pole = np.array(options.pole) pole /= np.linalg.norm(pole) -# --- loop over input files ------------------------------------------------------------------------- - -if filenames == []: filenames = [None] - for name in filenames: - try: - table = damask.ASCIItable(name = name, - buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ - - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - if not table.label_dimension(options.quaternion) == 4: - damask.util.croak('input {} does not have dimension 4.'.format(options.quaternion)) - table.close(dismiss = True) # close ASCIItable and remove empty file - continue - - column = table.label_index(options.quaternion) - -# ------------------------------------------ assemble header --------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.labels_append(['{}_pole_{}{}{}'.format(i+1,*options.pole) for i in range(2)]) - table.head_write() - -# ------------------------------------------ process data ------------------------------------------ - outputAlive = True - while outputAlive and table.data_read(): # read next data line of ASCII table - o = damask.Rotation(np.array(list(map(float,table.data[column:column+4])))) - - rotatedPole = o*pole # rotate pole according to crystal orientation - (x,y) = rotatedPole[0:2]/(1.+abs(pole[2])) # stereographic projection - - table.data_append([np.sqrt(x*x+y*y),np.arctan2(y,x)] if options.polar else [x,y]) # cartesian coordinates - - outputAlive = table.data_write() # output processed line - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + orientation = table.get(options.quaternion) + poles = np.empty((orientation.shape[0],2)) + for i,o in enumerate(orientation): + rotatedPole = damask.Rotation(o)*pole # rotate pole according to crystal orientation + (x,y) = rotatedPole[0:2]/(1.+abs(pole[2])) # stereographic projection + poles[i] = [np.sqrt(x*x+y*y),np.arctan2(y,x)] if options.polar else [x,y] # cartesian coordinates + + table.add('pole_{}{}{}'.format(*options.pole), + poles, + scriptID+' '+' '.join(sys.argv[1:])) + table.to_ASCII(sys.stdout if name is None else name) From 503626473a5ed13e5aa5cc929737128ba1d6c861 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 17:38:16 +0100 Subject: [PATCH 120/148] bugfix: wrong grid order for x fast, z slow, the shape of the array needs to be reversed --- python/damask/grid_filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index a1e1ff06d..664198630 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -147,7 +147,7 @@ def cell_displacement_avg(size,F): """ F_avg = np.average(F,axis=(0,1,2)) - return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3],size)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3][::-1],size)) def cell_coord0_2_DNA(coord0,ordered=True): """ @@ -232,7 +232,7 @@ def node_displacement_avg(size,F): """ F_avg = np.average(F,axis=(0,1,2)) - return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3],size)) + return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3][::-1],size)) def cell_2_node(cell_data): From 2cd2d6f5065ade2eacab31a1bee880afb14cbed9 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 18:07:04 +0100 Subject: [PATCH 121/148] cell_2_node/node_2_cell work only for periodic data hence, coordinates and displacements cannot be converted easily --- python/damask/grid_filters.py | 67 ++++++++++++++++++++++++++++++- python/tests/test_grid_filters.py | 7 ++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 664198630..69e7dd6b1 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -97,6 +97,8 @@ def cell_coord0(grid,size,origin=np.zeros(3)): number of grid points. size : numpy.ndarray physical size of the periodic field. + origin : numpy.ndarray, optional + physical origin of the periodic field. Default is [0.0,0.0,0.0]. """ start = origin + size/grid*.5 @@ -149,6 +151,36 @@ def cell_displacement_avg(size,F): F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),cell_coord0(F.shape[:3][::-1],size)) +def cell_displacement(size,F): + """ + Cell center displacement field from deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ + return cell_displacement_avg(size,F) + cell_displacement_fluct(size,F) + +def cell_coord(size,F,origin=np.zeros(3)): + """ + Cell center positions. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + origin : numpy.ndarray, optional + physical origin of the periodic field. Default is [0.0,0.0,0.0]. + + """ + return cell_coord0(F.shape[:3][::-1],size,origin) + cell_displacement(size,F) + def cell_coord0_2_DNA(coord0,ordered=True): """ Return grid 'DNA', i.e. grid, size, and origin from array of cell positions. @@ -196,6 +228,8 @@ def node_coord0(grid,size,origin=np.zeros(3)): number of grid points. size : numpy.ndarray physical size of the periodic field. + origin : numpy.ndarray, optional + physical origin of the periodic field. Default is [0.0,0.0,0.0]. """ x, y, z = np.meshgrid(np.linspace(origin[2],size[2]+origin[2],1+grid[2]), @@ -234,9 +268,38 @@ def node_displacement_avg(size,F): F_avg = np.average(F,axis=(0,1,2)) return np.einsum('ml,ijkl->ijkm',F_avg-np.eye(3),node_coord0(F.shape[:3][::-1],size)) +def node_displacement(size,F): + """ + Nodal displacement field from deformation gradient field. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + + """ + return node_displacement_avg(size,F) + node_displacement_fluct(size,F) + +def node_coord(size,F,origin=np.zeros(3)): + """ + Nodal positions. + + Parameters + ---------- + size : numpy.ndarray + physical size of the periodic field. + F : numpy.ndarray + deformation gradient field. + origin : numpy.ndarray, optional + physical origin of the periodic field. Default is [0.0,0.0,0.0]. + + """ + return node_coord0(F.shape[:3][::-1],size,origin) + node_displacement(size,F) def cell_2_node(cell_data): - """Interpolate cell data to nodal data.""" + """Interpolate periodic cell data to nodal data.""" n = ( cell_data + np.roll(cell_data,1,(0,1,2)) + np.roll(cell_data,1,(0,)) + np.roll(cell_data,1,(1,)) + np.roll(cell_data,1,(2,)) + np.roll(cell_data,1,(0,1)) + np.roll(cell_data,1,(1,2)) + np.roll(cell_data,1,(2,0)))*0.125 @@ -244,7 +307,7 @@ def cell_2_node(cell_data): return np.pad(n,((0,1),(0,1),(0,1))+((0,0),)*len(cell_data.shape[3:]),mode='wrap') def node_2_cell(node_data): - """Interpolate nodal data to cell data.""" + """Interpolate periodic nodal data to cell data.""" c = ( node_data + np.roll(node_data,1,(0,1,2)) + np.roll(node_data,1,(0,)) + np.roll(node_data,1,(1,)) + np.roll(node_data,1,(2,)) + np.roll(node_data,1,(0,1)) + np.roll(node_data,1,(1,2)) + np.roll(node_data,1,(2,0)))*0.125 diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index b23fad549..c8442eee4 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -35,6 +35,13 @@ class TestGridFilters: _grid,_size,_origin = eval('grid_filters.{}_coord0_2_DNA(coord0.reshape((-1,3)))'.format(mode)) assert np.allclose(grid,_grid) and np.allclose(size,_size) and np.allclose(origin,_origin) + def test_displacement_fluct_equivalence(self): + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + F = np.random.random(tuple(grid)+(3,3)) + assert np.allclose(grid_filters.node_displacement_fluct(size,F), + grid_filters.cell_2_node(grid_filters.cell_displacement_fluct(size,F))) + @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_avg_vanishes(self,mode): From da33ba17bcd17e738731e5ba6379875e1a4b53da Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 18:17:04 +0100 Subject: [PATCH 122/148] using central (and tested) functionality --- processing/post/addCompatibilityMismatch.py | 64 ++------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/processing/post/addCompatibilityMismatch.py b/processing/post/addCompatibilityMismatch.py index e4b6d940d..553fa9390 100755 --- a/processing/post/addCompatibilityMismatch.py +++ b/processing/post/addCompatibilityMismatch.py @@ -13,58 +13,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -#-------------------------------------------------------------------------------------------------- -def deformationAvgFFT(F,grid,size,nodal=False,transformed=False): - """Calculate average cell center (or nodal) deformation for deformation gradient field specified in each grid cell""" - if nodal: - x, y, z = np.meshgrid(np.linspace(0,size[2],1+grid[2]), - np.linspace(0,size[1],1+grid[1]), - np.linspace(0,size[0],1+grid[0]), - indexing = 'ij') - else: - x, y, z = np.meshgrid(np.linspace(size[2]/grid[2]/2.,size[2]-size[2]/grid[2]/2.,grid[2]), - np.linspace(size[1]/grid[1]/2.,size[1]-size[1]/grid[1]/2.,grid[1]), - np.linspace(size[0]/grid[0]/2.,size[0]-size[0]/grid[0]/2.,grid[0]), - indexing = 'ij') - - origCoords = np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) - - F_fourier = F if transformed else np.fft.rfftn(F,axes=(0,1,2)) # transform or use provided data - Favg = np.real(F_fourier[0,0,0,:,:])/grid.prod() # take zero freq for average - avgDeformation = np.einsum('ml,ijkl->ijkm',Favg,origCoords) # dX = Favg.X - - return avgDeformation - -#-------------------------------------------------------------------------------------------------- -def displacementFluctFFT(F,grid,size,nodal=False,transformed=False): - """Calculate cell center (or nodal) displacement for deformation gradient field specified in each grid cell""" - integrator = 0.5j * size / math.pi - - kk, kj, ki = np.meshgrid(np.where(np.arange(grid[2])>grid[2]//2,np.arange(grid[2])-grid[2],np.arange(grid[2])), - np.where(np.arange(grid[1])>grid[1]//2,np.arange(grid[1])-grid[1],np.arange(grid[1])), - np.arange(grid[0]//2+1), - indexing = 'ij') - k_s = np.concatenate((ki[:,:,:,None],kj[:,:,:,None],kk[:,:,:,None]),axis = 3) - k_sSquared = np.einsum('...l,...l',k_s,k_s) - k_sSquared[0,0,0] = 1.0 # ignore global average frequency - -#-------------------------------------------------------------------------------------------------- -# integration in Fourier space - - displacement_fourier = -np.einsum('ijkml,ijkl,l->ijkm', - F if transformed else np.fft.rfftn(F,axes=(0,1,2)), - k_s, - integrator, - ) / k_sSquared[...,np.newaxis] - -#-------------------------------------------------------------------------------------------------- -# backtransformation to real space - - displacement = np.fft.irfftn(displacement_fourier,grid[::-1],axes=(0,1,2)) - - return damask.grid_filters.cell_2_node(displacement) if nodal else displacement - - def volTetrahedron(coords): """ Return the volume of the tetrahedron with given vertices or sides. @@ -230,15 +178,11 @@ for name in filenames: table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) - N = grid.prod() - - F_fourier = np.fft.rfftn(table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),axes=(0,1,2)) # perform transform only once... - nodes = displacementFluctFFT(F_fourier,grid,size,True,transformed=True)\ - + deformationAvgFFT (F_fourier,grid,size,True,transformed=True) - + F = table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3) + nodes = damask.grid_filters.node_coord(size,F) + if options.shape: - centres = displacementFluctFFT(F_fourier,grid,size,False,transformed=True)\ - + deformationAvgFFT (F_fourier,grid,size,False,transformed=True) + centres = damask.grid_filters.cell_coord(size,F) shapeMismatch = shapeMismatch( size,table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),nodes,centres) table.add('shapeMismatch(({}))'.format(options.defgrad), shapeMismatch.reshape((-1,1)), From b2934988649c267cea3820534e4e88eab8393fa4 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 18:19:54 +0100 Subject: [PATCH 123/148] fixing prospector complaints --- processing/post/addCompatibilityMismatch.py | 10 +++++----- processing/post/addIPFcolor.py | 1 + processing/post/addNorm.py | 1 + processing/post/addPole.py | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/processing/post/addCompatibilityMismatch.py b/processing/post/addCompatibilityMismatch.py index 553fa9390..8aff13c53 100755 --- a/processing/post/addCompatibilityMismatch.py +++ b/processing/post/addCompatibilityMismatch.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 import os -import math import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -61,10 +61,10 @@ def volTetrahedron(coords): def volumeMismatch(size,F,nodes): """ - Calculates the volume mismatch + Calculates the volume mismatch. volume mismatch is defined as the difference between volume of reconstructed - (compatible) cube and determinant of defgrad at the FP + (compatible) cube and determinant of deformation gradient at Fourier point. """ coords = np.empty([8,3]) vMismatch = np.empty(grid[::-1]) @@ -97,11 +97,11 @@ def volumeMismatch(size,F,nodes): def shapeMismatch(size,F,nodes,centres): """ - Routine to calculate the shape mismatch + Routine to calculate the shape mismatch. shape mismatch is defined as difference between the vectors from the central point to the corners of reconstructed (combatible) volume element and the vectors calculated by deforming - the initial volume element with the current deformation gradient + the initial volume element with the current deformation gradient. """ coordsInitial = np.empty([8,3]) sMismatch = np.empty(grid[::-1]) diff --git a/processing/post/addIPFcolor.py b/processing/post/addIPFcolor.py index 5114b6a91..014b0147d 100755 --- a/processing/post/addIPFcolor.py +++ b/processing/post/addIPFcolor.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np diff --git a/processing/post/addNorm.py b/processing/post/addNorm.py index e58947de0..4ac2bf899 100755 --- a/processing/post/addNorm.py +++ b/processing/post/addNorm.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np diff --git a/processing/post/addPole.py b/processing/post/addPole.py index 9f4982ff5..58f9235dc 100755 --- a/processing/post/addPole.py +++ b/processing/post/addPole.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np From 6989679d3bea0e8abafa681f122219dc06bb105d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 19:03:36 +0100 Subject: [PATCH 124/148] using central functionality - Table class for table data - grid_filters for grid related functions --- processing/post/addEuclideanDistance.py | 124 +++++++----------------- processing/post/addGaussian.py | 83 +++------------- python/damask/grid_filters.py | 13 +++ 3 files changed, 61 insertions(+), 159 deletions(-) diff --git a/processing/post/addEuclideanDistance.py b/processing/post/addEuclideanDistance.py index 1ca2169f6..d2604b4fc 100755 --- a/processing/post/addEuclideanDistance.py +++ b/processing/post/addEuclideanDistance.py @@ -121,13 +121,14 @@ parser.set_defaults(pos = 'pos', ) (options,filenames) = parser.parse_args() +if filenames == []: filenames = [None] if options.type is None: - parser.error('no feature type selected.') + parser.error('no feature type selected.') if not set(options.type).issubset(set(list(itertools.chain(*map(lambda x: x['names'],features))))): - parser.error('type must be chosen from (%s).'%(', '.join(map(lambda x:'|'.join(x['names']),features))) ) + parser.error('type must be chosen from (%s).'%(', '.join(map(lambda x:'|'.join(x['names']),features))) ) if 'biplane' in options.type and 'boundary' in options.type: - parser.error('only one from aliases "biplane" and "boundary" possible.') + parser.error('only one from aliases "biplane" and "boundary" possible.') feature_list = [] for i,feature in enumerate(features): @@ -137,104 +138,49 @@ for i,feature in enumerate(features): feature_list.append(i) # remember valid features break -# --- loop over input files ------------------------------------------------------------------------- - -if filenames == []: filenames = [None] - for name in filenames: - try: table = damask.ASCIItable(name = name, buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.get(options.pos)) - table.head_read() + neighborhood = neighborhoods[options.neighborhood] + diffToNeighbor = np.empty(list(grid+2)+[len(neighborhood)],'i') + microstructure = periodic_3Dpad(table.get(options.id).astype('i').reshape(grid,order='F')) -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - - if not 3 >= table.label_dimension(options.pos) >= 1: - errors.append('coordinates "{}" need to have one, two, or three dimensions.'.format(options.pos)) - - if table.label_dimension(options.id) != 1: errors.append('grain identifier {} not found.'.format(options.id)) - else: idCol = table.label_index(options.id) - - if remarks != []: - damask.util.croak(remarks) - remarks = [] - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header --------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - for feature in feature_list: - table.labels_append('ED_{}({})'.format(features[feature]['names'][0],options.id)) # extend ASCII header with new labels - table.head_write() - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray() - - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) - N = grid.prod() - - if N != len(table.data): errors.append('data count {} does not match grid {}.'.format(N,'x'.join(map(str,grid)))) - else: remarks.append('grid: {}x{}x{}'.format(*grid)) - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ process value field ----------------------------------- - - stack = [table.data] - - neighborhood = neighborhoods[options.neighborhood] - diffToNeighbor = np.empty(list(grid+2)+[len(neighborhood)],'i') - microstructure = periodic_3Dpad(table.data[:,idCol].astype('i').reshape(grid,order='F')) - - for i,p in enumerate(neighborhood): - stencil = np.zeros((3,3,3),'i') - stencil[1,1,1] = -1 - stencil[p[0]+1, - p[1]+1, - p[2]+1] = 1 - diffToNeighbor[:,:,:,i] = ndimage.convolve(microstructure,stencil) # compare ID at each point... + for i,p in enumerate(neighborhood): + stencil = np.zeros((3,3,3),'i') + stencil[1,1,1] = -1 + stencil[p[0]+1, + p[1]+1, + p[2]+1] = 1 + diffToNeighbor[:,:,:,i] = ndimage.convolve(microstructure,stencil) # compare ID at each point... # ...to every one in the specified neighborhood # for same IDs at both locations ==> 0 - diffToNeighbor = np.sort(diffToNeighbor) # sort diff such that number of changes in diff (steps)... + diffToNeighbor = np.sort(diffToNeighbor) # sort diff such that number of changes in diff (steps)... # ...reflects number of unique neighbors - uniques = np.where(diffToNeighbor[1:-1,1:-1,1:-1,0] != 0, 1,0) # initialize unique value counter (exclude myself [= 0]) + uniques = np.where(diffToNeighbor[1:-1,1:-1,1:-1,0] != 0, 1,0) # initialize unique value counter (exclude myself [= 0]) - for i in range(1,len(neighborhood)): # check remaining points in neighborhood - uniques += np.where(np.logical_and( - diffToNeighbor[1:-1,1:-1,1:-1,i] != 0, # not myself? - diffToNeighbor[1:-1,1:-1,1:-1,i] != diffToNeighbor[1:-1,1:-1,1:-1,i-1], - ), # flip of ID difference detected? - 1,0) # count that flip + for i in range(1,len(neighborhood)): # check remaining points in neighborhood + uniques += np.where(np.logical_and( + diffToNeighbor[1:-1,1:-1,1:-1,i] != 0, # not myself? + diffToNeighbor[1:-1,1:-1,1:-1,i] != diffToNeighbor[1:-1,1:-1,1:-1,i-1], + ), # flip of ID difference detected? + 1,0) # count that flip - distance = np.ones((len(feature_list),grid[0],grid[1],grid[2]),'d') + distance = np.ones((len(feature_list),grid[0],grid[1],grid[2]),'d') - for i,feature_id in enumerate(feature_list): - distance[i,:,:,:] = np.where(uniques >= features[feature_id]['aliens'],0.0,1.0) # seed with 0.0 when enough unique neighbor IDs are present - distance[i,:,:,:] = ndimage.morphology.distance_transform_edt(distance[i,:,:,:])*[options.scale]*3 + for i,feature_id in enumerate(feature_list): + distance[i,:,:,:] = np.where(uniques >= features[feature_id]['aliens'],0.0,1.0) # seed with 0.0 when enough unique neighbor IDs are present + distance[i,:,:,:] = ndimage.morphology.distance_transform_edt(distance[i,:,:,:])*[options.scale]*3 - distance = distance.reshape([len(feature_list),grid.prod(),1],order='F') - for i in range(len(feature_list)): - stack.append(distance[i,:]) + distance = distance.reshape([len(feature_list),grid.prod(),1],order='F') -# ------------------------------------------ output result ----------------------------------------- - if len(stack) > 1: table.data = np.hstack(tuple(stack)) - table.data_writeArray('%.12g') + for i,feature in enumerate(feature_list): + table.add('ED_{}({})'.format(features[feature]['names'][0],options.id), + distance[i,:], + scriptID+' '+' '.join(sys.argv[1:])) -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addGaussian.py b/processing/post/addGaussian.py index 9b601a1dc..e43b162da 100755 --- a/processing/post/addGaussian.py +++ b/processing/post/addGaussian.py @@ -30,7 +30,7 @@ parser.add_option('-p','--pos','--periodiccellcenter', type = 'string', metavar = 'string', help = 'label of coordinates [%default]') parser.add_option('-s','--scalar', - dest = 'scalar', + dest = 'labels', action = 'extend', metavar = '', help = 'label(s) of scalar field values') parser.add_option('-o','--order', @@ -56,78 +56,21 @@ parser.set_defaults(pos = 'pos', ) (options,filenames) = parser.parse_args() - -if options.scalar is None: - parser.error('no data column specified.') - -# --- loop over input files ------------------------------------------------------------------------ - if filenames == []: filenames = [None] +if options.labels is None: parser.error('no data column specified.') + for name in filenames: - try: table = damask.ASCIItable(name = name,buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + damask.grid_filters.coord0_check(table.get(options.pos)) - table.head_read() + for label in options.labels: + table.add('Gauss{}({})'.format(options.sigma,label), + ndimage.filters.gaussian_filter(table.get(label).reshape((-1)), + options.sigma,options.order, + mode = 'wrap' if options.periodic else 'nearest'), + scriptID+' '+' '.join(sys.argv[1:])) -# ------------------------------------------ sanity checks ---------------------------------------- - - items = { - 'scalar': {'dim': 1, 'shape': [1], 'labels':options.scalar, 'active':[], 'column': []}, - } - errors = [] - remarks = [] - column = {} - - if table.label_dimension(options.pos) != 3: errors.append('coordinates {} are not a vector.'.format(options.pos)) - else: colCoord = table.label_index(options.pos) - - for type, data in items.items(): - for what in (data['labels'] if data['labels'] is not None else []): - dim = table.label_dimension(what) - if dim != data['dim']: remarks.append('column {} is not a {}.'.format(what,type)) - else: - items[type]['active'].append(what) - items[type]['column'].append(table.label_index(what)) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - for type, data in items.items(): - for label in data['active']: - table.labels_append(['Gauss{}({})'.format(options.sigma,label)]) # extend ASCII header with new labels - table.head_write() - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray() - - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) - -# ------------------------------------------ process value field ----------------------------------- - - stack = [table.data] - for type, data in items.items(): - for i,label in enumerate(data['active']): - stack.append(ndimage.filters.gaussian_filter(table.data[:,data['column'][i]], - options.sigma,options.order, - mode = 'wrap' if options.periodic else 'nearest' - ).reshape([table.data.shape[0],1]) - ) - -# ------------------------------------------ output result ----------------------------------------- - if len(stack) > 1: table.data = np.hstack(tuple(stack)) - table.data_writeArray('%.12g') - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close input ASCII table (works for stdin) + table.to_ASCII(sys.stdout if name is None else name) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index 69e7dd6b1..cd19932ca 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -217,6 +217,19 @@ def cell_coord0_2_DNA(coord0,ordered=True): return (grid,size,origin) +def coord0_check(coord0): + """ + Check whether coordinates lie on a regular grid + + Parameters + ---------- + coord0 : numpy.ndarray + array of undeformed cell coordinates. + + """ + cell_coord0_2_DNA(coord0,ordered=True) + + def node_coord0(grid,size,origin=np.zeros(3)): """ From 5b7139dc220ecd53ec8f928eb8a27ff54942f43a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 19:04:29 +0100 Subject: [PATCH 125/148] using specialized class --- processing/post/addEuclideanDistance.py | 1 + processing/post/addGaussian.py | 2 +- processing/post/averageDown.py | 5 +++-- processing/post/blowUp.py | 5 +++-- python/damask/grid_filters.py | 2 +- python/damask/util.py | 24 ------------------------ 6 files changed, 9 insertions(+), 30 deletions(-) diff --git a/processing/post/addEuclideanDistance.py b/processing/post/addEuclideanDistance.py index d2604b4fc..eaf91b894 100755 --- a/processing/post/addEuclideanDistance.py +++ b/processing/post/addEuclideanDistance.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import itertools diff --git a/processing/post/addGaussian.py b/processing/post/addGaussian.py index e43b162da..5f3ec5d60 100755 --- a/processing/post/addGaussian.py +++ b/processing/post/addGaussian.py @@ -2,9 +2,9 @@ import os import sys +from io import StringIO from optparse import OptionParser -import numpy as np from scipy import ndimage import damask diff --git a/processing/post/averageDown.py b/processing/post/averageDown.py index d94bc8dbd..d4e2a3529 100755 --- a/processing/post/averageDown.py +++ b/processing/post/averageDown.py @@ -65,7 +65,8 @@ for name in filenames: outname = os.path.join(os.path.dirname(name), prefix+os.path.basename(name)) if name else name, buffered = False) - except: continue + except IOError: + continue damask.util.report(scriptName,name) # ------------------------------------------ read header ------------------------------------------ @@ -95,7 +96,7 @@ for name in filenames: table.data_readArray() if (options.grid is None or options.size is None): - grid,size = damask.util.coordGridAndSize(table.data[:,table.label_indexrange(options.pos)]) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.data[:,table.label_indexrange(options.pos)]) else: grid = np.array(options.grid,'i') size = np.array(options.size,'d') diff --git a/processing/post/blowUp.py b/processing/post/blowUp.py index 3dccb1aaf..32cc1909d 100755 --- a/processing/post/blowUp.py +++ b/processing/post/blowUp.py @@ -55,7 +55,8 @@ for name in filenames: outname = os.path.join(os.path.dirname(name), prefix+os.path.basename(name)) if name else name, buffered = False) - except: continue + except IOError: + continue damask.util.report(scriptName,name) # ------------------------------------------ read header ------------------------------------------ @@ -82,7 +83,7 @@ for name in filenames: table.data_readArray(options.pos) table.data_rewind() - grid,size = damask.util.coordGridAndSize(table.data) + grid,size,origin = damask.grid_filters.cell_coord0_2_DNA(table.data) packing = np.array(options.packing,'i') outSize = grid*packing diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index cd19932ca..93e61f5d8 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -219,7 +219,7 @@ def cell_coord0_2_DNA(coord0,ordered=True): def coord0_check(coord0): """ - Check whether coordinates lie on a regular grid + Check whether coordinates lie on a regular grid. Parameters ---------- diff --git a/python/damask/util.py b/python/damask/util.py index cf041f946..558439a4c 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -119,30 +119,6 @@ def execute(cmd, if process.returncode != 0: raise RuntimeError('{} failed with returncode {}'.format(cmd,process.returncode)) return out,error -def coordGridAndSize(coordinates): - """Determines grid count and overall physical size along each dimension of an ordered array of coordinates.""" - dim = coordinates.shape[1] - coords = [np.unique(coordinates[:,i]) for i in range(dim)] - mincorner = np.array(list(map(min,coords))) - maxcorner = np.array(list(map(max,coords))) - grid = np.array(list(map(len,coords)),'i') - size = grid/np.maximum(np.ones(dim,'d'), grid-1.0) * (maxcorner-mincorner) # size from edge to edge = dim * n/(n-1) - size = np.where(grid > 1, size, min(size[grid > 1]/grid[grid > 1])) # spacing for grid==1 equal to smallest among other ones - delta = size/grid - - N = grid.prod() - - if N != len(coordinates): - raise ValueError('Data count {} does not match grid {}.'.format(len(coordinates),' x '.join(map(repr,grid)))) - - if np.any(np.abs(np.log10((coords[0][1:]-coords[0][:-1])/delta[0])) > 0.01) \ - or np.any(np.abs(np.log10((coords[1][1:]-coords[1][:-1])/delta[1])) > 0.01): - raise ValueError('regular grid spacing {} violated.'.format(' x '.join(map(repr,delta)))) - if dim==3 and np.any(np.abs(np.log10((coords[2][1:]-coords[2][:-1])/delta[2])) > 0.01): - raise ValueError('regular grid spacing {} violated.'.format(' x '.join(map(repr,delta)))) - - return grid,size - # ----------------------------- class extendableOption(Option): """ From a7d60dc52a3b016fdaf5bb3f8a7d8c7042e61de6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 19:10:41 +0100 Subject: [PATCH 126/148] not used anymore geom class has own report function --- python/damask/util.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index 558439a4c..0065daba5 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -7,9 +7,6 @@ from optparse import Option from queue import Queue from threading import Thread - -import numpy as np - class bcolors: """ ASCII Colors (Blender code). @@ -64,19 +61,6 @@ def report(who = None, croak( (emph(who)+': ' if who is not None else '') + (what if what is not None else '') + '\n' ) -# ----------------------------- -def report_geom(info, - what = ['grid','size','origin','homogenization','microstructures']): - """Reports (selected) geometry information.""" - output = { - 'grid' : 'grid a b c: {}'.format(' x '.join(list(map(str,info['grid' ])))), - 'size' : 'size x y z: {}'.format(' x '.join(list(map(str,info['size' ])))), - 'origin' : 'origin x y z: {}'.format(' : '.join(list(map(str,info['origin'])))), - 'homogenization' : 'homogenization: {}'.format(info['homogenization']), - 'microstructures' : 'microstructures: {}'.format(info['microstructures']), - } - for item in what: croak(output[item.lower()]) - # ----------------------------- def emph(what): """Formats string with emphasis.""" From 343da2e54d04b5271939302748a20e260d181f45 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 20:18:33 +0100 Subject: [PATCH 127/148] Table class instead of ASCIItable --- processing/post/addCumulative.py | 76 ++++-------------------- processing/post/addDerivative.py | 81 +++++--------------------- processing/post/vtk_pointCloud.py | 35 +---------- processing/post/vtk_rectilinearGrid.py | 47 +-------------- 4 files changed, 31 insertions(+), 208 deletions(-) diff --git a/processing/post/addCumulative.py b/processing/post/addCumulative.py index c94737b94..958c6a70a 100755 --- a/processing/post/addCumulative.py +++ b/processing/post/addCumulative.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -22,79 +23,26 @@ Add cumulative (sum of first to current row) values for given label(s). """, version = scriptID) parser.add_option('-l','--label', - dest='label', + dest='labels', action = 'extend', metavar = '', help = 'columns to cumulate') - parser.add_option('-p','--product', dest='product', action = 'store_true', help = 'product of values instead of sum') (options,filenames) = parser.parse_args() - -if options.label is None: - parser.error('no data column(s) specified.') - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] +if options.labels is None: + parser.error('no data column(s) specified.') + for name in filenames: - try: - table = damask.ASCIItable(name = name, buffered = False) - except IOError: - continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + for label in options.labels: + table.add('cum_{}({})'.format('prod' if options.product else 'sum',label), + np.cumprod(table.get(label),0) if options.product else np.cumsum(table.get(label),0), + scriptID+' '+' '.join(sys.argv[1:])) - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - columns = [] - dims = [] - how = 'prod' if options.product else 'sum' - - for what in options.label: - dim = table.label_dimension(what) - if dim < 0: remarks.append('column {} not found...'.format(what)) - else: - dims.append(dim) - columns.append(table.label_index(what)) - table.labels_append('cum_{}({})'.format(how,what) if dim == 1 else - ['{}_cum_{}({})'.format(i+1,how,what) for i in range(dim)] ) # extend ASCII header with new labels - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header --------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.head_write() - -# ------------------------------------------ process data ------------------------------------------ - mask = [] - for col,dim in zip(columns,dims): mask += range(col,col+dim) # isolate data columns to cumulate - cumulated = np.ones(len(mask)) if options.product else np.zeros(len(mask)) # prepare output field - - outputAlive = True - while outputAlive and table.data_read(): # read next data line of ASCII table - if options.product: - for i,col in enumerate(mask): - cumulated[i] *= float(table.data[col]) # cumulate values (multiplication) - else: - for i,col in enumerate(mask): - cumulated[i] += float(table.data[col]) # cumulate values (addition) - table.data_append(cumulated) - - outputAlive = table.data_write() # output processed line - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/addDerivative.py b/processing/post/addDerivative.py index 8ebfdf2da..4e9410794 100755 --- a/processing/post/addDerivative.py +++ b/processing/post/addDerivative.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import numpy as np @@ -30,7 +31,7 @@ def derivative(coordinates,what): (coordinates[0] - coordinates[1]) result[-1,:] = (what[-1,:] - what[-2,:]) / \ (coordinates[-1] - coordinates[-2]) - + return result @@ -48,78 +49,26 @@ parser.add_option('-c','--coordinates', type = 'string', metavar='string', help = 'heading of coordinate column') parser.add_option('-l','--label', - dest = 'label', + dest = 'labels', action = 'extend', metavar = '', help = 'heading of column(s) to differentiate') (options,filenames) = parser.parse_args() - -if options.coordinates is None: - parser.error('no coordinate column specified.') -if options.label is None: - parser.error('no data column specified.') - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] +if options.coordinates is None: + parser.error('no coordinate column specified.') +if options.labels is None: + parser.error('no data column specified.') + for name in filenames: - try: table = damask.ASCIItable(name = name, - buffered = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# ------------------------------------------ read header ------------------------------------------ + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + for label in options.labels: + table.add('d({})/d({})'.format(label,options.coordinates), + derivative(table.get(options.coordinates),table.get(label)), + scriptID+' '+' '.join(sys.argv[1:])) - table.head_read() - -# ------------------------------------------ sanity checks ---------------------------------------- - - errors = [] - remarks = [] - columns = [] - dims = [] - - if table.label_dimension(options.coordinates) != 1: - errors.append('coordinate column {} is not scalar.'.format(options.coordinates)) - - for what in options.label: - dim = table.label_dimension(what) - if dim < 0: remarks.append('column {} not found...'.format(what)) - else: - dims.append(dim) - columns.append(table.label_index(what)) - table.labels_append('d({})/d({})'.format(what,options.coordinates) if dim == 1 else - ['{}_d({})/d({})'.format(i+1,what,options.coordinates) for i in range(dim)] ) # extend ASCII header with new labels - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - -# ------------------------------------------ assemble header -------------------------------------- - - table.info_append(scriptID + '\t' + ' '.join(sys.argv[1:])) - table.head_write() - -# ------------------------------------------ process data ------------------------------------------ - - table.data_readArray() - - mask = [] - for col,dim in zip(columns,dims): mask += range(col,col+dim) # isolate data columns to differentiate - - differentiated = derivative(table.data[:,table.label_index(options.coordinates)].reshape((len(table.data),1)), - table.data[:,mask]) # calculate numerical derivative - - table.data = np.hstack((table.data,differentiated)) - -# ------------------------------------------ output result ----------------------------------------- - - table.data_writeArray() - -# ------------------------------------------ output finalization ----------------------------------- - - table.close() # close ASCII tables + table.to_ASCII(sys.stdout if name is None else name) diff --git a/processing/post/vtk_pointCloud.py b/processing/post/vtk_pointCloud.py index 06aad0aca..3977358ec 100755 --- a/processing/post/vtk_pointCloud.py +++ b/processing/post/vtk_pointCloud.py @@ -33,49 +33,20 @@ parser.set_defaults(pos = 'pos', ) (options, filenames) = parser.parse_args() - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] for name in filenames: - try: table = damask.ASCIItable(name = name, - buffered = False, - readonly = True) - except: continue damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- - - table.head_read() - - errors = [] - remarks = [] - coordDim = table.label_dimension(options.pos) - if not 3 >= coordDim >= 1: errors.append('coordinates "{}" need to have one, two, or three dimensions.'.format(options.pos)) - elif coordDim < 3: remarks.append('appending {} dimension{} to coordinates "{}"...'.format(3-coordDim, - 's' if coordDim < 2 else '', - options.pos)) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss=True) - continue + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) # ------------------------------------------ process data --------------------------------------- - table.data_readArray(options.pos) - if table.data.shape[1] < 3: - table.data = np.hstack((table.data, - np.zeros((table.data.shape[0], - 3-table.data.shape[1]),dtype='f'))) # fill coords up to 3D with zeros - Polydata = vtk.vtkPolyData() Points = vtk.vtkPoints() Vertices = vtk.vtkCellArray() - for p in table.data: + for p in table.get(options.pos): pointID = Points.InsertNextPoint(p) Vertices.InsertNextCell(1) Vertices.InsertCellPoint(pointID) @@ -104,5 +75,3 @@ for name in filenames: writer.Write() if name is None: sys.stdout.write(writer.GetOutputString()) - - table.close() diff --git a/processing/post/vtk_rectilinearGrid.py b/processing/post/vtk_rectilinearGrid.py index bb29a5d4c..f502f3962 100755 --- a/processing/post/vtk_rectilinearGrid.py +++ b/processing/post/vtk_rectilinearGrid.py @@ -40,48 +40,14 @@ parser.set_defaults(mode = 'cell', ) (options, filenames) = parser.parse_args() - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] for name in filenames: - try: table = damask.ASCIItable(name = name, - buffered = False, - labeled = True, - readonly = True, - ) - except: continue damask.util.report(scriptName,name) -# --- interpret header ---------------------------------------------------------------------------- - - table.head_read() - - remarks = [] - errors = [] - coordDim = table.label_dimension(options.pos) - if not 3 >= coordDim >= 1: errors.append('coordinates "{}" need to have one, two, or three dimensions.'.format(options.pos)) - elif coordDim < 3: remarks.append('appending {} dimension{} to coordinates "{}"...'.format(3-coordDim, - 's' if coordDim < 2 else '', - options.pos)) - - if remarks != []: damask.util.croak(remarks) - if errors != []: - damask.util.croak(errors) - table.close(dismiss=True) - continue - -# --------------- figure out size and grid --------------------------------------------------------- - - table.data_readArray(options.pos) - if table.data.shape[1] < 3: - table.data = np.hstack((table.data, - np.zeros((table.data.shape[0], - 3-table.data.shape[1]),dtype='f'))) # fill coords up to 3D with zeros - - coords = [np.unique(table.data[:,i]) for i in range(3)] + table = damask.Table.from_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + coords = [np.unique(table.get(options.pos)[:,i]) for i in range(3)] if options.mode == 'cell': coords = [0.5 * np.array([3.0 * coords[i][0] - coords[i][0 + int(len(coords[i]) > 1)]] + \ [coords[i][j-1] + coords[i][j] for j in range(1,len(coords[i]))] + \ @@ -90,13 +56,6 @@ for name in filenames: grid = np.array(list(map(len,coords)),'i') N = grid.prod() if options.mode == 'point' else (grid-1).prod() - if N != len(table.data): - errors.append('data count {} does not match grid {}x{}x{}.'.format(N,*(grid - (options.mode == 'cell')) )) - if errors != []: - damask.util.croak(errors) - table.close(dismiss = True) - continue - # ------------------------------------------ process data --------------------------------------- rGrid = vtk.vtkRectilinearGrid() @@ -135,5 +94,3 @@ for name in filenames: writer.Write() if name is None: sys.stdout.write(writer.GetOutputString()) - - table.close() From e08b096f082deaac6cef62d702c469c8f2559557 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 21 Dec 2019 23:43:56 +0100 Subject: [PATCH 128/148] just to make sure ... --- python/tests/test_grid_filters.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index c8442eee4..fdddaf3a1 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -30,18 +30,36 @@ class TestGridFilters: grid = np.random.randint(8,32,(3)) size = np.random.random(3) origin = np.random.random(3) - coord0 = eval('grid_filters.{}_coord0(grid,size,origin)'.format(mode)) # noqa _grid,_size,_origin = eval('grid_filters.{}_coord0_2_DNA(coord0.reshape((-1,3)))'.format(mode)) assert np.allclose(grid,_grid) and np.allclose(size,_size) and np.allclose(origin,_origin) def test_displacement_fluct_equivalence(self): + """Ensure that fluctuations are periodic.""" size = np.random.random(3) grid = np.random.randint(8,32,(3)) F = np.random.random(tuple(grid)+(3,3)) assert np.allclose(grid_filters.node_displacement_fluct(size,F), grid_filters.cell_2_node(grid_filters.cell_displacement_fluct(size,F))) + def test_interpolation_nonperiodic(self): + size = np.random.random(3) + grid = np.random.randint(8,32,(3)) + F = np.random.random(tuple(grid)+(3,3)) + assert np.allclose(grid_filters.node_coord(size,F) [1:-1,1:-1,1:-1],grid_filters.cell_2_node( + grid_filters.cell_coord(size,F))[1:-1,1:-1,1:-1]) + + @pytest.mark.parametrize('mode',[('cell'),('node')]) + def test_coord0_origin(self,mode): + origin= np.random.random(3) + size = np.random.random(3) # noqa + grid = np.random.randint(8,32,(3)) + shifted = eval('grid_filters.{}_coord0(grid,size,origin)'.format(mode)) + unshifted = eval('grid_filters.{}_coord0(grid,size)'.format(mode)) + if mode == 'cell': + assert np.allclose(shifted,unshifted+np.broadcast_to(origin,tuple(grid[::-1]) +(3,))) + elif mode == 'node': + assert np.allclose(shifted,unshifted+np.broadcast_to(origin,tuple(grid[::-1]+1)+(3,))) @pytest.mark.parametrize('mode',[('cell'),('node')]) def test_displacement_avg_vanishes(self,mode): From fe463515d01452e6bc7d864f18dc7e7ba3e2e946 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 22 Dec 2019 07:07:34 +0100 Subject: [PATCH 129/148] following prospector advice --- processing/post/vtk_pointCloud.py | 2 +- processing/post/vtk_rectilinearGrid.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/processing/post/vtk_pointCloud.py b/processing/post/vtk_pointCloud.py index 3977358ec..44f719267 100755 --- a/processing/post/vtk_pointCloud.py +++ b/processing/post/vtk_pointCloud.py @@ -2,10 +2,10 @@ import os import sys +from io import StringIO from optparse import OptionParser import vtk -import numpy as np import damask diff --git a/processing/post/vtk_rectilinearGrid.py b/processing/post/vtk_rectilinearGrid.py index f502f3962..2ccad6319 100755 --- a/processing/post/vtk_rectilinearGrid.py +++ b/processing/post/vtk_rectilinearGrid.py @@ -2,6 +2,7 @@ import os import sys +from io import StringIO from optparse import OptionParser import vtk From cde6853a20073c8ce465d8b488a194a7fb561ae3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 22 Dec 2019 07:20:23 +0100 Subject: [PATCH 130/148] do not repeat code --- processing/post/DADF5_postResults.py | 47 +++++++--------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/processing/post/DADF5_postResults.py b/processing/post/DADF5_postResults.py index efbf84b98..a6dc0b34a 100755 --- a/processing/post/DADF5_postResults.py +++ b/processing/post/DADF5_postResults.py @@ -39,61 +39,36 @@ for filename in options.filenames: results = damask.DADF5(filename) if not results.structured: continue - delta = results.size/results.grid*0.5 - x, y, z = np.meshgrid(np.linspace(delta[2],results.size[2]-delta[2],results.grid[2]), - np.linspace(delta[1],results.size[1]-delta[1],results.grid[1]), - np.linspace(delta[0],results.size[0]-delta[0],results.grid[0]), - indexing = 'ij') - - coords = np.concatenate((z[:,:,:,None],y[:,:,:,None],x[:,:,:,None]),axis = 3) + if results.version_major == 0 and results.version_minor >= 5: + coords = damask.grid_filters.cell_coord0(results.grid,results.size,results.origin) + else: + coords = damask.grid_filters.cell_coord0(results.grid,results.size) N_digits = int(np.floor(np.log10(int(results.increments[-1][3:]))))+1 N_digits = 5 # hack to keep test intact for i,inc in enumerate(results.iter_visible('increments')): print('Output step {}/{}'.format(i+1,len(results.increments))) - header = '1 header\n' - - data = np.array([int(inc[3:]) for j in range(np.product(results.grid))]).reshape([np.product(results.grid),1]) - header+= 'inc' - - coords = coords.reshape([np.product(results.grid),3]) - data = np.concatenate((data,coords),1) - header+=' 1_pos 2_pos 3_pos' + table = damask.Table(np.ones(np.product(results.grid),dtype=int)*int(inc[3:]),{'inc':(1,)}) + table.add('pos',coords.reshape((-1,3))) results.set_visible('materialpoints',False) results.set_visible('constituents', True) for label in options.con: x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0,plain=True) - d = np.product(np.shape(array)[1:]) - data = np.concatenate((data,np.reshape(array,[np.product(results.grid),d])),1) - - if d>1: - header+= ''.join([' {}_{}'.format(j+1,label) for j in range(d)]) - else: - header+=' '+label + if len(x) != 0: + table.add(label,results.read_dataset(x,0,plain=True).reshape((results.grid.prod(),-1))) results.set_visible('constituents', False) results.set_visible('materialpoints',True) for label in options.mat: x = results.get_dataset_location(label) - if len(x) == 0: - continue - array = results.read_dataset(x,0,plain=True) - d = np.product(np.shape(array)[1:]) - data = np.concatenate((data,np.reshape(array,[np.product(results.grid),d])),1) - - if d>1: - header+= ''.join([' {}_{}'.format(j+1,label) for j in range(d)]) - else: - header+=' '+label + if len(x) != 0: + table.add(label,results.read_dataset(x,0,plain=True).reshape((results.grid.prod(),-1))) dirname = os.path.abspath(os.path.join(os.path.dirname(filename),options.dir)) if not os.path.isdir(dirname): os.mkdir(dirname,0o755) file_out = '{}_inc{}.txt'.format(os.path.splitext(os.path.split(filename)[-1])[0], inc[3:].zfill(N_digits)) - np.savetxt(os.path.join(dirname,file_out),data,header=header,comments='') + table.to_ASCII(os.path.join(dirname,file_out)) From b4ae91f81770904682811ac6da62fc4ddc83fd93 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 22 Dec 2019 07:43:07 +0100 Subject: [PATCH 131/148] proper alignment --- env/DAMASK.csh | 2 +- env/DAMASK.sh | 2 +- env/DAMASK.zsh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/env/DAMASK.csh b/env/DAMASK.csh index 1b16e444b..b1b9dfb98 100644 --- a/env/DAMASK.csh +++ b/env/DAMASK.csh @@ -19,7 +19,7 @@ endif # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap -# http://superuser.com/questions/220059/what-parameters-has-ulimit +# http://superuser.com/questions/220059/what-parameters-has-ulimit limit stacksize unlimited # maximum stack size (kB) # disable output in case of scp diff --git a/env/DAMASK.sh b/env/DAMASK.sh index 56696a0e8..50760b76d 100644 --- a/env/DAMASK.sh +++ b/env/DAMASK.sh @@ -47,7 +47,7 @@ PROCESSING=$(type -p postResults || true 2>/dev/null) # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap -# http://superuser.com/questions/220059/what-parameters-has-ulimit +# http://superuser.com/questions/220059/what-parameters-has-ulimit ulimit -s unlimited 2>/dev/null # maximum stack size (kB) # disable output in case of scp diff --git a/env/DAMASK.zsh b/env/DAMASK.zsh index 8ac97fe18..066d56dd6 100644 --- a/env/DAMASK.zsh +++ b/env/DAMASK.zsh @@ -38,7 +38,7 @@ PROCESSING=$(which postResults || true 2>/dev/null) # still, http://software.intel.com/en-us/forums/topic/501500 suggest to fix it # more info https://jblevins.org/log/segfault # https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap -# http://superuser.com/questions/220059/what-parameters-has-ulimit +# http://superuser.com/questions/220059/what-parameters-has-ulimit ulimit -s unlimited 2>/dev/null # maximum stack size (kB) # disable output in case of scp From 48c21045d7e7335b90b1c7633c7a749db0c5fe40 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 22 Dec 2019 09:04:50 +0100 Subject: [PATCH 132/148] centralized functionality for ang import --- processing/misc/ang_toTable.py | 49 ++------- python/damask/table.py | 50 ++++++++- python/tests/reference/Table/simple.ang | 138 ++++++++++++++++++++++++ python/tests/test_Table.py | 11 ++ 4 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 python/tests/reference/Table/simple.ang diff --git a/processing/misc/ang_toTable.py b/processing/misc/ang_toTable.py index 19fdcd55b..5579f2466 100755 --- a/processing/misc/ang_toTable.py +++ b/processing/misc/ang_toTable.py @@ -1,8 +1,10 @@ -#!/usr/bin/env python2.7 -# -*- coding: UTF-8 no BOM -*- +#!/usr/bin/env python3 import os +import sys +from io import StringIO from optparse import OptionParser + import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] @@ -19,47 +21,10 @@ Convert TSL/EDAX *.ang file to ASCIItable """, version = scriptID) (options, filenames) = parser.parse_args() - -# --- loop over input files ------------------------------------------------------------------------- - if filenames == []: filenames = [None] for name in filenames: - try: - table = damask.ASCIItable(name = name, - outname = os.path.splitext(name)[0]+'.txt' if name else name, - buffered = False, labeled = False) - except: continue - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) -# --- interpret header ----------------------------------------------------------------------------- - - table.head_read() - -# --- read comments -------------------------------------------------------------------------------- - - table.info_clear() - while table.data_read(advance = False) and table.line.startswith('#'): # cautiously (non-progressing) read header - table.info_append(table.line) # add comment to info part - table.data_read() # wind forward - - table.labels_clear() - table.labels_append(['1_Euler','2_Euler','3_Euler', - '1_pos','2_pos', - 'IQ','CI','PhaseID','Intensity','Fit', - ], # OIM Analysis 7.2 Manual, p 403 (of 517) - reset = True) - -# ------------------------------------------ assemble header --------------------------------------- - - table.head_write() - -#--- write remainder of data file ------------------------------------------------------------------ - - outputAlive = True - while outputAlive and table.data_read(): - outputAlive = table.data_write() - -# ------------------------------------------ finalize output --------------------------------------- - - table.close() + table = damask.Table.from_ang(StringIO(''.join(sys.stdin.read())) if name is None else name) + table.to_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'.txt') diff --git a/python/damask/table.py b/python/damask/table.py index 5aa74106c..a5ce50237 100644 --- a/python/damask/table.py +++ b/python/damask/table.py @@ -3,6 +3,8 @@ import re import pandas as pd import numpy as np +from . import version + class Table(): """Store spreadsheet-like data.""" @@ -20,7 +22,7 @@ class Table(): Additional, human-readable information. """ - self.comments = [] if comments is None else [c for c in comments] + self.comments = ['table.py v {}'.format(version)] if not comments else [c for c in comments] self.data = pd.DataFrame(data=data) self.shapes = shapes self.__label_condensed() @@ -69,13 +71,16 @@ class Table(): f = open(fname) except TypeError: f = fname + f.seek(0) header,keyword = f.readline().split() if keyword == 'header': header = int(header) else: raise Exception - comments = [f.readline()[:-1] for i in range(1,header)] + + comments = ['table.py:from_ASCII v {}'.format(version)] + comments+= [f.readline()[:-1] for i in range(1,header)] labels = f.readline().split() shapes = {} @@ -95,6 +100,47 @@ class Table(): return Table(data,shapes,comments) + @staticmethod + def from_ang(fname): + """ + Create table from TSL ang file. + + A valid TSL ang file needs to contains the following columns: + * Euler angles (Bunge notation) in radians, 3 floats, label 'eu'. + * Spatial position in meters, 2 floats, label 'pos'. + * Image quality, 1 float, label 'IQ'. + * Confidence index, 1 float, label 'CI'. + * Phase ID, 1 int, label 'ID'. + * SEM signal, 1 float, label 'intensity'. + * Fit, 1 float, label 'fit'. + + Parameters + ---------- + fname : file, str, or pathlib.Path + Filename or file for reading. + + """ + shapes = {'eu':(3,), 'pos':(2,), + 'IQ':(1,), 'CI':(1,), 'ID':(1,), 'intensity':(1,), 'fit':(1,)} + try: + f = open(fname) + except TypeError: + f = fname + f.seek(0) + + content = f.readlines() + + comments = ['table.py:from_ang v {}'.format(version)] + for line in content: + if line.startswith('#'): + comments.append(line.strip()) + else: + break + + data = np.loadtxt(content) + + return Table(data,shapes,comments) + @property def labels(self): return list(self.shapes.keys()) diff --git a/python/tests/reference/Table/simple.ang b/python/tests/reference/Table/simple.ang new file mode 100644 index 000000000..8e009e2dc --- /dev/null +++ b/python/tests/reference/Table/simple.ang @@ -0,0 +1,138 @@ +# TEM_PIXperUM 1.000000 +# x-star 240.000000 +# y-star 240.000000 +# z-star 240.000000 +# WorkingDistance 20.000000 +# +# Phase 1 +# MaterialName Iron(Alpha) +# Formula +# Info +# Symmetry 43 +# LatticeConstants 2.870 2.870 2.870 90.000 90.000 90.000 +# NumberFamilies 100 +# hklFamilies 9223440 0 2 32763 0.000000 32763 +# hklFamilies 0 0 0 9218712 0.000000 9218712 +# hklFamilies 0 0 3801155 0 0.000000 0 +# hklFamilies 5570652 6619251 7536754 -1203738484 0.000000 -1203738484 +# hklFamilies 7143516 5111900 7864421 32763 0.000000 32763 +# hklFamilies 6488180 7274604 6553717 9220480 0.000000 9220480 +# hklFamilies 3145820 2949169 3145777 0 0.000000 0 +# hklFamilies 3014704 7209057 103 9220488 0.000000 9220488 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9220032 0.000000 9220032 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 -1203728363 0.000000 -1203728363 +# hklFamilies 0 0 0 32763 0.000000 32763 +# hklFamilies 0 0 0 9218628 0.000000 9218628 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9218504 0.000000 9218504 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9219904 0.000000 9219904 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 0 -0.000046 0 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 256 0.000000 256 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 -1203753636 0.000000 -1203753636 +# hklFamilies 0 0 0 32763 0.000000 32763 +# hklFamilies 0 0 0 9220576 0.000000 9220576 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9218736 0.000000 9218736 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 103219574 0.000000 103219574 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9220576 0.000000 9220576 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9220692 0.000000 9220692 +# hklFamilies 1434293657 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9218584 0.000000 9218584 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 9219976 0.000000 9219976 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 0 0 256 0.000000 256 +# hklFamilies 0 0 69473872 0 0.000000 0 +# hklFamilies 0 1889785611 -1546188227 -1203753636 -0.000046 -1203753636 +# hklFamilies 9224144 0 1434294456 32763 0.000000 32763 +# hklFamilies 0 9224160 0 9220672 0.000000 9220672 +# hklFamilies -1168390977 32763 851982 0 0.000000 0 +# hklFamilies 0 304 0 9218816 0.000000 9218816 +# hklFamilies 27030208 0 1434297593 0 0.000000 0 +# hklFamilies 0 9224160 0 101654020 0.000000 101654020 +# hklFamilies 9224064 0 0 0 0.000000 0 +# hklFamilies 0 25563456 0 9220672 0.000000 9220672 +# hklFamilies 9224544 0 25559040 0 0.000000 0 +# hklFamilies 0 25559788 0 9220788 0.000000 9220788 +# hklFamilies 176 0 304 24 0.000000 24 +# hklFamilies 0 25562304 0 4 0.000000 4 +# hklFamilies 9224208 0 0 0 0.000000 0 +# hklFamilies 0 281 0 9220032 0.000000 9220032 +# hklFamilies 0 0 0 0 0.000000 0 +# hklFamilies 0 -1168390977 32763 9220660 0.000000 9220660 +# hklFamilies 21 0 -1168390977 8 0.000000 8 +# hklFamilies 32763 2490388 0 24 0.000000 24 +# hklFamilies 48 0 69650048 25 0.000000 25 +# hklFamilies 0 -1216995621 32763 65535 -0.000046 65535 +# hklFamilies 0 0 25562688 1 0.000000 1 +# hklFamilies 0 0 21776 0 -0.000058 0 +# hklFamilies 25562688 0 25559724 0 0.000000 0 +# hklFamilies 0 25559040 0 1179652 0.000000 1179652 +# hklFamilies 25559724 0 25562304 32763 0.000000 32763 +# hklFamilies 0 48 0 9219904 0.000000 9219904 +# hklFamilies 25562304 0 28 0 0.000000 0 +# hklFamilies 0 0 0 8781958 0.000000 8781958 +# hklFamilies 31 0 0 0 0.000000 0 +# hklFamilies 0 0 0 103304392 0.000000 103304392 +# hklFamilies 3 0 48 0 0.000000 0 +# hklFamilies 0 9224505 0 103219694 -0.000046 103219694 +# hklFamilies 27000832 0 -1168393705 0 0.000000 0 +# hklFamilies 32763 25559040 0 9220192 0.000000 9220192 +# hklFamilies 0 32763 31 0 0.000000 0 +# hklFamilies 0 0 0 9219872 0.000000 9219872 +# hklFamilies 69729712 0 9224640 0 0.000000 0 +# hklFamilies 0 69729904 0 1397706823 0.000000 1397706823 +# hklFamilies 69911504 0 0 59 0.000000 59 +# hklFamilies 0 27007968 0 103219200 0.000000 103219200 +# hklFamilies 0 0 -1216843775 0 0.000000 0 +# hklFamilies 32763 69911504 0 0 0.000000 0 +# hklFamilies -1168296496 32763 9225328 0 0.000000 0 +# hklFamilies 0 1434343267 0 9632160 0.000000 9632160 +# hklFamilies 69908840 0 -1216995621 0 0.000000 0 +# hklFamilies 32763 256 0 9632112 0.000000 9632112 +# hklFamilies 0 0 399376220 0 0.000000 0 +# hklFamilies 21776 1966087 4456474 262148 0.000000 262148 +# hklFamilies 9224704 0 1434198234 0 0.000000 0 +# hklFamilies 0 0 0 9704044 0.000000 9704044 +# hklFamilies -1168373699 32763 1 0 0.000000 0 +# hklFamilies 0 69911504 0 94961568 -0.000046 94961568 +# hklFamilies 1 0 69911504 0 0.000000 0 +# hklFamilies 0 10 0 9220016 0.000000 9220016 +# hklFamilies -1 0 27030208 0 0.000000 0 +# hklFamilies 0 1434488087 18 9219992 -0.000046 9219992 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# ElasticConstants 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +# Categories1 1 1 1 1 +# +# GRID: SqrGrid +# XSTEP: 0.050000 +# YSTEP: 0.050000 +# NCOLS_ODD: 2 +# NCOLS_EVEN: 2 +# NROWS: 2 +# +# OPERATOR: +# +# SAMPLEID: +# +# SCANID: +# +0.0 0.0 0.0 0.00 0.00 60.0 20.0 1 2.0 1.5 +0.0 2.0 0.0 0.05 0.00 60.0 20.0 1 2.0 1.5 +0.0 2.0 0.0 0.00 0.05 60.0 20.0 1 2.0 1.5 +0.0 0.0 1.0 0.05 0.05 60.0 20.0 1 2.0 1.5 diff --git a/python/tests/test_Table.py b/python/tests/test_Table.py index 2046d3803..818a55f40 100644 --- a/python/tests/test_Table.py +++ b/python/tests/test_Table.py @@ -47,6 +47,17 @@ class TestTable: new = Table.from_ASCII(f) assert all(default.data==new.data) + def test_read_ang_str(self,reference_dir): + new = Table.from_ang(os.path.join(reference_dir,'simple.ang')) + assert new.data.shape == (4,10) and \ + new.labels == ['eu', 'pos', 'IQ', 'CI', 'ID', 'intensity', 'fit'] + + def test_read_ang_file(self,reference_dir): + f = open(os.path.join(reference_dir,'simple.ang')) + new = Table.from_ang(f) + assert new.data.shape == (4,10) and \ + new.labels == ['eu', 'pos', 'IQ', 'CI', 'ID', 'intensity', 'fit'] + @pytest.mark.parametrize('fname',['datatype-mix.txt','whitespace-mix.txt']) def test_read_strange(self,reference_dir,fname): with open(os.path.join(reference_dir,fname)) as f: From 3a08a8bbe27ec3b6b74bbda5516ec97747f5b6ed Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Fri, 10 Jan 2020 12:07:30 -0500 Subject: [PATCH 133/148] always using intrinsic init when assigning quaternions as output variables --- src/quaternions.f90 | 68 +++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 8efb985ed..e55d3804e 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -142,32 +142,29 @@ type(quaternion) pure function init__(array) real(pReal), intent(in), dimension(4) :: array - init__%w=array(1) - init__%x=array(2) - init__%y=array(3) - init__%z=array(4) + init__%w = array(1) + init__%x = array(2) + init__%y = array(3) + init__%z = array(4) end function init__ !--------------------------------------------------------------------------------------------------- -!> assing a quaternion +!> assigning a quaternion !--------------------------------------------------------------------------------------------------- elemental pure subroutine assign_quat__(self,other) type(quaternion), intent(out) :: self type(quaternion), intent(in) :: other - self%w = other%w - self%x = other%x - self%y = other%y - self%z = other%z - + self = [other%w,other%x,other%y,other%z] + end subroutine assign_quat__ !--------------------------------------------------------------------------------------------------- -!> assing a 4-vector +!> assigning a 4-vector !--------------------------------------------------------------------------------------------------- pure subroutine assign_vec__(self,other) @@ -189,11 +186,8 @@ type(quaternion) elemental pure function add__(self,other) class(quaternion), intent(in) :: self,other - add__%w = self%w + other%w - add__%x = self%x + other%x - add__%y = self%y + other%y - add__%z = self%z + other%z - + add__ = [self%w + other%w,self%x + other%x,self%y + other%y,self%z + other%z] + end function add__ @@ -204,11 +198,8 @@ type(quaternion) elemental pure function pos__(self) class(quaternion), intent(in) :: self - pos__%w = self%w - pos__%x = self%x - pos__%y = self%y - pos__%z = self%z - + pos__ = [self%w,self%x,self%y,self%z] + end function pos__ @@ -219,26 +210,20 @@ type(quaternion) elemental pure function sub__(self,other) class(quaternion), intent(in) :: self,other - sub__%w = self%w - other%w - sub__%x = self%x - other%x - sub__%y = self%y - other%y - sub__%z = self%z - other%z - + sub__ = [self%w - other%w,self%x - other%x,self%y - other%y,self%z - other%z] + end function sub__ !--------------------------------------------------------------------------------------------------- -!> unary positive operator +!> unary negative operator !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function neg__(self) class(quaternion), intent(in) :: self - neg__%w = -self%w - neg__%x = -self%x - neg__%y = -self%y - neg__%z = -self%z - + neg__ = [-self%w,-self%x,-self%y,-self%z] + end function neg__ @@ -258,18 +243,15 @@ end function mul_quat__ !--------------------------------------------------------------------------------------------------- -!> multiplication of quaternions with scalar +!> multiplication of quaternion with scalar !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function mul_scal__(self,scal) class(quaternion), intent(in) :: self real(pReal), intent(in) :: scal - mul_scal__%w = self%w*scal - mul_scal__%x = self%x*scal - mul_scal__%y = self%y*scal - mul_scal__%z = self%z*scal - + mul_scal__ = [self%w,self%x,self%y,self%z]*scal + end function mul_scal__ @@ -418,7 +400,7 @@ type(quaternion) elemental pure function conjg__(a) class(quaternion), intent(in) :: a - conjg__ = quaternion([a%w, -a%x, -a%y, -a%z]) + conjg__ = [a%w, -a%x, -a%y, -a%z] end function conjg__ @@ -430,7 +412,7 @@ type(quaternion) elemental pure function quat_homomorphed(self) class(quaternion), intent(in) :: self - quat_homomorphed = quaternion(-[self%w,self%x,self%y,self%z]) + quat_homomorphed = -[self%w,self%x,self%y,self%z] end function quat_homomorphed @@ -444,6 +426,7 @@ pure function asArray(self) class(quaternion), intent(in) :: self asArray = [self%w,self%x,self%y,self%z] + if (self%w < 0) asArray = -asArray end function asArray @@ -484,6 +467,7 @@ subroutine unitTest type(quaternion) :: q, q_2 call random_number(qu) + if (qu(1) < 0.0_pReal) qu = -qu q = qu q_2 = q + q @@ -492,10 +476,10 @@ subroutine unitTest q_2 = q - q if(any(dNeq0(q_2%asArray()))) call IO_error(401,ext_msg='sub__') - q_2 = q * 5.0_preal + q_2 = q * 5.0_pReal if(any(dNeq(q_2%asArray(),5.0_pReal*qu))) call IO_error(401,ext_msg='mul__') - q_2 = q / 0.5_preal + q_2 = q / 0.5_pReal if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='div__') q_2 = q From 79cafebffe5ae92c029ab8af987c8401ec3ec8f1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 03:08:39 +0100 Subject: [PATCH 134/148] following https://www.python.org/dev/peps/pep-0257/ --- src/quaternions.f90 | 106 ++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index e55d3804e..3df230e31 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -31,7 +31,8 @@ !> @author Marc De Graef, Carnegie Mellon University !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH !> @brief general quaternion math, not limited to unit quaternions -!> @details w is the real part, (x, y, z) are the imaginary parts. +!> @details w is the real part, (x, y, z) are the imaginary parts. +!> @details https://users.aalto.fi/~ssarkka/pub/quat.pdf !--------------------------------------------------------------------------------------------------- module quaternions use prec @@ -117,6 +118,14 @@ module quaternions interface log module procedure log__ end interface log + + interface real + module procedure real__ + end interface real + + interface aimag + module procedure aimag__ + end interface aimag private :: & unitTest @@ -125,18 +134,18 @@ contains !-------------------------------------------------------------------------------------------------- -!> @brief doing self test +!> @brief do self test !-------------------------------------------------------------------------------------------------- subroutine quaternions_init - write(6,'(/,a)') ' <<<+- quaternions init -+>>>' + write(6,'(/,a)') ' <<<+- quaternions init -+>>>'; flush(6) call unitTest end subroutine quaternions_init !--------------------------------------------------------------------------------------------------- -!> constructor for a quaternion from a 4-vector +!> construct a quaternion from a 4-vector !--------------------------------------------------------------------------------------------------- type(quaternion) pure function init__(array) @@ -151,7 +160,7 @@ end function init__ !--------------------------------------------------------------------------------------------------- -!> assigning a quaternion +!> assign a quaternion !--------------------------------------------------------------------------------------------------- elemental pure subroutine assign_quat__(self,other) @@ -164,7 +173,7 @@ end subroutine assign_quat__ !--------------------------------------------------------------------------------------------------- -!> assigning a 4-vector +!> assign a 4-vector !--------------------------------------------------------------------------------------------------- pure subroutine assign_vec__(self,other) @@ -180,7 +189,7 @@ end subroutine assign_vec__ !--------------------------------------------------------------------------------------------------- -!> addition of two quaternions +!> add a quaternion !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function add__(self,other) @@ -192,7 +201,7 @@ end function add__ !--------------------------------------------------------------------------------------------------- -!> unary positive operator +!> return (unary positive operator) !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function pos__(self) @@ -204,7 +213,7 @@ end function pos__ !--------------------------------------------------------------------------------------------------- -!> subtraction of two quaternions +!> subtract a quaternion !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function sub__(self,other) @@ -216,7 +225,7 @@ end function sub__ !--------------------------------------------------------------------------------------------------- -!> unary negative operator +!> negate (unary negative operator) !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function neg__(self) @@ -228,7 +237,7 @@ end function neg__ !--------------------------------------------------------------------------------------------------- -!> multiplication of two quaternions +!> multiply with a quaternion !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function mul_quat__(self,other) @@ -243,7 +252,7 @@ end function mul_quat__ !--------------------------------------------------------------------------------------------------- -!> multiplication of quaternion with scalar +!> multiply with a scalar !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function mul_scal__(self,scal) @@ -256,7 +265,7 @@ end function mul_scal__ !--------------------------------------------------------------------------------------------------- -!> division of two quaternions +!> divide by a quaternion !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function div_quat__(self,other) @@ -268,7 +277,7 @@ end function div_quat__ !--------------------------------------------------------------------------------------------------- -!> divisiont of quaternions by scalar +!> divide by a scalar !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function div_scal__(self,scal) @@ -281,7 +290,7 @@ end function div_scal__ !--------------------------------------------------------------------------------------------------- -!> equality of two quaternions +!> test equality !--------------------------------------------------------------------------------------------------- logical elemental pure function eq__(self,other) @@ -294,7 +303,7 @@ end function eq__ !--------------------------------------------------------------------------------------------------- -!> inequality of two quaternions +!> test inequality !--------------------------------------------------------------------------------------------------- logical elemental pure function neq__(self,other) @@ -306,20 +315,7 @@ end function neq__ !--------------------------------------------------------------------------------------------------- -!> quaternion to the power of a scalar -!--------------------------------------------------------------------------------------------------- -type(quaternion) elemental pure function pow_scal__(self,expon) - - class(quaternion), intent(in) :: self - real(pReal), intent(in) :: expon - - pow_scal__ = exp(log(self)*expon) - -end function pow_scal__ - - -!--------------------------------------------------------------------------------------------------- -!> quaternion to the power of a quaternion +!> raise to the power of a quaternion !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function pow_quat__(self,expon) @@ -332,7 +328,20 @@ end function pow_quat__ !--------------------------------------------------------------------------------------------------- -!> exponential of a quaternion +!> raise to the power of a scalar +!--------------------------------------------------------------------------------------------------- +type(quaternion) elemental pure function pow_scal__(self,expon) + + class(quaternion), intent(in) :: self + real(pReal), intent(in) :: expon + + pow_scal__ = exp(log(self)*expon) + +end function pow_scal__ + + +!--------------------------------------------------------------------------------------------------- +!> take exponential !> ToDo: Lacks any check for invalid operations !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function exp__(self) @@ -340,7 +349,7 @@ type(quaternion) elemental pure function exp__(self) class(quaternion), intent(in) :: self real(pReal) :: absImag - absImag = norm2([self%x, self%y, self%z]) + absImag = norm2(aimag(self)) exp__ = exp(self%w) * [ cos(absImag), & self%x/absImag * sin(absImag), & @@ -351,7 +360,7 @@ end function exp__ !--------------------------------------------------------------------------------------------------- -!> logarithm of a quaternion +!> take logarithm !> ToDo: Lacks any check for invalid operations !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function log__(self) @@ -359,7 +368,7 @@ type(quaternion) elemental pure function log__(self) class(quaternion), intent(in) :: self real(pReal) :: absImag - absImag = norm2([self%x, self%y, self%z]) + absImag = norm2(aimag(self)) log__ = [log(abs(self)), & self%x/absImag * acos(self%w/abs(self)), & @@ -370,7 +379,7 @@ end function log__ !--------------------------------------------------------------------------------------------------- -!> norm of a quaternion +!> return norm !--------------------------------------------------------------------------------------------------- real(pReal) elemental pure function abs__(a) @@ -382,7 +391,7 @@ end function abs__ !--------------------------------------------------------------------------------------------------- -!> dot product of two quaternions +!> calculate dot product !--------------------------------------------------------------------------------------------------- real(pReal) elemental pure function dot_product__(a,b) @@ -394,7 +403,7 @@ end function dot_product__ !--------------------------------------------------------------------------------------------------- -!> conjugate complex of a quaternion +!> take conjugate complex !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function conjg__(a) @@ -406,7 +415,7 @@ end function conjg__ !--------------------------------------------------------------------------------------------------- -!> homomorphed quaternion of a quaternion +!> homomorph !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function quat_homomorphed(self) @@ -418,7 +427,7 @@ end function quat_homomorphed !--------------------------------------------------------------------------------------------------- -!> quaternion as plain array +!> return as plain array !--------------------------------------------------------------------------------------------------- pure function asArray(self) @@ -432,7 +441,7 @@ end function asArray !--------------------------------------------------------------------------------------------------- -!> real part of a quaternion +!> real part (scalar) !--------------------------------------------------------------------------------------------------- pure function real__(self) @@ -445,7 +454,7 @@ end function real__ !--------------------------------------------------------------------------------------------------- -!> imaginary part of a quaternion +!> imaginary part (3-vector) !--------------------------------------------------------------------------------------------------- pure function aimag__(self) @@ -463,37 +472,36 @@ end function aimag__ subroutine unitTest real(pReal), dimension(4) :: qu - type(quaternion) :: q, q_2 call random_number(qu) if (qu(1) < 0.0_pReal) qu = -qu q = qu - + q_2 = q + q if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='add__') - + q_2 = q - q if(any(dNeq0(q_2%asArray()))) call IO_error(401,ext_msg='sub__') - + q_2 = q * 5.0_pReal if(any(dNeq(q_2%asArray(),5.0_pReal*qu))) call IO_error(401,ext_msg='mul__') - + q_2 = q / 0.5_pReal if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='div__') - + q_2 = q if(q_2 /= q) call IO_error(401,ext_msg='eq__') if(any(dNeq(q%asArray(),qu))) call IO_error(401,ext_msg='eq__') if(dNeq(q%real(), qu(1))) call IO_error(401,ext_msg='real()') if(any(dNeq(q%aimag(), qu(2:4)))) call IO_error(401,ext_msg='aimag()') - + q_2 = q%homomorphed() if(q /= q_2* (-1.0_pReal)) call IO_error(401,ext_msg='homomorphed') if(dNeq(q_2%real(), qu(1)* (-1.0_pReal))) call IO_error(401,ext_msg='homomorphed/real') if(any(dNeq(q_2%aimag(),qu(2:4)*(-1.0_pReal)))) call IO_error(401,ext_msg='homomorphed/aimag') - + q_2 = conjg(q) if(dNeq(q_2%real(), q%real())) call IO_error(401,ext_msg='conjg/real') if(any(dNeq(q_2%aimag(),q%aimag()*(-1.0_pReal)))) call IO_error(401,ext_msg='conjg/aimag') From aefd401e8cdda0ad4164f09728595f4c2467d7b1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 03:11:45 +0100 Subject: [PATCH 135/148] this is a quaternion class it is meant to represent any quaternion, not only unit quaternions/rotations that follow a specific convention. Need to check in rotations.f90 where the homomorph should happen --- src/quaternions.f90 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 3df230e31..9a14fa211 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -435,7 +435,6 @@ pure function asArray(self) class(quaternion), intent(in) :: self asArray = [self%w,self%x,self%y,self%z] - if (self%w < 0) asArray = -asArray end function asArray @@ -475,7 +474,7 @@ subroutine unitTest type(quaternion) :: q, q_2 call random_number(qu) - if (qu(1) < 0.0_pReal) qu = -qu + qu = (qu-0.5_pReal) * 2.0_pReal q = qu q_2 = q + q From c7180c329571a952b37bca68953910c82588ef79 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 03:50:17 +0100 Subject: [PATCH 136/148] some more tests for quaternion operations --- src/quaternions.f90 | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 9a14fa211..252135eec 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -475,7 +475,10 @@ subroutine unitTest call random_number(qu) qu = (qu-0.5_pReal) * 2.0_pReal - q = qu + q = quaternion(qu) + + q_2= qu + if(any(dNeq(q%asArray(),q_2%asArray()))) call IO_error(401,ext_msg='assign_vec__') q_2 = q + q if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='add__') @@ -489,8 +492,13 @@ subroutine unitTest q_2 = q / 0.5_pReal if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='div__') + q_2 = q * 0.3_pReal + if(dNeq0(abs(q)) .and. q_2 == q) call IO_error(401,ext_msg='eq__') + q_2 = q - if(q_2 /= q) call IO_error(401,ext_msg='eq__') + if(q_2 /= q) call IO_error(401,ext_msg='neq__') + + if(dNeq(abs(q),norm2(qu))) call IO_error(401,ext_msg='abs__') if(any(dNeq(q%asArray(),qu))) call IO_error(401,ext_msg='eq__') if(dNeq(q%real(), qu(1))) call IO_error(401,ext_msg='real()') @@ -504,6 +512,11 @@ subroutine unitTest q_2 = conjg(q) if(dNeq(q_2%real(), q%real())) call IO_error(401,ext_msg='conjg/real') if(any(dNeq(q_2%aimag(),q%aimag()*(-1.0_pReal)))) call IO_error(401,ext_msg='conjg/aimag') + + if (norm2(aimag(q)) * abs(real(q)) > 0.0_pReal) then + if (dNeq0(abs(q-exp(log(q))),1.0e-12_pReal)) call IO_error(401,ext_msg='exp/log') + if (dNeq0(abs(q-log(exp(q))),1.0e-12_pReal)) call IO_error(401,ext_msg='log/exp') + endif end subroutine unitTest From 115716b8c21df19868bc5f52c53c0ba2ce38724f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 03:58:12 +0100 Subject: [PATCH 137/148] polishing/use existing functions --- src/quaternions.f90 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 252135eec..a49f53007 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -195,7 +195,8 @@ type(quaternion) elemental pure function add__(self,other) class(quaternion), intent(in) :: self,other - add__ = [self%w + other%w,self%x + other%x,self%y + other%y,self%z + other%z] + add__ = [ self%w, self%x, self%y ,self%z] & + + [other%w, other%x, other%y,other%z] end function add__ @@ -207,7 +208,7 @@ type(quaternion) elemental pure function pos__(self) class(quaternion), intent(in) :: self - pos__ = [self%w,self%x,self%y,self%z] + pos__ = self * (+1.0_pReal) end function pos__ @@ -219,7 +220,8 @@ type(quaternion) elemental pure function sub__(self,other) class(quaternion), intent(in) :: self,other - sub__ = [self%w - other%w,self%x - other%x,self%y - other%y,self%z - other%z] + sub__ = [ self%w, self%x, self%y ,self%z] & + - [other%w, other%x, other%y,other%z] end function sub__ @@ -231,7 +233,7 @@ type(quaternion) elemental pure function neg__(self) class(quaternion), intent(in) :: self - neg__ = [-self%w,-self%x,-self%y,-self%z] + neg__ = self * (-1.0_pReal) end function neg__ @@ -333,7 +335,7 @@ end function pow_quat__ type(quaternion) elemental pure function pow_scal__(self,expon) class(quaternion), intent(in) :: self - real(pReal), intent(in) :: expon + real(pReal), intent(in) :: expon pow_scal__ = exp(log(self)*expon) @@ -421,7 +423,7 @@ type(quaternion) elemental pure function quat_homomorphed(self) class(quaternion), intent(in) :: self - quat_homomorphed = -[self%w,self%x,self%y,self%z] + quat_homomorphed = - self end function quat_homomorphed From de95ca590608c150f081e8fa4d9f247feb188118 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 04:15:51 +0100 Subject: [PATCH 138/148] inverse of a quaternion --- src/quaternions.f90 | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index a49f53007..0ce7765e6 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -82,12 +82,13 @@ module quaternions procedure, public :: conjg__ procedure, public :: exp__ procedure, public :: log__ - - procedure, public :: homomorphed => quat_homomorphed - procedure, public :: asArray procedure, public :: real => real__ procedure, public :: aimag => aimag__ + procedure, public :: homomorphed + procedure, public :: asArray + procedure, public :: inverse + end type interface assignment (=) @@ -137,7 +138,6 @@ contains !> @brief do self test !-------------------------------------------------------------------------------------------------- subroutine quaternions_init - write(6,'(/,a)') ' <<<+- quaternions init -+>>>'; flush(6) call unitTest @@ -419,13 +419,13 @@ end function conjg__ !--------------------------------------------------------------------------------------------------- !> homomorph !--------------------------------------------------------------------------------------------------- -type(quaternion) elemental pure function quat_homomorphed(self) +type(quaternion) elemental pure function homomorphed(self) class(quaternion), intent(in) :: self - quat_homomorphed = - self + homomorphed = - self -end function quat_homomorphed +end function homomorphed !--------------------------------------------------------------------------------------------------- @@ -467,6 +467,18 @@ pure function aimag__(self) end function aimag__ +!--------------------------------------------------------------------------------------------------- +!> inverse +!--------------------------------------------------------------------------------------------------- +type(quaternion) elemental pure function inverse(self) + + class(quaternion), intent(in) :: self + + inverse = conjg(self)/abs(self)**2.0_pReal + +end function inverse + + !-------------------------------------------------------------------------------------------------- !> @brief check correctness of (some) quaternions functions !-------------------------------------------------------------------------------------------------- @@ -478,7 +490,7 @@ subroutine unitTest call random_number(qu) qu = (qu-0.5_pReal) * 2.0_pReal q = quaternion(qu) - + q_2= qu if(any(dNeq(q%asArray(),q_2%asArray()))) call IO_error(401,ext_msg='assign_vec__') @@ -515,9 +527,15 @@ subroutine unitTest if(dNeq(q_2%real(), q%real())) call IO_error(401,ext_msg='conjg/real') if(any(dNeq(q_2%aimag(),q%aimag()*(-1.0_pReal)))) call IO_error(401,ext_msg='conjg/aimag') + if(abs(q) > 0.0_pReal) then + q_2 = q * q%inverse() + if( dNeq(real(q_2), 1.0_pReal,1.0e-15_pReal)) call IO_error(401,ext_msg='inverse/real') + if(any(dNeq0(aimag(q_2), 1.0e-15_pReal))) call IO_error(401,ext_msg='inverse/aimag') + endif + if (norm2(aimag(q)) * abs(real(q)) > 0.0_pReal) then - if (dNeq0(abs(q-exp(log(q))),1.0e-12_pReal)) call IO_error(401,ext_msg='exp/log') - if (dNeq0(abs(q-log(exp(q))),1.0e-12_pReal)) call IO_error(401,ext_msg='log/exp') + if (dNeq0(abs(q-exp(log(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='exp/log') + if (dNeq0(abs(q-log(exp(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='log/exp') endif end subroutine unitTest From f02851959765844654bd43265cfd34b1c5cc6a6a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 04:44:30 +0100 Subject: [PATCH 139/148] some facts from wikipedia as tests --- src/quaternions.f90 | 50 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 0ce7765e6..d474f3994 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -79,9 +79,9 @@ module quaternions procedure, public :: abs__ procedure, public :: dot_product__ - procedure, public :: conjg__ procedure, public :: exp__ procedure, public :: log__ + procedure, public :: conjg => conjg__ procedure, public :: real => real__ procedure, public :: aimag => aimag__ @@ -138,6 +138,7 @@ contains !> @brief do self test !-------------------------------------------------------------------------------------------------- subroutine quaternions_init + write(6,'(/,a)') ' <<<+- quaternions init -+>>>'; flush(6) call unitTest @@ -259,7 +260,7 @@ end function mul_quat__ type(quaternion) elemental pure function mul_scal__(self,scal) class(quaternion), intent(in) :: self - real(pReal), intent(in) :: scal + real(pReal), intent(in) :: scal mul_scal__ = [self%w,self%x,self%y,self%z]*scal @@ -284,7 +285,7 @@ end function div_quat__ type(quaternion) elemental pure function div_scal__(self,scal) class(quaternion), intent(in) :: self - real(pReal), intent(in) :: scal + real(pReal), intent(in) :: scal div_scal__ = [self%w,self%x,self%y,self%z]/scal @@ -492,50 +493,57 @@ subroutine unitTest q = quaternion(qu) q_2= qu - if(any(dNeq(q%asArray(),q_2%asArray()))) call IO_error(401,ext_msg='assign_vec__') + if(any(dNeq(q%asArray(),q_2%asArray()))) call IO_error(401,ext_msg='assign_vec__') q_2 = q + q - if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='add__') + if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='add__') q_2 = q - q - if(any(dNeq0(q_2%asArray()))) call IO_error(401,ext_msg='sub__') + if(any(dNeq0(q_2%asArray()))) call IO_error(401,ext_msg='sub__') q_2 = q * 5.0_pReal - if(any(dNeq(q_2%asArray(),5.0_pReal*qu))) call IO_error(401,ext_msg='mul__') + if(any(dNeq(q_2%asArray(),5.0_pReal*qu))) call IO_error(401,ext_msg='mul__') q_2 = q / 0.5_pReal - if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='div__') + if(any(dNeq(q_2%asArray(),2.0_pReal*qu))) call IO_error(401,ext_msg='div__') q_2 = q * 0.3_pReal - if(dNeq0(abs(q)) .and. q_2 == q) call IO_error(401,ext_msg='eq__') + if(dNeq0(abs(q)) .and. q_2 == q) call IO_error(401,ext_msg='eq__') q_2 = q - if(q_2 /= q) call IO_error(401,ext_msg='neq__') + if(q_2 /= q) call IO_error(401,ext_msg='neq__') - if(dNeq(abs(q),norm2(qu))) call IO_error(401,ext_msg='abs__') + if(dNeq(abs(q),norm2(qu))) call IO_error(401,ext_msg='abs__') + if(dNeq(abs(q)**2.0_pReal, real(q*q%conjg()))) call IO_error(401,ext_msg='abs__/*conjg') - if(any(dNeq(q%asArray(),qu))) call IO_error(401,ext_msg='eq__') - if(dNeq(q%real(), qu(1))) call IO_error(401,ext_msg='real()') - if(any(dNeq(q%aimag(), qu(2:4)))) call IO_error(401,ext_msg='aimag()') + if(any(dNeq(q%asArray(),qu))) call IO_error(401,ext_msg='eq__') + if(dNeq(q%real(), qu(1))) call IO_error(401,ext_msg='real()') + if(any(dNeq(q%aimag(), qu(2:4)))) call IO_error(401,ext_msg='aimag()') q_2 = q%homomorphed() - if(q /= q_2* (-1.0_pReal)) call IO_error(401,ext_msg='homomorphed') - if(dNeq(q_2%real(), qu(1)* (-1.0_pReal))) call IO_error(401,ext_msg='homomorphed/real') - if(any(dNeq(q_2%aimag(),qu(2:4)*(-1.0_pReal)))) call IO_error(401,ext_msg='homomorphed/aimag') + if(q /= q_2* (-1.0_pReal)) call IO_error(401,ext_msg='homomorphed') + if(dNeq(q_2%real(), qu(1)* (-1.0_pReal))) call IO_error(401,ext_msg='homomorphed/real') + if(any(dNeq(q_2%aimag(),qu(2:4)*(-1.0_pReal)))) call IO_error(401,ext_msg='homomorphed/aimag') q_2 = conjg(q) - if(dNeq(q_2%real(), q%real())) call IO_error(401,ext_msg='conjg/real') - if(any(dNeq(q_2%aimag(),q%aimag()*(-1.0_pReal)))) call IO_error(401,ext_msg='conjg/aimag') + if(dNeq(abs(q),abs(q_2))) call IO_error(401,ext_msg='conjg/abs') + if(q /= conjg(q_2)) call IO_error(401,ext_msg='conjg/involution') + if(dNeq(q_2%real(), q%real())) call IO_error(401,ext_msg='conjg/real') + if(any(dNeq(q_2%aimag(),q%aimag()*(-1.0_pReal)))) call IO_error(401,ext_msg='conjg/aimag') if(abs(q) > 0.0_pReal) then q_2 = q * q%inverse() if( dNeq(real(q_2), 1.0_pReal,1.0e-15_pReal)) call IO_error(401,ext_msg='inverse/real') if(any(dNeq0(aimag(q_2), 1.0e-15_pReal))) call IO_error(401,ext_msg='inverse/aimag') + + q_2 = q/abs(q) + q_2 = conjg(q_2) - inverse(q_2) + if(any(dNeq0(q_2%asArray(),1.0e-15_pReal))) call IO_error(401,ext_msg='inverse/conjg') endif if (norm2(aimag(q)) * abs(real(q)) > 0.0_pReal) then - if (dNeq0(abs(q-exp(log(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='exp/log') - if (dNeq0(abs(q-log(exp(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='log/exp') + if (dNeq0(abs(q-exp(log(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='exp/log') + if (dNeq0(abs(q-log(exp(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='log/exp') endif end subroutine unitTest From 3a6819f548897d20a04a1df5498b764ee79d5413 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 05:14:17 +0100 Subject: [PATCH 140/148] check for invalid operations --- src/quaternions.f90 | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index d474f3994..bdd44b93b 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -345,7 +345,6 @@ end function pow_scal__ !--------------------------------------------------------------------------------------------------- !> take exponential -!> ToDo: Lacks any check for invalid operations !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function exp__(self) @@ -354,17 +353,18 @@ type(quaternion) elemental pure function exp__(self) absImag = norm2(aimag(self)) - exp__ = exp(self%w) * [ cos(absImag), & - self%x/absImag * sin(absImag), & - self%y/absImag * sin(absImag), & - self%z/absImag * sin(absImag)] + exp__ = merge(exp(self%w) * [ cos(absImag), & + self%x/absImag * sin(absImag), & + self%y/absImag * sin(absImag), & + self%z/absImag * sin(absImag)], & + IEEE_value(1.0_pReal,IEEE_SIGNALING_NAN), & + dNeq0(absImag)) end function exp__ !--------------------------------------------------------------------------------------------------- !> take logarithm -!> ToDo: Lacks any check for invalid operations !--------------------------------------------------------------------------------------------------- type(quaternion) elemental pure function log__(self) @@ -373,10 +373,12 @@ type(quaternion) elemental pure function log__(self) absImag = norm2(aimag(self)) - log__ = [log(abs(self)), & - self%x/absImag * acos(self%w/abs(self)), & - self%y/absImag * acos(self%w/abs(self)), & - self%z/absImag * acos(self%w/abs(self))] + log__ = merge([log(abs(self)), & + self%x/absImag * acos(self%w/abs(self)), & + self%y/absImag * acos(self%w/abs(self)), & + self%z/absImag * acos(self%w/abs(self))], & + IEEE_value(1.0_pReal,IEEE_SIGNALING_NAN), & + dNeq0(absImag)) end function log__ @@ -541,7 +543,7 @@ subroutine unitTest if(any(dNeq0(q_2%asArray(),1.0e-15_pReal))) call IO_error(401,ext_msg='inverse/conjg') endif - if (norm2(aimag(q)) * abs(real(q)) > 0.0_pReal) then + if (norm2(aimag(q)) > 0.0_pReal) then if (dNeq0(abs(q-exp(log(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='exp/log') if (dNeq0(abs(q-log(exp(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='log/exp') endif From 842666cc20a4aafd1608f22e70ab1b152c1f9396 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 11:23:53 +0100 Subject: [PATCH 141/148] no overlap with Marc's code --- src/quaternions.f90 | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index bdd44b93b..886c7d071 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -1,38 +1,9 @@ -! ################################################################### -! Copyright (c) 2013-2015, Marc De Graef/Carnegie Mellon University -! Modified 2017-2019, Martin Diehl/Max-Planck-Institut für Eisenforschung GmbH -! All rights reserved. -! -! Redistribution and use in source and binary forms, with or without modification, are -! permitted provided that the following conditions are met: -! -! - Redistributions of source code must retain the above copyright notice, this list -! of conditions and the following disclaimer. -! - Redistributions in binary form must reproduce the above copyright notice, this -! list of conditions and the following disclaimer in the documentation and/or -! other materials provided with the distribution. -! - Neither the names of Marc De Graef, Carnegie Mellon University nor the names -! of its contributors may be used to endorse or promote products derived from -! this software without specific prior written permission. -! -! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -! ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -! LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -! USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -! ################################################################### - !--------------------------------------------------------------------------------------------------- -!> @author Marc De Graef, Carnegie Mellon University !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH +!> @author Philip Eisenlohr, Michigan State University !> @brief general quaternion math, not limited to unit quaternions !> @details w is the real part, (x, y, z) are the imaginary parts. -!> @details https://users.aalto.fi/~ssarkka/pub/quat.pdf +!> @details https://en.wikipedia.org/wiki/Quaternion !--------------------------------------------------------------------------------------------------- module quaternions use prec From e762cb4dfd5352ac18b2d7f53c926dacac1ad779 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 12:36:35 +0100 Subject: [PATCH 142/148] issue with gfortran < 9 the false branch of merge seems to be evaluated which results in a signaling NaN --- src/quaternions.f90 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index 886c7d071..a67f345a0 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -513,12 +513,14 @@ subroutine unitTest q_2 = conjg(q_2) - inverse(q_2) if(any(dNeq0(q_2%asArray(),1.0e-15_pReal))) call IO_error(401,ext_msg='inverse/conjg') endif - + +#if !(defined(__GFORTRAN__) && __GNUC__ < 9) if (norm2(aimag(q)) > 0.0_pReal) then if (dNeq0(abs(q-exp(log(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='exp/log') if (dNeq0(abs(q-log(exp(q))),1.0e-13_pReal)) call IO_error(401,ext_msg='log/exp') endif - +#endif + end subroutine unitTest From ac112d2d36f783b942186c5d6fa003bb62f194d3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 13:55:56 +0100 Subject: [PATCH 143/148] tolerance needed for optimized code --- src/quaternions.f90 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quaternions.f90 b/src/quaternions.f90 index a67f345a0..0ca404880 100644 --- a/src/quaternions.f90 +++ b/src/quaternions.f90 @@ -487,7 +487,8 @@ subroutine unitTest if(q_2 /= q) call IO_error(401,ext_msg='neq__') if(dNeq(abs(q),norm2(qu))) call IO_error(401,ext_msg='abs__') - if(dNeq(abs(q)**2.0_pReal, real(q*q%conjg()))) call IO_error(401,ext_msg='abs__/*conjg') + if(dNeq(abs(q)**2.0_pReal, real(q*q%conjg()),1.0e-14_pReal)) & + call IO_error(401,ext_msg='abs__/*conjg') if(any(dNeq(q%asArray(),qu))) call IO_error(401,ext_msg='eq__') if(dNeq(q%real(), qu(1))) call IO_error(401,ext_msg='real()') From 300f1b7015cf644e4c175aea43483d651493bff8 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Sat, 11 Jan 2020 11:36:22 -0500 Subject: [PATCH 144/148] added options to return "natural" versions of asQ, asRodrig, and asAxisAngle --- python/damask/orientation.py | 40 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/python/damask/orientation.py b/python/damask/orientation.py index a63444155..a86ba9331 100644 --- a/python/damask/orientation.py +++ b/python/damask/orientation.py @@ -170,9 +170,18 @@ class Rotation: ################################################################################################ # convert to different orientation representations (numpy arrays) - def asQuaternion(self): - """Unit quaternion: (q, p_1, p_2, p_3).""" - return self.quaternion.asArray() + def asQuaternion(self, + quaternion = False): + """ + Unit quaternion [q, p_1, p_2, p_3] unless quaternion == True: damask.quaternion object. + + Parameters + ---------- + quaternion : bool, optional + return quaternion as DAMASK object. + + """ + return self.quaternion if quaternion else self.quaternion.asArray() def asEulers(self, degrees = False): @@ -190,33 +199,36 @@ class Rotation: return eu def asAxisAngle(self, - degrees = False): + degrees = False, + pair = False): """ - Axis angle pair: ([n_1, n_2, n_3], ω). + Axis angle representation [n_1, n_2, n_3, ω] unless pair == True: ([n_1, n_2, n_3], ω). Parameters ---------- degrees : bool, optional return rotation angle in degrees. + pair : bool, optional + return tuple of axis and angle. """ ax = qu2ax(self.quaternion.asArray()) if degrees: ax[3] = np.degrees(ax[3]) - return ax + return (ax[:3],np.degrees(ax[3])) if pair else ax def asMatrix(self): """Rotation matrix.""" return qu2om(self.quaternion.asArray()) def asRodrigues(self, - vector=False): + vector = False): """ - Rodrigues-Frank vector: ([n_1, n_2, n_3], tan(ω/2)). + Rodrigues-Frank vector representation [n_1, n_2, n_3, tan(ω/2)] unless vector == True: [n_1, n_2, n_3] * tan(ω/2). Parameters ---------- vector : bool, optional - return as array of length 3, i.e. scale the unit vector giving the rotation axis. + return as actual Rodrigues--Frank vector, i.e. rotation axis scaled by tan(ω/2). """ ro = qu2ro(self.quaternion.asArray()) @@ -252,8 +264,8 @@ class Rotation: acceptHomomorph = False, P = -1): - qu = quaternion if isinstance(quaternion, np.ndarray) and quaternion.dtype == np.dtype(float) \ - else np.array(quaternion,dtype=float) + qu = quaternion if isinstance(quaternion, np.ndarray) and quaternion.dtype == np.dtype(float) \ + else np.array(quaternion,dtype=float) if P > 0: qu[1:4] *= -1 # convert from P=1 to P=-1 if qu[0] < 0.0: if acceptHomomorph: @@ -1193,9 +1205,9 @@ class Orientation: ref = orientations[0] for o in orientations: closest.append(o.equivalentOrientations( - ref.disorientation(o, - SST = False, # select (o[ther]'s) sym orientation - symmetries = True)[2]).rotation) # with lowest misorientation + ref.disorientation(o, + SST = False, # select (o[ther]'s) sym orientation + symmetries = True)[2]).rotation) # with lowest misorientation return Orientation(Rotation.fromAverage(closest,weights),ref.lattice) From d4535dadb4ea4cf6973fc5f9964bd49fbf33dde3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Jan 2020 20:33:29 +0100 Subject: [PATCH 145/148] use American english --- processing/post/addCompatibilityMismatch.py | 4 ++-- src/mesh_grid.f90 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/processing/post/addCompatibilityMismatch.py b/processing/post/addCompatibilityMismatch.py index 8aff13c53..29e874614 100755 --- a/processing/post/addCompatibilityMismatch.py +++ b/processing/post/addCompatibilityMismatch.py @@ -182,8 +182,8 @@ for name in filenames: nodes = damask.grid_filters.node_coord(size,F) if options.shape: - centres = damask.grid_filters.cell_coord(size,F) - shapeMismatch = shapeMismatch( size,table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),nodes,centres) + centers = damask.grid_filters.cell_coord(size,F) + shapeMismatch = shapeMismatch( size,table.get(options.defgrad).reshape(grid[2],grid[1],grid[0],3,3),nodes,centers) table.add('shapeMismatch(({}))'.format(options.defgrad), shapeMismatch.reshape((-1,1)), scriptID+' '+' '.join(sys.argv[1:])) diff --git a/src/mesh_grid.f90 b/src/mesh_grid.f90 index 3d839e1c9..62ce67397 100644 --- a/src/mesh_grid.f90 +++ b/src/mesh_grid.f90 @@ -296,7 +296,7 @@ end subroutine readGeom !--------------------------------------------------------------------------------------------------- -!> @brief Calculate undeformed position of IPs/cell centres (pretend to be an element) +!> @brief Calculate undeformed position of IPs/cell centers (pretend to be an element) !--------------------------------------------------------------------------------------------------- function IPcoordinates0(grid,geomSize,grid3Offset) From 8f99dd85ac5ec48ba6152e152d234228e491e212 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 11 Jan 2020 22:16:08 +0100 Subject: [PATCH 146/148] [skip ci] updated version information after successful test of v2.0.3-1308-g612e8e34 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 287da9b11..cf29053c3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1294-g034367fa +v2.0.3-1308-g612e8e34 From 96b2b6f602053f0e1d2fc0e9669f291ed9ebcd28 Mon Sep 17 00:00:00 2001 From: Test User Date: Sun, 12 Jan 2020 20:41:01 +0100 Subject: [PATCH 147/148] [skip ci] updated version information after successful test of v2.0.3-1354-gef5a8a3a --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index cf29053c3..7712dccc1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1308-g612e8e34 +v2.0.3-1354-gef5a8a3a From ce1afbeabae1dd6263fcccbad54caaba1aed1f7a Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 13 Jan 2020 01:11:24 +0100 Subject: [PATCH 148/148] [skip ci] updated version information after successful test of v2.0.3-1406-g5fc1abae --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7712dccc1..a7308c0ee 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.3-1354-gef5a8a3a +v2.0.3-1406-g5fc1abae