Exemplo n.º 1
0
def test_invalid_assignment():
    """Invalid assignments to `material` or `spec` throw a TypeError."""
    with pytest.raises(TypeError):
        MeasurementRun("name",
                       spec=Condition("value of pi",
                                      value=NominalReal(3.14159, '')))
    with pytest.raises(TypeError):
        MeasurementRun("name", material=FileLink("filename", "url"))
Exemplo n.º 2
0
def test_source():
    """Test that source can be set, serialized, and deserialized."""
    source = PerformedSource(performed_by="Marie Curie",
                             performed_date="1898-07-01")
    measurement = MeasurementRun(name="Polonium", source=source)
    assert loads(dumps(measurement)).source.performed_by == "Marie Curie"

    with pytest.raises(TypeError):
        MeasurementRun(name="Polonium", source="Marie Curie on 1898-07-01")
Exemplo n.º 3
0
def test_material_soft_link():
    """Test that a measurement run can link to a material run, and that it survives serde."""
    dye = MaterialRun("rhodamine",
                      file_links=FileLink(filename='a.csv', url='/a/path'))
    assert dye.measurements == [], "default value of .measurements should be an empty list"

    # The .measurements member should not be settable
    with pytest.raises(AttributeError):
        dye.measurements = [MeasurementRun()]

    absorbance = MeasurementRun(name="Absorbance",
                                uids={'id': str(uuid4())},
                                properties=[
                                    Property(name='Abs at 500 nm',
                                             value=NominalReal(0.1, ''))
                                ])
    assert absorbance.material is None, "Measurements should have None as the material by default"
    absorbance.material = dye
    assert absorbance.material == dye, "Material not set correctly for measurement"
    assert dye.measurements == [
        absorbance
    ], "Soft-link from material to measurement not created"

    fluorescence = MeasurementRun(name="Fluorescence",
                                  uids={'id': str(uuid4())},
                                  properties=[
                                      Property(name='PL counts at 550 nm',
                                               value=NominalReal(30000, ''))
                                  ],
                                  material=dye)

    assert fluorescence.material == dye, "Material not set correctly for measurement"
    assert dye.measurements == [absorbance, fluorescence], \
        "Soft-link from material to measurements not created"

    assert loads(dumps(absorbance)) == absorbance, \
        "Measurement should remain unchanged when serialized"
    assert loads(dumps(fluorescence)) == fluorescence, \
        "Measurement should remain unchanged when serialized"

    # Serializing the material breaks the material-->measurement link.
    assert loads(dumps(dye)).measurements == [], \
        "Measurement information should be removed when material is serialized"

    assert 'measurements' in repr(dye)
    assert 'material' in repr(fluorescence)
    assert 'material' in repr(absorbance)

    substitute_links(dye.measurements)
    assert 'measurements' in repr(dye)
Exemplo n.º 4
0
def test_substitution_without_id():
    """Test that trying to substitute links if uids haven't been assigned throws an error."""
    mat = MaterialRun("A material with no id")
    meas = MeasurementRun("A measurement with no id", material=mat)
    with pytest.raises(ValueError):
        substitute_links(
            meas
        ), "subbed = substitute_links should fail if objects don't have uids"

    with pytest.raises(ValueError):
        substitute_links([meas, mat]), \
            "subbed = substitute_links should fail if objects don't have uids"

    with pytest.raises(ValueError):
        substitute_links(meas.as_dict()), \
            "subbed = substitute_links should fail if objects don't have uids"

    # Create a dictionary in which either the key or value is missing a uid
    meas.add_uid('id', str(uuid4()))
    with pytest.raises(ValueError):
        substitute_links({mat: meas}), \
            "subbed = substitute_links should fail if objects don't have uids"
    with pytest.raises(ValueError):
        substitute_links({meas: mat}), \
            "subbed = substitute_links should fail if objects don't have uids"
