def test_dict_serialization(): """Test that a dictionary can be serialized and then deserialized as a gemd 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
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")
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")) with pytest.raises(TypeError): MeasurementRun() # Name is required
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
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
def test_template_validations(caplog): """Make sure template validations and level controls behave as expected.""" msr_tmpl = MeasurementTemplate( name="Measurement Template", properties=[PropertyTemplate("Name", bounds=RealBounds(0, 1, ""))], conditions=[ConditionTemplate("Name", bounds=RealBounds(0, 1, ""))], parameters=[ParameterTemplate("Name", bounds=RealBounds(0, 1, ""))], ) msr_spec = MeasurementSpec("Measurement Spec", template=msr_tmpl) msr_run = MeasurementRun("MeasurementRun", spec=msr_spec) with validation_level(WarningLevel.IGNORE): msr_run.properties.append(Property("Name", value=NominalReal(-1, ""))) msr_run.conditions.append(Condition("Name", value=NominalReal(-1, ""))) msr_run.parameters.append(Parameter("Name", value=NominalReal(-1, ""))) assert len(caplog.records) == 0, "Logging records wasn't empty" with validation_level(WarningLevel.WARNING): msr_run.properties.append(Property("Name", value=NominalReal(-1, ""))) assert len( caplog.records) == 1, "WARNING didn't warn on invalid Property." msr_run.conditions.append(Condition("Name", value=NominalReal(-1, ""))) assert len( caplog.records) == 2, "WARNING didn't warn on invalid Condition." msr_run.parameters.append(Parameter("Name", value=NominalReal(-1, ""))) assert len( caplog.records) == 3, "WARNING didn't warn on invalid Parameter." with validation_level(WarningLevel.FATAL): with pytest.raises(ValueError): msr_run.properties.append( Property("Name", value=NominalReal(-1, ""))) with pytest.raises(ValueError): msr_run.conditions.append( Condition("Name", value=NominalReal(-1, ""))) with pytest.raises(ValueError): msr_run.parameters.append( Parameter("Name", value=NominalReal(-1, "")))
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["context"]) == 5) assert (native_object["object"]["type"] == LinkByUID.typ) # serialize all of the nodes native_batch = json.loads( dumps([material, process, measurement, ingredient])) assert (len(native_batch["context"]) == 5) assert (len(native_batch["object"]) == 4) assert (all(x["type"] == LinkByUID.typ for x in native_batch["object"]))
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("Dummy")] 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" assert 'measurements' in repr(dye) assert 'material' in repr(fluorescence) assert 'material' in repr(absorbance) subbed = substitute_links(dye) assert 'measurements' in repr(subbed)
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 == []
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_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 = GEMDJson().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"])
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(GEMDJson().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 GEMDJson().thin_dumps(LinkByUID('scope', 'my_id')) == expected_json # Check that objects lacking .uid attributes will raise an exception when dumped with pytest.raises(TypeError): GEMDJson().thin_dumps({{'key': 'value'}})
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"
def add_measurement(material: MaterialRun, *, name: str = None, template: MeasurementTemplate = None, attributes: List[BaseAttribute] = None, ) -> MeasurementRun: """ Add a measurement run-spec set to a MaterialRun. Parameters ---------- material: MaterialRun The `material` for the returned MeasurementRun name: str The name of the measurement. Defaults to `template.name` if `template` is defined. template: MeasurementTemplate The MeasurementTemplate. attributes: List[BaseAttribute] Attributes you want associated with this MeasurementRun. Returns -------- MeasurementRun A MeasurementRun with linked material, spec and template """ if name is None: if template is None: raise ValueError("Either a name or a template must be provided") else: name = template.name my_measurement_spec = MeasurementSpec(name, template=template) my_measurement_run = MeasurementRun(name, spec=my_measurement_spec, material=material) if attributes is not None: for attribute in attributes: if isinstance(attribute, Property): my_measurement_run.properties.append(attribute) elif isinstance(attribute, Condition): my_measurement_run.conditions.append(attribute) elif isinstance(attribute, Parameter): my_measurement_run.parameters.append(attribute) else: raise ValueError(f"Unhandled Attribute type {type(attribute)}") return my_measurement_run
def test_scope_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, scope=native_id) assert subbed.material == LinkByUID.from_entity(mat, scope=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, scope=native_id) assert subbed == [LinkByUID.from_entity(meas, scope=native_id)]
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("The Measurement", 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"
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("material", 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 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( name="3 Point Bend", 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
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 test_dependencies(): """Test that dependency lists make sense.""" prop = PropertyTemplate(name="name", bounds=IntegerBounds(0, 1)) cond = ConditionTemplate(name="name", bounds=IntegerBounds(0, 1)) param = ParameterTemplate(name="name", bounds=IntegerBounds(0, 1)) template = MeasurementTemplate("measurement template", parameters=[param], conditions=[cond], properties=[prop]) spec = MeasurementSpec("A spec", template=template) mat = MaterialRun(name="mr") meas = MeasurementRun("A run", spec=spec, material=mat, properties=[ Property(prop.name, template=prop, value=NominalInteger(1)) ], conditions=[ Condition(cond.name, template=cond, value=NominalInteger(1)) ], parameters=[ Parameter(param.name, template=param, value=NominalInteger(1)) ]) assert template not in meas.all_dependencies() assert spec in meas.all_dependencies() assert mat in meas.all_dependencies() assert prop in meas.all_dependencies() assert cond in meas.all_dependencies() assert param in meas.all_dependencies()
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
def make_cake(seed=None, tmpl=None, cake_spec=None, toothpick_img=None): """Define all objects that go into making a demo cake.""" import struct import hashlib if seed is not None: random.seed(seed) # 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() ###################################################################### # Parent Objects if tmpl is None: tmpl = make_cake_templates() if cake_spec is None: cake_spec = make_cake_spec(tmpl) ###################################################################### # Objects cake_obj = make_instance(cake_spec) operators = ['gwash', 'jadams', 'thomasj', 'jmadison', 'jmonroe'] producers = ['Fresh Farm', 'Sunnydale', 'Greenbrook'] drygoods = ['Acme', 'A1', 'Reliable', "Big Box"] cake_obj.process.source = PerformedSource( performed_by=random.choice(operators), performed_date='2015-03-14') def _randomize_object(item): # Add in the randomized particular values if not isinstance(item, (MaterialRun, ProcessRun, IngredientRun)): return item.add_uid(DEMO_SCOPE, '{}-{}'.format(item.spec.uids[DEMO_SCOPE], run_key)) if item.spec.tags is not None: item.tags = list(item.spec.tags) if item.spec.notes: # Neither None or empty string item.notes = 'The spec says "{}"'.format(item.spec.notes) if isinstance(item, MaterialRun): if 'raw material' in item.tags: if 'produce' in item.tags: supplier = random.choice(producers) else: supplier = random.choice(drygoods) item.name = "{} {}".format(supplier, item.spec.name) if isinstance(item, ProcessRun): if item.template.name == "Procuring": item.source = PerformedSource(performed_by='hamilton', performed_date='2015-02-17') item.name = "{} {}".format(item.template.name, item.output_material.name) else: item.source = cake_obj.process.source if isinstance(item, IngredientRun): 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: # The only element here is dry mix, and it's almost entirely flour item.volume_fraction = \ NormalReal(mean=0.01 * (fuzz - 0.5) + item.spec.volume_fraction.nominal, std=0.005, 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) recursive_foreach(cake_obj, _randomize_object) frosting = \ next(x.material for x in cake_obj.process.ingredients if 'rosting' in x.name) baked = \ next(x.material for x in cake_obj.process.ingredients if 'aked' in x.name) def _find_name(name, material): """Recursively search for the right material.""" if name in 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_obj) salt = _find_name('Salt', cake_obj) sugar = _find_name('Sugar', cake_obj) # Add measurements cake_taste = MeasurementRun(name='Final Taste', material=cake_obj) cake_appearance = MeasurementRun(name='Final Appearance', material=cake_obj) 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) if toothpick_img is not None: baked_doneness.file_links.append(toothpick_img) # 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 # Note that while specs are regenerated each make_cake invocation, they are all identical 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.lower()) msr.add_uid( DEMO_SCOPE, '{}--{}-{}'.format(msr.spec.uids[DEMO_SCOPE], msr.material.spec.uids[DEMO_SCOPE], run_key)) ###################################################################### # 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.process.conditions.append( Condition(name='Oven temperature', origin="measured", value=NominalReal(nominal=362, 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")) cake_obj.notes = cake_obj.notes + "; Très délicieux! 😀" cake_obj.file_links = [ FileLink( filename="Photo", url='https://storcpdkenticomedia.blob.core.windows.net/media/' 'recipemanagementsystem/media/recipe-media-files/recipes/retail/x17/' '16730-beckys-butter-cake-600x600.jpg?ext=.jpg') ] return cake_obj
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("name", material=mat) assert meas.material == mat assert loads(dumps(meas)) == meas
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
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: # The only element here is dry mix, and it's almost entirely flour item.volume_fraction = \ NormalReal(mean=0.01 * (fuzz - 0.5) + item.spec.volume_fraction.nominal, std=0.005, 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