def test_material_spec(): """Test that Process/Material Spec link survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) # Create MaterialSpec without a ProcessSpec prop = Property( name="The material is a solid", value=DiscreteCategorical(probabilities="solid") ) mat_spec = MaterialSpec(name="a material spec", properties=PropertyAndConditions(prop)) assert mat_spec.process is None, \ "MaterialSpec should be initialized with no ProcessSpec, by default" # Assign a ProcessSpec to mat_spec, first ensuring that the type is enforced with pytest.raises(TypeError): mat_spec.process = 17 mat_spec.process = proc_spec # Assert circular links assert dumps(proc_spec.output_material.process) == dumps(proc_spec), \ "ProcessSpec should link to MaterialSpec that links back to itself" assert dumps(mat_spec.process.output_material) == dumps(mat_spec), \ "MaterialSpec should link to ProcessSpec that links back to itself" # Make copies of both specs mat_spec_copy = loads(dumps(mat_spec)) proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy.output_material == mat_spec, \ "Serialization should preserve link from ProcessSpec to MaterialSpec" assert mat_spec_copy.process == proc_spec, \ "Serialization should preserve link from MaterialSpec to ProcessSpec"
def test_process_reassignment(): """Test that a material can be assigned to a new process.""" drying = ProcessSpec("drying") welding = ProcessSpec("welding") powder = MaterialSpec("Powder", process=welding) assert powder.process == welding assert welding.output_material == powder powder.process = drying assert powder.process == drying assert drying.output_material == powder assert welding.output_material is None
def test_circular_crawl(): """Test that make_instance can handle a circular set of linked objects.""" proc = ProcessSpec("process name") mat = MaterialSpec("material name", process=proc) IngredientSpec(name="ingredient name", material=mat, process=proc) mat_run = make_instance(mat) assert mat_run == mat_run.process.ingredients[0].material
def test_serialized_history(): """Test the serialization of a complete material history.""" # Create several runs and specs linked together buy_spec = LinkByUID("id", "pr723") cookie_dough_spec = MaterialSpec("cookie dough spec", process=buy_spec) buy_cookie_dough = ProcessRun("Buy cookie dough", uids={'id': '32283'}, spec=buy_spec) cookie_dough = MaterialRun("cookie dough", process=buy_cookie_dough, spec=cookie_dough_spec) bake = ProcessRun("bake cookie dough", conditions=[ Condition("oven temp", origin='measured', value=NominalReal(357, 'degF'))]) IngredientRun(material=cookie_dough, process=bake, number_fraction=NominalReal(1, '')) cookie = MaterialRun("cookie", process=bake, tags=["chocolate chip", "drop"]) MeasurementRun("taste", material=cookie, properties=[ Property("taste", value=DiscreteCategorical("scrumptious"))]) cookie_history = complete_material_history(cookie) # There are 7 entities in the serialized list: cookie dough (spec & run), buy cookie dough, # cookie dough ingredient, bake cookie dough, cookie, taste assert len(cookie_history) == 7 for entity in cookie_history: assert len(entity['uids']) > 0, "Serializing material history should assign uids." # Check that the measurement points to the material taste_dict = next(x for x in cookie_history if x.get('type') == 'measurement_run') cookie_dict = next(x for x in cookie_history if x.get('name') == 'cookie') scope = taste_dict.get('material').get('scope') assert taste_dict.get('material').get('id') == cookie_dict.get('uids').get(scope) # Check that both the material spec and the process run point to the same process spec. # Because that spec was initially a LinkByUID, this also tests the methods ability to # serialize a LinkByUID. cookie_dough_spec_dict = next(x for x in cookie_history if x.get('type') == 'material_spec') buy_cookie_dough_dict = next(x for x in cookie_history if x.get('name') == 'Buy cookie dough') assert cookie_dough_spec_dict.get('process') == buy_spec.as_dict() assert buy_cookie_dough_dict.get('spec') == buy_spec.as_dict()
def test_ingredient_spec(): """Tests that a process can house an ingredient, and that pairing survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) IngredientSpec(name='Input', material=MaterialSpec(name='Raw'), process=proc_spec) # Make copies of both specs proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy == proc_spec, "Full structure wasn't preserved across serialization"
def __init__(self, name: str, uids: Optional[Dict[str, str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, process: Optional[TaurusProcessSpec] = None, properties: Optional[List[PropertyAndConditions]] = None, template: Optional[TaurusMaterialTemplate] = None, file_links: Optional[List[FileLink]] = None): DataConcepts.__init__(self, TaurusMaterialSpec.typ) TaurusMaterialSpec.__init__(self, name=name, uids=set_default_uid(uids), tags=tags, process=process, properties=properties, template=template, file_links=file_links, notes=notes)
def test_make_instance(): """Build up several linked objects and test their properties.""" msr_spec = MeasurementSpec() assert isinstance(make_instance(msr_spec), MeasurementRun) mat_spec = MaterialSpec(name='Mat name') mat_spec.process = ProcessSpec(name='Pro name') IngredientSpec(name='Ing label', process=mat_spec.process) mat_spec.process.ingredients[0].material = MaterialSpec(name='Baby mat name') mat_run = make_instance(mat_spec) assert isinstance(mat_run, MaterialRun) assert isinstance(mat_run.process, ProcessRun) assert isinstance(mat_run.process.ingredients[0], IngredientRun) assert isinstance(mat_run.process.ingredients[0].material, MaterialRun) assert mat_run.process.spec == mat_run.spec.process ingredient = mat_run.process.ingredients[0] assert ingredient.spec == mat_run.spec.process.ingredients[0] assert ingredient.material.spec == mat_run.spec.process.ingredients[0].material
def make_strehlow_objects(table=None): """Make a table with Strehlow & Cook data.""" tmpl = make_templates() if table is None: table = import_table() # Specs msr_spec = MeasurementSpec(name='Band gap', template=tmpl["Band gap measurement"]) def real_mapper(prop): """Mapping methods for RealBounds.""" if 'uncertainty' in prop['scalars'][0]: val = NormalReal(mean=float(prop['scalars'][0]['value']), units=prop['units'], std=float(prop['scalars'][0]['uncertainty'])) else: val = NominalReal(nominal=float(prop['scalars'][0]['value']), units=prop['units']) return val content_map = { RealBounds: real_mapper, CategoricalBounds: lambda prop: NominalCategorical(category=prop['scalars'][0]['value']), type(None): lambda bnd: 'Label' } datapoints = [] compounds = dict() for row in table: formula = formula_clean(row['chemicalFormula']) spec = compounds.get( formula, MaterialSpec(name=formula_latex(formula), template=tmpl["Chemical"], process=ProcessSpec( name="Sample preparation", template=tmpl["Sample preparation"]))) run = make_instance(spec) datapoints.append(run) if not spec.properties: spec.properties.append( PropertyAndConditions(property=Property( name=spec.template.properties[0][0].name, value=EmpiricalFormula(formula=formula), template=spec.template.properties[0][0]))) msr = make_instance(msr_spec) msr.material = run # 2 categories in the PIF need to be split to avoid repeat Attribute Templates in a Run name_map = {'Phase': 'Crystal system', 'Transition': 'Bands'} origin_map = { 'EXPERIMENTAL': Origin.MEASURED, 'COMPUTATIONAL': Origin.COMPUTED } seen = set( ) # Some conditions come in from multiple properties on the same object for prop in row['properties']: origin = origin_map.get(prop.get('dataType', None), Origin.UNKNOWN) if 'method' in prop: method = 'Method: ' + prop['method']['name'] else: method = 'Method: unreported' for attr in [prop] + prop.get('conditions', []): if attr['name'] in seen: # Early return if it's a repeat continue seen.add(attr['name']) template = tmpl[attr['name']] # Figure out if we need to split this column if attr['name'] in name_map: value = attr['scalars'][0]['value'] if value not in template.bounds.categories: template = tmpl[name_map[attr['name']]] # Move into GEMD structure if type(template) == PropertyTemplate: msr.properties.append( Property(name=template.name, template=template, value=content_map[type( template.bounds)](attr), origin=origin, notes=method)) elif type(template) == ConditionTemplate: msr.conditions.append( Condition(name=template.name, template=template, value=content_map[type( template.bounds)](attr), origin=origin, notes=method)) return datapoints
def test_invalid_assignment(): """Invalid assignments to `process` or `template` throw a TypeError.""" with pytest.raises(TypeError): MaterialSpec("name", process=["Process 1", "Process 2"]) with pytest.raises(TypeError): MaterialSpec("name", template=MaterialSpec("another spec"))
def make_cake_spec(): """Define a recipe for making a cake.""" ############################################################################################### # Templates tmpl = make_cake_templates() ############################################################################################### # Objects cake = MaterialSpec( name="Abstract Cake", template=tmpl["Dessert"], process=ProcessSpec( name='Icing, in General', template=tmpl["Icing"], tags=[ 'spreading' ], notes='The act of covering a baked output with frosting' ), file_links=FileLink( filename="Becky's Butter Cake", url='https://www.landolakes.com/recipe/16730/becky-s-butter-cake/' ), tags=[ 'cake::butter cake', 'dessert::baked::cake', 'iced::chocolate' ], notes='Butter cake recipe reminiscent of the 1-2-3-4 cake that Grandma may have baked.' ) ######################## frosting = MaterialSpec( name="Abstract Frosting", template=tmpl["Dessert"], process=ProcessSpec( name='Mixing Frosting, in General', template=tmpl["Mixing"], tags=[ 'mixing' ], notes='Combining ingredients to make a sweet frosting' ), tags=[ 'frosting::chocolate', 'topping::chocolate' ], notes='Chocolate frosting' ) IngredientSpec( name="{} input".format(frosting.name), tags=list(frosting.tags), notes='Seems like a lot of frosting', labels=['coating'], process=cake.process, material=frosting, absolute_quantity=NominalReal(nominal=0.751, units='kg') ) baked_cake = MaterialSpec( name="Abstract Baked Cake", template=tmpl["Generic Material"], process=ProcessSpec( name='Baking, in General', template=tmpl["Baking in an oven"], tags=[ 'oven::baking' ], notes='Using heat to convert batter into a solid matrix' ), tags=[ ], notes='The cakey part of the cake' ) IngredientSpec( name="{} input".format(baked_cake.name), tags=list(baked_cake.tags), labels=['substrate'], process=cake.process, material=baked_cake ) ######################## batter = MaterialSpec( name="Abstract Batter", template=tmpl["Generic Material"], process=ProcessSpec( name='Mixing Batter, in General', template=tmpl["Mixing"], tags=[ 'mixing' ], notes='Combining ingredients to make a baking feedstock' ), tags=[ ], notes='The fluid that converts to cake with heat' ) IngredientSpec( name="{} input".format(batter.name), tags=list(batter.tags), labels=['precursor'], process=baked_cake.process, material=batter ) ######################## wetmix = MaterialSpec( name="Abstract Wet Mix", template=tmpl["Generic Material"], process=ProcessSpec( name='Mixing Wet, in General', template=tmpl["Mixing"], tags=[ 'mixing' ], notes='Combining wet ingredients to make a baking feedstock' ), tags=[ ], notes='The wet fraction of a batter' ) IngredientSpec( name="{} input".format(wetmix.name), tags=list(wetmix.tags), labels=['wet'], process=batter.process, material=wetmix ) drymix = MaterialSpec( name="Abstract Dry Mix", template=tmpl["Generic Material"], process=ProcessSpec( name='Mixing Dry, in General', template=tmpl["Mixing"], tags=[ 'mixing' ], notes='Combining dry ingredients to make a baking feedstock' ), tags=[ ], notes='The dry fraction of a batter' ) IngredientSpec( name="{} input".format(drymix.name), tags=list(drymix.tags), labels=['dry'], process=batter.process, material=drymix, absolute_quantity=NominalReal(nominal=3.052, units='cups') ) ######################## flour = MaterialSpec( name="Abstract Flour", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Flour, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing all purpose flour' ), tags=[ ], notes='All-purpose flour' ) IngredientSpec( name="{} input".format(flour.name), tags=list(flour.tags), labels=['dry'], process=drymix.process, material=flour, volume_fraction=NominalReal(nominal=0.9829, units='') # 3 cups ) baking_powder = MaterialSpec( name="Abstract Baking Powder", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Baking Powder, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing baking powder' ), tags=[ ], notes='Leavening agent for cake' ) IngredientSpec( name="{} input".format(baking_powder.name), tags=list(baking_powder.tags), labels=['leavening', 'dry'], process=drymix.process, material=baking_powder, volume_fraction=NominalReal(nominal=0.0137, units='') # 2 teaspoons ) salt = MaterialSpec( name="Abstract Salt", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Salt, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing salt' ), tags=[ ], notes='Plain old NaCl' ) IngredientSpec( name="{} input".format(salt.name), tags=list(salt.tags), labels=['dry', 'seasoning'], process=drymix.process, material=salt, volume_fraction=NominalReal(nominal=0.0034, units='') # 1/2 teaspoon ) sugar = MaterialSpec( name="Abstract Sugar", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Sugar, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing all purpose flour' ), tags=[ ], notes='Sugar' ) IngredientSpec( name="{} input".format(sugar.name), tags=list(sugar.tags), labels=['wet', 'sweetener'], process=wetmix.process, material=sugar, absolute_quantity=NominalReal(nominal=2, units='cups') ) butter = MaterialSpec( name="Abstract Butter", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Butter, in General', template=tmpl["Procurement"], tags=[ 'purchase::produce' ], notes='Purchasing butter' ), tags=[ ], notes='Shortening for making rich, buttery baked goods' ) IngredientSpec( name="{} input".format(butter.name), tags=list(butter.tags), labels=['wet', 'shortening'], process=wetmix.process, material=butter, absolute_quantity=NominalReal(nominal=1, units='cups') ) IngredientSpec( name="{} input".format(butter.name), tags=list(butter.tags), labels=['shortening'], process=frosting.process, material=butter, mass_fraction=NominalReal(nominal=0.1434, units='') # 1/2 c @ 0.911 g/cc ) eggs = MaterialSpec( name="Abstract Eggs", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Eggs, in General', template=tmpl["Procurement"], tags=[ 'purchase::produce' ], notes='Purchasing eggs' ), tags=[ ], notes='' ) IngredientSpec( name="{} input".format(eggs.name), tags=list(eggs.tags), labels=['wet'], process=wetmix.process, material=eggs, absolute_quantity=NominalReal(nominal=4, units='') ) vanilla = MaterialSpec( name="Abstract Vanilla", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Vanilla, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing vanilla' ), tags=[ ], notes='' ) IngredientSpec( name="{} input".format(vanilla.name), tags=list(vanilla.tags), labels=['wet', 'flavoring'], process=wetmix.process, material=vanilla, absolute_quantity=NominalReal(nominal=2, units='teaspoons') ) IngredientSpec( name="{} input".format(vanilla.name), tags=list(vanilla.tags), labels=['flavoring'], process=frosting.process, material=vanilla, mass_fraction=NominalReal(nominal=0.0231, units='') # 2 tsp @ 0.879 g/cc ) milk = MaterialSpec( name="Abstract Milk", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Milk, in General', template=tmpl["Procurement"], tags=[ 'purchase::produce' ], notes='Purchasing milk' ), tags=[ ], notes='' ) IngredientSpec( name="{} input".format(milk.name), tags=list(milk.tags), labels=['wet'], process=batter.process, material=milk, absolute_quantity=NominalReal(nominal=1, units='cup') ) IngredientSpec( name="{} input".format(milk.name), tags=list(milk.tags), labels=[], process=frosting.process, material=milk, mass_fraction=NominalReal(nominal=0.0816, units='') # 1/4 c @ 1.037 g/cc ) chocolate = MaterialSpec( name="Abstract Chocolate", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Chocolate, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing chocolate' ), tags=[ ], notes='' ) IngredientSpec( name="{} input".format(chocolate.name), tags=list(chocolate.tags), labels=['flavoring'], process=frosting.process, material=chocolate, mass_fraction=NominalReal(nominal=0.1132, units='') # 3 oz. ) powder_sugar = MaterialSpec( name="Abstract Powdered Sugar", template=tmpl["Generic Material"], process=ProcessSpec( name='Buying Powdered Sugar, in General', template=tmpl["Procurement"], tags=[ 'purchase::dry-goods' ], notes='Purchasing powdered sugar' ), tags=[ ], notes='Granulated sugar mixed with corn starch' ) IngredientSpec( name="{} input".format(powder_sugar.name), tags=list(powder_sugar.tags), labels=['flavoring'], process=frosting.process, material=powder_sugar, mass_fraction=NominalReal(nominal=0.6387, units='') # 4 c @ 30 g/ 0.25 cups ) return cake