Exemplo n.º 5
0
def test_native_id_substitution():
    """Test that the native id gets serialized, when specified."""
    native_id = 'id1'
    # Create measurement and material with two ids
    mat = MaterialRun("A material",
                      uids={
                          native_id: str(uuid4()),
                          "an_id": str(uuid4()),
                          "another_id": str(uuid4())
                      })
    meas = MeasurementRun("A measurement",
                          material=mat,
                          uids={
                              "some_id": str(uuid4()),
                              native_id: str(uuid4()),
                              "an_id": str(uuid4())
                          })

    # Turn the material pointer into a LinkByUID using native_id
    subbed = substitute_links(meas, native_uid=native_id)
    assert subbed.material == LinkByUID.from_entity(mat, name=native_id)

    # Put the measurement into a list and convert that into a LinkByUID using native_id
    measurements_list = [meas]
    subbed = substitute_links(measurements_list, native_uid=native_id)
    assert subbed == [LinkByUID.from_entity(meas, name=native_id)]
Exemplo n.º 6
0
def make_flexural_test_measurement(my_id, deflection, extra_tags=frozenset()):
    """
    Compute the stree, strain, and modulus.

    According to https://en.wikipedia.org/wiki/Three-point_flexural_test
    """
    stress = 3 * applied_force * span / (2 * thickness * thickness * width)
    strain = 6 * deflection * thickness / (span * span)
    modulus = stress / strain

    measurement = MeasurementRun(
        uids={"my_id": my_id},
        tags=["3_pt_bend", "mechanical", "flex"] + list(extra_tags),
        properties=[
            Property(name="flexural stress",
                     value=NormalReal(stress, std=(0.01 * stress),
                                      units="MPa"),
                     origin=Origin.MEASURED),
            Property(name="flexural strain",
                     value=NormalReal(strain, std=(0.01 * strain), units=""),
                     origin=Origin.MEASURED),
            Property(name="flexural modulus",
                     value=NormalReal(modulus,
                                      std=(0.01 * modulus),
                                      units="MPa"),
                     origin=Origin.MEASURED),
            Property(name="deflection",
                     value=NominalReal(deflection, units="mm"),
                     origin=Origin.MEASURED)
        ])
    return measurement
Exemplo n.º 7
0
def test_dict_serialization():
    """Test that a dictionary can be serialized and then deserialized as a taurus object."""
    process = ProcessRun("A process")
    mat = MaterialRun("A material", process=process)
    meas = MeasurementRun("A measurement", material=mat)
    copy = loads(dumps(meas.as_dict()))
    assert copy == meas
Exemplo n.º 8
0
def test_measurement_reassignment():
    """Check that a measurement run can be re-assigned to a new material run."""
    sample1 = MaterialRun("Sample 1")
    sample2 = MaterialRun("Sample 2")
    mass = MeasurementRun("Mass of sample", material=sample1)
    volume = MeasurementRun("Volume of sample", material=sample1)
    assert mass.material == sample1
    assert set(sample1.measurements) == {mass, volume}
    assert sample2.measurements == []

    mass.material = sample2
    assert mass.material == sample2
    assert sample1.measurements == [volume]
    assert sample2.measurements == [mass]

    mass.material = None
    assert mass.material is None
    assert sample2.measurements == []
Exemplo n.º 9
0
def test_deserialize():
    """Round-trip serde should leave the object unchanged."""
    condition = Condition(name="A condition", value=NominalReal(7, ''))
    parameter = Parameter(name="A parameter", value=NormalReal(mean=17, std=1, units=''))
    measurement = MeasurementRun(tags="A tag on a measurement", conditions=condition,
                                 parameters=parameter)
    copy_meas = copy(measurement)
    assert(copy_meas.conditions[0].value == measurement.conditions[0].value)
    assert(copy_meas.parameters[0].value == measurement.parameters[0].value)
    assert(copy_meas.uids["auto"] == measurement.uids["auto"])
