From 38f9c1977c361cf6b0e2e12e5e121519f3247e02 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 16 Dec 2023 10:44:24 +0100 Subject: [PATCH 1/9] attributes should depend on view hidden attributes contain the view-independent variables, getter for currently visible quantities --- PRIVATE | 2 +- python/damask/_result.py | 108 +++++++++++++++++++++--------------- python/tests/test_Result.py | 8 +-- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/PRIVATE b/PRIVATE index 29ef436ac..5a715996e 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 29ef436acca5417aebc945b688642c34697af911 +Subproject commit 5a715996e6e8418a59fbcaf3715a2516ad05ed51 diff --git a/python/damask/_result.py b/python/damask/_result.py index 6ffbd0352..02812b79a 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -119,29 +119,29 @@ class Result: self.add_curl = self.add_divergence = self.add_gradient = None # type: ignore r = re.compile(rf'{prefix_inc}([0-9]+)') - self.increments = sorted([i for i in f.keys() if r.match(i)],key=util.natural_sort) - self.times = np.around([f[i].attrs['t/s'] for i in self.increments],12) - if len(self.increments) == 0: + self._increments = sorted([i for i in f.keys() if r.match(i)],key=util.natural_sort) + self._times = np.around([f[i].attrs['t/s'] for i in self._increments],12) + if len(self._increments) == 0: raise ValueError('incomplete DADF5 file') self.N_materialpoints, self.N_constituents = np.shape(f['cell_to/phase']) - self.homogenization = f['cell_to/homogenization']['label'].astype('str') - self.homogenizations = sorted(np.unique(self.homogenization),key=util.natural_sort) - self.phase = f['cell_to/phase']['label'].astype('str') - self.phases = sorted(np.unique(self.phase),key=util.natural_sort) + self.homogenization = f['cell_to/homogenization']['label'].astype('str') + self._homogenizations = sorted(np.unique(self.homogenization),key=util.natural_sort) + self.phase = f['cell_to/phase']['label'].astype('str') + self._phases = sorted(np.unique(self.phase),key=util.natural_sort) - self.fields: List[str] = [] - for c in self.phases: - self.fields += f['/'.join([self.increments[0],'phase',c])].keys() - for m in self.homogenizations: - self.fields += f['/'.join([self.increments[0],'homogenization',m])].keys() - self.fields = sorted(set(self.fields),key=util.natural_sort) # make unique + fields: List[str] = [] + for c in self._phases: + fields += f['/'.join([self._increments[0],'phase',c])].keys() + for m in self._homogenizations: + fields += f['/'.join([self._increments[0],'homogenization',m])].keys() + self._fields = sorted(set(fields),key=util.natural_sort) # make unique - self.visible = {'increments': self.increments, - 'phases': self.phases, - 'homogenizations': self.homogenizations, - 'fields': self.fields, + self.visible = {'increments': self._increments, + 'phases': self._phases, + 'homogenizations': self._homogenizations, + 'fields': self._fields, } self.fname = Path(fname).expanduser().absolute() @@ -223,24 +223,24 @@ class Result: if what == 'increments': choice = [c if isinstance(c,str) and c.startswith(prefix_inc) else - self.increments[c] if isinstance(c,int) and c<0 else + self._increments[c] if isinstance(c,int) and c<0 else f'{prefix_inc}{c}' for c in choice] elif what == 'times': - atol = 1e-2 * np.min(np.diff(self.times)) + atol = 1e-2 * np.min(np.diff(self._times)) what = 'increments' if choice == ['*']: - choice = self.increments + choice = self._increments else: iterator = np.array(choice).astype(float) choice = [] for c in iterator: - idx = np.searchsorted(self.times,c,side='left') - if idx0 and np.isclose(c,self.times[idx-1],rtol=0,atol=atol): - choice.append(self.increments[idx-1]) + idx = np.searchsorted(self._times,c,side='left') + if idx0 and np.isclose(c,self._times[idx-1],rtol=0,atol=atol): + choice.append(self._increments[idx-1]) - valid = _match(choice,getattr(self,what)) + valid = _match(choice,getattr(self,'_'+what)) existing = set(self.visible[what]) if action == 'set': @@ -273,9 +273,9 @@ class Result: """ s,e = map(lambda x: int(x.split(prefix_inc)[-1] if isinstance(x,str) and x.startswith(prefix_inc) else x), - (self.incs[ 0] if start is None else start, - self.incs[-1] if end is None else end)) - return [i for i in self.incs if s <= i <= e] + (self._incs[ 0] if start is None else start, + self._incs[-1] if end is None else end)) + return [i for i in self._incs if s <= i <= e] def times_in_range(self, start: Optional[float] = None, @@ -296,9 +296,9 @@ class Result: Time of each increment within the given bounds. """ - s,e = (self.times[ 0] if start is None else start, - self.times[-1] if end is None else end) - return [t for t in self.times if s <= t <= e] + s,e = (self._times[ 0] if start is None else start, + self._times[-1] if end is None else end) + return [t for t in self._times if s <= t <= e] def view(self,*, @@ -533,7 +533,7 @@ class Result: msg = [] with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: - msg += [f'\n{inc} ({self.times[self.increments.index(inc)]} s)'] + msg += [f'\n{inc} ({self._times[self._increments.index(inc)]} s)'] for ty in ['phase','homogenization']: msg += [f' {ty}'] for label in self.visible[ty+'s']: @@ -566,8 +566,28 @@ class Result: return files @property - def incs(self): - return [int(i.split(prefix_inc)[-1]) for i in self.increments] + def _incs(self): + return [int(i.split(prefix_inc)[-1]) for i in self._increments] + + @property + def increments(self): + return [int(i.split(prefix_inc)[-1]) for i in self.visible['increments']] + + @property + def times(self): + return NotImplementedError + + @property + def phases(self): + return [copy.deepcopy(self.visible['phases'])] + + @property + def homogenizations(self): + return [copy.deepcopy(self.visible['homogenizations'])] + + @property + def fields(self): + return [copy.deepcopy(self.visible['fields'])] @property @@ -1723,7 +1743,7 @@ class Result: DADF5 file at a stable relative path. """ - if self.N_constituents != 1 or len(self.phases) != 1 or not self.structured: + if self.N_constituents != 1 or len(self._phases) != 1 or not self.structured: raise TypeError('XDMF output requires structured grid with single phase and single constituent.') attribute_type_map = defaultdict(lambda:'Matrix', ( ((),'Scalar'), ((3,),'Vector'), ((3,3),'Tensor')) ) @@ -1749,7 +1769,7 @@ class Result: time.attrib = {'TimeType': 'List'} time_data = ET.SubElement(time, 'DataItem') - times = [self.times[self.increments.index(i)] for i in self.visible['increments']] + times = [self._times[self._increments.index(i)] for i in self.visible['increments']] time_data.attrib = {'Format': 'XML', 'NumberType': 'Float', 'Dimensions': f'{len(times)}'} @@ -1876,7 +1896,7 @@ class Result: v.comments = [util.execution_stamp('Result','export_VTK')] - N_digits = int(np.floor(np.log10(max(1,self.incs[-1]))))+1 + N_digits = int(np.floor(np.log10(max(1,self._incs[-1]))))+1 constituents_ = constituents if isinstance(constituents,Iterable) else \ (range(self.N_constituents) if constituents is None else [constituents]) # type: ignore @@ -1966,7 +1986,7 @@ class Result: if self.N_constituents != 1 or not self.structured: raise TypeError('DREAM3D output requires structured grid with single constituent.') - N_digits = int(np.floor(np.log10(max(1,self.incs[-1]))))+1 + N_digits = int(np.floor(np.log10(max(1,self._incs[-1]))))+1 at_cell_ph,in_data_ph,_,_ = self._mappings() @@ -2036,12 +2056,12 @@ class Result: cell_ensemble.create_dataset(name='PhaseName',data = phase_name, dtype=h5py.Datatype(tid)) cell_ensemble.attrs['AttributeMatrixType'] = np.array([11],np.uint32) - cell_ensemble.attrs['TupleDimensions'] = np.array([len(self.phases) + 1], np.uint64) + cell_ensemble.attrs['TupleDimensions'] = np.array([len(self._phases) + 1], np.uint64) for group in ['CrystalStructures','PhaseTypes','PhaseName']: add_attribute(cell_ensemble[group], 'ComponentDimensions', np.array([1],np.uint64)) - add_attribute(cell_ensemble[group], 'Tuple Axis Dimensions', f'x={len(self.phases)+1}') + add_attribute(cell_ensemble[group], 'Tuple Axis Dimensions', f'x={len(self._phases)+1}') add_attribute(cell_ensemble[group], 'DataArrayVersion', np.array([2],np.int32)) - add_attribute(cell_ensemble[group], 'TupleDimensions', np.array([len(self.phases) + 1],np.uint64)) + add_attribute(cell_ensemble[group], 'TupleDimensions', np.array([len(self._phases) + 1],np.uint64)) for group in ['CrystalStructures','PhaseTypes']: add_attribute(cell_ensemble[group], 'ObjectType', 'DataArray') add_attribute(cell_ensemble['PhaseName'], 'ObjectType', 'StringDataArray') @@ -2129,9 +2149,9 @@ class Result: f_out[inc]['geometry'].create_dataset('u_n',data=u_n) - for label in self.homogenizations: + for label in self._homogenizations: f_in[inc]['homogenization'].copy(label,f_out[inc]['homogenization'],shallow=True) - for label in self.phases: + for label in self._phases: f_in[inc]['phase'].copy(label,f_out[inc]['phase'],shallow=True) for ty in ['phase','homogenization']: diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index bad55c98d..ac7a9f0a8 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -126,7 +126,7 @@ class TestResult: @pytest.mark.parametrize('sign',[+1,-1]) def test_view_approxtimes(self,default,inc,sign): eps = sign*1e-3 - assert [default.increments[inc]] == default.view(times=default.times[inc]+eps).visible['increments'] + assert [default._increments[inc]] == default.view(times=default._times[inc]+eps).visible['increments'] def test_add_invalid(self,default): default.add_absolute('xxxx') @@ -470,7 +470,7 @@ class TestResult: assert np.array_equal(dset,cur[path]) else: c = [_.decode() for _ in cur[path]] - r = ['Unknown Phase Type'] + result.phases + r = ['Unknown Phase Type'] + result._phases assert c == r grp = os.path.split(path)[0] for attr in ref[grp].attrs: @@ -650,8 +650,8 @@ class TestResult: 'check_compile_job1.hdf5',]) def test_export_DADF5(self,res_path,tmp_path,fname): r = Result(res_path/fname) - r = r.view(phases = random.sample(r.phases,1)) - r = r.view(increments = random.sample(r.increments,np.random.randint(1,len(r.increments)))) + r = r.view(phases = random.sample(r._phases,1)) + r = r.view(increments = random.sample(r._increments,np.random.randint(1,len(r._increments)))) r.export_DADF5(tmp_path/fname) r_exp = Result(tmp_path/fname) assert str(r.get()) == str(r_exp.get()) From 8def54c8621c1df19aca009dd76b971b15bc6239 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 17 Dec 2023 06:51:38 +0100 Subject: [PATCH 2/9] fixed getters for visible entities storing time information as dictionary simplifies many operations --- python/damask/_result.py | 32 ++++++++++++++++---------------- python/tests/test_Result.py | 3 ++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 02812b79a..8109c0b1a 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -120,7 +120,7 @@ class Result: r = re.compile(rf'{prefix_inc}([0-9]+)') self._increments = sorted([i for i in f.keys() if r.match(i)],key=util.natural_sort) - self._times = np.around([f[i].attrs['t/s'] for i in self._increments],12) + self._times = {int(i.split('_')[1]):np.around(f[i].attrs['t/s'],12) for i in self._increments} if len(self._increments) == 0: raise ValueError('incomplete DADF5 file') @@ -226,7 +226,8 @@ class Result: self._increments[c] if isinstance(c,int) and c<0 else f'{prefix_inc}{c}' for c in choice] elif what == 'times': - atol = 1e-2 * np.min(np.diff(self._times)) + times = list(self._times.values()) + atol = 1e-2 * np.min(np.diff(times)) what = 'increments' if choice == ['*']: choice = self._increments @@ -234,10 +235,10 @@ class Result: iterator = np.array(choice).astype(float) choice = [] for c in iterator: - idx = np.searchsorted(self._times,c,side='left') - if idx0 and np.isclose(c,self._times[idx-1],rtol=0,atol=atol): + elif idx>0 and np.isclose(c,times[idx-1],rtol=0,atol=atol): choice.append(self._increments[idx-1]) valid = _match(choice,getattr(self,'_'+what)) @@ -296,9 +297,9 @@ class Result: Time of each increment within the given bounds. """ - s,e = (self._times[ 0] if start is None else start, - self._times[-1] if end is None else end) - return [t for t in self._times if s <= t <= e] + s,e = (self.times[ 0] if start is None else start, + self.times[-1] if end is None else end) + return [t for t in self.times if s <= t <= e] def view(self,*, @@ -533,7 +534,7 @@ class Result: msg = [] with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: - msg += [f'\n{inc} ({self._times[self._increments.index(inc)]} s)'] + msg += [f'\n{inc} ({self._times[int(inc.split("_")[1])]} s)'] for ty in ['phase','homogenization']: msg += [f' {ty}'] for label in self.visible[ty+'s']: @@ -575,19 +576,19 @@ class Result: @property def times(self): - return NotImplementedError + return [self._times[i] for i in self.increments] @property def phases(self): - return [copy.deepcopy(self.visible['phases'])] + return self.visible['phases'] @property def homogenizations(self): - return [copy.deepcopy(self.visible['homogenizations'])] + return self.visible['homogenizations'] @property def fields(self): - return [copy.deepcopy(self.visible['fields'])] + return self.visible['fields'] @property @@ -1769,11 +1770,10 @@ class Result: time.attrib = {'TimeType': 'List'} time_data = ET.SubElement(time, 'DataItem') - times = [self._times[self._increments.index(i)] for i in self.visible['increments']] time_data.attrib = {'Format': 'XML', 'NumberType': 'Float', - 'Dimensions': f'{len(times)}'} - time_data.text = ' '.join(map(str,times)) + 'Dimensions': f'{len(self.times)}'} + time_data.text = ' '.join(map(str,self.times)) attributes = [] data_items = [] diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index ac7a9f0a8..dc2daf461 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -126,7 +126,8 @@ class TestResult: @pytest.mark.parametrize('sign',[+1,-1]) def test_view_approxtimes(self,default,inc,sign): eps = sign*1e-3 - assert [default._increments[inc]] == default.view(times=default._times[inc]+eps).visible['increments'] + times = list(default._times.values()) + assert [default._increments[inc]] == default.view(times=times[inc]+eps).visible['increments'] def test_add_invalid(self,default): default.add_absolute('xxxx') From 7544e81349f0b5bb6b8a6a66e9408c3ffa50950d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 17 Dec 2023 06:57:17 +0100 Subject: [PATCH 3/9] dictionary of visible items should not be exposed to users getters for individual items are easier to understand Note: increments_in_range and times_in_range operate now only on the visible increments --- python/damask/_result.py | 96 ++++++++++++++++++------------------- python/tests/test_Result.py | 7 +-- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 8109c0b1a..658f0b922 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -138,11 +138,11 @@ class Result: fields += f['/'.join([self._increments[0],'homogenization',m])].keys() self._fields = sorted(set(fields),key=util.natural_sort) # make unique - self.visible = {'increments': self._increments, - 'phases': self._phases, - 'homogenizations': self._homogenizations, - 'fields': self._fields, - } + self._visible = {'increments': self._increments, + 'phases': self._phases, + 'homogenizations': self._homogenizations, + 'fields': self._fields, + } self.fname = Path(fname).expanduser().absolute() @@ -172,7 +172,7 @@ class Result: header = [f'Created by {f.attrs["creator"]}', f' on {f.attrs["created"]}', f' executing "{f.attrs["call"]}"'] - visible_increments = self.visible['increments'] + visible_increments = self._visible['increments'] first = self.view(increments=visible_increments[0:1]).list_data() @@ -242,14 +242,14 @@ class Result: choice.append(self._increments[idx-1]) valid = _match(choice,getattr(self,'_'+what)) - existing = set(self.visible[what]) + existing = set(self._visible[what]) if action == 'set': - dup.visible[what] = sorted(set(valid), key=util.natural_sort) + dup._visible[what] = sorted(set(valid), key=util.natural_sort) elif action == 'add': - dup.visible[what] = sorted(existing.union(valid), key=util.natural_sort) + dup._visible[what] = sorted(existing.union(valid), key=util.natural_sort) elif action == 'del': - dup.visible[what] = sorted(existing.difference(valid), key=util.natural_sort) + dup._visible[what] = sorted(existing.difference(valid), key=util.natural_sort) return dup @@ -474,10 +474,10 @@ class Result: raise PermissionError('rename datasets') with h5py.File(self.fname,'a') as f: - for inc in self.visible['increments']: + for inc in self._visible['increments']: for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): path_src = '/'.join([inc,ty,label,field,name_src]) path_dst = '/'.join([inc,ty,label,field,name_dst]) if path_src in f.keys(): @@ -513,10 +513,10 @@ class Result: raise PermissionError('delete datasets') with h5py.File(self.fname,'a') as f: - for inc in self.visible['increments']: + for inc in self._visible['increments']: for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): path = '/'.join([inc,ty,label,field,name]) if path in f.keys(): del f[path] @@ -533,13 +533,13 @@ class Result: """ msg = [] with h5py.File(self.fname,'r') as f: - for inc in self.visible['increments']: + for inc in self._visible['increments']: msg += [f'\n{inc} ({self._times[int(inc.split("_")[1])]} s)'] for ty in ['phase','homogenization']: msg += [f' {ty}'] - for label in self.visible[ty+'s']: + for label in self._visible[ty+'s']: msg += [f' {label}'] - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): msg += [f' {field}'] for d in f['/'.join([inc,ty,label,field])].keys(): dataset = f['/'.join([inc,ty,label,field,d])] @@ -572,7 +572,7 @@ class Result: @property def increments(self): - return [int(i.split(prefix_inc)[-1]) for i in self.visible['increments']] + return [int(i.split(prefix_inc)[-1]) for i in self._visible['increments']] @property def times(self): @@ -580,15 +580,15 @@ class Result: @property def phases(self): - return self.visible['phases'] + return self._visible['phases'] @property def homogenizations(self): - return self.visible['homogenizations'] + return self._visible['homogenizations'] @property def fields(self): - return self.visible['fields'] + return self._visible['fields'] @property @@ -1449,7 +1449,7 @@ class Result: 'meta':d.data.dtype.metadata}} r = func(**dataset,**args) result = r['data'].reshape((-1,)+r['data'].shape[3:]) - for x in self.visible[ty[0]+'s']: + for x in self._visible[ty[0]+'s']: if ty[0] == 'phase': result1 = result[at_cell_ph[0][x]] if ty[0] == 'homogenization': @@ -1512,10 +1512,10 @@ class Result: groups = [] with h5py.File(self.fname,'r') as f: - for inc in self.visible['increments']: + for inc in self._visible['increments']: for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): group = '/'.join([inc,ty,label,field]) if set(datasets.values()).issubset(f[group].keys()): groups.append(group) @@ -1568,14 +1568,14 @@ class Result: in_data_ph = [] for c in range(self.N_constituents): at_cell_ph.append({label: np.where(self.phase[:,c] == label)[0] \ - for label in self.visible['phases']}) + for label in self._visible['phases']}) in_data_ph.append({label: f['/'.join(['cell_to','phase'])]['entry'][at_cell_ph[c][label]][:,c] \ - for label in self.visible['phases']}) + for label in self._visible['phases']}) at_cell_ho = {label: np.where(self.homogenization[:] == label)[0] \ - for label in self.visible['homogenizations']} + for label in self._visible['homogenizations']} in_data_ho = {label: f['/'.join(['cell_to','homogenization'])]['entry'][at_cell_ho[label]] \ - for label in self.visible['homogenizations']} + for label in self._visible['homogenizations']} return at_cell_ph,in_data_ph,at_cell_ho,in_data_ho @@ -1608,16 +1608,16 @@ class Result: r: Dict[str,Any] = {} with h5py.File(self.fname,'r') as f: - for inc in util.show_progress(self.visible['increments']): + for inc in util.show_progress(self._visible['increments']): r[inc] = {'phase':{},'homogenization':{},'geometry':{}} for out in _match(output,f['/'.join([inc,'geometry'])].keys()): r[inc]['geometry'][out] = _read(f['/'.join([inc,'geometry',out])]) for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: + for label in self._visible[ty+'s']: r[inc][ty][label] = {} - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): r[inc][ty][label][field] = {} for out in _match(output,f['/'.join([inc,ty,label,field])].keys()): r[inc][ty][label][field][out] = _read(f['/'.join([inc,ty,label,field,out])]) @@ -1683,15 +1683,15 @@ class Result: with h5py.File(self.fname,'r') as f: - for inc in util.show_progress(self.visible['increments']): + for inc in util.show_progress(self._visible['increments']): r[inc] = {'phase':{},'homogenization':{},'geometry':{}} for out in _match(output,f['/'.join([inc,'geometry'])].keys()): r[inc]['geometry'][out] = ma.array(_read(f['/'.join([inc,'geometry',out])]),fill_value = fill_float) for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): if field not in r[inc][ty].keys(): r[inc][ty][field] = {} @@ -1784,7 +1784,7 @@ class Result: hdf5_link = (hdf5_dir if absolute_path else Path(os.path.relpath(hdf5_dir,out_dir.resolve())))/hdf5_name with h5py.File(self.fname,'r') as f: - for inc in self.visible['increments']: + for inc in self._visible['increments']: grid = ET.SubElement(collection,'Grid') grid.attrib = {'GridType': 'Uniform', @@ -1819,8 +1819,8 @@ class Result: 'Dimensions': '{} {} {} 3'.format(*(self.cells[::-1]+1))} data_items[-1].text = f'{hdf5_link}:/{inc}/geometry/u_n' for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f['/'.join([inc,ty,label])].keys()): for out in _match(output,f['/'.join([inc,ty,label,field])].keys()): name = '/'.join([inc,ty,label,field,out]) shape = f[name].shape[1:] @@ -1915,15 +1915,15 @@ class Result: created = f.attrs['created'] if h5py3 else f.attrs['created'].decode() v.comments += [f'{creator} ({created})'] - for inc in util.show_progress(self.visible['increments']): + for inc in util.show_progress(self._visible['increments']): u = _read(f['/'.join([inc,'geometry','u_n' if mode.lower() == 'cell' else 'u_p'])]) v = v.set('u',u) for ty in ['phase','homogenization']: - for field in self.visible['fields']: + for field in self._visible['fields']: outs: Dict[str, np.ma.core.MaskedArray] = {} - for label in self.visible[ty+'s']: + for label in self._visible[ty+'s']: if field not in f['/'.join([inc,ty,label])].keys(): continue for out in _match(output,f['/'.join([inc,ty,label,field])].keys()): @@ -1995,14 +1995,14 @@ class Result: out_dir.mkdir(parents=True,exist_ok=True) with h5py.File(self.fname,'r') as f: - for inc in util.show_progress(self.visible['increments']): + for inc in util.show_progress(self._visible['increments']): for c in range(self.N_constituents): crystal_structure = [999] phase_name = ['Unknown Phase Type'] cell_orientation = np.zeros((np.prod(self.cells),3),np.float32) phase_ID = np.zeros((np.prod(self.cells)),dtype=np.int32) count = 1 - for label in self.visible['phases']: + for label in self._visible['phases']: try: data = _read(f['/'.join([inc,'phase',label,'mechanical',q])]) lattice = data.dtype.metadata['lattice'] @@ -2137,7 +2137,7 @@ class Result: f_out['cell_to'].create_dataset('homogenization',data=mapping_homog.flatten()) - for inc in util.show_progress(self.visible['increments']): + for inc in util.show_progress(self._visible['increments']): f_in.copy(inc,f_out,shallow=True) if mapping is None: for label in ['u_p','u_n']: @@ -2155,8 +2155,8 @@ class Result: f_in[inc]['phase'].copy(label,f_out[inc]['phase'],shallow=True) for ty in ['phase','homogenization']: - for label in self.visible[ty+'s']: - for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()): + for label in self._visible[ty+'s']: + for field in _match(self._visible['fields'],f_in['/'.join([inc,ty,label])].keys()): p = '/'.join([inc,ty,label,field]) for out in _match(output,f_in[p].keys()): cp(f_in[p],f_out[p],out,None if mapping is None else mappings[ty][label.encode()]) diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index dc2daf461..65ae054d5 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -78,6 +78,7 @@ class TestResult: def test_view_all(self,default): + default = Result(default.fname) a = default.view(increments=True).get('F') assert dict_equal(a,default.view(increments='*').get('F')) @@ -95,7 +96,7 @@ class TestResult: label = 'increments' if what == 'times' else what assert n0.get('F') is n1.get('F') is None and \ - len(n0.visible[label]) == len(n1.visible[label]) == 0 + len(n0._visible[label]) == len(n1._visible[label]) == 0 @pytest.mark.parametrize('what',['increments','times','phases','fields']) # ToDo: discuss homogenizations def test_view_more(self,default,what): @@ -116,7 +117,7 @@ class TestResult: label = 'increments' if what == 'times' else what assert n0.get('F') is n1.get('F') is None and \ - len(n0.visible[label]) == len(n1.visible[label]) == 0 + len(n0._visible[label]) == len(n1._visible[label]) == 0 def test_view_invalid_incstimes(self,default): with pytest.raises(ValueError): @@ -127,7 +128,7 @@ class TestResult: def test_view_approxtimes(self,default,inc,sign): eps = sign*1e-3 times = list(default._times.values()) - assert [default._increments[inc]] == default.view(times=times[inc]+eps).visible['increments'] + assert [default._increments[inc]] == default.view(times=times[inc]+eps)._visible['increments'] def test_add_invalid(self,default): default.add_absolute('xxxx') From db09ca37e60822cf6a15bf18a388b3c1e9f93526 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 17 Dec 2023 07:02:53 +0100 Subject: [PATCH 4/9] convenient shortcut --- python/damask/_result.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/python/damask/_result.py b/python/damask/_result.py index 658f0b922..24061ff28 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -444,6 +444,19 @@ class Result: return self._manage_view('del',increments,times,phases,homogenizations,fields) + def view_reset(self): + """ + Reset to initial view. + + Returns + ------- + modified_view : damask.Result + View with all attributes visible. + + """ + return self.view(increments='*',phases='*',homogenizations='*',fieds='*',protected=True) + + def rename(self, name_src: str, name_dst: str): From 95e45ab0730b05cd9745246aa3915ed975436a5e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 17 Dec 2023 13:05:11 +0100 Subject: [PATCH 5/9] avoid inconsistencies --- python/damask/_result.py | 11 ++++------- python/damask/util.py | 6 ++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 24061ff28..4167dbdea 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -2,7 +2,6 @@ import re import fnmatch import os import copy -import datetime import xml.etree.ElementTree as ET # noqa import xml.dom.minidom import functools @@ -1471,9 +1470,8 @@ class Result: path = '/'.join(['/',increment[0],ty[0],x,field[0]]) h5_dataset = f[path].create_dataset(r['label'],data=result1) - now = datetime.datetime.now().astimezone() - h5_dataset.attrs['created'] = now.strftime('%Y-%m-%d %H:%M:%S%z') if h5py3 else \ - now.strftime('%Y-%m-%d %H:%M:%S%z').encode() + h5_dataset.attrs['created'] = util.time_stamp() if h5py3 else \ + util.time_stamp().encode() for l,v in r['meta'].items(): h5_dataset.attrs[l.lower()]=v.encode() if not h5py3 and type(v) is str else v @@ -1558,9 +1556,8 @@ class Result: compression_opts = 6 if compress else None, shuffle=True,fletcher32=True) - now = datetime.datetime.now().astimezone() - dataset.attrs['created'] = now.strftime('%Y-%m-%d %H:%M:%S%z') if h5py3 else \ - now.strftime('%Y-%m-%d %H:%M:%S%z').encode() + dataset.attrs['created'] = util.time_stamp() if h5py3 else \ + util.time_stamp().encode() for l,v in result['meta'].items(): dataset.attrs[l.lower()]=v.encode() if not h5py3 and type(v) is str else v diff --git a/python/damask/util.py b/python/damask/util.py index eda668564..599eebf55 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -214,13 +214,15 @@ def open_text(fname: _FileHandle, return fname if not isinstance(fname, (str,_Path)) else \ open(_Path(fname).expanduser(),mode,newline=('\n' if mode == 'w' else None)) +def time_stamp() -> str: + """Timestamp the execution of a (function within a) class.""" + return _datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z') def execution_stamp(class_name: str, function_name: _Optional[str] = None) -> str: """Timestamp the execution of a (function within a) class.""" - now = _datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z') _function_name = '' if function_name is None else f'.{function_name}' - return f'damask.{class_name}{_function_name} v{_version} ({now})' + return f'damask.{class_name}{_function_name} v{_version} ({time_stamp()})' def natural_sort(key: str) -> _List[_Union[int, str]]: From 939d5070b3359bc7fd32a50d7dc2014f623c5867 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 17 Dec 2023 15:44:35 +0100 Subject: [PATCH 6/9] matching name and leave write protection status unchanged --- python/damask/_result.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 4167dbdea..5eba62d8d 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -443,9 +443,9 @@ class Result: return self._manage_view('del',increments,times,phases,homogenizations,fields) - def view_reset(self): + def view_all(self): """ - Reset to initial view. + Make all attributes visible. Returns ------- @@ -453,7 +453,7 @@ class Result: View with all attributes visible. """ - return self.view(increments='*',phases='*',homogenizations='*',fieds='*',protected=True) + return self.view(increments='*',phases='*',homogenizations='*',fieds='*') def rename(self, From 809f5f455be1a738388586c4cae6384dc3f7f073 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 18 Dec 2023 16:43:41 +0100 Subject: [PATCH 7/9] test new functionality --- python/.coveragerc | 2 ++ python/damask/_result.py | 4 ++-- python/tests/test_Result.py | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/python/.coveragerc b/python/.coveragerc index e15d64f56..d1d5590a6 100644 --- a/python/.coveragerc +++ b/python/.coveragerc @@ -1,2 +1,4 @@ [run] source = damask +[report] +show_missing = true diff --git a/python/damask/_result.py b/python/damask/_result.py index 5eba62d8d..96a281808 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -453,7 +453,7 @@ class Result: View with all attributes visible. """ - return self.view(increments='*',phases='*',homogenizations='*',fieds='*') + return self.view(increments='*',phases='*',homogenizations='*',fields='*') def rename(self, @@ -1920,7 +1920,7 @@ class Result: out_dir.mkdir(parents=True,exist_ok=True) with h5py.File(self.fname,'r') as f: - if self.version_minor >= 13: + if self.version_major == 1 or self.version_minor >= 13: creator = f.attrs['creator'] if h5py3 else f.attrs['creator'].decode() created = f.attrs['created'] if h5py3 else f.attrs['created'].decode() v.comments += [f'{creator} ({created})'] diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 65ae054d5..5ac27840c 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -79,7 +79,7 @@ class TestResult: def test_view_all(self,default): default = Result(default.fname) - a = default.view(increments=True).get('F') + a = default.view_all().get('F') assert dict_equal(a,default.view(increments='*').get('F')) assert dict_equal(a,default.view(increments=default.increments_in_range(0,np.iinfo(int).max)).get('F')) @@ -130,6 +130,21 @@ class TestResult: times = list(default._times.values()) assert [default._increments[inc]] == default.view(times=times[inc]+eps)._visible['increments'] + def test_getters(self,default): + file_layout = default.get('non-existing',prune=False,flatten=False) + for i in default.increments: + increment = file_layout[f'increment_{i}'] + fields = [] + for p in default.phases: + phase = increment['phase'][p] + for f in default.fields: + fields.append(phase[f]) + for h in default.homogenizations: + homogenization = increment['homogenization'][h] + for f in default.fields: + fields.append(homogenization[f]) + assert len(fields) > 0 + def test_add_invalid(self,default): default.add_absolute('xxxx') From e93618bc8902af01f2c67b5de3e4ad1798931ea3 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Mon, 18 Dec 2023 23:02:02 +0000 Subject: [PATCH 8/9] Correct docstring --- python/damask/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/util.py b/python/damask/util.py index 599eebf55..caeebe265 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -215,7 +215,7 @@ def open_text(fname: _FileHandle, open(_Path(fname).expanduser(),mode,newline=('\n' if mode == 'w' else None)) def time_stamp() -> str: - """Timestamp the execution of a (function within a) class.""" + """Provide current time as formatted string.""" return _datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z') def execution_stamp(class_name: str, From 877dc43ffe272e0a030bcd8b32d44329847978c3 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Tue, 2 Jan 2024 11:44:36 -0500 Subject: [PATCH 9/9] use updated PRIVATE --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 62df7f24f..bb989504b 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 62df7f24f2a95fda255f7d20b130afcfeecb1b4a +Subproject commit bb989504bea36fe2dbb881bc9232b0ea3193631a