From 62c85db745758c1496408b689c93d9b23b78eff7 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 5 Apr 2021 07:53:19 +0200 Subject: [PATCH] out of place behavior --- python/damask/_result.py | 192 ++++++++++-------------------------- python/tests/test_Result.py | 74 ++++++-------- 2 files changed, 84 insertions(+), 182 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 5139852d4..008579586 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -2,6 +2,7 @@ import multiprocessing as mp import re import fnmatch import os +import copy import datetime import xml.etree.ElementTree as ET import xml.dom.minidom @@ -27,11 +28,13 @@ h5py3 = h5py.__version__[0] == '3' def _read(dataset): + """Read a dataset and its metadata into a numpy.ndarray.""" metadata = {k:(v if h5py3 else v.decode()) for k,v in dataset.attrs.items()} dtype = np.dtype(dataset.dtype,metadata=metadata) return np.array(dataset,dtype=dtype) def _match(requested,existing): + """Find matches among two sets of labels""" def flatten_list(list_of_lists): return [e for e_ in list_of_lists for e in e_] @@ -47,6 +50,7 @@ def _match(requested,existing): key=util.natural_sort) def _empty(dataset,N_materialpoints,fill_float,fill_int): + """Create empty numpy.ma.MaskedArray.""" return ma.array(np.empty((N_materialpoints,)+dataset.shape[1:],dataset.dtype), fill_value = fill_float if dataset.dtype in np.sctypes['float'] else fill_int, mask = True) @@ -123,17 +127,21 @@ class Result: self._allow_modification = False + def __copy__(self): + """Create deep copy.""" + return copy.deepcopy(self) + + copy = __copy__ + + def __repr__(self): """Show summary of file content.""" visible_increments = self.visible['increments'] - self.view('increments',visible_increments[0:1]) - first = self.list_data() + first = self.view('increments',visible_increments[0:1]).list_data() - self.view('increments',visible_increments[-1:]) - last = '' if len(visible_increments) < 2 else self.list_data() - - self.view('increments',visible_increments) + last = '' if len(visible_increments) < 2 else \ + self.view('increments',visible_increments[-1:]).list_data() in_between = '' if len(visible_increments) < 3 else \ ''.join([f'\n{inc}\n ...\n' for inc in visible_increments[1:-1]]) @@ -186,24 +194,31 @@ class Result: valid = _match(choice,getattr(self,what)) existing = set(self.visible[what]) + dup = self.copy() if action == 'set': - self.visible[what] = sorted(set(valid), key=util.natural_sort) + dup.visible[what] = sorted(set(valid), key=util.natural_sort) elif action == 'add': add = existing.union(valid) - self.visible[what] = sorted(add, key=util.natural_sort) + dup.visible[what] = sorted(add, key=util.natural_sort) elif action == 'del': diff = existing.difference(valid) - self.visible[what] = sorted(diff, key=util.natural_sort) + dup.visible[what] = sorted(diff, key=util.natural_sort) + + return dup def allow_modification(self): """Allow to overwrite existing data.""" print(util.warn('Warning: Modification of existing datasets allowed!')) - self._allow_modification = True + dup = self.copy() + dup._allow_modification = True + return dup def disallow_modification(self): """Disallow to overwrite existing data (default case).""" - self._allow_modification = False + dup = self.copy() + dup._allow_modification = False + return dup def increments_in_range(self,start,end): @@ -247,28 +262,6 @@ class Result: return selected - def iterate(self,what): - """ - Iterate over visible items and view them independently. - - Parameters - ---------- - what : str - Attribute to change (must be from self.visible). - - """ - datasets = self.visible[what] - last_view = datasets.copy() - for dataset in datasets: - if last_view != self.visible[what]: - self._manage_view('set',what,datasets) - raise Exception - self._manage_view('set',what,dataset) - last_view = self.visible[what] - yield dataset - self._manage_view('set',what,datasets) - - def view(self,what,datasets): """ Set view. @@ -279,10 +272,10 @@ class Result: Attribute to change (must be from self.visible). datasets : list of str or bool Name of datasets as list; supports ? and * wildcards. - True is equivalent to [*], False is equivalent to []. + True is equivalent to *, False is equivalent to []. """ - self._manage_view('set',what,datasets) + return self._manage_view('set',what,datasets) def view_more(self,what,datasets): @@ -295,10 +288,10 @@ class Result: Attribute to change (must be from self.visible). datasets : list of str or bool Name of datasets as list; supports ? and * wildcards. - True is equivalent to [*], False is equivalent to []. + True is equivalent to *, False is equivalent to []. """ - self._manage_view('add',what,datasets) + return self._manage_view('add',what,datasets) def view_less(self,what,datasets): @@ -311,10 +304,10 @@ class Result: Attribute to change (must be from self.visible). datasets : list of str or bool Name of datasets as list; supports ? and * wildcards. - True is equivalent to [*], False is equivalent to []. + True is equivalent to *, False is equivalent to []. """ - self._manage_view('del',what,datasets) + return self._manage_view('del',what,datasets) def rename(self,name_old,name_new): @@ -334,11 +327,11 @@ class Result: with h5py.File(self.fname,'a') as f: for inc in self.visible['increments']: - for ty in ['phases','homogenizations']: - for label in self.visible[ty]: + for ty in ['phase','homogenization']: + for label in self.visible[ty+'s']: for field in self.visible['fields']: - path_old = '/'.join([inc,ty[:-1],label,field,name_old]) - path_new = '/'.join([inc,ty[:-1],label,field,name_new]) + path_old = '/'.join([inc,ty,label,field,name_old]) + path_new = '/'.join([inc,ty,label,field,name_new]) if path_old in f.keys(): f[path_new] = f[path_old] f[path_new].attrs['renamed'] = f'original name: {name_old}' if h5py3 else \ @@ -351,48 +344,25 @@ class Result: # compatibility hack de = 'Description' if self.version_minor < 12 else 'description' un = 'Unit' if self.version_minor < 12 else 'unit' - message = '' + msg = '' with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: - ''.join([message,f'\n{inc} ({self.times[self.increments.index(inc)]}s)\n']) - for ty in ['phases','homogenizations']: - ' '.join([message,f'{ty[:-1]}\n']) - for label in self.visible[ty]: - ' '.join([message,f'{label}\n']) + msg = ''.join([msg,f'\n{inc} ({self.times[self.increments.index(inc)]}s)\n']) + for ty in ['phase','homogenization']: + msg = ' '.join([msg,f'{ty}\n']) + for label in self.visible[ty+'s']: + msg = ' '.join([msg,f'{label}\n']) for field in self.visible['fields']: - ' '.join([message,f'{field}\n']) - for d in f['/'.join([inc,ty[:-1],label,field])].keys(): - dataset = f['/'.join([inc,ty[:-1],label,field,d])] + msg = ' '.join([msg,f'{field}\n']) + for d in f['/'.join([inc,ty,label,field])].keys(): + dataset = f['/'.join([inc,ty,label,field,d])] unit = f' / {dataset.attrs[un]}' if h5py3 else \ f' / {dataset.attrs[un].decode()}' description = dataset.attrs[de] if h5py3 else \ dataset.attrs[de].decode() - ' '.join([message,f'{d}{unit}: {description}\n']) + msg = ' '.join([msg,f'{d}{unit}: {description}\n']) - return message - - - def get_dataset_location(self,label): - """Return the location of all active datasets with given label.""" - path = [] - with h5py.File(self.fname,'r') as f: - for i in self.visible['increments']: - k = '/'.join([i,'geometry',label]) - try: - f[k] - path.append(k) - except KeyError: - pass - for o,p in zip(['phases','homogenizations'],['fields','fields']): - for oo in self.visible[o]: - for pp in self.visible[p]: - k = '/'.join([i,o[:-1],oo,pp,label]) - try: - f[k] - path.append(k) - except KeyError: - pass - return path + return msg def enable_user_function(self,func): @@ -400,60 +370,6 @@ class Result: print(f'Function {func.__name__} enabled in add_calculation.') - def read_dataset(self,path,c=0,plain=False): - """ - Dataset for all points/cells. - - If more than one path is given, the dataset is composed of the individual contributions. - - Parameters - ---------- - path : list of strings - The name of the datasets to consider. - c : int, optional - The constituent to consider. Defaults to 0. - plain: boolean, optional - Convert into plain numpy datatype. - Only relevant for compound datatype, e.g. the orientation. - Defaults to False. - - """ - # compatibility hack - name = 'Name' if self.version_minor < 12 else 'label' - member = 'Position' if self.version_minor < 12 else 'entry' - grp = 'mapping' if self.version_minor < 12 else 'cell_to' - with h5py.File(self.fname,'r') as f: - shape = (self.N_materialpoints,) + np.shape(f[path[0]])[1:] - if len(shape) == 1: shape = shape +(1,) - dataset = np.full(shape,np.nan,dtype=np.dtype(f[path[0]])) - for pa in path: - label = pa.split('/')[2] - - if pa.split('/')[1] == 'geometry': - dataset = np.array(f[pa]) - continue - - p = np.where(f[f'{grp}/phase'][:,c][name] == str.encode(label))[0] - if len(p)>0: - u = (f[f'{grp}/phase'][member][p,c]) - a = np.array(f[pa]) - if len(a.shape) == 1: - a=a.reshape([a.shape[0],1]) - dataset[p,:] = a[u,:] - - p = np.where(f[f'{grp}/homogenization'][name] == str.encode(label))[0] - if len(p)>0: - u = (f[f'{grp}/homogenization'][member][p.tolist()]) - a = np.array(f[pa]) - if len(a.shape) == 1: - a=a.reshape([a.shape[0],1]) - dataset[p,:] = a[u,:] - - if plain and dataset.dtype.names is not None: - return dataset.view(('float64',len(dataset.dtype.names))) - else: - return dataset - @property def coordinates0_point(self): """Return initial coordinates of the cell centers.""" @@ -1044,10 +960,10 @@ class Result: groups = [] with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: - for ty in ['phases','homogenizations']: - for label in self.visible[ty]: + for ty in ['phase','homogenization']: + for label in self.visible[ty+'s']: for field in self.visible['fields']: - group = '/'.join([inc,ty[:-1],label,field]) + group = '/'.join([inc,ty,label,field]) if set(datasets.values()).issubset(f[group].keys()): groups.append(group) if len(groups) == 0: @@ -1176,11 +1092,11 @@ class Result: 'Dimensions': '{} {} {} 3'.format(*(self.cells+1))} data_items[-1].text=f'{os.path.split(self.fname)[1]}:/{inc}/geometry/u_n' - for ty in ['phases','homogenizations']: - for label in self.visible[ty]: + for ty in ['phase','homogenization']: + for label in self.visible[ty+'s']: for field in self.visible['fields']: - for out in _match(output,f['/'.join((inc,ty[:-1],label,field))].keys()): - name = '/'.join([inc,ty[:-1],label,field,out]) + for out in _match(output,f['/'.join((inc,ty,label,field))].keys()): + name = '/'.join([inc,ty,label,field,out]) shape = f[name].shape[1:] dtype = f[name].dtype diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 4a597c027..32eac281e 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -24,8 +24,7 @@ def default(tmp_path,ref_path): fname = '12grains6x7x8_tensionY.hdf5' shutil.copy(ref_path/fname,tmp_path) f = Result(tmp_path/fname) - f.view('times',20.0) - return f + return f.view('times',20.0) @pytest.fixture def single_phase(tmp_path,ref_path): @@ -58,49 +57,37 @@ class TestResult: def test_view_all(self,default): - default.view('increments',True) - a = default.read('F') + a = default.view('increments',True).read('F') - default.view('increments','*') - assert dict_equal(a,default.read('F')) - default.view('increments',default.increments_in_range(0,np.iinfo(int).max)) - assert dict_equal(a,default.read('F')) + assert dict_equal(a,default.view('increments','*').read('F')) + assert dict_equal(a,default.view('increments',default.increments_in_range(0,np.iinfo(int).max)).read('F')) - default.view('times',True) - assert dict_equal(a,default.read('F')) - default.view('times','*') - assert dict_equal(a,default.read('F')) - default.view('times',default.times_in_range(0.0,np.inf)) - assert dict_equal(a,default.read('F')) + assert dict_equal(a,default.view('times',True).read('F')) + assert dict_equal(a,default.view('times','*').read('F')) + assert dict_equal(a,default.view('times',default.times_in_range(0.0,np.inf)).read('F')) @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_view_none(self,default,what): - default.view(what,False) - a = default.read('F') - default.view(what,[]) - b = default.read('F') + a = default.view(what,False).read('F') + b = default.view(what,[]).read('F') assert a == b == {} @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_view_more(self,default,what): - default.view(what,False) - default.view_more(what,'*') - a = default.read('F') + empty = default.view(what,False) - default.view(what,True) - b = default.read('F') + a = empty.view_more(what,'*').read('F') + b = empty.view_more(what,True).read('F') assert dict_equal(a,b) @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_view_less(self,default,what): - default.view(what,True) - default.view_less(what,'*') - a = default.read('F') + full = default.view(what,True) - default.view(what,False) - b = default.read('F') + a = full.view_less(what,'*').read('F') + b = full.view_less(what,True).read('F') assert a == b == {} @@ -279,41 +266,41 @@ class TestResult: @pytest.mark.parametrize('overwrite',['off','on']) def test_add_overwrite(self,default,overwrite): - default.view('times',default.times_in_range(0,np.inf)[-1]) + last = default.view('times',default.times_in_range(0,np.inf)[-1]) - default.add_stress_Cauchy() - with h5py.File(default.fname,'r') as f: + last.add_stress_Cauchy() + with h5py.File(last.fname,'r') as f: created_first = default.place('sigma').dtype.metadata['created'] created_first = datetime.strptime(created_first,'%Y-%m-%d %H:%M:%S%z') if overwrite == 'on': - default.allow_modification() + last = last.allow_modification() else: - default.disallow_modification() + last = last.disallow_modification() time.sleep(2.) try: - default.add_calculation('sigma','#sigma#*0.0+311.','not the Cauchy stress') + last.add_calculation('sigma','#sigma#*0.0+311.','not the Cauchy stress') except ValueError: pass - with h5py.File(default.fname,'r') as f: - created_second = default.place('sigma').dtype.metadata['created'] + with h5py.File(last.fname,'r') as f: + created_second = last.place('sigma').dtype.metadata['created'] created_second = datetime.strptime(created_second,'%Y-%m-%d %H:%M:%S%z') if overwrite == 'on': - assert created_first < created_second and np.allclose(default.place('sigma'),311.) + assert created_first < created_second and np.allclose(last.place('sigma'),311.) else: - assert created_first == created_second and not np.allclose(default.place('sigma'),311.) + assert created_first == created_second and not np.allclose(last.place('sigma'),311.) @pytest.mark.parametrize('allowed',['off','on']) def test_rename(self,default,allowed): if allowed == 'on': F = default.place('F') - default.allow_modification() + default = default.allow_modification() default.rename('F','new_name') assert np.all(F == default.place('new_name')) - default.disallow_modification() + default = default.disallow_modification() with pytest.raises(PermissionError): default.rename('P','another_new_name') @@ -333,8 +320,7 @@ class TestResult: @pytest.mark.parametrize('fname',['12grains6x7x8_tensionY.hdf5'],ids=range(1)) @pytest.mark.parametrize('inc',[4,0],ids=range(2)) def test_vtk(self,request,tmp_path,ref_path,update,output,fname,inc): - result = Result(ref_path/fname) - result.view('increments',inc) + result = Result(ref_path/fname).view('increments',inc) os.chdir(tmp_path) result.save_VTK(output) fname = fname.split('.')[0]+f'_inc{(inc if type(inc) == int else inc[0]):0>2}.vtr' @@ -387,7 +373,7 @@ class TestResult: def test_read(self,update,request,ref_path,view,output,compress,strip): result = Result(ref_path/'4grains2x4x3_compressionY.hdf5') for key,value in view.items(): - result.view(key,value) + result = result.view(key,value) fname = request.node.name cur = result.read(output,compress,strip) @@ -412,7 +398,7 @@ class TestResult: def test_place(self,update,request,ref_path,view,output,compress,strip,constituents): result = Result(ref_path/'4grains2x4x3_compressionY.hdf5') for key,value in view.items(): - result.view(key,value) + result = result.view(key,value) fname = request.node.name cur = result.place(output,compress,strip,constituents)