def test_complex_substitutions(): """Make sure accounting works for realistic objects.""" root = MaterialRun("root", process=ProcessRun("root", spec=ProcessSpec("root")), spec=MaterialSpec("root")) root.spec.process = root.process.spec input = MaterialRun("input", process=ProcessRun("input", spec=ProcessSpec("input")), spec=MaterialSpec("input")) input.spec.process = input.process.spec IngredientRun(process=root.process, material=input, spec=IngredientSpec("ingredient", process=root.process.spec, material=input.spec)) param = ParameterTemplate("Param", bounds=RealBounds(-1, 1, "m")) root.process.spec.template = ProcessTemplate("Proc", parameters=[param]) root.process.parameters.append( Parameter("Param", value=NormalReal(0, 1, 'm'), template=param)) links = flatten(root, scope="test-scope") index = make_index(links) rebuild = substitute_objects(links, index, inplace=True) rebuilt_root = next(x for x in rebuild if x.name == root.name and x.typ == root.typ) all_objs = recursive_flatmap(rebuilt_root, func=lambda x: [x], unidirectional=False) unique = [x for i, x in enumerate(all_objs) if i == all_objs.index(x)] assert not any(isinstance(x, LinkByUID) for x in unique), "All are objects" assert len(links) == len(unique), "Objects are missing"
def test_scope_control(): """Serializing a nested object should be identical to individually serializing each piece.""" input_material = MaterialSpec() process = ProcessSpec() IngredientSpec(material=input_material, process=process) material = MaterialSpec(process=process) # Verify the default scope is there default_json = GEMDJson() default_text = default_json.dumps(material) assert "auto" in default_text assert "custom" not in default_text # Clear out ids input_material.uids = {} process.uids = {} process.ingredients[0].uids = {} input_material.uids = {} material.uids = {} # Verify the default scope is there custom_json = GEMDJson(scope='custom') custom_text = custom_json.dumps(material) assert "auto" not in custom_text assert "custom" in custom_text
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_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_default_scope(): """Test flatten exceptions around providing a scope.""" ps_one = ProcessSpec(name="one") with pytest.raises(ValueError): flatten(ps_one) pr_one = ProcessRun(name="one", uids={'my': 'outer'}, spec=ps_one) with pytest.raises(ValueError): flatten(pr_one) ps_one.uids['my'] = 'id' assert len(flatten(pr_one)) == 2 two = ProcessRun(name="two", spec=ProcessSpec(name="two")) assert len(flatten(two, scope="my")) == 2
def test_template_access(): """A process run's template should be equal to its spec's template.""" template = ProcessTemplate("process template", uids={'id': str(uuid4())}) spec = ProcessSpec("A spec", uids={'id': str(uuid4())}, template=template) proc = ProcessRun("A run", uids={'id': str(uuid4())}, spec=spec) assert proc.template == template proc.spec = LinkByUID.from_entity(spec) assert proc.template is None
def test_dependencies(): """Test that dependency lists make sense.""" ps = ProcessSpec(name="ps") pr = ProcessRun(name="pr", spec=ps) ms = MaterialSpec(name="ms", process=ps) mr = MaterialRun(name="mr", spec=ms, process=pr) assert ps not in mr.all_dependencies() assert pr in mr.all_dependencies() assert ms in mr.all_dependencies()
def test_equality(): """Test that equality check works as expected.""" spec1 = ProcessSpec("A spec") spec2 = ProcessSpec("A spec", tags=["a tag"]) assert spec1 != spec2 spec3 = deepcopy(spec1) assert spec1 == spec3, "Copy somehow failed" IngredientSpec("An ingredient", process=spec3) assert spec1 != spec3 spec4 = deepcopy(spec3) assert spec4 == spec3, "Copy somehow failed" spec4.ingredients[0].tags.append('A tag') assert spec4 != spec3 spec5 = next(x for x in flatten(spec4, 'test-scope') if isinstance(x, ProcessSpec)) assert spec5 == spec4, "Flattening removes measurement references, but that's okay"
def make_node(name: str, *, process_name: str = None, process_template: ProcessTemplate = None, material_template: MaterialTemplate = None) -> MaterialRun: """ Generate a material-process spec-run quadruple. Parameters ---------- name: str Name of the MaterialRun and MaterialSpec. process_name: str Name of the ProcessRun and ProcessSpec. Defaults to `process_template.name` if `process_template` is defined, else `name`. process_template: ProcessTemplate ProcessTemplate for the quadruple. material_template: MaterialTemplate MaterialTemplate for the quadruple. Returns -------- MaterialRun A MaterialRun with linked processes, specs and templates """ if process_name is None: if process_template is None: process_name = name else: process_name = process_template.name my_process_spec = ProcessSpec( name=process_name, template=process_template ) my_process_run = ProcessRun( name=process_name, spec=my_process_spec ) my_mat_spec = MaterialSpec( name=name, process=my_process_spec, template=material_template ) my_mat_run = MaterialRun( name=name, process=my_process_run, spec=my_mat_spec ) return my_mat_run
def test_substitute_equivalence(): """PLA-6423: verify that substitutions match up.""" spec = ProcessSpec(name="old spec", uids={'scope': 'spec'}) run = ProcessRun(name="old run", uids={'scope': 'run'}, spec=LinkByUID(id='spec', scope="scope")) # make a dictionary from ids to objects, to be used in substitute_objects gem_index = make_index([run, spec]) substitute_objects(obj=run, index=gem_index, inplace=True) assert spec == run.spec
def test_flatten_bounds(): """Test that flatten works when the objects contain other objects.""" bounds = CategoricalBounds(categories=["foo", "bar"]) template = ProcessTemplate( "spam", conditions=[(ConditionTemplate(name="eggs", bounds=bounds), bounds)] ) spec = ProcessSpec(name="spec", template=template) flat = flatten(spec, 'test-scope') assert len(flat) == 2, "Expected 2 flattened objects"
def test_inplace_v_not(): """Test that client can copy a dictionary in which keys are BaseEntity objects.""" spec = ProcessSpec("A process spec", uids={'id': str(uuid4()), 'auto': str(uuid4())}) run1 = ProcessRun("A process run", spec=spec, uids={'id': str(uuid4()), 'auto': str(uuid4())}) run2 = ProcessRun("Another process run", spec=spec, uids={'id': str(uuid4())}) process_dict = {spec: [run1, run2]} subbed = substitute_links(process_dict) assert subbed != process_dict # This is true because the hashes change, even if objects equal substitute_links(process_dict, inplace=True) assert subbed == process_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 test_flatten_empty_history(): """Test that flatten works when the objects are empty and go through a whole history.""" procured = ProcessSpec(name="procured") input = MaterialSpec(name="foo", process=procured) transform = ProcessSpec(name="transformed") ingredient = IngredientSpec(name="input", material=input, process=transform) procured_run = ProcessRun(name="procured", spec=procured) input_run = MaterialRun(name="foo", process=procured_run, spec=input) transform_run = ProcessRun(name="transformed", spec=transform) ingredient_run = IngredientRun(material=input_run, process=transform_run, spec=ingredient) assert len(flatten(procured)) == 1 assert len(flatten(input)) == 1 assert len(flatten(ingredient)) == 3 assert len(flatten(transform)) == 3 assert len(flatten(procured_run)) == 3 assert len(flatten(input_run)) == 3 assert len(flatten(ingredient_run)) == 7 assert len(flatten(transform_run)) == 7
def test_repeated_objects(): """Test that objects aren't double counted.""" ct = ConditionTemplate(name="color", bounds=CategoricalBounds(categories=["black", "white"])) pt = ProcessTemplate(name="painting", conditions=[ct]) ps = ProcessSpec(name='painting', template=pt, conditions=Condition(name='Paint color', value=NominalCategorical("black"), template=ct ) ) assert len(recursive_flatmap(ps, lambda x: [x])) == 3
def test_template_check_generator(): """Verify that the generator throws exceptions.""" spec1 = ProcessSpec("A spec") with pytest.raises(ValueError): # Can't find class spec1._generate_template_check(validate=lambda x, y: True) with pytest.raises(ValueError): # Can't find attribute spec1._generate_template_check(validate=ProcessSpec.name.fget)
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)
def test_many_ingredients(): """Test that ingredients remain connected to processes when round-robined through json.""" proc = ProcessRun("foo", spec=ProcessSpec("sfoo")) expected = [] for i in range(10): mat = MaterialRun(name=str(i), spec=MaterialSpec("s{}".format(i))) i_spec = IngredientSpec(name="i{}".format(i), material=mat.spec, process=proc.spec) IngredientRun(process=proc, material=mat, spec=i_spec) expected.append("i{}".format(i)) reloaded = loads(dumps(proc)) assert len(list(reloaded.ingredients)) == 10 names = [x.name for x in reloaded.ingredients] assert sorted(names) == sorted(expected)
def test_process_spec(): """Tests that the Process Spec/Run connection persists when serializing.""" # Create the ProcessSpec condition1 = Condition(name="a condition on the process in general") spec = ProcessSpec("Spec", conditions=condition1) # Create the ProcessRun with a link to the ProcessSpec from above condition2 = Condition( name="a condition on this process run in particular") process = ProcessRun("Run", conditions=condition2, spec=spec) copy_process = loads(dumps(process)) assert dumps(copy_process.spec) == dumps(spec), \ "Process spec should be preserved through serialization"
def test_register_classes_override(): """Test that register_classes overrides existing entries in the class index.""" class MyProcessSpec(ProcessSpec): pass normal = GEMDJson() custom = GEMDJson() custom.register_classes({MyProcessSpec.typ: MyProcessSpec}) obj = ProcessSpec(name="foo") assert not isinstance(normal.copy(obj), MyProcessSpec),\ "Class registration bled across GEMDJson() objects" assert isinstance(custom.copy(obj), ProcessSpec),\ "Custom GEMDJson didn't deserialize as MyProcessSpec"
def test_object_key_substitution(): """Test that client can copy a dictionary in which keys are BaseEntity objects.""" spec = ProcessSpec("A process spec", uids={'id': str(uuid4()), 'auto': str(uuid4())}) run1 = ProcessRun("A process run", spec=spec, uids={'id': str(uuid4()), 'auto': str(uuid4())}) run2 = ProcessRun("Another process run", spec=spec, uids={'id': str(uuid4())}) process_dict = {spec: [run1, run2]} subbed = substitute_links(process_dict, scope='auto') for key, value in subbed.items(): assert key == LinkByUID.from_entity(spec, scope='auto') assert LinkByUID.from_entity(run1, scope='auto') in value assert LinkByUID.from_entity(run2) in value reverse_process_dict = {run2: spec} subbed = substitute_links(reverse_process_dict, scope='auto') for key, value in subbed.items(): assert key == LinkByUID.from_entity(run2) assert value == LinkByUID.from_entity(spec, scope='auto')
def test_equality(): """Test that equality check works as expected.""" spec = ProcessSpec("A spec", tags=["a tag"]) run1 = ProcessRun("A process", spec=spec) run2 = deepcopy(run1) assert run1 == run2, "Copy somehow failed" IngredientRun(process=run2) assert run1 != run2 run3 = deepcopy(run2) assert run3 == run2, "Copy somehow failed" run3.ingredients[0].tags.append('A tag') assert run3 != run2 run4 = next(x for x in flatten(run3, 'test-scope') if isinstance(x, ProcessRun)) assert run4 == run3, "Flattening removes measurement references, but that's okay"
def test_make_index(): """Test functionality of make_index method.""" ps1 = ProcessSpec(name="hello", uids={"test_scope": "test_value"}) pr1 = ProcessRun( name="world", spec=LinkByUID(scope="test_scope", id="test_value"), uids={"test_scope": "another_test_value", "other_test": "also_valid" }, ) ms1 = MaterialSpec( name="material", process=LinkByUID(scope="test_scope", id="test_value"), uids={"second_scope": "this_is_an_id"}, ) mr1 = MaterialRun( name="material_run", spec=LinkByUID(scope="second_scope", id="this_is_an_id"), process=LinkByUID(scope="test_scope", id="another_test_value"), ) pr2 = ProcessRun( name="goodbye", spec=LinkByUID.from_entity(ps1), uids={"test_scope": "the_other_value"}, ) mr2 = MaterialRun( name="cruel", spec=LinkByUID.from_entity(ms1), process=LinkByUID.from_entity(pr2), ) gems = [ps1, pr1, ms1, mr1, pr2, mr2] gem_index = make_index(gems) for gem in gems: for scope in gem.uids: assert (scope, gem.uids[scope]) in gem_index assert gem_index[(scope, gem.uids[scope])] == gem # Make sure substitute_objects can consume the index subbed = substitute_objects(mr1, gem_index) assert subbed.spec.uids == ms1.uids
def test_signature(): """Exercise various permutations of the substitute_links sig.""" spec = ProcessSpec("A process spec", uids={'my': 'spec'}) with pytest.warns(DeprecationWarning): run1 = ProcessRun("First process run", uids={'my': 'run1'}, spec=spec) assert isinstance(substitute_links(run1, native_uid='my').spec, LinkByUID) run2 = ProcessRun("Second process run", uids={'my': 'run2'}, spec=spec) assert isinstance(substitute_links(run2, scope='my').spec, LinkByUID) run3 = ProcessRun("Third process run", uids={'my': 'run3'}, spec=spec) assert isinstance(substitute_links(run3, 'my').spec, LinkByUID) with pytest.raises(ValueError): # Test deprecated auto-population run4 = ProcessRun("Fourth process run", uids={'my': 'run4'}, spec=spec) assert isinstance(substitute_links(run4, 'other', allow_fallback=False).spec, LinkByUID) with pytest.warns(DeprecationWarning): with pytest.raises(ValueError): # Test deprecated auto-population run5 = ProcessRun("Fifth process run", uids={'my': 'run4'}, spec=spec) assert isinstance(substitute_links(run5, scope="my", native_uid="my").spec, LinkByUID)
def test_equality(): """Test that __eq__ and _cached_equals behave as expected.""" from gemd.entity.object import ProcessSpec, IngredientSpec, MaterialSpec from gemd.entity.link_by_uid import LinkByUID one = ProcessSpec("Object", tags=["tags!"], uids={"scope": "id"}) assert one == LinkByUID(scope="scope", id="id"), "Objects equal their links" assert one == ("scope", "id"), "Objects equal their equivalent tuples" assert one != ("scope", "id", "extra"), "But not if they are too long" assert one != ("epocs", "id"), "Or have the wrong scope" assert one != ("scope", "di"), "Or have the wrong id" junk = MaterialSpec("Object", tags=["tags!"], uids={"scope": "id"}) assert one != junk, "Objects don't match across types" two = ProcessSpec("Object", tags=["tags!"], uids={"scope": "id"}, notes="Notes!") assert one != two, "Objects don't match unless the fields do" one.notes = "Notes!" assert one == two, "And then they will" # It ignores the ingredient length mismatch if the uids matched one_ing = IngredientSpec("Ingredient", process=one) assert one == two assert two == one # And a cycle is not a problem one_mat = MaterialSpec("Material", tags=["other tags!"], process=one) two_mat = MaterialSpec("Material", tags=["other tags!"], process=two) one_ing.material = two_mat IngredientSpec("Ingredient", process=two, material=one_mat) # two_ing assert one == two assert two == one # Order doesn't matter for tags one.tags = ["One", "Two", "Three", "Four"] two.tags = ["Four", "One", "Three", "Two"] assert one == two assert two == one
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
def test_recursive_flatmap_maintains_order(): """Test flatmap preserves order in lists.""" p1 = ProcessSpec(name="one") p2 = ProcessSpec(name="two") orig = [p1, p2] assert [x.name for x in orig] == recursive_flatmap(orig, lambda x: [x.name])
def test_invalid_assignment(): """Invalid assignments to `spec` throw a TypeError.""" with pytest.raises(TypeError): ProcessRun("name", spec=[ProcessSpec("spec")]) with pytest.raises(TypeError): ProcessRun() # Name is required
def test_invalid_assignment(): """Omitting a name throws a TypeError.""" with pytest.raises(TypeError): ProcessSpec() # Name is required
def make_data_island(density, bulk_modulus, firing_temperature, binders, powders, tag=None): """Helper function to create a relatively involved data island.""" binder_specs = keymap(lambda x: MaterialSpec(name=x), binders) powder_specs = keymap(lambda x: MaterialSpec(name=x), powders) binder_runs = keymap(lambda x: MaterialRun(name=x.name, spec=x), binder_specs) powder_runs = keymap(lambda x: MaterialRun(name=x.name, spec=x), powder_specs) all_input_materials = keymap(lambda x: x.spec.name, merge(binder_runs, powder_runs)) mixing_composition = Condition( name="composition", value=NominalComposition(all_input_materials)) mixing_process = ProcessRun(name="Mixing", tags=["mixing"], conditions=[mixing_composition]) binder_ingredients = [] for run in binder_runs: binder_ingredients.append( IngredientRun( material=run, process=mixing_process, mass_fraction=NominalReal(binders[run.spec.name], ''), )) powder_ingredients = [] for run in powder_runs: powder_ingredients.append( IngredientRun( material=run, process=mixing_process, mass_fraction=NominalReal(powders[run.spec.name], ''), )) green_sample = MaterialRun("Green", process=mixing_process) measured_firing_temperature = Condition( name="Firing Temperature", value=UniformReal(firing_temperature - 0.5, firing_temperature + 0.5, 'degC'), template=firing_temperature_template) specified_firing_setting = Parameter(name="Firing setting", value=DiscreteCategorical("hot")) firing_spec = ProcessSpec("Firing", template=firing_template) firing_process = ProcessRun(name=firing_spec.name, conditions=[measured_firing_temperature], parameters=[specified_firing_setting], spec=firing_spec) IngredientRun(material=green_sample, process=firing_process, mass_fraction=NormalReal(1.0, 0.0, ''), volume_fraction=NormalReal(1.0, 0.0, ''), number_fraction=NormalReal(1.0, 0.0, '')) measured_density = Property(name="Density", value=NominalReal(density, ''), template=density_template) measured_modulus = Property(name="Bulk modulus", value=NormalReal(bulk_modulus, bulk_modulus / 100.0, '')) measurement_spec = MeasurementSpec("Mechanical Properties", template=measurement_template) measurement = MeasurementRun( measurement_spec.name, properties=[measured_density, measured_modulus], spec=measurement_spec) tags = [tag] if tag else [] material_spec = MaterialSpec("Coupon", template=material_template) material_run = MaterialRun(material_spec.name, process=firing_process, tags=tags, spec=material_spec) measurement.material = material_run return material_run