Exemplo n.º 10
0
def test_template_access():
    """A measurement run's template should be equal to its spec's template."""
    template = MeasurementTemplate("measurement template",
                                   uids={'id': str(uuid4())})
    spec = MeasurementSpec("A spec",
                           uids={'id': str(uuid4())},
                           template=template)
    meas = MeasurementRun("A run", uids={'id': str(uuid4())}, spec=spec)
    assert meas.template == template

    meas.spec = LinkByUID.from_entity(spec)
    assert meas.template is None
Exemplo n.º 11
0
def test_attribute_serde():
    """An attribute with a link to an attribute template should be copy-able."""
    prop_tmpl = PropertyTemplate(name='prop_tmpl',
                                 bounds=RealBounds(0, 2, 'm')
                                 )
    prop = Property(name='prop',
                    template=prop_tmpl,
                    value=NominalReal(1, 'm')
                    )
    meas_spec = MeasurementSpec("a spec")
    meas = MeasurementRun("a measurement", spec=meas_spec, properties=[prop])
    assert loads(dumps(prop)) == prop
    assert loads(dumps(meas)) == meas
    assert isinstance(prop.template, PropertyTemplate)
Exemplo n.º 12
0
def test_thin_dumps():
    """Test that thin_dumps turns pointers into links and doesn't work on non-BaseEntity."""
    mat = MaterialRun("The actual material")
    meas_spec = MeasurementSpec("measurement", uids={'my_scope': '324324'})
    meas = MeasurementRun("The measurement", spec=meas_spec, material=mat)

    thin_copy = MeasurementRun.build(json.loads(thin_dumps(meas)))
    assert isinstance(thin_copy, MeasurementRun)
    assert isinstance(thin_copy.material, LinkByUID)
    assert isinstance(thin_copy.spec, LinkByUID)
    assert thin_copy.spec.id == meas_spec.uids['my_scope']

    with pytest.raises(TypeError):
        thin_dumps(LinkByUID('scope', 'id'))
Exemplo n.º 13
0
def test_measurement_spec():
    """Test the measurement spec/run connection survives ser/de."""
    condition = Condition(name="Temp condition",
                          value=NominalReal(nominal=298, units='kelvin'))
    parameter = Parameter(name="Important parameter")
    spec = MeasurementSpec(name="Precise way to do a measurement",
                           parameters=parameter,
                           conditions=condition)

    # Create a measurement run from this measurement spec
    measurement = MeasurementRun(conditions=condition, spec=spec)

    copy = loads(dumps(measurement))
    assert dumps(copy.spec) == dumps(measurement.spec), \
        "Measurement spec should be preserved if measurement run is serialized"
Exemplo n.º 14
0
def ingest_table(material_run, table):
    """Ingest a material run into an existing table."""
    for _, row in table.iterrows():
        exp = MeasurementRun()
        for prop_name in known_properties:
            if prop_name in row:
                exp.properties.append(
                    Property(name=prop_name,
                             value=NominalReal(row[prop_name], '')))
        for cond_name in known_conditions:
            if cond_name in row:
                exp.conditions.append(
                    Condition(name=cond_name,
                              value=NominalReal(row[cond_name], '')))
        exp.material = material_run

    return material_run
Exemplo n.º 15
0
def test_thin_dumps():
    """Test that thin_dumps turns pointers into links."""
    mat = MaterialRun("The actual material")
    meas_spec = MeasurementSpec("measurement", uids={'my_scope': '324324'})
    meas = MeasurementRun("The measurement", spec=meas_spec, material=mat)

    thin_copy = MeasurementRun.build(json.loads(TaurusJson().thin_dumps(meas)))
    assert isinstance(thin_copy, MeasurementRun)
    assert isinstance(thin_copy.material, LinkByUID)
    assert isinstance(thin_copy.spec, LinkByUID)
    assert thin_copy.spec.id == meas_spec.uids['my_scope']

    # Check that LinkByUID objects are correctly converted their JSON equivalent
    expected_json = '{"id": "my_id", "scope": "scope", "type": "link_by_uid"}'
    assert TaurusJson().thin_dumps(LinkByUID('scope', 'my_id')) == expected_json

    # Check that objects lacking .uid attributes will raise an exception when dumped
    with pytest.raises(TypeError):
        TaurusJson().thin_dumps({{'key': 'value'}})
