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_material_run(): """ Test the ability to create a MaterialRun that is linked to a MaterialSpec. Make sure all enumerated values are respected, and check consistency after serializing and deserializing. """ # Define a property, and make sure that an inappropriate value for origin throws ValueError with pytest.raises(ValueError): prop = Property(name="A property", origin="bad origin", value=NominalReal(17, units='')) # Create a MaterialSpec with a property prop = Property(name="A property", origin="specified", value=NominalReal(17, units='')) mat_spec = MaterialSpec( name="a specification for a material", properties=PropertyAndConditions(prop), notes="Funny lookin'" ) # Make sure that when property is serialized, origin (an enumeration) is serialized as a string copy_prop = json.loads(dumps(mat_spec)) copy_origin = copy_prop["context"][0]["properties"][0]['property']['origin'] assert isinstance(copy_origin, str) # Create a MaterialRun, and make sure an inappropriate value for sample_type throws ValueError with pytest.raises(ValueError): mat = MaterialRun("name", spec=mat_spec, sample_type="imaginary") mat = MaterialRun("name", spec=mat_spec, sample_type="virtual") # ensure that serialization does not change the MaterialRun copy = loads(dumps(mat)) assert dumps(copy) == dumps(mat), \ "Material run is modified by serialization or deserialization"
def test_build(): """Test that build recreates the material.""" spec = MaterialSpec("A spec", properties=PropertyAndConditions( property=Property("a property", value=NominalReal(3, ''))), tags=["a tag"]) mat = MaterialRun(name="a material", spec=spec) mat_dict = mat.as_dict() mat_dict['spec'] = mat.spec.as_dict() assert MaterialRun.build(mat_dict) == mat
def test_mat_spec_properties(caplog): """Make sure template validations and level controls behave as expected.""" prop_tmpl = PropertyTemplate("Name", bounds=IntegerBounds(0, 2)) cond_tmpl = ConditionTemplate("Name", bounds=IntegerBounds(0, 2)) mat_tmpl = MaterialTemplate("Material Template", properties=[[prop_tmpl, IntegerBounds(0, 1)]]) mat_spec = MaterialSpec("Material Spec", template=mat_tmpl) good_prop = PropertyAndConditions( property=Property("Name", value=NominalInteger(1), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(1), template=cond_tmpl)] ) bad_prop = PropertyAndConditions( property=Property("Name", value=NominalInteger(2), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(1), template=cond_tmpl)] ) bad_cond = PropertyAndConditions( # This will pass since we don't have a condition constraint property=Property("Name", value=NominalInteger(1), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(2), template=cond_tmpl)] ) with validation_level(WarningLevel.IGNORE): mat_spec.properties.append(good_prop) assert len(caplog.records) == 0, "Warning encountered on IGNORE" mat_spec.properties.append(bad_prop) assert len(caplog.records) == 0, "Warning encountered on IGNORE" mat_spec.properties.append(bad_cond) assert len(caplog.records) == 0, "Warning encountered on IGNORE" with validation_level(WarningLevel.WARNING): mat_spec.properties.append(good_prop) assert len(caplog.records) == 0, "Warning encountered on Good value" mat_spec.properties.append(bad_prop) assert len(caplog.records) == 1, "No warning encountered on Bad Value" mat_spec.properties.append(bad_cond) assert len(caplog.records) == 1, "Warning encountered on Bad condition" with validation_level(WarningLevel.FATAL): mat_spec.properties.append(good_prop) # This is fine with pytest.raises(ValueError): mat_spec.properties.append(bad_prop) mat_spec.properties.append(bad_cond) # This should probably not be fine
def add_attribute(target: Union[HasProperties, HasConditions, HasParameters], template: Union[PropertyTemplate, ConditionTemplate, ParameterTemplate], value: Union[BaseValue, str, float, int] ) -> Union[Property, Condition, Parameter]: """ Generate an attribute, and then add it attribute to a GEMD object. Parameters ---------- target: BaseObject The object to attach the attribute to template: AttributeTemplate The AttributeTemplate for the attribute. value: Union[BaseValue, str, float, int]) The value for the attribute. Accepts any GEMD Value type, or will attempt to generate an appropriate Value given a str, float or int. Returns -------- BaseAttribute The generated attribute """ attribute = make_attribute(template=template, value=value) attr_class = type(attribute) if isinstance(target, MaterialSpec): if attr_class is Property: target.properties.append(PropertyAndConditions(property=attribute)) elif attr_class is Condition: if len(target.properties) == 0: raise ValueError("Cannot add a condition to a MaterialSpec " "before it has at least one property.") target.properties[-1].conditions.append(attribute) else: raise ValueError(f"Attribute {attr_class} is incompatible with target {type(target)}.") else: # All other target types if attr_class is Property and isinstance(target, HasProperties): target.properties.append(attribute) elif attr_class is Condition and isinstance(target, HasConditions): target.conditions.append(attribute) elif attr_class is Parameter and isinstance(target, HasParameters): target.parameters.append(attribute) else: raise ValueError(f"A {attr_class} cannot be added to a {type(target)}.") return attribute
def test_dependencies(): """Test that dependency lists make sense.""" prop = PropertyTemplate(name="name", bounds=IntegerBounds(0, 1)) cond = ConditionTemplate(name="name", bounds=IntegerBounds(0, 1)) template = MaterialTemplate("measurement template") spec = MaterialSpec("A spec", template=template, properties=[PropertyAndConditions( property=Property("name", template=prop, value=NominalInteger(1)), conditions=[ Condition("name", template=cond, value=NominalInteger(1)) ] )]) assert template in spec.all_dependencies() assert cond in spec.all_dependencies() assert prop in spec.all_dependencies()
def test_equality(): """Test that equality check works as expected.""" spec = MaterialSpec("A spec", properties=PropertyAndConditions( property=Property("a property", value=NominalReal(3, ''))), tags=["a tag"]) mat1 = MaterialRun("A material", spec=spec) mat2 = MaterialRun("A material", spec=spec, tags=["A tag"]) assert mat1 == deepcopy(mat1) assert mat1 != mat2 assert mat1 != "A material" mat3 = deepcopy(mat1) assert mat1 == mat3, "Copy somehow failed" MeasurementRun("A measurement", material=mat3) assert mat1 != mat3 mat4 = deepcopy(mat3) assert mat4 == mat3, "Copy somehow failed" mat4.measurements[0].tags.append('A tag') assert mat4 != mat3 mat5 = next(x for x in flatten(mat4, 'test-scope') if isinstance(x, MaterialRun)) assert mat5 == mat4, "Flattening removes measurement references, but that's okay"
def test_mixins(): """Measurement templates have all 3 mixin traits.""" obj = MeasurementTemplate( "Name", properties=[PropertyTemplate("Name", bounds=IntegerBounds(0, 1))], conditions=[ConditionTemplate("Name", bounds=IntegerBounds(0, 1))], parameters=[ParameterTemplate("Name", bounds=IntegerBounds(0, 1))], ) with pytest.raises(TypeError): obj.properties.append( ConditionTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.properties.append( ParameterTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.conditions.append(PropertyTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.conditions.append( ParameterTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.parameters.append( ConditionTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.parameters.append(PropertyTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): # You passed a `scalar` to extend obj.properties.extend((PropertyTemplate("3", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3))) with pytest.raises(ValueError): # You passed a `scalar` to extend obj.properties = (PropertyTemplate("3", bounds=IntegerBounds(1, 3)), IntegerBounds(0, 5)) obj.properties.append( (PropertyTemplate("2", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3))) obj.properties.extend([ PropertyTemplate("3", bounds=IntegerBounds(0, 5)), (PropertyTemplate("4", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3)) ]) obj.conditions.insert(1, ConditionTemplate("2", bounds=IntegerBounds(0, 1))) obj.parameters[0] = ParameterTemplate("Name", bounds=IntegerBounds(0, 1)) for x in (obj.properties, obj.conditions, obj.parameters): assert isinstance(x, ValidList) for y in x: assert isinstance(y, list) assert len(y) == 2 assert isinstance(y[0], AttributeTemplate) if y[1] is not None: assert isinstance(y[1], BaseBounds) second = MeasurementTemplate( "Name", properties=[ PropertyTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], conditions=[ ConditionTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], parameters=[ ParameterTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], ) assert len(second.properties) == 1 assert len(second.conditions) == 1 assert len(second.parameters) == 1 good_val = NominalInteger(1) bad_val = NominalInteger(2) assert second.validate_condition(Condition("Other name", value=good_val, template=second.conditions[0][0])), \ "Condition with template and good value didn't validate." assert not second.validate_condition(Condition("Other name", value=bad_val, template=second.conditions[0][0])), \ "Condition with template and bad value DID validate." assert second.validate_parameter(Parameter("Other name", value=good_val, template=second.parameters[0][0])), \ "Parameter with template and good value didn't validate." assert not second.validate_parameter(Parameter("Other name", value=bad_val, template=second.parameters[0][0])), \ "Parameter with template and bad value DID validate." assert second.validate_property(Property("Other name", value=good_val, template=second.properties[0][0])), \ "Property with template and good value didn't validate." assert not second.validate_property(Property("Other name", value=bad_val, template=second.properties[0][0])), \ "Property with template and bad value DID validate." assert second.validate_condition(Condition("Name", value=good_val)), \ "Condition without template and good value didn't validate." assert not second.validate_condition(Condition("Name", value=bad_val)), \ "Condition without template and bad value DID validate." assert second.validate_parameter(Parameter("Name", value=good_val)), \ "Parameter without template and good value didn't validate." assert not second.validate_parameter(Parameter("Name", value=bad_val)), \ "Parameter without template and bad value DID validate." assert second.validate_property(Property("Name", value=good_val)), \ "Property without template and good value didn't validate." assert not second.validate_property(Property("Name", value=bad_val)), \ "Property without template and bad value DID validate." assert second.validate_condition(Condition("Other name", value=bad_val)), \ "Unmatched condition and bad value didn't validate." assert second.validate_parameter(Parameter("Other name", value=bad_val)), \ "Unmatched parameter and bad value didn't validate." assert second.validate_property(Property("Other name", value=bad_val)), \ "Unmatched property and bad value didn't validate." second.conditions[0][1] = None second.parameters[0][1] = None second.properties[0][1] = None assert second.validate_condition(Condition("Name", value=good_val)), \ "Condition and good value with passthrough didn't validate." assert second.validate_parameter(Parameter("Name", value=good_val)), \ "Parameter and good value with passthrough didn't validate." assert second.validate_property(Property("Name", value=good_val)), \ "Property and good value with passthrough didn't validate." assert second.validate_property( PropertyAndConditions(property=Property("Name", value=good_val))), \ "PropertyAndConditions didn't fall back to Property."
def make_cake_spec(tmpl=None): """Define a recipe for making a cake.""" ############################################################################################### # Templates if tmpl is None: tmpl = make_cake_templates() def _make_ingredient(*, material, process, **kwargs): """Convenience method to utilize material fields in creating an ingredient's arguments.""" return IngredientSpec(name=material.name.lower(), tags=list(material.tags), material=material, process=process, uids={ DEMO_SCOPE: "{}--{}".format(material.uids[DEMO_SCOPE], process.uids[DEMO_SCOPE]) }, **kwargs) def _make_material(*, material_name, template, process_tmpl_name, process_kwargs, **material_kwargs): """Convenience method to reuse material name in creating a material's arguments.""" process_name = "{} {}".format(process_tmpl_name, material_name) return MaterialSpec( name=material_name, uids={DEMO_SCOPE: material_name.lower().replace(' ', '-')}, template=template, process=ProcessSpec( name=process_name, uids={DEMO_SCOPE: process_name.lower().replace(' ', '-')}, template=tmpl[process_tmpl_name], **process_kwargs), **material_kwargs) ############################################################################################### # Objects cake_obj = _make_material( material_name="Cake", process_tmpl_name="Icing", process_kwargs={ "tags": ['spreading'], "notes": 'The act of covering a baked output with frosting' }, template=tmpl["Dessert"], properties=[ PropertyAndConditions( Property(name="Tastiness", value=NominalInteger(5), template=tmpl["Tastiness"], origin="specified")) ], 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 = _make_material( material_name="Frosting", process_tmpl_name="Mixing", process_kwargs={ "tags": ['mixing'], "parameters": [ Parameter(name='Mixer speed setting', template=tmpl['Mixer speed setting'], origin='specified', value=NominalInteger(2)) ], "notes": 'Combining ingredients to make a sweet frosting' }, template=tmpl["Dessert"], tags=['frosting::chocolate', 'topping::chocolate'], notes='Chocolate frosting') _make_ingredient(material=frosting, notes='Seems like a lot of frosting', labels=['coating'], process=cake_obj.process, absolute_quantity=NominalReal(nominal=0.751, units='kg')) baked_cake = _make_material( material_name="Baked Cake", process_tmpl_name="Baking", process_kwargs={ "tags": ['oven::baking'], "conditions": [ Condition(name='Cooking time', template=tmpl['Cooking time'], origin=Origin.SPECIFIED, value=NormalReal(mean=50, std=5, units='min')) ], "parameters": [ Parameter(name='Oven temperature setting', template=tmpl['Oven temperature setting'], origin="specified", value=NominalReal(nominal=350, units='degF')) ], "notes": 'Using heat to convert batter into a solid matrix' }, template=tmpl["Baked Good"], properties=[ PropertyAndConditions( property=Property(name="Toothpick test", value=NominalCategorical("completely clean"), template=tmpl["Toothpick test"])), PropertyAndConditions( property=Property(name="Color", value=NominalCategorical("Golden brown"), template=tmpl["Color"], origin="specified")) ], tags=['substrate'], notes='The cakey part of the cake') _make_ingredient(material=baked_cake, labels=['substrate'], process=cake_obj.process) ######################## batter = _make_material( material_name="Batter", process_tmpl_name="Mixing", process_kwargs={ "tags": ['mixing'], "parameters": [ Parameter(name='Mixer speed setting', template=tmpl['Mixer speed setting'], origin='specified', value=NominalInteger(2)) ], "notes": 'Combining ingredients to make a baking feedstock' }, template=tmpl["Generic Material"], tags=['mixture'], notes='The fluid that converts to cake with heat') _make_ingredient(material=batter, labels=['precursor'], process=baked_cake.process) ######################## wetmix = _make_material( material_name="Wet Ingredients", process_tmpl_name="Mixing", process_kwargs={ "tags": ['mixing'], "parameters": [ Parameter(name='Mixer speed setting', template=tmpl['Mixer speed setting'], origin='specified', value=NominalInteger(2)) ], "notes": 'Combining wet ingredients to make a baking feedstock' }, template=tmpl["Generic Material"], tags=["mixture"], notes='The wet fraction of a batter') _make_ingredient(material=wetmix, labels=['wet'], process=batter.process) drymix = _make_material( material_name="Dry Ingredients", process_tmpl_name="Mixing", process_kwargs={ "tags": ['mixing'], "notes": 'Combining dry ingredients to make a baking feedstock' }, template=tmpl["Generic Material"], tags=["mixture"], notes='The dry fraction of a batter') _make_ingredient(material=drymix, labels=['dry'], process=batter.process, absolute_quantity=NominalReal(nominal=3.052, units='cups')) ######################## flour = _make_material( material_name="Flour", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing all purpose flour' }, template=tmpl["Nutritional Material"], properties=[ PropertyAndConditions( property=Property(name="Nutritional Information", value=NominalComposition({ "dietary-fiber": 1, "sugars": 1, "other-carbohydrate": 20, "protein": 4, "other": 4 }), template=tmpl["Nutritional Information"], origin="specified"), conditions=Condition(name="Serving Size", value=NominalReal(30, 'g'), template=tmpl["Sample Mass"], origin="specified")) ], tags=['raw material', 'flour', 'dry-goods'], notes='All-purpose flour') _make_ingredient( material=flour, labels=['dry'], process=drymix.process, volume_fraction=NominalReal(nominal=0.9829, units='') # 3 cups ) baking_powder = _make_material( material_name="Baking Powder", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing baking powder' }, template=tmpl["Generic Material"], tags=['raw material', 'leavening', 'dry-goods'], notes='Leavening agent for cake') _make_ingredient( material=baking_powder, labels=['leavening', 'dry'], process=drymix.process, volume_fraction=NominalReal(nominal=0.0137, units='') # 2 teaspoons ) salt = _make_material(material_name="Salt", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing salt' }, template=tmpl["Formulaic Material"], tags=['raw material', 'seasoning', 'dry-goods'], notes='Plain old NaCl', properties=[ PropertyAndConditions( Property(name='Formula', value=EmpiricalFormula("NaCl"))) ]) _make_ingredient( material=salt, labels=['dry', 'seasoning'], process=drymix.process, volume_fraction=NominalReal(nominal=0.0034, units='') # 1/2 teaspoon ) sugar = _make_material( material_name="Sugar", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing granulated sugar' }, template=tmpl["Formulaic Material"], tags=['raw material', 'sweetener', 'dry-goods'], notes='Sugar', properties=[ PropertyAndConditions( Property(name="Formula", value=EmpiricalFormula("C12H22O11"))), PropertyAndConditions( Property(name='SMILES', value=Smiles( "C(C1C(C(C(C(O1)OC2(C(C(C(O2)CO)O)O)CO)O)O)O)O"), template=tmpl["Molecular Structure"])) ]) _make_ingredient(material=sugar, labels=['wet', 'sweetener'], process=wetmix.process, absolute_quantity=NominalReal(nominal=2, units='cups')) butter = _make_material( material_name="Butter", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::produce'], "notes": 'Purchasing butter' }, template=tmpl["Generic Material"], tags=['raw material', 'produce', 'shortening', 'dairy'], notes='Shortening for making rich, buttery baked goods') _make_ingredient(material=butter, labels=['wet', 'shortening'], process=wetmix.process, absolute_quantity=NominalReal(nominal=1, units='cups')) _make_ingredient( material=butter, labels=['shortening'], process=frosting.process, mass_fraction=NominalReal(nominal=0.1434, units='') # 1/2 c @ 0.911 g/cc ) eggs = _make_material(material_name="Eggs", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::produce'], "notes": 'Purchasing eggs' }, template=tmpl["Generic Material"], tags=[ 'raw material', 'produce', ], notes='A custard waiting to happen') _make_ingredient(material=eggs, labels=['wet'], process=wetmix.process, absolute_quantity=NominalReal(nominal=4, units='')) vanilla = _make_material( material_name="Vanilla", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::solution'], "notes": 'Purchasing vanilla' }, template=tmpl["Generic Material"], tags=['raw material', 'seasoning'], notes= 'Vanilla Extract is mostly alcohol but the most important component ' 'is vanillin (see attached structure)', properties=[ PropertyAndConditions( Property( name='Component Structure', value=InChI( "InChI=1S/C8H8O3/c1-11-8-4-6(5-9)2-3-7(8)10/h2-5,10H,1H3" ), template=tmpl["Molecular Structure"])) ]) _make_ingredient(material=vanilla, labels=['wet', 'flavoring'], process=wetmix.process, absolute_quantity=NominalReal(nominal=2, units='teaspoons')) _make_ingredient( material=vanilla, labels=['flavoring'], process=frosting.process, mass_fraction=NominalReal(nominal=0.0231, units='') # 2 tsp @ 0.879 g/cc ) milk = _make_material(material_name="Milk", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::produce'], "notes": 'Purchasing milk' }, template=tmpl["Generic Material"], tags=['raw material', 'produce', 'dairy'], notes='') _make_ingredient(material=milk, labels=['wet'], process=batter.process, absolute_quantity=NominalReal(nominal=1, units='cup')) _make_ingredient( material=milk, labels=[], process=frosting.process, mass_fraction=NominalReal(nominal=0.0816, units='') # 1/4 c @ 1.037 g/cc ) chocolate = _make_material(material_name="Chocolate", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing chocolate' }, template=tmpl["Generic Material"], tags=['raw material'], notes='') _make_ingredient( material=chocolate, labels=['flavoring'], process=frosting.process, mass_fraction=NominalReal(nominal=0.1132, units='') # 3 oz. ) powder_sugar = _make_material( material_name="Powdered Sugar", process_tmpl_name="Procuring", process_kwargs={ "tags": ['purchase::dry-goods'], "notes": 'Purchasing powdered sugar' }, template=tmpl["Generic Material"], tags=['raw material', 'sweetener', 'dry-goods'], notes='Granulated sugar mixed with corn starch') _make_ingredient( material=powder_sugar, labels=['flavoring'], process=frosting.process, mass_fraction=NominalReal(nominal=0.6387, units='') # 4 c @ 30 g/ 0.25 cups ) return cake_obj
def make_cake_spec(tmpl=None): """Define a recipe for making a cake.""" ############################################################################################### # Templates if tmpl is None: tmpl = make_cake_templates() count = dict() def ingredient_kwargs(material): # Pulls the elements of a material that all ingredients consume out count[material.name] = count.get(material.name, 0) + 1 return { "name": "{} input{}".format(material.name.replace('Abstract ', ''), " (Again)" * (count[material.name] - 1)), "tags": list(material.tags), "material": material } ############################################################################################### # Objects cake = MaterialSpec( name="Abstract Cake", template=tmpl["Dessert"], process=ProcessSpec( name='Icing Cake, in General', template=tmpl["Icing"], tags=['spreading'], notes='The act of covering a baked output with frosting'), properties=[ PropertyAndConditions( Property(name="Tastiness", value=NominalInteger(5), template=tmpl["Tastiness"], origin="specified")) ], 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(**ingredient_kwargs(frosting), notes='Seems like a lot of frosting', labels=['coating'], process=cake.process, 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(**ingredient_kwargs(baked_cake), labels=['substrate'], process=cake.process) ######################## 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(**ingredient_kwargs(batter), labels=['precursor'], process=baked_cake.process) ######################## 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(**ingredient_kwargs(wetmix), labels=['wet'], process=batter.process) 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(**ingredient_kwargs(drymix), labels=['dry'], process=batter.process, absolute_quantity=NominalReal(nominal=3.052, units='cups')) ######################## flour = MaterialSpec( name="Abstract Flour", template=tmpl["Nutritional Material"], properties=[ PropertyAndConditions( property=Property(name="Nutritional Information", value=NominalComposition({ "dietary-fiber": 1, "sugars": 1, "other-carbohydrate": 20, "protein": 4, "other": 4 }), template=tmpl["Nutritional Information"], origin="specified"), conditions=Condition(name="Serving Size", value=NominalReal(30, 'g'), template=tmpl["Sample Mass"], origin="specified")) ], 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( **ingredient_kwargs(flour), labels=['dry'], process=drymix.process, 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( **ingredient_kwargs(baking_powder), labels=['leavening', 'dry'], process=drymix.process, volume_fraction=NominalReal(nominal=0.0137, units='') # 2 teaspoons ) salt = MaterialSpec(name="Abstract Salt", template=tmpl["Formulaic Material"], process=ProcessSpec(name='Buying Salt, in General', template=tmpl["Procurement"], tags=['purchase::dry-goods'], notes='Purchasing salt'), tags=[], notes='Plain old NaCl', properties=[ PropertyAndConditions( Property(name='Formula', value=EmpiricalFormula("NaCl"))) ]) IngredientSpec( **ingredient_kwargs(salt), labels=['dry', 'seasoning'], process=drymix.process, volume_fraction=NominalReal(nominal=0.0034, units='') # 1/2 teaspoon ) sugar = MaterialSpec( name="Abstract Sugar", template=tmpl["Formulaic Material"], process=ProcessSpec(name='Buying Sugar, in General', template=tmpl["Procurement"], tags=['purchase::dry-goods'], notes='Purchasing all purpose flour'), tags=[], notes='Sugar', properties=[ PropertyAndConditions( Property(name="Formula", value=EmpiricalFormula("C12H22O11"))), PropertyAndConditions( Property(name='SMILES', value=Smiles( "C(C1C(C(C(C(O1)OC2(C(C(C(O2)CO)O)O)CO)O)O)O)O"), template=tmpl["Molecular Structure"])) ]) IngredientSpec(**ingredient_kwargs(sugar), labels=['wet', 'sweetener'], process=wetmix.process, 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(**ingredient_kwargs(butter), labels=['wet', 'shortening'], process=wetmix.process, absolute_quantity=NominalReal(nominal=1, units='cups')) IngredientSpec( **ingredient_kwargs(butter), labels=['shortening'], process=frosting.process, 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(**ingredient_kwargs(eggs), labels=['wet'], 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= 'Vanilla Extract is mostly alcohol but the most important component ' 'is vanillin (see attached structure)', properties=[ PropertyAndConditions( Property( name='Component Structure', value=InChI( "InChI=1S/C8H8O3/c1-11-8-4-6(5-9)2-3-7(8)10/h2-5,10H,1H3" ), template=tmpl["Molecular Structure"])) ]) IngredientSpec(**ingredient_kwargs(vanilla), labels=['wet', 'flavoring'], process=wetmix.process, absolute_quantity=NominalReal(nominal=2, units='teaspoons')) IngredientSpec( **ingredient_kwargs(vanilla), labels=['flavoring'], process=frosting.process, 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(**ingredient_kwargs(milk), labels=['wet'], process=batter.process, absolute_quantity=NominalReal(nominal=1, units='cup')) IngredientSpec( **ingredient_kwargs(milk), labels=[], process=frosting.process, 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( **ingredient_kwargs(chocolate), labels=['flavoring'], process=frosting.process, 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( **ingredient_kwargs(powder_sugar), labels=['flavoring'], process=frosting.process, mass_fraction=NominalReal(nominal=0.6387, units='') # 4 c @ 30 g/ 0.25 cups ) # Crawl tree and annotate with uids; only add ids if there's nothing there recursive_foreach( cake, lambda obj: obj.uids or obj.add_uid(DEMO_SCOPE, obj.name)) return cake