def test_process_reassignment(): """Test that a material can be assigned to a new process.""" drying = ProcessSpec("drying") welding = ProcessSpec("welding") powder = MaterialSpec("Powder", process=welding) assert powder.process == welding assert welding.output_material == powder powder.process = drying assert powder.process == drying assert drying.output_material == powder assert welding.output_material is None
def test_material_spec(): """Test that Process/Material Spec link survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) # Create MaterialSpec without a ProcessSpec prop = Property(name="The material is a solid", value=DiscreteCategorical(probabilities="solid")) mat_spec = MaterialSpec(name="a material spec", properties=PropertyAndConditions(prop)) assert mat_spec.process is None, \ "MaterialSpec should be initialized with no ProcessSpec, by default" # Assign a ProcessSpec to mat_spec, first ensuring that the type is enforced with pytest.raises(TypeError): mat_spec.process = 17 mat_spec.process = proc_spec # Assert circular links assert dumps(proc_spec.output_material.process) == dumps(proc_spec), \ "ProcessSpec should link to MaterialSpec that links back to itself" assert dumps(mat_spec.process.output_material) == dumps(mat_spec), \ "MaterialSpec should link to ProcessSpec that links back to itself" # Make copies of both specs mat_spec_copy = loads(dumps(mat_spec)) proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy.output_material == mat_spec, \ "Serialization should preserve link from ProcessSpec to MaterialSpec" assert mat_spec_copy.process == proc_spec, \ "Serialization should preserve link from MaterialSpec to ProcessSpec"
def test_circular_crawl(): """Test that make_instance can handle a circular set of linked objects.""" proc = ProcessSpec("process name") mat = MaterialSpec("material name", process=proc) IngredientSpec(name="ingredient name", material=mat, process=proc) mat_run = make_instance(mat) assert mat_run == mat_run.process.ingredients[0].material
def __init__(self, name: str, *, uids: Optional[Dict[str, str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, conditions: Optional[List[Condition]] = None, parameters: Optional[List[Parameter]] = None, template: Optional[GEMDProcessTemplate] = None, file_links: Optional[List[FileLink]] = None ): if uids is None: uids = dict() DataConcepts.__init__(self, GEMDProcessSpec.typ) GEMDProcessSpec.__init__(self, name=name, uids=uids, tags=tags, conditions=conditions, parameters=parameters, template=template, file_links=file_links, notes=notes)
def test_template_access(): """A process run's template should be equal to its spec's template.""" template = ProcessTemplate("process template", uids={'id': str(uuid4())}) spec = ProcessSpec("A spec", uids={'id': str(uuid4())}, template=template) proc = ProcessRun("A run", uids={'id': str(uuid4())}, spec=spec) assert proc.template == template proc.spec = LinkByUID.from_entity(spec) assert proc.template is None
def test_ingredient_reassignment(): """Check that an ingredient spec can be re-assigned to a new process spec.""" boiling = ProcessSpec("Boil potatoes") frying = ProcessSpec("Fry potatoes") oil = IngredientSpec(name="Oil", process=boiling) potatoes = IngredientSpec(name="Potatoes", 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 test_ingredient_spec(): """Tests that a process can house an ingredient, and that pairing survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) IngredientSpec(name='Input', material=MaterialSpec(name='Raw'), process=proc_spec) # Make copies of both specs proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy == proc_spec, "Full structure wasn't preserved across serialization"
def test_process_spec(): """Tests that the Process Spec/Run connection persists when serializing.""" # Create the ProcessSpec condition1 = Condition(name="a condition on the process in general") spec = ProcessSpec(conditions=condition1) # Create the ProcessRun with a link to the ProcessSpec from above condition2 = Condition(name="a condition on this process run in particular") process = ProcessRun(conditions=condition2, spec=spec) copy_process = loads(dumps(process)) assert dumps(copy_process.spec) == dumps(spec), \ "Process spec should be preserved through serialization"
def test_make_instance(): """Build up several linked objects and test their properties.""" msr_spec = MeasurementSpec() assert isinstance(make_instance(msr_spec), MeasurementRun) mat_spec = MaterialSpec(name='Mat name') mat_spec.process = ProcessSpec(name='Pro name') IngredientSpec(name='Ing label', process=mat_spec.process) mat_spec.process.ingredients[0].material = MaterialSpec(name='Baby mat name') mat_run = make_instance(mat_spec) assert isinstance(mat_run, MaterialRun) assert isinstance(mat_run.process, ProcessRun) assert isinstance(mat_run.process.ingredients[0], IngredientRun) assert isinstance(mat_run.process.ingredients[0].material, MaterialRun) assert mat_run.process.spec == mat_run.spec.process ingredient = mat_run.process.ingredients[0] assert ingredient.spec == mat_run.spec.process.ingredients[0] assert ingredient.material.spec == mat_run.spec.process.ingredients[0].material
def make_strehlow_objects(table=None, template_scope=DEMO_TEMPLATE_SCOPE): """Make a table with Strehlow & Cook data.""" tmpl = make_templates(template_scope) if table is None: table = import_table() # Specs msr_spec = MeasurementSpec(name='Band gap', template=tmpl["Band gap measurement"] ) def real_mapper(prop): """Mapping methods for RealBounds.""" if 'uncertainty' in prop['scalars'][0]: if prop['units'] == 'eV': # Arbitrarily convert to attojoules mean = convert_units(value=float(prop['scalars'][0]['value']), starting_unit=prop['units'], final_unit='aJ' ) std = convert_units(value=float(prop['scalars'][0]['value']), starting_unit=prop['units'], final_unit='aJ' ) val = NormalReal(mean=mean, units='aJ', std=std ) else: 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 content_map = { RealBounds: real_mapper, CategoricalBounds: lambda prop: NominalCategorical(category=prop['scalars'][0]['value']), type(None): lambda bnd: 'Label' } datapoints = [] compounds = dict() for row in table: formula = formula_clean(row['chemicalFormula']) if formula not in compounds: compounds[formula] = MaterialSpec( name=formula_latex(formula), template=tmpl["Chemical"], process=ProcessSpec(name="Sample preparation", template=tmpl["Sample preparation"] )) spec = compounds[formula] run = make_instance(spec) datapoints.append(run) if not spec.properties: spec.properties.append( PropertyAndConditions( property=Property(name=spec.template.properties[0][0].name, value=EmpiricalFormula(formula=formula), template=spec.template.properties[0][0]) )) msr = make_instance(msr_spec) msr.material = run # 2 categories in the PIF need to be split to avoid repeat Attribute Templates in a Run name_map = { 'Phase': 'Crystal system', 'Transition': 'Bands' } origin_map = { 'EXPERIMENTAL': Origin.MEASURED, 'COMPUTATIONAL': Origin.COMPUTED } seen = set() # Some conditions come in from multiple properties on the same object for prop in row['properties']: origin = origin_map.get(prop.get('dataType', None), Origin.UNKNOWN) if 'method' in prop: method = 'Method: ' + prop['method']['name'] else: method = 'Method: unreported' for attr in [prop] + prop.get('conditions', []): if attr['name'] in seen: # Early return if it's a repeat continue seen.add(attr['name']) template = tmpl[attr['name']] # Figure out if we need to split this column if attr['name'] in name_map: value = attr['scalars'][0]['value'] if value not in template.bounds.categories: template = tmpl[name_map[attr['name']]] # Move into GEMD structure if type(template) == PropertyTemplate: msr.properties.append( Property(name=template.name, template=template, value=content_map[type(template.bounds)](attr), origin=origin, notes=method )) elif type(template) == ConditionTemplate: msr.conditions.append( Condition(name=template.name, template=template, value=content_map[type(template.bounds)](attr), origin=origin, notes=method )) return datapoints
def test_invalid_assignment(): """Invalid assignments to `spec` throw a TypeError.""" with pytest.raises(TypeError): ProcessRun("name", spec=[ProcessSpec("spec")])