attributes should depend on view

hidden attributes contain the view-independent variables, getter for
currently visible quantities
This commit is contained in:
Martin Diehl 2023-12-16 10:44:24 +01:00
parent 84af516cdb
commit 38f9c1977c
No known key found for this signature in database
GPG Key ID: 1FD50837275A0A9B
3 changed files with 69 additions and 49 deletions

@ -1 +1 @@
Subproject commit 29ef436acca5417aebc945b688642c34697af911 Subproject commit 5a715996e6e8418a59fbcaf3715a2516ad05ed51

View File

@ -119,29 +119,29 @@ class Result:
self.add_curl = self.add_divergence = self.add_gradient = None # type: ignore self.add_curl = self.add_divergence = self.add_gradient = None # type: ignore
r = re.compile(rf'{prefix_inc}([0-9]+)') 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._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 = np.around([f[i].attrs['t/s'] for i in self._increments],12)
if len(self.increments) == 0: if len(self._increments) == 0:
raise ValueError('incomplete DADF5 file') raise ValueError('incomplete DADF5 file')
self.N_materialpoints, self.N_constituents = np.shape(f['cell_to/phase']) self.N_materialpoints, self.N_constituents = np.shape(f['cell_to/phase'])
self.homogenization = f['cell_to/homogenization']['label'].astype('str') self.homogenization = f['cell_to/homogenization']['label'].astype('str')
self.homogenizations = sorted(np.unique(self.homogenization),key=util.natural_sort) self._homogenizations = sorted(np.unique(self.homogenization),key=util.natural_sort)
self.phase = f['cell_to/phase']['label'].astype('str') self.phase = f['cell_to/phase']['label'].astype('str')
self.phases = sorted(np.unique(self.phase),key=util.natural_sort) self._phases = sorted(np.unique(self.phase),key=util.natural_sort)
self.fields: List[str] = [] fields: List[str] = []
for c in self.phases: for c in self._phases:
self.fields += f['/'.join([self.increments[0],'phase',c])].keys() fields += f['/'.join([self._increments[0],'phase',c])].keys()
for m in self.homogenizations: for m in self._homogenizations:
self.fields += f['/'.join([self.increments[0],'homogenization',m])].keys() fields += f['/'.join([self._increments[0],'homogenization',m])].keys()
self.fields = sorted(set(self.fields),key=util.natural_sort) # make unique self._fields = sorted(set(fields),key=util.natural_sort) # make unique
self.visible = {'increments': self.increments, self.visible = {'increments': self._increments,
'phases': self.phases, 'phases': self._phases,
'homogenizations': self.homogenizations, 'homogenizations': self._homogenizations,
'fields': self.fields, 'fields': self._fields,
} }
self.fname = Path(fname).expanduser().absolute() self.fname = Path(fname).expanduser().absolute()
@ -223,24 +223,24 @@ class Result:
if what == 'increments': if what == 'increments':
choice = [c if isinstance(c,str) and c.startswith(prefix_inc) else 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] f'{prefix_inc}{c}' for c in choice]
elif what == 'times': elif what == 'times':
atol = 1e-2 * np.min(np.diff(self.times)) atol = 1e-2 * np.min(np.diff(self._times))
what = 'increments' what = 'increments'
if choice == ['*']: if choice == ['*']:
choice = self.increments choice = self._increments
else: else:
iterator = np.array(choice).astype(float) iterator = np.array(choice).astype(float)
choice = [] choice = []
for c in iterator: for c in iterator:
idx = np.searchsorted(self.times,c,side='left') idx = np.searchsorted(self._times,c,side='left')
if idx<len(self.times) and np.isclose(c,self.times[idx],rtol=0,atol=atol): if idx<len(self._times) and np.isclose(c,self._times[idx],rtol=0,atol=atol):
choice.append(self.increments[idx]) choice.append(self._increments[idx])
elif idx>0 and np.isclose(c,self.times[idx-1],rtol=0,atol=atol): elif idx>0 and np.isclose(c,self._times[idx-1],rtol=0,atol=atol):
choice.append(self.increments[idx-1]) choice.append(self._increments[idx-1])
valid = _match(choice,getattr(self,what)) valid = _match(choice,getattr(self,'_'+what))
existing = set(self.visible[what]) existing = set(self.visible[what])
if action == 'set': 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), 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[ 0] if start is None else start,
self.incs[-1] if end is None else end)) self._incs[-1] if end is None else end))
return [i for i in self.incs if s <= i <= e] return [i for i in self._incs if s <= i <= e]
def times_in_range(self, def times_in_range(self,
start: Optional[float] = None, start: Optional[float] = None,
@ -296,9 +296,9 @@ class Result:
Time of each increment within the given bounds. Time of each increment within the given bounds.
""" """
s,e = (self.times[ 0] if start is None else start, s,e = (self._times[ 0] if start is None else start,
self.times[-1] if end is None else end) self._times[-1] if end is None else end)
return [t for t in self.times if s <= t <= e] return [t for t in self._times if s <= t <= e]
def view(self,*, def view(self,*,
@ -533,7 +533,7 @@ class Result:
msg = [] msg = []
with h5py.File(self.fname,'r') as f: 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[self.increments.index(inc)]} s)'] msg += [f'\n{inc} ({self._times[self._increments.index(inc)]} s)']
for ty in ['phase','homogenization']: for ty in ['phase','homogenization']:
msg += [f' {ty}'] msg += [f' {ty}']
for label in self.visible[ty+'s']: for label in self.visible[ty+'s']:
@ -566,8 +566,28 @@ class Result:
return files return files
@property @property
def incs(self): def _incs(self):
return [int(i.split(prefix_inc)[-1]) for i in self.increments] 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 @property
@ -1723,7 +1743,7 @@ class Result:
DADF5 file at a stable relative path. 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.') 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')) ) attribute_type_map = defaultdict(lambda:'Matrix', ( ((),'Scalar'), ((3,),'Vector'), ((3,3),'Tensor')) )
@ -1749,7 +1769,7 @@ class Result:
time.attrib = {'TimeType': 'List'} time.attrib = {'TimeType': 'List'}
time_data = ET.SubElement(time, 'DataItem') 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', time_data.attrib = {'Format': 'XML',
'NumberType': 'Float', 'NumberType': 'Float',
'Dimensions': f'{len(times)}'} 'Dimensions': f'{len(times)}'}
@ -1876,7 +1896,7 @@ class Result:
v.comments = [util.execution_stamp('Result','export_VTK')] 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 \ constituents_ = constituents if isinstance(constituents,Iterable) else \
(range(self.N_constituents) if constituents is None else [constituents]) # type: ignore (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: if self.N_constituents != 1 or not self.structured:
raise TypeError('DREAM3D output requires structured grid with single constituent.') 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() 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.create_dataset(name='PhaseName',data = phase_name, dtype=h5py.Datatype(tid))
cell_ensemble.attrs['AttributeMatrixType'] = np.array([11],np.uint32) 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']: for group in ['CrystalStructures','PhaseTypes','PhaseName']:
add_attribute(cell_ensemble[group], 'ComponentDimensions', np.array([1],np.uint64)) 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], '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']: for group in ['CrystalStructures','PhaseTypes']:
add_attribute(cell_ensemble[group], 'ObjectType', 'DataArray<uint32_t>') add_attribute(cell_ensemble[group], 'ObjectType', 'DataArray<uint32_t>')
add_attribute(cell_ensemble['PhaseName'], 'ObjectType', 'StringDataArray') add_attribute(cell_ensemble['PhaseName'], 'ObjectType', 'StringDataArray')
@ -2129,9 +2149,9 @@ class Result:
f_out[inc]['geometry'].create_dataset('u_n',data=u_n) 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) 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) f_in[inc]['phase'].copy(label,f_out[inc]['phase'],shallow=True)
for ty in ['phase','homogenization']: for ty in ['phase','homogenization']:

View File

@ -126,7 +126,7 @@ class TestResult:
@pytest.mark.parametrize('sign',[+1,-1]) @pytest.mark.parametrize('sign',[+1,-1])
def test_view_approxtimes(self,default,inc,sign): def test_view_approxtimes(self,default,inc,sign):
eps = sign*1e-3 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): def test_add_invalid(self,default):
default.add_absolute('xxxx') default.add_absolute('xxxx')
@ -470,7 +470,7 @@ class TestResult:
assert np.array_equal(dset,cur[path]) assert np.array_equal(dset,cur[path])
else: else:
c = [_.decode() for _ in cur[path]] c = [_.decode() for _ in cur[path]]
r = ['Unknown Phase Type'] + result.phases r = ['Unknown Phase Type'] + result._phases
assert c == r assert c == r
grp = os.path.split(path)[0] grp = os.path.split(path)[0]
for attr in ref[grp].attrs: for attr in ref[grp].attrs:
@ -650,8 +650,8 @@ class TestResult:
'check_compile_job1.hdf5',]) 'check_compile_job1.hdf5',])
def test_export_DADF5(self,res_path,tmp_path,fname): def test_export_DADF5(self,res_path,tmp_path,fname):
r = Result(res_path/fname) r = Result(res_path/fname)
r = r.view(phases = random.sample(r.phases,1)) 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(increments = random.sample(r._increments,np.random.randint(1,len(r._increments))))
r.export_DADF5(tmp_path/fname) r.export_DADF5(tmp_path/fname)
r_exp = Result(tmp_path/fname) r_exp = Result(tmp_path/fname)
assert str(r.get()) == str(r_exp.get()) assert str(r.get()) == str(r_exp.get())