"""Representation of a material's engineering properties and other data."""
import yaml
from materials.property import Property, StateDependentProperty
[docs]def build_properties(properties_dict_yaml):
"""Create a dict of Property from a (YAML-derived) dictionary.
Arguments:
properties_dict_yaml (dict): A dict of material property data derived from a YAML file.
Returns:
properties_dict_py (dict): keys are property name strings, values are Property objects.
"""
properties_dict_py = {} # Dictionary of properties as python objects
for property_name, property_dict in properties_dict_yaml.items():
if 'variations_with_state' in property_dict:
prop = StateDependentProperty(property_name, property_dict)
else:
prop = Property(property_name, property_dict)
# TODO check that the property was properly constructed.
properties_dict_py[property_name] = prop
return properties_dict_py
[docs]class Material:
"""An engineering material, in a particular form and condition."""
def __init__(self, name, form=None, condition=None, category=None, subcategory=None,
references=None, properties_dict=None, elemental_composition=None):
"""Create a Material.
We don't recommend using this function directly, instead use
`load_from_yaml` to create a Material object from a YAML file
of material property data.
Arguments:
name (string): Name of the material.
form (string): The form in which the material was produced,
e.g. 'extruded', 'forged', etc. We use form in the same sense as MMPDS [1].
The form can effect some properties of the material.
condition (string): The condition, heat treatment, or temper of the material.
MMPDS [1] uses 'condition' or 'temper' to refer to this concept,
depending on the alloy family.
Different alloy families use different condition/temper designations,
these designations are described in MMPDS or the relevant materials standards.
The condition can effect some properties of the material.
category (string): The broad category to which the material belongs,
e.g. 'metal', 'ceramic' or 'plastic'.
subcategory (string): The subcategory to which the material belongs,
e.g. 'aluminum alloy' or 'thermoplastic'.
references (list of string): References from which the material property data was gathered.
Each string in the list should be a bibtex entry for one reference.
properties_dict (dict): A dict of material property data derived from a YAML file.
elemental_composition (dict): A dict of material's elemental composition, as [min, max]
percent by mass.
References:
[1] Richard C Rice and Jana L Jackson and John Bakuckas and Steven Thompson,
"Metallic Materials Properties Development and Standardization (MMPDS)",
U.S. Department of Transportation, Federal Aviation Administration, 2001.
"""
self.name = name
self.form = form
self.condition = condition
self.category = category
self.subcategory = subcategory
self.references = references
self.elemental_composition = elemental_composition
if self.elemental_composition is not None:
for element, limits in self.elemental_composition.items():
# limits[0] is element minimum percent by mass,
# limits[1] is element maximum percent by mass.
if not (0. <= limits[0] <= limits[1] <= 100.):
raise ValueError(
'Elemental composition limits on {:s} are not valid: '.format(element)
+ 'min = {:.3f} %, max = {:.3f} %'.format(*limits))
if properties_dict is not None:
self.properties = build_properties(properties_dict)
def __getitem__(self, key):
"""Get a property of the material by name.
A Material is primarily a collection of properties, so we use []
as a shorthand to access a property.
i.e. `mat[key]` is a shorthand for `mat.properties[key]`.
Argument:
key (string): the name of a property.
Returns:
Property: `self.properties[key]`
"""
if key not in self.properties:
raise KeyError(
'This Material does not have a {:s} property'.format(key)
+ '\nThe valid keys are {}'.format(self.properties.keys())
)
return self.properties[key]
[docs] def elements_table_str(self):
"""Create (as a string) a table of the elemental composition data."""
ELEM_IND = 0
VALUE_IND = 1
MIN_IND = 0
MAX_IND = 1
element_percent_format_str = ' {:2.2f}'
string = 'Elemental composition:\n'
sorted_by_max_mass = sorted(
self.elemental_composition.items(), key=lambda kv: kv[VALUE_IND][MAX_IND], reverse=True)
string += 'Element: '
for pair in sorted_by_max_mass:
string += '{:>7}'.format(pair[ELEM_IND])
string += '\nMax % by mass: '
for pair in sorted_by_max_mass:
string += element_percent_format_str.format(pair[VALUE_IND][MAX_IND])
string += '\nMin % by mass: '
for pair in sorted_by_max_mass:
string += element_percent_format_str.format(pair[VALUE_IND][MIN_IND])
return string
def __str__(self):
string = self.name
string += '\n' + '-' * len(self.name) + '\n'
string += '{:s}, {:s}\n'.format(self.category, self.subcategory)
string += '\n' + self.elements_table_str() + '\n\n'
string += 'Properties for the "{:s}" form, "{:s}" condition:\n'.format(self.form, self.condition)
string += '\n\n'.join([str(prop) for prop in self.properties.values()])
return string
[docs]def load_from_yaml(filename, form, condition):
"""Load a material from a YAML file.
Arguments:
filename (string): Path to the YAML file containing the material data.
form (string): The form in which the material was produced,
e.g. 'extruded', 'forged', etc. We use form in the same sense as MMPDS [1].
The form can effect some properties of the material.
Must be a key in the `forms` section of the YAML file.
condition (string): The condition, heat treatment, or temper of the material.
MMPDS [1] uses 'condition' or 'temper' to refer to this concept,
depending on the alloy family.
Different alloy families use different condition/temper designations,
these designations are described in MMPDS or the relevant materials standards.
The condition can effect some properties of the material.
Must be a key in the `conditions` section of the YAML file for the given form.
Returns:
Material
"""
with open(filename, 'r') as yaml_stream:
matl_dict = yaml.load(yaml_stream)
# Check that the reqested form and condition are present
if not form in matl_dict['forms']:
raise ValueError('Form {:s} not present in {:s}'.format(form, filename))
if not condition in matl_dict['forms'][form]['conditions']:
raise ValueError('Condition {:s} not present in {:s}, {:s}'.format(
condition, form, filename))
name = matl_dict['name']
category = matl_dict['category']
if 'subcategory' in matl_dict:
subcategory = matl_dict['subcategory']
else:
subcategory = None
references = matl_dict['references']
elemental_composition = matl_dict['elemental_composition']
properties_dict = matl_dict['forms'][form]['conditions'][condition]['properties']
matl = Material(name, form, condition, category, subcategory,
references, properties_dict, elemental_composition)
return matl