Exemplo n.º 16
0
def test_recursive_foreach():
    """Test that recursive foreach will actually walk through a material history."""
    mat_run = MaterialRun("foo")
    process_run = ProcessRun("bar")
    IngredientRun(process=process_run, material=mat_run)
    output = MaterialRun(process=process_run)

    # property templates are trickier than templates because they are referenced in attributes
    template = PropertyTemplate("prop", bounds=RealBounds(0, 1, ""))
    prop = Property("prop", value=NominalReal(1.0, ""), template=template)
    MeasurementRun("check", material=output, properties=prop)

    types = []
    recursive_foreach(output, lambda x: types.append(x.typ))

    expected = [
        "ingredient_run", "material_run", "material_run", "process_run",
        "measurement_run", "property_template"
    ]
    assert sorted(types) == sorted(expected)
Exemplo n.º 17
0
def test_serialize():
    """Serializing a nested object should be identical to individually serializing each piece."""
    condition = Condition(name="A condition", value=NominalReal(7, ''))
    parameter = Parameter(name="A parameter", value=NormalReal(mean=17, std=1, units=''))
    input_material = MaterialRun(tags="input")
    process = ProcessRun(tags="A tag on a process run")
    ingredient = IngredientRun(material=input_material, process=process)
    material = MaterialRun(tags=["A tag on a material"], process=process)
    measurement = MeasurementRun(tags="A tag on a measurement", conditions=condition,
                                 parameters=parameter, material=material)

    # serialize the root of the tree
    native_object = json.loads(dumps(measurement))
    # ingredients don't get serialized on the process
    assert(len(native_object[0]) == 3)
    assert(native_object[1]["type"] == LinkByUID.typ)

    # serialize all of the nodes
    native_batch = json.loads(dumps([material, process, measurement, ingredient]))
    assert(len(native_batch[0]) == 5)
    assert(len(native_batch[1]) == 4)
    assert(all(x["type"] == LinkByUID.typ for x in native_batch[1]))
