Example #1
0
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[0][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"
Example #2
0
def _parse_value(val):
    """Example field-parsing logic."""
    # If the string is complicated, split it up and try to get uncertainty and/or units
    if isinstance(val, str) and len(val.split()) > 1:
        toks = val.split()
        mean = float(toks[0])
        std = -1
        if toks[1] in {"+-", "+/-"}:
            std = float(toks[2])

        try:
            unit = units.parse_units(toks[-1])
        except (ValueError, units.UndefinedUnitError):
            print("Couldn't find {}".format(toks[-1]))
            unit = ''

        if std >= 0:
            return NormalReal(mean=mean, std=std, units=unit)
        else:
            return NominalReal(mean, units=unit)
    # if it is just a number wrap it in a nominal value
    elif isinstance(val, (float, int)):
        return NominalReal(val, '')
    # if it is a single string, its either a single number of a category
    elif isinstance(val, str):
        try:
            num = float(val)
            return NominalReal(num, '')
        except ValueError:
            return DiscreteCategorical(val)
    else:
        raise ValueError("Couldn't parse {}".format(val))
Example #3
0
def test_serialized_history():
    """Test the serialization of a complete material history."""
    # Create several runs and specs linked together
    buy_spec = LinkByUID("id", "pr723")
    cookie_dough_spec = MaterialSpec("cookie dough spec", process=buy_spec)
    buy_cookie_dough = ProcessRun("Buy cookie dough", uids={'id': '32283'}, spec=buy_spec)
    cookie_dough = MaterialRun("cookie dough", process=buy_cookie_dough, spec=cookie_dough_spec)
    bake = ProcessRun("bake cookie dough", conditions=[
        Condition("oven temp", origin='measured', value=NominalReal(357, 'degF'))])
    IngredientRun(material=cookie_dough,
                  process=bake, number_fraction=NominalReal(1, ''))
    cookie = MaterialRun("cookie", process=bake, tags=["chocolate chip", "drop"])
    MeasurementRun("taste", material=cookie, properties=[
        Property("taste", value=DiscreteCategorical("scrumptious"))])

    cookie_history = complete_material_history(cookie)
    # There are 7 entities in the serialized list: cookie dough (spec & run), buy cookie dough,
    # cookie dough ingredient, bake cookie dough, cookie, taste
    assert len(cookie_history) == 7
    for entity in cookie_history:
        assert len(entity['uids']) > 0, "Serializing material history should assign uids."

    # Check that the measurement points to the material
    taste_dict = next(x for x in cookie_history if x.get('type') == 'measurement_run')
    cookie_dict = next(x for x in cookie_history if x.get('name') == 'cookie')
    scope = taste_dict.get('material').get('scope')
    assert taste_dict.get('material').get('id') == cookie_dict.get('uids').get(scope)

    # Check that both the material spec and the process run point to the same process spec.
    # Because that spec was initially a LinkByUID, this also tests the methods ability to
    # serialize a LinkByUID.
    cookie_dough_spec_dict = next(x for x in cookie_history if x.get('type') == 'material_spec')
    buy_cookie_dough_dict = next(x for x in cookie_history if x.get('name') == 'Buy cookie dough')
    assert cookie_dough_spec_dict.get('process') == buy_spec.as_dict()
    assert buy_cookie_dough_dict.get('spec') == buy_spec.as_dict()
Example #4
0
def test_unit_normalization():
    """Make sure units are internally represented in a reasonable way."""
    assert NominalReal(2.7, "newton / m^2").units == NominalReal(
        2.7, "m^-1 newton / meter").units

    val = NominalReal(2.7, "cm")
    assert val.units == "centimeter"
    assert val.dump()["units"] == "centimeter"
Example #5
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)
Example #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
Example #7
0
def test_access_data():
    """Demonstrate and test access patterns within the data island."""
    binders = {
        "Polyethylene Glycol 100M": 0.02,
        "Sodium lignosulfonate": 0.004,
        "Polyvinyl Acetate": 0.0001
    }
    powders = {"Al2O3": 0.96}
    island = make_data_island(
        density=1.0,
        bulk_modulus=300.0,
        firing_temperature=750.0,
        binders=binders,
        powders=powders,
        tag="Me"
    )

    # read the density value
    assert(island.measurements[0].properties[0].value == NominalReal(1.0, ''))
    # read the bulk modulus value
    assert(island.measurements[0].properties[1].value == NormalReal(300.0, 3.0, ''))
    # read the firing temperature
    assert(island.process.conditions[0].value == UniformReal(749.5, 750.5, 'degC'))
    assert(island.process.parameters[0].value == DiscreteCategorical({"hot": 1.0}))

    # read the quantity of alumina
    quantities = island.process.ingredients[0].material.process.conditions[0].value.quantities
    assert(list(
        keyfilter(lambda x: x == "Al2O3", quantities).values()
    )[0] == 0.96)

    # check that the serialization results in the correct number of objects in the preface
    # (note that neither measurements nor ingredients are serialized)
    assert(len(json.loads(dumps(island))["context"]) == 26)
Example #8
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"))
Example #9
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
Example #10
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"])
Example #11
0
 def real_mapper(prop):
     """Mapping methods for RealBounds."""
     if 'uncertainty' in prop['scalars'][0]:
         val = NormalReal(mean=float(prop['scalars'][0]['value']),
                          units=prop['units'],
                          std=float(prop['scalars'][0]['uncertainty']))
     else:
         val = NominalReal(nominal=float(prop['scalars'][0]['value']),
                           units=prop['units'])
     return val
Example #12
0
def test_build():
    """Test that build recreates the material."""
    spec = MaterialSpec(
        "A spec",
        properties=PropertyAndConditions(
            property=Property("a property", value=NominalReal(3, ''))),
        tags=["a tag"])
    mat = MaterialRun(name="a material", spec=spec)
    mat_dict = mat.as_dict()
    mat_dict['spec'] = mat.spec.as_dict()
    assert MaterialRun.build(mat_dict) == mat
Example #13
0
def test_invalid_assignment():
    """Test that invalid assignments throw the appropriate errors."""
    with pytest.raises(ValueError):
        Property(value=NominalReal(10, ''))
    with pytest.raises(TypeError):
        Property(name="property", value=10)
    with pytest.raises(TypeError):
        Property(name="property",
                 template=ProcessTemplate("wrong kind of template"))
    with pytest.raises(ValueError):
        Property(name="property", origin=None)
Example #14
0
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"
Example #15
0
def test_material_attachment():
    """
    Attach a material run to an ingredient run.

    Check that the ingredient can be built, and that the connection survives ser/de.
    """
    flour = MaterialRun("flour", sample_type='unknown')
    flour_ingredient = IngredientRun(material=flour,
                                     absolute_quantity=NominalReal(500, 'g'),
                                     name='500 g flour')

    flour_ingredient_copy = IngredientRun.build(flour_ingredient.dump())
    assert flour_ingredient_copy == flour_ingredient
def test_template_assignment():
    """Test that an object and its attributes can both be assigned templates."""
    humidity_template = ConditionTemplate("Humidity",
                                          RealBounds(0.5, 0.75, ""))
    template = ProcessTemplate(
        "Dry", conditions=[[humidity_template,
                            RealBounds(0.5, 0.65, "")]])
    ProcessSpec("Dry a polymer",
                template=template,
                conditions=[
                    Condition("Humidity",
                              value=NominalReal(0.6, ""),
                              template=humidity_template)
                ])
Example #17
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)
def test_simple_deserialization(valid_data, attribute_list):
    for (key, Attribute, AttributeTemplate) in attribute_list:
        valid_data['type'] = key
        valid_data['template']['type'] = key + '_template'
        attribute: Attribute = Attribute.build(valid_data)
        assert attribute.name == 'mass'
        assert attribute.notes == 'This is a note'
        assert attribute.value == NominalReal(5.0, units='gram')
        assert attribute.template == \
               AttributeTemplate('mass', uids={'id': valid_data['template']['uids']['id']},
                                 description='mass of object', bounds=RealBounds(0.0, 20.0, 'gram')
                                 )
        assert attribute.origin == 'measured'
        assert attribute.file_links == []
        assert attribute.typ == key
Example #19
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"
Example #20
0
def test_simple_deserialization(valid_data):
    """Ensure that a deserialized Material Spec looks sane."""
    material_spec: MaterialSpec = MaterialSpec.build(valid_data)
    assert material_spec.uids == {'id': valid_data['uids']['id']}
    assert material_spec.name == 'spec of material'
    assert material_spec.tags == []
    assert material_spec.notes is None
    assert material_spec.process is None
    assert material_spec.properties[0] == \
        PropertyAndConditions(Property("color", origin='specified',
                                       value=NominalCategorical("tan")),
                              conditions=[Condition('temperature', origin='specified',
                                                    value=NominalReal(300, units='kelvin'))])
    assert material_spec.template is None
    assert material_spec.file_links == []
    assert material_spec.typ == 'material_spec'
Example #21
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)
def test_simple_deserialization(valid_data):
    """Ensure that a deserialized Process Run looks sane."""
    process_run: ProcessRun = ProcessRun.build(valid_data)
    assert process_run.uids == {'id': valid_data['uids']['id'], 'my_id': 'process1-v1'}
    assert process_run.tags == ['baking::cakes', 'danger::low']
    assert process_run.conditions[0] == Condition(name='oven temp',
                                                         value=NominalReal(203.0, ''),
                                                         origin='measured')
    assert process_run.parameters == []
    assert process_run.file_links == []
    assert process_run.template is None
    assert process_run.output_material is None
    assert process_run.spec == \
           ProcessSpec(name="Spec for proc 1",
                       uids={'id': valid_data['spec']['uids']['id']},
                       conditions=[Condition(name='oven temp', value=UniformReal(175, 225, ''),
                                             origin='specified')]
                       )
    assert process_run.name == 'Process 1'
    assert process_run.notes == 'make sure to use oven mitts'
    assert process_run.typ == 'process_run'
Example #23
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]))
Example #24
0
def test_default_units():
    """An empty string should turn into the 'dimensionless' unit."""
    assert NominalReal(2.7, "") == NominalReal(2.7, "dimensionless")
Example #25
0
def test_invalid_assignment():
    """Invalid assignments to `process` or `material` throw a TypeError."""
    with pytest.raises(TypeError):
        IngredientSpec(name="name", material=NominalReal(3, ''))
    with pytest.raises(TypeError):
        IngredientSpec(name="name", process="process")
Example #26
0
    assert set(boiling.ingredients) == {oil, potatoes}
    assert frying.ingredients == []

    oil.process = frying
    assert oil.process == frying
    assert boiling.ingredients == [potatoes]
    assert frying.ingredients == [oil]

    potatoes.process = frying
    assert potatoes.process == frying
    assert boiling.ingredients == []
    assert set(frying.ingredients) == {oil, potatoes}


VALID_QUANTITIES = [
    NominalReal(14.0, ''),
    UniformReal(0.5, 0.6, 'm'),
    NormalReal(-0.3, 0.6, "kg")
]

INVALID_QUANTITIES = [
    NominalCategorical("blue"),
    NominalInteger(5),
    EmpiricalFormula("CH4"),
    0.33,
    "0.5"
]


@pytest.mark.parametrize("valid_quantity", VALID_QUANTITIES)
def test_valid_quantities(valid_quantity):
Example #27
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
Example #28
0
def test_units():
    """Make sure units can be set without error."""
    NominalReal(2.7, "")
    NominalReal(2.7, "cm")
    NominalReal(2.7, "cm^2/joule")
    NominalReal(2.7, "")
Example #29
0
def test_taurus_object_serde():
    """Test that an unspecified taurus object can be serialized and deserialized."""
    good_obj = SampleClass("Can be serialized", NominalReal(17, ''))
    copy = SampleClass.build(good_obj.dump())
    assert copy.prop_value == good_obj.prop_value
    assert copy.prop_string == good_obj.prop_string
Example #30
0
def test_bad_object_serde():
    """Test that a 'mystery' object cannot be serialized."""
    bad_obj = SampleClass("Cannot be serialized", NominalReal(34, ''),
                          UnserializableClass(1))
    with pytest.raises(AttributeError):
        bad_obj.dump()