diff --git a/.gitattributes b/.gitattributes index b096658f8..c2785cf15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ *.jpg binary *.hdf5 binary *.pdf binary +*.dream3d binary # ignore files from MSC.Marc in language statistics installation/mods_MarcMentat/20*/* linguist-vendored diff --git a/processing/pre/geom_fromDREAM3D.py b/processing/pre/geom_fromDREAM3D.py index daf7d2ab9..54ed4ca0a 100755 --- a/processing/pre/geom_fromDREAM3D.py +++ b/processing/pre/geom_fromDREAM3D.py @@ -1,12 +1,8 @@ #!/usr/bin/env python3 import os -import sys from optparse import OptionParser -import h5py -import numpy as np - import damask @@ -64,88 +60,12 @@ parser.set_defaults(pointwise = 'CellData', if options.basegroup is None: parser.error('No base group selected') -rootDir ='DataContainers' - - if filenames == []: parser.error('no input file specified.') for name in filenames: - damask.util.report(scriptName,name) + damask.util.report(scriptName,name) - errors = [] + geom = damask.Geom.load_DREAM3D(name,options.basegroup,options.pointwise) + damask.util.croak(geom) - inFile = h5py.File(name, 'r') - group_geom = os.path.join(rootDir,options.basegroup,'_SIMPL_GEOMETRY') - try: - size = inFile[os.path.join(group_geom,'DIMENSIONS')][...] \ - * inFile[os.path.join(group_geom,'SPACING')][...] - grid = inFile[os.path.join(group_geom,'DIMENSIONS')][...] - origin = inFile[os.path.join(group_geom,'ORIGIN')][...] - except KeyError: - errors.append('Geometry data ({}) not found'.format(group_geom)) - - - group_pointwise = os.path.join(rootDir,options.basegroup,options.pointwise) - if options.average is None: - label = 'Point' - - dataset = os.path.join(group_pointwise,options.quaternion) - try: - quats = np.reshape(inFile[dataset][...],(np.product(grid),4)) - rot = [damask.Rotation.from_quaternion(q,True,P=+1) for q in quats] - except KeyError: - errors.append('Pointwise orientation (quaternion) data ({}) not readable'.format(dataset)) - - dataset = os.path.join(group_pointwise,options.phase) - try: - phase = np.reshape(inFile[dataset][...],(np.product(grid))) - except KeyError: - errors.append('Pointwise phase data ({}) not readable'.format(dataset)) - - microstructure = np.arange(1,np.product(grid)+1,dtype=int).reshape(grid,order='F') - - - else: - label = 'Grain' - - dataset = os.path.join(group_pointwise,options.microstructure) - try: - microstructure = np.transpose(inFile[dataset][...].reshape(grid[::-1]),(2,1,0)) # convert from C ordering - except KeyError: - errors.append('Link between pointwise and grain average data ({}) not readable'.format(dataset)) - - group_average = os.path.join(rootDir,options.basegroup,options.average) - - dataset = os.path.join(group_average,options.quaternion) - try: - rot = [damask.Rotation.from_quaternion(q,True,P=+1) for q in inFile[dataset][...][1:]] # skip first entry (unindexed) - except KeyError: - errors.append('Average orientation data ({}) not readable'.format(dataset)) - - dataset = os.path.join(group_average,options.phase) - try: - phase = [i[0] for i in inFile[dataset][...]][1:] # skip first entry (unindexed) - except KeyError: - errors.append('Average phase data ({}) not readable'.format(dataset)) - - if errors != []: - damask.util.croak(errors) - continue - - config_header = [''] - for i in range(np.nanmax(microstructure)): - config_header += ['[{}{}]'.format(label,i+1), - '(gauss)\tphi1 {:.2f}\tPhi {:.2f}\tphi2 {:.2f}'.format(*rot[i].as_Eulers(degrees = True)), - ] - config_header += [''] - for i in range(np.nanmax(microstructure)): - config_header += ['[{}{}]'.format(label,i+1), - '(constituent)\tphase {}\ttexture {}\tfraction 1.0'.format(phase[i],i+1), - ] - - header = [scriptID + ' ' + ' '.join(sys.argv[1:])]\ - + config_header - geom = damask.Geom(microstructure,size,origin,comments=header) - damask.util.croak(geom) - - geom.save_ASCII(os.path.splitext(name)[0]+'.geom',compress=False) + geom.save_ASCII(os.path.splitext(name)[0]+'.geom',compress=False) diff --git a/processing/pre/geom_fromTable.py b/processing/pre/geom_fromTable.py index a0de6a4c5..29a8b8852 100755 --- a/processing/pre/geom_fromTable.py +++ b/processing/pre/geom_fromTable.py @@ -2,11 +2,8 @@ import os import sys -from io import StringIO from optparse import OptionParser -import numpy as np - import damask @@ -40,64 +37,21 @@ parser.add_option('-q', '--quaternion', dest = 'quaternion', type = 'string', metavar='string', help = 'quaternion label') -parser.add_option('--axes', - dest = 'axes', - type = 'string', nargs = 3, metavar = ' '.join(['string']*3), - help = 'orientation coordinate frame in terms of position coordinate frame [+x +y +z]') - -parser.set_defaults(pos = 'pos', - ) +parser.set_defaults(pos= 'pos') (options,filenames) = parser.parse_args() if filenames == []: filenames = [None] -if np.sum([options.quaternion is not None, - options.microstructure is not None]) != 1: - parser.error('need either microstructure or quaternion (and optionally phase) as input.') -if options.microstructure is not None and options.phase is not None: - parser.error('need either microstructure or phase (and mandatory quaternion) as input.') -if options.axes is not None and not set(options.axes).issubset(set(['x','+x','-x','y','+y','-y','z','+z','-z'])): - parser.error('invalid axes {} {} {}.'.format(*options.axes)) - for name in filenames: damask.util.report(scriptName,name) - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - table.sort_by(['{}_{}'.format(i,options.pos) for i in range(3,0,-1)]) # x fast, y slow - grid,size,origin = damask.grid_filters.cell_coord0_gridSizeOrigin(table.get(options.pos)) + labels = [] + for l in [options.quaternion,options.phase,options.microstructure]: + if l is not None: labels.append(l) - config_header = table.comments - - if options.microstructure: - microstructure = table.get(options.microstructure).reshape(grid,order='F') - - elif options.quaternion: - q = table.get(options.quaternion) - phase = table.get(options.phase).astype(int) if options.phase else \ - np.ones((table.data.shape[0],1),dtype=int) - - unique,unique_inverse = np.unique(np.hstack((q,phase)),return_inverse=True,axis=0) - microstructure = unique_inverse.reshape(grid,order='F') + 1 - - config_header = [''] - for i,data in enumerate(unique): - ori = damask.Rotation(data[0:4]) - config_header += ['[Grain{}]'.format(i+1), - '(gauss)\tphi1 {:.2f}\tPhi {:.2f}\tphi2 {:.2f}'.format(*ori.as_Eulers(degrees = True)), - ] - if options.axes is not None: config_header += ['axes\t{} {} {}'.format(*options.axes)] - - config_header += [''] - for i,data in enumerate(unique): - config_header += ['[Grain{}]'.format(i+1), - '(constituent)\tphase {}\ttexture {}\tfraction 1.0'.format(int(data[4]),i+1), - ] - - header = [scriptID + ' ' + ' '.join(sys.argv[1:])]\ - + config_header - geom = damask.Geom(microstructure,size,origin, - comments=header) + t = damask.Table.load(name) + geom = damask.Geom.from_table(t,options.pos,labels) damask.util.croak(geom) geom.save_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'.geom',compress=False) diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 955993607..600f64138 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -10,21 +10,21 @@ with open(_Path(__file__).parent/_Path('VERSION')) as _f: # make classes directly accessible as damask.Class from ._environment import Environment as _ # noqa environment = _() -from ._table import Table # noqa -from ._vtk import VTK # noqa -from ._colormap import Colormap # noqa -from ._rotation import Rotation # noqa -from ._lattice import Symmetry, Lattice# noqa -from ._orientation import Orientation # noqa -from ._result import Result # noqa -from ._geom import Geom # noqa -from ._config import Config # noqa -from ._configmaterial import ConfigMaterial # noqa -from . import solver # noqa from . import util # noqa from . import seeds # noqa -from . import grid_filters # noqa from . import mechanics # noqa +from . import solver # noqa +from . import grid_filters # noqa +from ._lattice import Symmetry, Lattice# noqa +from ._table import Table # noqa +from ._rotation import Rotation # noqa +from ._vtk import VTK # noqa +from ._colormap import Colormap # noqa +from ._orientation import Orientation # noqa +from ._config import Config # noqa +from ._configmaterial import ConfigMaterial # noqa +from ._geom import Geom # noqa +from ._result import Result # noqa diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 26006fef7..a7ecec108 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -2,6 +2,7 @@ import copy import numpy as np +from . import grid_filters from . import Config from . import Lattice from . import Rotation @@ -17,12 +18,73 @@ class ConfigMaterial(Config): ---------- fname : file, str, or pathlib.Path, optional Filename or file for writing. Defaults to 'material.yaml'. - **kwargs : dict + **kwargs Keyword arguments parsed to yaml.dump. """ super().save(fname,**kwargs) + + @staticmethod + def from_table(table,coordinates=None,constituents={},**kwargs): + """ + Load from an ASCII table. + + Parameters + ---------- + table : damask.Table + Table that contains material information. + coordinates : str, optional + Label of spatial coordiates. Used for sorting and performing a + sanity check. Default to None, in which case no sorting or checking is + peformed. + constituents : dict, optional + Entries for 'constituents'. The key is the name and the value specifies + the label of the data column in the table + **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 + >>> cm.from_table(t,'pos',{'O':'qu','phase':'phase'},homogenization='homog') + material: + - constituents: + - O: [0.19, 0.8, 0.24, -0.51] + fraction: 1.0 + phase: Aluminum + homogenization: SX + - constituents: + - O: [0.8, 0.19, 0.24, -0.51] + fraction: 1.0 + phase: Steel + homogenization: SX + + """ + if coordinates is not None: + t = table.sort_by([f'{i}_{coordinates}' for i in range(3,0,-1)]) + grid_filters.coord0_check(t.get(coordinates)) + else: + t = table + + constituents_ = {k:t.get(v) for k,v in constituents.items()} + kwargs_ = {k:t.get(v) for k,v in kwargs.items()} + + _,idx = np.unique(np.hstack(list({**constituents_,**kwargs_}.values())),return_index=True,axis=0) + + constituents_ = {k:v[idx].squeeze() for k,v in constituents_.items()} + kwargs_ = {k:v[idx].squeeze() for k,v in kwargs_.items()} + + return ConfigMaterial().material_add(constituents_,**kwargs_) + + @property def is_complete(self): """Check for completeness.""" @@ -62,10 +124,10 @@ class ConfigMaterial(Config): print(f'No lattice specified in phase {k}') ok = False - #for k,v in self['homogenization'].items(): - # if 'N_constituents' not in v: - # print(f'No. of constituents not specified in homogenization {k}'} - # ok = False + for k,v in self['homogenization'].items(): + if 'N_constituents' not in v: + print(f'No. of constituents not specified in homogenization {k}') + ok = False if phase - set(self['phase']): print(f'Phase(s) {phase-set(self["phase"])} missing') @@ -158,3 +220,80 @@ class ConfigMaterial(Config): except KeyError: continue return dup + + + def material_add(self,constituents,**kwargs): + """ + Add material entries. + + Parameters + ---------- + constituents : dict + Entries for 'constituents'. The key is the name and the value specifies + the label of the data column in the table + **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 + >>> m = damask.ConfigMaterial() + >>> O = damask.Rotation.from_random(3).as_quaternion() + >>> phase = ['Aluminum','Steel','Aluminum'] + >>> m.material_add(constituents={'phase':phase,'O':O},homogenization='SX') + material: + - constituents: + - O: [0.577764, -0.146299, -0.617669, 0.513010] + fraction: 1.0 + phase: Aluminum + homogenization: SX + - constituents: + - O: [0.184176, 0.340305, 0.737247, 0.553840] + fraction: 1.0 + phase: Steel + homogenization: SX + - constituents: + - O: [0.0886257, -0.144848, 0.615674, -0.769487] + fraction: 1.0 + phase: Aluminum + homogenization: SX + + """ + c = [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)] + for k,v in kwargs.items(): + if hasattr(v,'__len__') and not isinstance(v,str): + if len(v) != len(c): + raise ValueError('len mismatch 1') + for i,vv in enumerate(v): + c[i][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() + else: + for i in range(len(c)): + c[i][k] = v + dup = copy.deepcopy(self) + if 'material' not in dup: dup['material'] = [] + dup['material'] +=c + + return dup + + + @staticmethod + def _constituents(N=1,**kwargs): + """Construct list of constituents.""" + for v in kwargs.values(): + if hasattr(v,'__len__') and not isinstance(v,str): N_material = len(v) + + if N == 1: + m = [[{'fraction':1.0}] for _ in range(N_material)] + for k,v in kwargs.items(): + if hasattr(v,'__len__') and not isinstance(v,str): + if len(v) != N_material: + raise ValueError('len mismatch 2') + for i,vv in enumerate(np.array(v)): + m[i][0][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() + else: + for i in range(N_material): + m[i][0][k] = v + return m + else: + raise NotImplementedError diff --git a/python/damask/_geom.py b/python/damask/_geom.py index 2ccbb1988..eb4dd055e 100644 --- a/python/damask/_geom.py +++ b/python/damask/_geom.py @@ -1,15 +1,17 @@ import copy import multiprocessing as mp from functools import partial +from os import path import numpy as np +import h5py from scipy import ndimage,spatial from . import environment -from . import Rotation from . import VTK from . import util from . import grid_filters +from . import Rotation class Geom: @@ -207,6 +209,68 @@ class Geom: return Geom(material.reshape(grid,order='F'),size,origin,comments) + + @staticmethod + def load_DREAM3D(fname,base_group,point_data=None,material='FeatureIds'): + """ + Load a DREAM.3D file. + + Parameters + ---------- + fname : str + Filename of the DREAM.3D file + base_group : str + Name of the group (folder) below 'DataContainers'. For example + 'SyntheticVolumeDataContainer'. + point_data : str, optional + Name of the group (folder) containing the point wise material data, + for example 'CellData'. Defaults to None, in which case points consecutively numbered. + material : str, optional + Name of the dataset containing the material ID. Defaults to + 'FeatureIds'. + + """ + root_dir ='DataContainers' + f = h5py.File(fname, 'r') + g = path.join(root_dir,base_group,'_SIMPL_GEOMETRY') + size = f[path.join(g,'DIMENSIONS')][()] * f[path.join(g,'SPACING')][()] + grid = f[path.join(g,'DIMENSIONS')][()] + origin = f[path.join(g,'ORIGIN')][()] + group_pointwise = path.join(root_dir,base_group,point_data) + + ma = np.arange(1,np.product(grid)+1,dtype=int) if point_data is None else \ + np.reshape(f[path.join(group_pointwise,material)],grid.prod()) + + return Geom(ma.reshape(grid,order='F'),size,origin,util.execution_stamp('Geom','load_DREAM3D')) + + + @staticmethod + def from_table(table,coordinates,labels): + """ + Load an ASCII table. + + Parameters + ---------- + table : damask.Table + Table that contains material information. + coordinates : str + Label of the column containing the spatial coordinates. + labels : str or list of str + Label(s) of the columns containing the material definition. + Each unique combintation of values results in a material. + + """ + t = table.sort_by([f'{i}_{coordinates}' for i in range(3,0,-1)]) + + grid,size,origin = grid_filters.cell_coord0_gridSizeOrigin(t.get(coordinates)) + + labels_ = [labels] if isinstance(labels,str) else labels + _,unique_inverse = np.unique(np.hstack([t.get(l) for l in labels_]),return_inverse=True,axis=0) + ma = unique_inverse.reshape(grid,order='F') + 1 + + return Geom(ma,size,origin,util.execution_stamp('Geom','from_table')) + + @staticmethod def _find_closest_seed(seeds, weights, point): return np.argmin(np.sum((np.broadcast_to(point,(len(seeds),3))-seeds)**2,axis=1) - weights) @@ -353,7 +417,7 @@ class Geom: Number of periods per unit cell. Defaults to 1. materials : (int, int), optional Material IDs. Defaults to (1,2). - + Notes ----- The following triply-periodic minimal surfaces are implemented: @@ -399,7 +463,7 @@ class Geom: def save(self,fname,compress=True): """ - Generates vtk rectilinear grid. + Store as vtk rectilinear grid. Parameters ---------- @@ -536,7 +600,7 @@ class Geom: coords_rot = R.broadcast_to(tuple(self.grid))@coords with np.errstate(all='ignore'): - mask = np.where(np.sum(np.power(coords_rot/r,2.0**exponent),axis=-1) > 1.0,True,False) + mask = np.sum(np.power(coords_rot/r,2.0**np.array(exponent)),axis=-1) > 1.0 if periodic: # translate back to center mask = np.roll(mask,((c-np.ones(3)*.5)*self.grid).astype(int),(0,1,2)) diff --git a/python/damask/_table.py b/python/damask/_table.py index 431cf1886..dd8df97b0 100644 --- a/python/damask/_table.py +++ b/python/damask/_table.py @@ -33,6 +33,10 @@ class Table: """Brief overview.""" return util.srepr(self.comments)+'\n'+self.data.__repr__() + def __len__(self): + """Number of rows.""" + return len(self.data) + def __copy__(self): """Copy Table.""" return copy.deepcopy(self) diff --git a/python/tests/reference/ConfigMaterial/material.yaml b/python/tests/reference/ConfigMaterial/material.yaml index 5c006c99c..97c6504bb 100644 --- a/python/tests/reference/ConfigMaterial/material.yaml +++ b/python/tests/reference/ConfigMaterial/material.yaml @@ -1,8 +1,10 @@ homogenization: SX: + N_constituents: 2 mech: {type: none} Taylor: - mech: {N_constituents: 2, type: isostrain} + N_constituents: 2 + mech: {type: isostrain} material: - constituents: