def __init__(self, *, uids: Optional[Dict[str, str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, material: Optional[GEMDMaterialRun] = None, process: Optional[GEMDProcessRun] = None, mass_fraction: Optional[ContinuousValue] = None, volume_fraction: Optional[ContinuousValue] = None, number_fraction: Optional[ContinuousValue] = None, absolute_quantity: Optional[ContinuousValue] = None, spec: Optional[GEMDIngredientSpec] = None, file_links: Optional[List[FileLink]] = None): if uids is None: uids = dict() DataConcepts.__init__(self, GEMDIngredientRun.typ) GEMDIngredientRun.__init__(self, uids=uids, tags=tags, notes=notes, material=material, process=process, mass_fraction=mass_fraction, volume_fraction=volume_fraction, number_fraction=number_fraction, absolute_quantity=absolute_quantity, spec=spec, file_links=file_links)
def test_id_case_sensitivitiy(): """Test that uids are case insensitive.""" with pytest.raises(ValueError): IngredientRun(uids={'my_id': 'sample1', 'My_ID': 'sample2'}) ingredient = IngredientRun(uids={'my_id': 'sample1'}) assert ingredient.uids['my_id'] == 'sample1' assert ingredient.uids['MY_id'] == 'sample1'
def test_invalid_assignment(): """Invalid assignments to `process` or `material` throw a TypeError.""" with pytest.raises(TypeError): IngredientRun(material=RealBounds(0, 5.0, '')) with pytest.raises(TypeError): IngredientRun(process="process") with pytest.raises(TypeError): IngredientRun(spec=5)
def test_link_by_uid(): """Test that linking works.""" root = MaterialRun(name='root', process=ProcessRun(name='root proc')) leaf = MaterialRun(name='leaf', process=ProcessRun(name='leaf proc')) IngredientRun(process=root.process, material=leaf) IngredientRun(process=root.process, material=LinkByUID.from_entity(leaf)) copy = loads(dumps(root)) assert copy.process.ingredients[0].material == copy.process.ingredients[ 1].material
def crawler(spec): from gemd.entity.object.measurement_spec import MeasurementSpec from gemd.entity.object.measurement_run import MeasurementRun from gemd.entity.object.material_spec import MaterialSpec from gemd.entity.object.material_run import MaterialRun from gemd.entity.object.ingredient_spec import IngredientSpec from gemd.entity.object.ingredient_run import IngredientRun from gemd.entity.object.process_spec import ProcessSpec from gemd.entity.object.process_run import ProcessRun if id(spec) in seen: return seen[id(spec)] if isinstance(spec, MeasurementSpec): seen[id(spec)] = MeasurementRun(name=spec.name, spec=spec) elif isinstance(spec, MaterialSpec): seen[id(spec)] = MaterialRun(name=spec.name, spec=spec) seen[id(spec)].process = crawler( spec.process) if spec.process else None elif isinstance(spec, IngredientSpec): seen[id(spec)] = IngredientRun(spec=spec) seen[id(spec)].material = crawler( spec.material) if spec.material else None elif isinstance(spec, ProcessSpec): seen[id(spec)] = ProcessRun(name=spec.name, spec=spec) for x in spec.ingredients: crawler(x).process = seen[id(spec)] else: raise TypeError( 'Passed object is not a spec-like object({})'.format( type(spec))) # Should we assume that the same MaterialSpec in different parts of the tree # yields the same MaterialRun? return seen[id(spec)]
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_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()
def test_uid_deser(): """Test that uids continue to be a CaseInsensitiveDict after deserialization.""" material = MaterialRun("Input material", tags="input", uids={'Sample ID': '500-B'}) ingredient = IngredientRun(material=material) ingredient_copy = loads(dumps(ingredient)) assert isinstance(ingredient_copy.uids, CaseInsensitiveDict) assert ingredient_copy.material == material assert ingredient_copy.material.uids['sample id'] == material.uids['Sample ID']
def test_name_persistance(): """Verify that a serialized IngredientRun doesn't lose its name.""" from gemd.entity.object import IngredientSpec from gemd.entity.link_by_uid import LinkByUID from gemd.json import GEMDJson je = GEMDJson() ms_link = LinkByUID(scope='local', id='mat_spec') mr_link = LinkByUID(scope='local', id='mat_run') ps_link = LinkByUID(scope='local', id='pro_spec') pr_link = LinkByUID(scope='local', id='pro_run') spec = IngredientSpec(name='Ingred', labels=['some', 'words'], process=ps_link, material=ms_link) run = IngredientRun(spec=spec, process=pr_link, material=mr_link) assert run.name == spec.name assert run.labels == spec.labels # Try changing them and make sure they change spec.name = 'Frank' spec.labels = ['other', 'words'] assert run.name == spec.name assert run.labels == spec.labels run.spec = LinkByUID(scope='local', id='ing_spec') # Name and labels are now stashed but not stored assert run == je.copy(run) assert run.name == spec.name assert run.labels == spec.labels # Test that serialization doesn't get confused after a deser and set spec_too = IngredientSpec(name='Jorge', labels=[], process=ps_link, material=ms_link) run.spec = spec_too assert run == je.copy(run) assert run.name == spec_too.name assert run.labels == spec_too.labels
def test_to_link(): """Test that to_link behaves as expected.""" obj = IngredientRun(uids={"Scope": "UID", "Second": "option"}) assert isinstance(obj.to_link(), LinkByUID), "Returns a useful LinkByUID" assert LinkByUID(scope="Scope", id="UID") == obj.to_link("Scope"), "Correct choice of UID" with pytest.raises(ValueError): IngredientRun().to_link(), "to_link on an object w/o IDs is fatal" with pytest.raises(ValueError): obj.to_link("Third"), "to_link with a scope that an object lacks is fatal" assert obj.to_link(scope="Third", allow_fallback=True).scope in obj.uids, \ "... unless allow_fallback is set"
def test_ingredient_run(): """Tests that a process can house an ingredient, and that pairing survives serialization.""" # Create a ProcessSpec proc_run = ProcessRun(name="a process spec", tags=["tag1", "tag2"]) ingred_run = IngredientRun(material=MaterialRun(name='Raw'), process=proc_run) # Make copies of both specs proc_run_copy = loads(dumps(proc_run)) assert proc_run_copy == proc_run, "Full structure wasn't preserved across serialization" assert 'process' in repr(ingred_run) assert 'ingredients' in repr(proc_run)
def test_many_ingredients(): """Test that ingredients remain connected to processes when round-robined through json.""" proc = ProcessRun("foo", spec=ProcessSpec("sfoo")) expected = [] for i in range(10): mat = MaterialRun(name=str(i), spec=MaterialSpec("s{}".format(i))) i_spec = IngredientSpec(name="i{}".format(i), material=mat.spec, process=proc.spec) IngredientRun(process=proc, material=mat, spec=i_spec) expected.append("i{}".format(i)) reloaded = loads(dumps(proc)) assert len(list(reloaded.ingredients)) == 10 names = [x.name for x in reloaded.ingredients] assert sorted(names) == sorted(expected)
def test_implicit_fields(): """These test that users can't directly set names and labels.""" name = 'name' labels = ['label', 'also'] with pytest.raises(TypeError): IngredientRun(name=name) with pytest.raises(TypeError): IngredientRun(labels=labels) run = IngredientRun() with pytest.raises(AttributeError): run.name = name with pytest.raises(AttributeError): run.labels = labels
def test_ingredient_reassignment(): """Check that an ingredient run can be re-assigned to a new process run.""" boiling = ProcessRun("Boil potatoes") frying = ProcessRun("Fry potatoes") oil = IngredientRun(process=boiling) potatoes = IngredientRun(process=boiling) assert oil.process == boiling 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}
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