05 Typehints config configmaterial

This commit is contained in:
Daniel Otto de Mentock 2022-02-01 07:30:00 +00:00 committed by Martin Diehl
parent 98381bff2a
commit 086ff42be5
3 changed files with 79 additions and 38 deletions

View File

@ -2,25 +2,34 @@ import copy
from io import StringIO from io import StringIO
from collections.abc import Iterable from collections.abc import Iterable
import abc import abc
from pathlib import Path
from typing import Union, Dict, Any, Type, TypeVar
import numpy as np import numpy as np
import yaml import yaml
from ._typehints import FileHandle
from . import Rotation from . import Rotation
MyType = TypeVar('MyType', bound='Config')
class NiceDumper(yaml.SafeDumper): class NiceDumper(yaml.SafeDumper):
"""Make YAML readable for humans.""" """Make YAML readable for humans."""
def write_line_break(self, data=None): def write_line_break(self,
data: str = None):
super().write_line_break(data) super().write_line_break(data)
if len(self.indents) == 1: if len(self.indents) == 1:
super().write_line_break() super().write_line_break()
def increase_indent(self, flow=False, indentless=False): def increase_indent(self,
flow: bool = False,
indentless: bool = False):
return super().increase_indent(flow, False) return super().increase_indent(flow, False)
def represent_data(self, data): def represent_data(self,
data: Any):
"""Cast Config objects and its subclasses to dict.""" """Cast Config objects and its subclasses to dict."""
if isinstance(data, dict) and type(data) != dict: if isinstance(data, dict) and type(data) != dict:
return self.represent_data(dict(data)) return self.represent_data(dict(data))
@ -31,14 +40,17 @@ class NiceDumper(yaml.SafeDumper):
else: else:
return super().represent_data(data) return super().represent_data(data)
def ignore_aliases(self, data): def ignore_aliases(self,
data: Any) -> bool:
"""Do not use references to existing objects.""" """Do not use references to existing objects."""
return True return True
class Config(dict): class Config(dict):
"""YAML-based configuration.""" """YAML-based configuration."""
def __init__(self,yml=None,**kwargs): def __init__(self,
yml: Union[str, Dict[str, Any]] = None,
**kwargs):
"""Initialize from YAML, dict, or key=value pairs.""" """Initialize from YAML, dict, or key=value pairs."""
if isinstance(yml,str): if isinstance(yml,str):
kwargs.update(yaml.safe_load(yml)) kwargs.update(yaml.safe_load(yml))
@ -47,7 +59,7 @@ class Config(dict):
super().__init__(**kwargs) super().__init__(**kwargs)
def __repr__(self): def __repr__(self) -> str:
"""Show as in file.""" """Show as in file."""
output = StringIO() output = StringIO()
self.save(output) self.save(output)
@ -55,14 +67,15 @@ class Config(dict):
return ''.join(output.readlines()) return ''.join(output.readlines())
def __copy__(self): def __copy__(self: MyType) -> MyType:
"""Create deep copy.""" """Create deep copy."""
return copy.deepcopy(self) return copy.deepcopy(self)
copy = __copy__ copy = __copy__
def __or__(self,other): def __or__(self: MyType,
other) -> MyType:
""" """
Update configuration with contents of other. Update configuration with contents of other.
@ -76,18 +89,24 @@ class Config(dict):
updated : damask.Config updated : damask.Config
Updated configuration. Updated configuration.
Note
----
This functionality is a backport for Python 3.8
""" """
duplicate = self.copy() duplicate = self.copy()
duplicate.update(other) duplicate.update(other)
return duplicate return duplicate
def __ior__(self,other): def __ior__(self: MyType,
other) -> MyType:
"""Update configuration with contents of other.""" """Update configuration with contents of other."""
return self.__or__(other) return self.__or__(other)
def delete(self,keys): def delete(self: MyType,
keys: Union[Iterable, str]) -> MyType:
""" """
Remove configuration keys. Remove configuration keys.
@ -109,7 +128,8 @@ class Config(dict):
@classmethod @classmethod
def load(cls,fname): def load(cls: Type[MyType],
fname: FileHandle) -> MyType:
""" """
Load from yaml file. Load from yaml file.
@ -124,14 +144,15 @@ class Config(dict):
Configuration from file. Configuration from file.
""" """
try: if isinstance(fname, (str, Path)):
fhandle = open(fname) fhandle = open(fname)
except TypeError: else:
fhandle = fname fhandle = fname
return cls(yaml.safe_load(fhandle)) return cls(yaml.safe_load(fhandle))
def save(self,
def save(self,fname,**kwargs): fname: FileHandle,
**kwargs):
""" """
Save to yaml file. Save to yaml file.
@ -143,9 +164,9 @@ class Config(dict):
Keyword arguments parsed to yaml.dump. Keyword arguments parsed to yaml.dump.
""" """
try: if isinstance(fname, (str, Path)):
fhandle = open(fname,'w',newline='\n') fhandle = open(fname,'w',newline='\n')
except TypeError: else:
fhandle = fname fhandle = fname
if 'width' not in kwargs: if 'width' not in kwargs:

View File

@ -1,10 +1,14 @@
import numpy as np import numpy as np
import h5py import h5py
from typing import Sequence, Dict, Any, Collection
from ._typehints import FileHandle
from . import Config from . import Config
from . import Rotation from . import Rotation
from . import Orientation from . import Orientation
from . import util from . import util
from . import Table
class ConfigMaterial(Config): class ConfigMaterial(Config):
""" """
@ -17,7 +21,9 @@ class ConfigMaterial(Config):
""" """
def __init__(self,d=None,**kwargs): def __init__(self,
d: Dict[str, Any] = None,
**kwargs):
""" """
New material configuration. New material configuration.
@ -30,14 +36,17 @@ class ConfigMaterial(Config):
Initial content specified as pairs of key=value. Initial content specified as pairs of key=value.
""" """
default: Collection
if d is None: if d is None:
for section,default in {'material':[],'homogenization':{},'phase':{}}.items(): for section, default in {'material':[],'homogenization':{},'phase':{}}.items():
if section not in kwargs: kwargs.update({section:default}) if section not in kwargs: kwargs.update({section:default})
super().__init__(d,**kwargs) super().__init__(d,**kwargs)
def save(self,fname='material.yaml',**kwargs): def save(self,
fname: FileHandle = 'material.yaml',
**kwargs):
""" """
Save to yaml file. Save to yaml file.
@ -53,7 +62,8 @@ class ConfigMaterial(Config):
@classmethod @classmethod
def load(cls,fname='material.yaml'): def load(cls,
fname: FileHandle = 'material.yaml') -> 'ConfigMaterial':
""" """
Load from yaml file. Load from yaml file.
@ -72,10 +82,14 @@ class ConfigMaterial(Config):
@staticmethod @staticmethod
def load_DREAM3D(fname, def load_DREAM3D(fname: str,
grain_data=None,cell_data=None,cell_ensemble_data='CellEnsembleData', grain_data: str = None,
phases='Phases',Euler_angles='EulerAngles',phase_names='PhaseName', cell_data: str = None,
base_group=None): cell_ensemble_data: str = 'CellEnsembleData',
phases: str = 'Phases',
Euler_angles: str = 'EulerAngles',
phase_names: str = 'PhaseName',
base_group: str = None) -> 'ConfigMaterial':
""" """
Load DREAM.3D (HDF5) file. Load DREAM.3D (HDF5) file.
@ -154,7 +168,8 @@ class ConfigMaterial(Config):
@staticmethod @staticmethod
def from_table(table,**kwargs): def from_table(table: Table,
**kwargs) -> 'ConfigMaterial':
""" """
Generate from an ASCII table. Generate from an ASCII table.
@ -207,7 +222,7 @@ class ConfigMaterial(Config):
@property @property
def is_complete(self): def is_complete(self) -> bool:
""" """
Check for completeness. Check for completeness.
@ -267,12 +282,11 @@ class ConfigMaterial(Config):
if homogenization - set(self['homogenization']): if homogenization - set(self['homogenization']):
print(f'Homogenization(s) {homogenization-set(self["homogenization"])} missing') print(f'Homogenization(s) {homogenization-set(self["homogenization"])} missing')
ok = False ok = False
return ok return ok
@property @property
def is_valid(self): def is_valid(self) -> bool:
""" """
Check for valid content. Check for valid content.
@ -316,7 +330,10 @@ class ConfigMaterial(Config):
return ok return ok
def material_rename_phase(self,mapping,ID=None,constituent=None): def material_rename_phase(self,
mapping: Dict[str, str],
ID: Sequence[int] = None,
constituent: Sequence[int] = None) -> 'ConfigMaterial':
""" """
Change phase name in material. Change phase name in material.
@ -347,7 +364,9 @@ class ConfigMaterial(Config):
return dup return dup
def material_rename_homogenization(self,mapping,ID=None): def material_rename_homogenization(self,
mapping: Dict[str, str],
ID: Sequence[int] = None) -> 'ConfigMaterial':
""" """
Change homogenization name in material. Change homogenization name in material.
@ -374,7 +393,8 @@ class ConfigMaterial(Config):
return dup return dup
def material_add(self,**kwargs): def material_add(self,
**kwargs: Any) -> 'ConfigMaterial':
""" """
Add material entries. Add material entries.
@ -453,7 +473,7 @@ class ConfigMaterial(Config):
N = max(N,s[0]) if len(s)>0 else N N = max(N,s[0]) if len(s)>0 else N
n = max(n,s[1]) if len(s)>1 else n n = max(n,s[1]) if len(s)>1 else n
mat = [{'constituents':[{} for _ in range(n)]} for _ in range(N)] mat: Sequence[dict] = [{'constituents':[{} for _ in range(n)]} for _ in range(N)]
if 'v' not in kwargs: if 'v' not in kwargs:
shaped['v'] = np.broadcast_to(1/n,(N,n)) shaped['v'] = np.broadcast_to(1/n,(N,n))
@ -461,7 +481,7 @@ class ConfigMaterial(Config):
map_shape = {'O':(N,n,4),'F_i':(N,n,3,3)} map_shape = {'O':(N,n,4),'F_i':(N,n,3,3)}
for k,v in shaped.items(): for k,v in shaped.items():
target = map_shape.get(k,(N,n)) target = map_shape.get(k,(N,n))
obj = np.broadcast_to(v.reshape(util.shapeshifter(v.shape,target,mode='right')),target) obj = np.broadcast_to(v.reshape(util.shapeshifter(v.shape, target, mode = 'right')), target)
for i in range(N): for i in range(N):
if k in ['phase','O','v','F_i']: if k in ['phase','O','v','F_i']:
for j in range(n): for j in range(n):