Exemplo n.º 18
0
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(spec=x), binder_specs)
    powder_runs = keymap(lambda x: MaterialRun(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(
        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(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(template=firing_template)
    firing_process = ProcessRun(
        conditions=[measured_firing_temperature],
        parameters=[specified_firing_setting],
        spec=firing_spec
    )
    IngredientRun(
        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(template=measurement_template)
    measurement = MeasurementRun(
        properties=[measured_density, measured_modulus],
        spec=measurement_spec
    )

    tags = [tag] if tag else []

    material_spec = MaterialSpec(template=material_template)
    material_run = MaterialRun(process=firing_process, tags=tags, spec=material_spec)
    measurement.material = material_run
    return material_run
Exemplo n.º 19
0
def ingest_material_run(data, material_spec=None, process_run=None):
    """Ingest material run with data, a material spec, and an originating process run."""
    if isinstance(data, list):
        return [ingest_material_run(x, material_spec) for x in data]

    if not isinstance(data, dict):
        raise ValueError("This ingester operates on dict, but got {}".format(type(data)))

    material = MaterialRun()

    sample_id = data.get("sample_id")
    if sample_id:
        material.add_uid("given_sample_id", sample_id)

    tags = data.get("tags")
    if tags:
        material.tags = tags

    for experiment in data.get("experiments", []):
        measurement = MeasurementRun()

        for name in set(known_properties.keys()).intersection(experiment.keys()):
            prop = Property(
                name=name,
                template=known_properties[name],
                value=_parse_value(experiment[name])
            )
            measurement.properties.append(prop)

        for name in set(known_conditions.keys()).intersection(experiment.keys()):
            cond = Condition(
                name=name,
                template=known_conditions[name],
                value=_parse_value(experiment[name])
            )
            measurement.conditions.append(cond)

        for name in set(known_parameters.keys()).intersection(experiment.keys()):
            param = Parameter(
                name=name,
                template=known_parameters[name],
                value=_parse_value(experiment[name])
            )
            measurement.parameters.append(param)

        scan_id = experiment.get("scan_id")
        if scan_id:
            measurement.add_uid("given_scan_id", scan_id)

        tags = experiment.get("tags")
        if tags:
            measurement.tags = tags

        measurement.material = material

    if material_spec:
        material.material_spec = material_spec

    if process_run:
        material.process = process_run

    return material
Exemplo n.º 20
0
def test_material_id_link():
    """Check that a measurement can be linked to a material that is a LinkByUID."""
    mat = LinkByUID('id', str(uuid4()))
    meas = MeasurementRun(material=mat)
    assert meas.material == mat
    assert loads(dumps(meas)) == meas
Exemplo n.º 21
0
def make_cake(seed=None, tmpl=None, cake_spec=None):
    """Define all objects that go into making a demo cake."""
    import struct
    import hashlib

    if seed is not None:
        random.seed(seed)
    ######################################################################
    # Parent Objects
    if tmpl is None:
        tmpl = make_cake_templates()
    if cake_spec is None:
        cake_spec = make_cake_spec(tmpl)

    ######################################################################
    # Objects
    cake = make_instance(cake_spec)
    operators = ['gwash', 'jadams', 'thomasj', 'jmadison', 'jmonroe']
    cake.process.source = PerformedSource(
        performed_by=random.choice(operators), performed_date='2015-03-14')
    # Replace Abstract/In General
    queue = [cake]
    while queue:
        item = queue.pop(0)
        if item.spec.tags is not None:
            item.tags = list(item.spec.tags)
        if item.spec.notes:  # None or empty string
            item.notes = 'The spec says "{}"'.format(item.spec.notes)

        if isinstance(item, MaterialRun):
            item.name = item.name.replace('Abstract ', '')
            queue.append(item.process)
        elif isinstance(item, ProcessRun):
            item.name = item.name.replace(', in General', '')
            queue.extend(item.ingredients)
            if item.template.name == "Procurement":
                item.source = PerformedSource(performed_by='hamilton',
                                              performed_date='2015-02-17')
            else:
                item.source = cake.process.source
        elif isinstance(item, IngredientRun):
            queue.append(item.material)
            fuzz = 0.95 + 0.1 * random.random()
            if item.spec.absolute_quantity is not None:
                item.absolute_quantity = \
                    NormalReal(mean=fuzz * item.spec.absolute_quantity.nominal,
                               std=0.05 * item.spec.absolute_quantity.nominal,
                               units=item.spec.absolute_quantity.units)
            if item.spec.volume_fraction is not None:
                item.volume_fraction = \
                    NormalReal(mean=fuzz * item.spec.volume_fraction.nominal,
                               std=0.05 * item.spec.volume_fraction.nominal,
                               units=item.spec.volume_fraction.units)
            if item.spec.mass_fraction is not None:
                item.mass_fraction = \
                    UniformReal(lower_bound=(fuzz - 0.05) * item.spec.mass_fraction.nominal,
                                upper_bound=(fuzz + 0.05) * item.spec.mass_fraction.nominal,
                                units=item.spec.mass_fraction.units)
            if item.spec.number_fraction is not None:
                item.number_fraction = \
                    NormalReal(mean=fuzz * item.spec.number_fraction.nominal,
                               std=0.05 * item.spec.number_fraction.nominal,
                               units=item.spec.number_fraction.units)

        else:
            raise TypeError("Unexpected object in the queue")

    frosting = \
        next(x.material for x in cake.process.ingredients if 'rosting' in x.name)
    baked = \
        next(x.material for x in cake.process.ingredients if 'aked' in x.name)

    def find_name(name, material):
        # Recursively search for the right material
        if name == material.name:
            return material
        for ingredient in material.process.ingredients:
            result = find_name(name, ingredient.material)
            if result:
                return result
        return

    flour = find_name('Flour', cake)
    salt = find_name('Salt', cake)
    sugar = find_name('Sugar', cake)

    # Add measurements
    cake_taste = MeasurementRun(name='Final Taste', material=cake)
    cake_appearance = MeasurementRun(name='Final Appearance', material=cake)
    frosting_taste = MeasurementRun(name='Frosting Taste', material=frosting)
    frosting_sweetness = MeasurementRun(name='Frosting Sweetness',
                                        material=frosting)
    baked_doneness = MeasurementRun(name='Baking doneness', material=baked)
    flour_content = MeasurementRun(name='Flour nutritional analysis',
                                   material=flour)
    salt_content = MeasurementRun(name='Salt elemental analysis',
                                  material=salt)
    sugar_content = MeasurementRun(name='Sugar elemental analysis',
                                   material=sugar)

    # and spec out the measurements
    cake_taste.spec = MeasurementSpec(name='Taste',
                                      template=tmpl['Taste test'])
    cake_appearance.spec = MeasurementSpec(name='Appearance')
    frosting_taste.spec = cake_taste.spec  # Taste
    frosting_sweetness.spec = MeasurementSpec(name='Sweetness')
    baked_doneness.spec = MeasurementSpec(name='Doneness',
                                          template=tmpl["Doneness"])
    flour_content.spec = MeasurementSpec(name='Nutritional analysis',
                                         template=tmpl["Nutritional Analysis"])
    salt_content.spec = MeasurementSpec(name='Elemental analysis',
                                        template=tmpl["Elemental Analysis"])
    sugar_content.spec = salt_content.spec

    for msr in (cake_taste, cake_appearance, frosting_taste,
                frosting_sweetness, baked_doneness, flour_content,
                salt_content, sugar_content):
        msr.spec.add_uid(DEMO_SCOPE, msr.spec.name)

    ######################################################################
    # Let's add some attributes
    baked.process.conditions.append(
        Condition(name='Cooking time',
                  template=tmpl['Cooking time'],
                  origin=Origin.MEASURED,
                  value=NominalReal(nominal=48, units='min')))
    baked.spec.process.conditions.append(
        Condition(name='Cooking time',
                  template=tmpl['Cooking time'],
                  origin=Origin.SPECIFIED,
                  value=NormalReal(mean=50, std=5, units='min')))
    baked.process.conditions.append(
        Condition(name='Oven temperature',
                  origin="measured",
                  value=NominalReal(nominal=362, units='degF')))
    baked.spec.process.parameters.append(
        Parameter(name='Oven temperature setting',
                  template=tmpl['Oven temperature setting'],
                  origin="specified",
                  value=NominalReal(nominal=350, units='degF')))
    cake_taste.properties.append(
        Property(name='Tastiness',
                 origin=Origin.MEASURED,
                 template=tmpl['Tastiness'],
                 value=UniformInteger(4, 5)))
    cake_appearance.properties.append(
        Property(name='Visual Appeal',
                 origin=Origin.MEASURED,
                 value=NominalInteger(nominal=5)))
    frosting_taste.properties.append(
        Property(name='Tastiness',
                 origin=Origin.MEASURED,
                 template=tmpl['Tastiness'],
                 value=NominalInteger(nominal=4)))
    frosting_sweetness.properties.append(
        Property(name='Sweetness (Sucrose-basis)',
                 origin=Origin.MEASURED,
                 value=NominalReal(nominal=1.7, units='')))

    baked_doneness.properties.append(
        Property(name='Toothpick test',
                 origin="measured",
                 template=tmpl["Toothpick test"],
                 value=NominalCategorical("crumbs")))
    baked_doneness.properties.append(
        Property(name='Color',
                 origin="measured",
                 template=tmpl["Color"],
                 value=DiscreteCategorical({
                     "Pale": 0.05,
                     "Golden brown": 0.65,
                     "Deep brown": 0.3
                 })))

    flour_content.properties.append(
        Property(name='Nutritional Information',
                 value=NominalComposition({
                     "dietary-fiber":
                     1 * (0.99 + 0.02 * random.random()),
                     "sugars":
                     1 * (0.99 + 0.02 * random.random()),
                     "other-carbohydrate":
                     20 * (0.99 + 0.02 * random.random()),
                     "protein":
                     4 * (0.99 + 0.02 * random.random()),
                     "other":
                     4 * (0.99 + 0.02 * random.random())
                 }),
                 template=tmpl["Nutritional Information"],
                 origin="measured"))
    flour_content.conditions.append(
        Condition(name='Sample Mass',
                  value=NormalReal(mean=99 + 2 * random.random(),
                                   std=1.5,
                                   units='mg'),
                  template=tmpl["Sample Mass"],
                  origin="measured"))
    flour_content.parameters.append(
        Parameter(name='Expected Sample Mass',
                  value=NominalReal(nominal=0.1, units='g'),
                  template=tmpl["Expected Sample Mass"],
                  origin="specified"))
    flour_content.spec.conditions.append(
        Condition(name='Sample Mass',
                  value=NominalReal(nominal=100, units='mg'),
                  template=tmpl["Sample Mass"],
                  origin="specified"))
    flour_content.spec.parameters.append(
        Parameter(name='Expected Sample Mass',
                  value=NominalReal(nominal=0.1, units='g'),
                  template=tmpl["Expected Sample Mass"],
                  origin="specified"))

    salt_content.properties.append(
        Property(name="Composition",
                 value=EmpiricalFormula(
                     formula="NaClCa0.006Si0.006O0.018K0.000015I0.000015"),
                 template=tmpl["Chemical Formula"],
                 origin="measured"))
    salt_content.conditions.append(
        Condition(name='Sample Mass',
                  value=NormalReal(mean=99 + 2 * random.random(),
                                   std=1.5,
                                   units='mg'),
                  template=tmpl["Sample Mass"],
                  origin="measured"))
    salt_content.parameters.append(
        Parameter(name='Expected Sample Mass',
                  value=NominalReal(nominal=0.1, units='g'),
                  template=tmpl["Expected Sample Mass"],
                  origin="specified"))
    salt_content.spec.conditions.append(
        Condition(name='Sample Mass',
                  value=NominalReal(nominal=100, units='mg'),
                  template=tmpl["Sample Mass"],
                  origin="specified"))

    sugar_content.properties.append(
        Property(
            name="Composition",
            value=EmpiricalFormula(formula='C11.996H21.995O10.997S0.00015'),
            template=tmpl["Chemical Formula"],
            origin="measured"))
    sugar_content.conditions.append(
        Condition(name='Sample Mass',
                  value=NormalReal(mean=99 + 2 * random.random(),
                                   std=1.5,
                                   units='mg'),
                  template=tmpl["Sample Mass"],
                  origin="measured"))
    sugar_content.spec.parameters.append(
        Parameter(name='Expected Sample Mass',
                  value=NominalReal(nominal=0.1, units='g'),
                  template=tmpl["Expected Sample Mass"],
                  origin="specified"))

    # Code to generate quasi-repeatable run annotations
    # Note there are potential machine dependencies
    md5 = hashlib.md5()
    for x in random.getstate()[1]:
        md5.update(struct.pack(">I", x))
    run_key = md5.hexdigest()

    # 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 + run_key))

    cake.notes = cake.notes + "; Très délicieux! 😀"
    cake.file_links = [
        FileLink(
            filename="Photo",
            url='https://www.landolakes.com/RecipeManagementSystem/media/'
            'Recipe-Media-Files/Recipes/Retail/x17/16730-beckys-butter-cake-600x600.jpg?ext=.jpg'
        )
    ]

    return cake