clarify out-of-place behavior and document return values
This commit is contained in:
parent
26f37d822d
commit
4d67c85a33
2
PRIVATE
2
PRIVATE
|
@ -1 +1 @@
|
|||
Subproject commit 23eac08c9f9638f8dae76710095222d00f948eec
|
||||
Subproject commit d58a002b0a43d240f143aee1396fdc766d87a886
|
|
@ -60,54 +60,6 @@ class ConfigMaterial(Config):
|
|||
return super(ConfigMaterial,cls).load(fname)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_table(table,**kwargs):
|
||||
"""
|
||||
Generate from an ASCII table.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
table : damask.Table
|
||||
Table that contains material information.
|
||||
**kwargs
|
||||
Keyword arguments where the key is the name and the value specifies
|
||||
the label of the data column in the table.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import damask
|
||||
>>> import damask.ConfigMaterial as cm
|
||||
>>> t = damask.Table.load('small.txt')
|
||||
>>> t
|
||||
pos pos pos qu qu qu qu phase homog
|
||||
0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX
|
||||
1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX
|
||||
1 1 1 0 0.8 0.19 0.24 -0.51 Steel SX
|
||||
>>> cm.from_table(t,O='qu',phase='phase',homogenization='homog')
|
||||
material:
|
||||
- constituents:
|
||||
- O: [0.19, 0.8, 0.24, -0.51]
|
||||
v: 1.0
|
||||
phase: Aluminum
|
||||
homogenization: SX
|
||||
- constituents:
|
||||
- O: [0.8, 0.19, 0.24, -0.51]
|
||||
v: 1.0
|
||||
phase: Steel
|
||||
homogenization: SX
|
||||
homogenization: {}
|
||||
phase: {}
|
||||
|
||||
"""
|
||||
kwargs_ = {k:table.get(v) for k,v in kwargs.items()}
|
||||
|
||||
_,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0)
|
||||
idx = np.sort(idx)
|
||||
kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()}
|
||||
|
||||
return ConfigMaterial().material_add(**kwargs_)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def load_DREAM3D(fname,
|
||||
grain_data=None,cell_data=None,cell_ensemble_data='CellEnsembleData',
|
||||
|
@ -181,9 +133,69 @@ class ConfigMaterial(Config):
|
|||
return base_config.material_add(**constituent,homogenization='direct')
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_table(table,**kwargs):
|
||||
"""
|
||||
Generate from an ASCII table.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
table : damask.Table
|
||||
Table that contains material information.
|
||||
**kwargs
|
||||
Keyword arguments where the key is the name and the value specifies
|
||||
the label of the data column in the table.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import damask
|
||||
>>> import damask.ConfigMaterial as cm
|
||||
>>> t = damask.Table.load('small.txt')
|
||||
>>> t
|
||||
pos pos pos qu qu qu qu phase homog
|
||||
0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX
|
||||
1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX
|
||||
1 1 1 0 0.8 0.19 0.24 -0.51 Steel SX
|
||||
>>> cm.from_table(t,O='qu',phase='phase',homogenization='homog')
|
||||
material:
|
||||
- constituents:
|
||||
- O: [0.19, 0.8, 0.24, -0.51]
|
||||
v: 1.0
|
||||
phase: Aluminum
|
||||
homogenization: SX
|
||||
- constituents:
|
||||
- O: [0.8, 0.19, 0.24, -0.51]
|
||||
v: 1.0
|
||||
phase: Steel
|
||||
homogenization: SX
|
||||
homogenization: {}
|
||||
phase: {}
|
||||
|
||||
"""
|
||||
kwargs_ = {k:table.get(v) for k,v in kwargs.items()}
|
||||
|
||||
_,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0)
|
||||
idx = np.sort(idx)
|
||||
kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()}
|
||||
|
||||
return ConfigMaterial().material_add(**kwargs_)
|
||||
|
||||
|
||||
@property
|
||||
def is_complete(self):
|
||||
"""Check for completeness."""
|
||||
"""
|
||||
Check for completeness.
|
||||
|
||||
Only the general file layout is considered.
|
||||
This check does not consider whether parameters for
|
||||
a particular phase/homogenization model are missing.
|
||||
|
||||
Returns
|
||||
-------
|
||||
complete : bool
|
||||
Whether the material.yaml definition is complete.
|
||||
|
||||
"""
|
||||
ok = True
|
||||
for top_level in ['homogenization','phase','material']:
|
||||
ok &= top_level in self
|
||||
|
@ -236,7 +248,19 @@ class ConfigMaterial(Config):
|
|||
|
||||
@property
|
||||
def is_valid(self):
|
||||
"""Check for valid content."""
|
||||
"""
|
||||
Check for valid content.
|
||||
|
||||
Only the generic file content is considered.
|
||||
This check does not consider whether parameters for a
|
||||
particular phase/homogenization mode are out of bounds.
|
||||
|
||||
Returns
|
||||
-------
|
||||
valid : bool
|
||||
Whether the material.yaml definition is valid.
|
||||
|
||||
"""
|
||||
ok = True
|
||||
|
||||
if 'phase' in self:
|
||||
|
@ -282,7 +306,7 @@ class ConfigMaterial(Config):
|
|||
|
||||
Returns
|
||||
-------
|
||||
cfg : damask.ConfigMaterial
|
||||
updated : damask.ConfigMaterial
|
||||
Updated material configuration.
|
||||
|
||||
"""
|
||||
|
@ -311,7 +335,7 @@ class ConfigMaterial(Config):
|
|||
|
||||
Returns
|
||||
-------
|
||||
cfg : damask.ConfigMaterial
|
||||
updated : damask.ConfigMaterial
|
||||
Updated material configuration.
|
||||
|
||||
"""
|
||||
|
@ -336,7 +360,7 @@ class ConfigMaterial(Config):
|
|||
|
||||
Returns
|
||||
-------
|
||||
cfg : damask.ConfigMaterial
|
||||
updated : damask.ConfigMaterial
|
||||
Updated material configuration.
|
||||
|
||||
Examples
|
||||
|
|
|
@ -57,11 +57,15 @@ def _empty_like(dataset,N_materialpoints,fill_float,fill_int):
|
|||
|
||||
class Result:
|
||||
"""
|
||||
Manipulate and read DADF5 files.
|
||||
Add data to and export from DADF5 files.
|
||||
|
||||
DADF5 (DAMASK HDF5) files contain DAMASK results.
|
||||
The group/folder structure reflects the input data
|
||||
in material.yaml.
|
||||
Their group/folder structure reflects the input data in material.yaml.
|
||||
|
||||
This class provides a custom view on the DADF5 file.
|
||||
Upon initialization, all attributes are visible.
|
||||
Derived quantities can be added to the file and existing data can be exported based on the current view.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,fname):
|
||||
|
@ -274,6 +278,11 @@ class Result:
|
|||
Name of datasets; supports '?' and '*' wildcards.
|
||||
True is equivalent to '*', False is equivalent to [].
|
||||
|
||||
Returns
|
||||
-------
|
||||
view : damask.Result
|
||||
View with where selected attributes are visible.
|
||||
|
||||
"""
|
||||
return self._manage_view('set',what,datasets)
|
||||
|
||||
|
@ -290,6 +299,11 @@ class Result:
|
|||
Name of datasets; supports '?' and '*' wildcards.
|
||||
True is equivalent to '*', False is equivalent to [].
|
||||
|
||||
Returns
|
||||
-------
|
||||
modified_view : damask.Result
|
||||
View with more visible attributes.
|
||||
|
||||
"""
|
||||
return self._manage_view('add',what,datasets)
|
||||
|
||||
|
@ -306,6 +320,11 @@ class Result:
|
|||
Name of datasets; supports '?' and '*' wildcards.
|
||||
True is equivalent to '*', False is equivalent to [].
|
||||
|
||||
Returns
|
||||
-------
|
||||
modified_view : damask.Result
|
||||
View with less visible attributes.
|
||||
|
||||
"""
|
||||
return self._manage_view('del',what,datasets)
|
||||
|
||||
|
@ -372,7 +391,7 @@ class Result:
|
|||
|
||||
@property
|
||||
def coordinates0_point(self):
|
||||
"""Return initial coordinates of the cell centers."""
|
||||
"""Initial/undeformed cell center coordinates."""
|
||||
if self.structured:
|
||||
return grid_filters.coordinates0_point(self.cells,self.size,self.origin).reshape(-1,3,order='F')
|
||||
else:
|
||||
|
@ -381,7 +400,7 @@ class Result:
|
|||
|
||||
@property
|
||||
def coordinates0_node(self):
|
||||
"""Return initial coordinates of the cell centers."""
|
||||
"""Initial/undeformed nodal coordinates."""
|
||||
if self.structured:
|
||||
return grid_filters.coordinates0_node(self.cells,self.size,self.origin).reshape(-1,3,order='F')
|
||||
else:
|
||||
|
@ -390,6 +409,7 @@ class Result:
|
|||
|
||||
@property
|
||||
def geometry0(self):
|
||||
"""Initial/undeformed geometry."""
|
||||
if self.structured:
|
||||
return VTK.from_rectilinear_grid(self.cells,self.size,self.origin)
|
||||
else:
|
||||
|
|
|
@ -223,6 +223,11 @@ class Table:
|
|||
fname : file, str, or pathlib.Path
|
||||
Filename or file for reading.
|
||||
|
||||
Returns
|
||||
-------
|
||||
loaded : damask.Table
|
||||
Table data from file.
|
||||
|
||||
"""
|
||||
try:
|
||||
f = open(fname)
|
||||
|
@ -275,6 +280,11 @@ class Table:
|
|||
fname : file, str, or pathlib.Path
|
||||
Filename or file for reading.
|
||||
|
||||
Returns
|
||||
-------
|
||||
loaded : damask.Table
|
||||
Table data from file.
|
||||
|
||||
"""
|
||||
try:
|
||||
f = open(fname)
|
||||
|
@ -334,14 +344,14 @@ class Table:
|
|||
----------
|
||||
label : str
|
||||
Column label.
|
||||
data : np.ndarray
|
||||
data : numpy.ndarray
|
||||
New data.
|
||||
info : str, optional
|
||||
Human-readable information about the new data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
updated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
|
@ -367,14 +377,14 @@ class Table:
|
|||
----------
|
||||
label : str
|
||||
Column label.
|
||||
data : np.ndarray
|
||||
data : numpy.ndarray
|
||||
Modified data.
|
||||
info : str, optional
|
||||
Human-readable information about the modified data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
|
@ -402,7 +412,7 @@ class Table:
|
|||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
|
@ -425,7 +435,7 @@ class Table:
|
|||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
|
@ -451,7 +461,7 @@ class Table:
|
|||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
|
@ -479,13 +489,13 @@ class Table:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
other : Table
|
||||
other : damask.Table
|
||||
Table to append.
|
||||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
Concatenated table.
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
if self.shapes != other.shapes or not self.data.columns.equals(other.data.columns):
|
||||
|
@ -504,13 +514,13 @@ class Table:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
other : Table
|
||||
other : damask.Table
|
||||
Table to join.
|
||||
|
||||
Returns
|
||||
-------
|
||||
table : Table
|
||||
Joined table.
|
||||
udated : damask.Table
|
||||
Updated table.
|
||||
|
||||
"""
|
||||
if set(self.shapes) & set(other.shapes) or self.data.shape[0] != other.data.shape[0]:
|
||||
|
|
|
@ -5,11 +5,12 @@ Notes
|
|||
-----
|
||||
The grids are defined as (x,y,z,...) where x is fastest and z is slowest.
|
||||
This convention is consistent with the layout in grid vtr files.
|
||||
|
||||
When converting to/from a plain list (e.g. storage in ASCII table),
|
||||
the following operations are required for tensorial data:
|
||||
|
||||
D3 = D1.reshape(cells+(-1,),order='F').reshape(cells+(3,3))
|
||||
D1 = D3.reshape(cells+(-1,)).reshape(-1,9,order='F')
|
||||
- D3 = D1.reshape(cells+(-1,),order='F').reshape(cells+(3,3))
|
||||
- D1 = D3.reshape(cells+(-1,)).reshape(-1,9,order='F')
|
||||
|
||||
"""
|
||||
from scipy import spatial as _spatial
|
||||
|
@ -29,10 +30,12 @@ def _ks(size,cells,first_order=False):
|
|||
Correction for first order derivatives, defaults to False.
|
||||
|
||||
"""
|
||||
k_sk = _np.where(_np.arange(cells[0])>cells[0]//2,_np.arange(cells[0])-cells[0],_np.arange(cells[0]))/size[0]
|
||||
k_sk = _np.where(_np.arange(cells[0])>cells[0]//2,
|
||||
_np.arange(cells[0])-cells[0],_np.arange(cells[0]))/size[0]
|
||||
if cells[0]%2 == 0 and first_order: k_sk[cells[0]//2] = 0 # Nyquist freq=0 for even cells (Johnson, MIT, 2011)
|
||||
|
||||
k_sj = _np.where(_np.arange(cells[1])>cells[1]//2,_np.arange(cells[1])-cells[1],_np.arange(cells[1]))/size[1]
|
||||
k_sj = _np.where(_np.arange(cells[1])>cells[1]//2,
|
||||
_np.arange(cells[1])-cells[1],_np.arange(cells[1]))/size[1]
|
||||
if cells[1]%2 == 0 and first_order: k_sj[cells[1]//2] = 0 # Nyquist freq=0 for even cells (Johnson, MIT, 2011)
|
||||
|
||||
k_si = _np.arange(cells[2]//2+1)/size[2]
|
||||
|
@ -40,74 +43,89 @@ def _ks(size,cells,first_order=False):
|
|||
return _np.stack(_np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij'), axis=-1)
|
||||
|
||||
|
||||
def curl(size,field):
|
||||
"""
|
||||
def curl(size,f):
|
||||
u"""
|
||||
Calculate curl of a vector or tensor field in Fourier space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : numpy.ndarray of shape (3)
|
||||
Physical size of the periodic field.
|
||||
field : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3)
|
||||
f : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3)
|
||||
Periodic field of which the curl is calculated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
∇ × f : numpy.ndarray
|
||||
Curl of f.
|
||||
|
||||
"""
|
||||
n = _np.prod(field.shape[3:])
|
||||
k_s = _ks(size,field.shape[:3],True)
|
||||
n = _np.prod(f.shape[3:])
|
||||
k_s = _ks(size,f.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
|
||||
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
|
||||
f_fourier = _np.fft.rfftn(f,axes=(0,1,2))
|
||||
curl_ = (_np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,f_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 3
|
||||
_np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,f_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3x3
|
||||
|
||||
return _np.fft.irfftn(curl_,axes=(0,1,2),s=field.shape[:3])
|
||||
return _np.fft.irfftn(curl_,axes=(0,1,2),s=f.shape[:3])
|
||||
|
||||
|
||||
def divergence(size,field):
|
||||
"""
|
||||
def divergence(size,f):
|
||||
u"""
|
||||
Calculate divergence of a vector or tensor field in Fourier space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : numpy.ndarray of shape (3)
|
||||
Physical size of the periodic field.
|
||||
field : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3)
|
||||
f : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3)
|
||||
Periodic field of which the divergence is calculated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
∇ · f : numpy.ndarray
|
||||
Divergence of f.
|
||||
|
||||
"""
|
||||
n = _np.prod(field.shape[3:])
|
||||
k_s = _ks(size,field.shape[:3],True)
|
||||
n = _np.prod(f.shape[3:])
|
||||
k_s = _ks(size,f.shape[:3],True)
|
||||
|
||||
field_fourier = _np.fft.rfftn(field,axes=(0,1,2))
|
||||
div_ = (_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
|
||||
f_fourier = _np.fft.rfftn(f,axes=(0,1,2))
|
||||
div_ = (_np.einsum('ijkl,ijkl ->ijk', k_s,f_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 1
|
||||
_np.einsum('ijkm,ijklm->ijkl',k_s,f_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3
|
||||
|
||||
return _np.fft.irfftn(div_,axes=(0,1,2),s=field.shape[:3])
|
||||
return _np.fft.irfftn(div_,axes=(0,1,2),s=f.shape[:3])
|
||||
|
||||
|
||||
def gradient(size,field):
|
||||
"""
|
||||
Calculate gradient of a scalar or vector field in Fourier space.
|
||||
def gradient(size,f):
|
||||
u"""
|
||||
Calculate gradient of a scalar or vector fieldin Fourier space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : numpy.ndarray of shape (3)
|
||||
Physical size of the periodic field.
|
||||
field : numpy.ndarray of shape (:,:,:,1) or (:,:,:,3)
|
||||
f : numpy.ndarray of shape (:,:,:,1) or (:,:,:,3)
|
||||
Periodic field of which the gradient is calculated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
∇ f : numpy.ndarray
|
||||
Divergence of f.
|
||||
|
||||
"""
|
||||
n = _np.prod(field.shape[3:])
|
||||
k_s = _ks(size,field.shape[:3],True)
|
||||
n = _np.prod(f.shape[3:])
|
||||
k_s = _ks(size,f.shape[:3],True)
|
||||
|
||||
field_fourier = _np.fft.rfftn(field,axes=(0,1,2))
|
||||
grad_ = (_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
|
||||
f_fourier = _np.fft.rfftn(f,axes=(0,1,2))
|
||||
grad_ = (_np.einsum('ijkl,ijkm->ijkm', f_fourier,k_s)*2.0j*_np.pi if n == 1 else # scalar, 1 -> 3
|
||||
_np.einsum('ijkl,ijkm->ijklm',f_fourier,k_s)*2.0j*_np.pi) # vector, 3 -> 3x3
|
||||
|
||||
return _np.fft.irfftn(grad_,axes=(0,1,2),s=field.shape[:3])
|
||||
return _np.fft.irfftn(grad_,axes=(0,1,2),s=f.shape[:3])
|
||||
|
||||
|
||||
def coordinates0_point(cells,size,origin=_np.zeros(3)):
|
||||
|
@ -123,6 +141,11 @@ def coordinates0_point(cells,size,origin=_np.zeros(3)):
|
|||
origin : numpy.ndarray, optional
|
||||
Physical origin of the periodic field. Defaults to [0.0,0.0,0.0].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_p_0 : numpy.ndarray
|
||||
Undeformed cell center coordinates.
|
||||
|
||||
"""
|
||||
start = origin + size/cells*.5
|
||||
end = origin + size - size/cells*.5
|
||||
|
@ -144,6 +167,11 @@ def displacement_fluct_point(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_p_fluct : numpy.ndarray
|
||||
Fluctuating part of the cell center displacements.
|
||||
|
||||
"""
|
||||
integrator = 0.5j*size/_np.pi
|
||||
|
||||
|
@ -171,6 +199,11 @@ def displacement_avg_point(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_p_avg : numpy.ndarray
|
||||
Average part of the cell center displacements.
|
||||
|
||||
"""
|
||||
F_avg = _np.average(F,axis=(0,1,2))
|
||||
return _np.einsum('ml,ijkl->ijkm',F_avg - _np.eye(3),coordinates0_point(F.shape[:3],size))
|
||||
|
@ -187,6 +220,11 @@ def displacement_point(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_p : numpy.ndarray
|
||||
Cell center displacements.
|
||||
|
||||
"""
|
||||
return displacement_avg_point(size,F) + displacement_fluct_point(size,F)
|
||||
|
||||
|
@ -204,6 +242,11 @@ def coordinates_point(size,F,origin=_np.zeros(3)):
|
|||
origin : numpy.ndarray of shape (3), optional
|
||||
Physical origin of the periodic field. Defaults to [0.0,0.0,0.0].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_p : numpy.ndarray
|
||||
Cell center coordinates.
|
||||
|
||||
"""
|
||||
return coordinates0_point(F.shape[:3],size,origin) + displacement_point(size,F)
|
||||
|
||||
|
@ -252,19 +295,6 @@ def cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True):
|
|||
return (cells,size,origin)
|
||||
|
||||
|
||||
def coordinates0_check(coordinates0):
|
||||
"""
|
||||
Check whether coordinates lie on a regular grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coordinates0 : numpy.ndarray
|
||||
Array of undeformed cell coordinates.
|
||||
|
||||
"""
|
||||
cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True)
|
||||
|
||||
|
||||
def coordinates0_node(cells,size,origin=_np.zeros(3)):
|
||||
"""
|
||||
Nodal positions (undeformed).
|
||||
|
@ -278,6 +308,11 @@ def coordinates0_node(cells,size,origin=_np.zeros(3)):
|
|||
origin : numpy.ndarray of shape (3), optional
|
||||
Physical origin of the periodic field. Defaults to [0.0,0.0,0.0].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_n_0 : numpy.ndarray
|
||||
Undeformed nodal coordinates.
|
||||
|
||||
"""
|
||||
return _np.stack(_np.meshgrid(_np.linspace(origin[0],size[0]+origin[0],cells[0]+1),
|
||||
_np.linspace(origin[1],size[1]+origin[1],cells[1]+1),
|
||||
|
@ -296,6 +331,11 @@ def displacement_fluct_node(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_n_fluct : numpy.ndarray
|
||||
Fluctuating part of the nodal displacements.
|
||||
|
||||
"""
|
||||
return point_to_node(displacement_fluct_point(size,F))
|
||||
|
||||
|
@ -311,6 +351,11 @@ def displacement_avg_node(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_n_avg : numpy.ndarray
|
||||
Average part of the nodal displacements.
|
||||
|
||||
"""
|
||||
F_avg = _np.average(F,axis=(0,1,2))
|
||||
return _np.einsum('ml,ijkl->ijkm',F_avg - _np.eye(3),coordinates0_node(F.shape[:3],size))
|
||||
|
@ -327,6 +372,11 @@ def displacement_node(size,F):
|
|||
F : numpy.ndarray
|
||||
Deformation gradient field.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_p : numpy.ndarray
|
||||
Nodal displacements.
|
||||
|
||||
"""
|
||||
return displacement_avg_node(size,F) + displacement_fluct_node(size,F)
|
||||
|
||||
|
@ -344,28 +394,15 @@ def coordinates_node(size,F,origin=_np.zeros(3)):
|
|||
origin : numpy.ndarray of shape (3), optional
|
||||
Physical origin of the periodic field. Defaults to [0.0,0.0,0.0].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_n : numpy.ndarray
|
||||
Nodal coordinates.
|
||||
|
||||
"""
|
||||
return coordinates0_node(F.shape[:3],size,origin) + displacement_node(size,F)
|
||||
|
||||
|
||||
def point_to_node(cell_data):
|
||||
"""Interpolate periodic point 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')
|
||||
|
||||
|
||||
def node_2_point(node_data):
|
||||
"""Interpolate periodic nodal data to point 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:]
|
||||
|
||||
|
||||
def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True):
|
||||
"""
|
||||
Return grid 'DNA', i.e. cells, size, and origin from 1D array of nodal positions.
|
||||
|
@ -402,6 +439,66 @@ def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True):
|
|||
return (cells,size,origin)
|
||||
|
||||
|
||||
def point_to_node(cell_data):
|
||||
"""
|
||||
Interpolate periodic point data to nodal data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cell_data : numpy.ndarray of shape (:,:,:,...)
|
||||
Data defined on the cell centers of a periodic grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
node_data : numpy.ndarray of shape (:,:,:,...)
|
||||
Data defined on the nodes of a periodic grid.
|
||||
"""
|
||||
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')
|
||||
|
||||
|
||||
def node_to_point(node_data):
|
||||
"""
|
||||
Interpolate periodic nodal data to point data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
node_data : numpy.ndarray of shape (:,:,:,...)
|
||||
Data defined on the nodes of a periodic grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cell_data : numpy.ndarray of shape (:,:,:,...)
|
||||
Data defined on the cell centers of a periodic grid.
|
||||
|
||||
"""
|
||||
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:]
|
||||
|
||||
|
||||
def coordinates0_valid(coordinates0):
|
||||
"""
|
||||
Check whether coordinates lie on a regular grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coordinates0 : numpy.ndarray
|
||||
Array of undeformed cell coordinates.
|
||||
|
||||
"""
|
||||
try:
|
||||
cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def regrid(size,F,cells):
|
||||
"""
|
||||
Return mapping from coordinates in deformed configuration to a regular grid.
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
"""Finite-strain continuum mechanics."""
|
||||
"""
|
||||
Finite-strain continuum mechanics.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Collection of routines to operate on numpy.ndarrays of shape (...,3,3).
|
||||
|
||||
"""
|
||||
|
||||
from . import tensor as _tensor
|
||||
from . import _rotation
|
||||
|
@ -154,7 +161,6 @@ def strain(F,t,m):
|
|||
return eps
|
||||
|
||||
|
||||
|
||||
def stress_Cauchy(P,F):
|
||||
"""
|
||||
Calculate the Cauchy stress (true stress).
|
||||
|
|
|
@ -24,6 +24,11 @@ def from_random(size,N_seeds,cells=None,rng_seed=None):
|
|||
A seed to initialize the BitGenerator. Defaults to None.
|
||||
If None, then fresh, unpredictable entropy will be pulled from the OS.
|
||||
|
||||
Returns
|
||||
-------
|
||||
new : numpy.ndarray of shape (N_seeds,3)
|
||||
Coordinates in 3D space.
|
||||
|
||||
"""
|
||||
rng = _np.random.default_rng(rng_seed)
|
||||
if cells is None:
|
||||
|
@ -56,6 +61,11 @@ def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,rng_seed=
|
|||
A seed to initialize the BitGenerator. Defaults to None.
|
||||
If None, then fresh, unpredictable entropy will be pulled from the OS.
|
||||
|
||||
Returns
|
||||
-------
|
||||
new : numpy.ndarray of shape (N_seeds,3)
|
||||
Coordinates in 3D space.
|
||||
|
||||
"""
|
||||
rng = _np.random.default_rng(rng_seed)
|
||||
coords = _np.empty((N_seeds,3))
|
||||
|
@ -94,6 +104,11 @@ def from_grid(grid,selection=None,invert=False,average=False,periodic=True):
|
|||
periodic : boolean, optional
|
||||
Center of gravity with periodic boundaries.
|
||||
|
||||
Returns
|
||||
-------
|
||||
new : numpy.ndarray of shape (...,3)
|
||||
Coordinates in 3D space.
|
||||
|
||||
"""
|
||||
material = grid.material.reshape((-1,1),order='F')
|
||||
mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \
|
||||
|
|
|
@ -60,7 +60,7 @@ class TestGridFilters:
|
|||
cell_field_x = np.interp(coordinates0_point_x,coordinates_node_x,node_field_x,period=np.pi*2.)
|
||||
cell_field = np.broadcast_to(cell_field_x.reshape(-1,1,1),cells)
|
||||
|
||||
assert np.allclose(cell_field,grid_filters.node_2_point(node_field))
|
||||
assert np.allclose(cell_field,grid_filters.node_to_point(node_field))
|
||||
|
||||
@pytest.mark.parametrize('mode',['point','node'])
|
||||
def test_coordinates0_origin(self,mode):
|
||||
|
@ -93,14 +93,24 @@ class TestGridFilters:
|
|||
F = np.broadcast_to(np.random.random((3,3)), tuple(cells)+(3,3))
|
||||
assert np.allclose(function(size,F),0.0)
|
||||
|
||||
@pytest.mark.parametrize('function',[grid_filters.coordinates0_check,
|
||||
grid_filters.cellsSizeOrigin_coordinates0_node,
|
||||
grid_filters.cellsSizeOrigin_coordinates0_point])
|
||||
@pytest.mark.parametrize('function',[grid_filters.cellsSizeOrigin_coordinates0_point,
|
||||
grid_filters.cellsSizeOrigin_coordinates0_node])
|
||||
def test_invalid_coordinates(self,function):
|
||||
invalid_coordinates = np.random.random((np.random.randint(12,52),3))
|
||||
with pytest.raises(ValueError):
|
||||
function(invalid_coordinates)
|
||||
|
||||
@pytest.mark.parametrize('function',[grid_filters.coordinates0_point,
|
||||
grid_filters.coordinates0_node])
|
||||
def test_valid_coordinates_check(self,function):
|
||||
valid_coordinates = function(np.random.randint(4,10,(3)),np.random.rand(3))
|
||||
assert grid_filters.coordinates0_valid(valid_coordinates.reshape(-1,3,order='F'))
|
||||
|
||||
def test_invalid_coordinates_check(self):
|
||||
invalid_coordinates = np.random.random((np.random.randint(12,52),3))
|
||||
assert not grid_filters.coordinates0_valid(invalid_coordinates)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('function',[grid_filters.cellsSizeOrigin_coordinates0_node,
|
||||
grid_filters.cellsSizeOrigin_coordinates0_point])
|
||||
def test_uneven_spaced_coordinates(self,function):
|
||||
|
|
Loading…
Reference in New Issue