예제 #1
0
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, 'test-scope')) == 1
    assert 'test-scope' in procured.uids
    assert len(flatten(input, 'test-scope')) == 1
    assert len(flatten(ingredient, 'test-scope')) == 3
    assert len(flatten(transform, 'test-scope')) == 3

    assert len(flatten(procured_run, 'test-scope')) == 3
    assert len(flatten(input_run, 'test-scope')) == 3
    assert len(flatten(ingredient_run, 'test-scope')) == 7
    assert len(flatten(transform_run, 'test-scope')) == 7
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 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(spec=mat_spec, sample_type="imaginary")
    mat = MaterialRun(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_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 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
예제 #6
0
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