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 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 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_invalid_instance(): """Calling make_instance on a non-spec should throw a TypeError.""" not_specs = [MeasurementRun("meas"), Condition("cond"), UniformReal(0, 1, ''), 'foo', 10] for not_spec in not_specs: with pytest.raises(TypeError): make_instance(not_spec)
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