Esempio n. 1
0
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"
Esempio n. 2
0
def test_sub_inplace_objects():
    """Verify consistency for GEMD objects."""
    run = IngredientRun(spec=IngredientSpec("string"), notes="note")
    run.spec = None

    _substitute_inplace(run,
                        applies=lambda x: isinstance(x, str),
                        sub=lambda x: f"{x}s")
    assert run.name == "strings"
    assert run.notes == "notes"
def test_name_persistance():
    """Verify that a serialized IngredientRun doesn't lose its name."""
    from gemd.entity.object import IngredientSpec
    from gemd.entity.link_by_uid import LinkByUID
    from gemd.json import GEMDJson

    je = GEMDJson()

    ms_link = LinkByUID(scope='local', id='mat_spec')
    mr_link = LinkByUID(scope='local', id='mat_run')
    ps_link = LinkByUID(scope='local', id='pro_spec')
    pr_link = LinkByUID(scope='local', id='pro_run')
    spec = IngredientSpec(name='Ingred',
                          labels=['some', 'words'],
                          process=ps_link,
                          material=ms_link)
    run = IngredientRun(spec=spec, process=pr_link, material=mr_link)
    assert run.name == spec.name
    assert run.labels == spec.labels

    # Try changing them and make sure they change
    spec.name = 'Frank'
    spec.labels = ['other', 'words']
    assert run.name == spec.name
    assert run.labels == spec.labels

    run.spec = LinkByUID(scope='local', id='ing_spec')
    # Name and labels are now stashed but not stored
    assert run == je.copy(run)
    assert run.name == spec.name
    assert run.labels == spec.labels

    # Test that serialization doesn't get confused after a deser and set
    spec_too = IngredientSpec(name='Jorge',
                              labels=[],
                              process=ps_link,
                              material=ms_link)
    run.spec = spec_too
    assert run == je.copy(run)
    assert run.name == spec_too.name
    assert run.labels == spec_too.labels
Esempio n. 4
0
 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)
Esempio n. 5
0
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"
Esempio n. 6
0
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
Esempio n. 7
0
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"
Esempio n. 8
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)) == 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
Esempio n. 9
0
def add_edge(input_material: MaterialRun,
             output_material: MaterialRun,
             *,
             name: str = None,
             mass_fraction: Union[float, ContinuousValue] = None,
             number_fraction: Union[float, ContinuousValue] = None,
             volume_fraction: Union[float, ContinuousValue] = None,
             absolute_quantity: Union[int, float, ContinuousValue] = None,
             absolute_units: str = None,
             ) -> IngredientRun:
    """
    Connect two material-process spec-run quadruples with ingredients.

    Parameters
    ----------
    input_material: MaterialRun
        The `material` for the returned IngredientRun
    output_material: MaterialRun
        The `process` for the returned IngredientRun will be
        `output_material.process`
    name: str
        The ingredient name.  Defaults to `input_material.name`.
    mass_fraction: float or ContinuousValue
        The mass fraction of the Ingredient Run.  0 <= x <= 1
    number_fraction: float or ContinuousValue
        The number fraction of the Ingredient Run.  0 <= x <= 1
    volume_fraction: float or ContinuousValue
        The volume fraction of the Ingredient Run.  0 <= x <= 1
    absolute_quantity: float or ContinuousValue
        The absolute quantity.  0 <= x
    absolute_units: str
        The absolute units.  Required if absolute_quantity is provided as a float

    Returns
    --------
    IngredientRun
        A IngredientRun with linked processes, specs and materials

    """
    output_spec = output_material.spec
    if not isinstance(output_spec, MaterialSpec) \
            or output_spec.process is None \
            or output_material.process is None:
        raise ValueError("Output Material must be a MaterialRun with connected "
                         "Specs and Processes.")
    if input_material.spec is None:
        raise ValueError("Input Material must be a MaterialRun with connected Spec.")

    if name is None:
        name = input_material.name
    my_ingredient_spec = IngredientSpec(name=name,
                                        process=output_spec.process,
                                        material=input_material.spec
                                        )
    my_ingredient_run = IngredientRun(spec=my_ingredient_spec,
                                      process=output_material.process,
                                      material=input_material
                                      )

    if mass_fraction is not None:
        if isinstance(mass_fraction, float):
            mass_fraction = NominalReal(nominal=mass_fraction, units='')
        my_ingredient_run.mass_fraction = mass_fraction

    if number_fraction is not None:
        if isinstance(number_fraction, float):
            number_fraction = NominalReal(nominal=number_fraction, units='')
        my_ingredient_run.number_fraction = number_fraction

    if volume_fraction is not None:
        if isinstance(volume_fraction, float):
            volume_fraction = NominalReal(nominal=volume_fraction, units='')
        my_ingredient_run.volume_fraction = volume_fraction

    if absolute_quantity is not None:
        if isinstance(absolute_quantity, float):
            if absolute_units is None:
                raise ValueError("Absolute Units are required if Absolute Quantity is not a Value")
            absolute_quantity = NominalReal(nominal=absolute_quantity, units=absolute_units)
            absolute_units = None
        my_ingredient_run.absolute_quantity = absolute_quantity

    if absolute_units is not None:
        raise ValueError("Absolute Units are only used if "
                         "Absolute Quantity is given as is a float.")

    return my_ingredient_run
Esempio n. 10
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