View File

@ -9,7 +9,7 @@ import re
import fractions import fractions
from collections import abc from collections import abc
from functools import reduce from functools import reduce
from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, Optional from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, SupportsIndex, Sequence
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
@ -427,7 +427,7 @@ def hybrid_IA(dist: np.ndarray,
def shapeshifter(fro: Tuple[int, ...], def shapeshifter(fro: Tuple[int, ...],
to: Tuple[int, ...], to: Tuple[int, ...],
mode: Literal['left','right'] = 'left', mode: Literal['left','right'] = 'left',
keep_ones: bool = False) -> Tuple[Optional[int], ...]: keep_ones: bool = False) -> Sequence[SupportsIndex]:
""" """
Return dimensions that reshape 'fro' to become broadcastable to 'to'. Return dimensions that reshape 'fro' to become broadcastable to 'to'.
@ -483,14 +483,14 @@ def shapeshifter(fro: Tuple[int, ...],
grp = match.groups() grp = match.groups()
except AssertionError: except AssertionError:
raise ValueError(f'Shapes can not be shifted {fro} --> {to}') raise ValueError(f'Shapes can not be shifted {fro} --> {to}')
fill: Tuple[Optional[int], ...] = () fill: Any = ()
for g,d in zip(grp,fro+(None,)): for g,d in zip(grp,fro+(None,)):
fill += (1,)*g.count(',')+(d,) fill += (1,)*g.count(',')+(d,)
return fill[:-1] return fill[:-1]
def shapeblender(a: Tuple[int, ...], def shapeblender(a: Tuple[int, ...],
b: Tuple[int, ...]) -> Tuple[int, ...]: b: Tuple[int, ...]) -> Sequence[SupportsIndex]:
""" """
Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'.