def test_invalid_constructor(): """Test types for constructor.""" with pytest.raises(ValueError): CategoricalBounds(categories="foo") with pytest.raises(ValueError): CategoricalBounds(categories={1, 2})
def test_no_data_objects(): objs = [ PropertyTemplate("pt", bounds=CategoricalBounds()), ConditionTemplate("ct", bounds=CategoricalBounds()) ] templates, data_objects = split_templates_from_objects(objs) assert len(templates) == 2 assert len(data_objects) == 0
def test_categories(): """Test categories setter.""" assert CategoricalBounds().categories == set() assert CategoricalBounds(categories={"foo", "bar"}).categories == { "foo", "bar" } assert CategoricalBounds(categories=["foo", "bar"]).categories == { "foo", "bar" }
def test_contains(): """Test basic contains logic.""" bounds = CategoricalBounds(categories={"spam", "eggs"}) assert bounds.contains(CategoricalBounds(categories={"spam"})) assert not bounds.contains(CategoricalBounds(categories={"spam", "foo"})) assert not bounds.contains(RealBounds(0.0, 2.0, '')) assert not bounds.contains(None) with pytest.raises(TypeError): bounds.contains({"spam", "eggs"})
def test_both_present(): objs = [ ProcessSpec("ps"), PropertyTemplate("pt", bounds=CategoricalBounds()), MeasurementSpec("ms"), ConditionTemplate("ct", bounds=CategoricalBounds()) ] templates, data_objects = split_templates_from_objects(objs) assert len(templates) == 2 assert len(data_objects) == 2
def test_numpy(): """Test that ndarrays, Series work as well.""" assert len(array_like()) < 5 # In case we extend at some point if len(array_like()) > 2: # Test numpy import numpy as np np_bounds = CategoricalBounds(np.array(["spam", "eggs"], dtype=object)) np_copy = loads(dumps(np_bounds)) assert np_copy == np_bounds if len(array_like()) > 3: # Test numpy import pandas as pd pd_bounds = CategoricalBounds(pd.Series(["spam", "eggs"])) pd_copy = loads(dumps(pd_bounds)) assert pd_copy == pd_bounds
def test_object_template_serde(): """Test serde of an object template.""" length_template = PropertyTemplate("Length", bounds=RealBounds(2.0, 3.5, 'cm')) sub_bounds = RealBounds(2.5, 3.0, 'cm') color_template = PropertyTemplate("Color", bounds=CategoricalBounds(["red", "green", "blue"])) # Properties are a mixture of property templates and [template, bounds], pairs block_template = MaterialTemplate("Block", properties=[[length_template, sub_bounds], color_template]) copy_template = MaterialTemplate.build(block_template.dump()) assert copy_template == block_template # Tests below exercise similar code, but for measurement and process templates pressure_template = ConditionTemplate("pressure", bounds=RealBounds(0.1, 0.11, 'MPa')) index_template = ParameterTemplate("index", bounds=IntegerBounds(2, 10)) meas_template = MeasurementTemplate("A measurement of length", properties=[length_template], conditions=[pressure_template], description="Description", parameters=[index_template], tags=["foo"]) assert MeasurementTemplate.build(meas_template.dump()) == meas_template proc_template = ProcessTemplate("Make an object", parameters=[index_template], conditions=[pressure_template], allowed_labels=["Label"], allowed_names=["first sample", "second sample"]) assert ProcessTemplate.build(proc_template.dump()) == proc_template # Check that serde still works if the template is a LinkByUID pressure_template.uids['id'] = '12345' # uids['id'] not populated by default proc_template.conditions[0][0] = LinkByUID('id', pressure_template.uids['id']) assert ProcessTemplate.build(proc_template.dump()) == proc_template
def test_object_template_validation(): """Test that attribute templates are validated against given bounds.""" length_template = PropertyTemplate("Length", bounds=RealBounds(2.0, 3.5, 'cm')) dial_template = ConditionTemplate("dial", bounds=IntegerBounds(0, 5)) color_template = ParameterTemplate("Color", bounds=CategoricalBounds(["red", "green", "blue"])) with pytest.raises(TypeError): MaterialTemplate() with pytest.raises(ValueError): MaterialTemplate("Block", properties=[[length_template, RealBounds(3.0, 4.0, 'cm')]]) with pytest.raises(ValueError): ProcessTemplate("a process", conditions=[[color_template, CategoricalBounds(["zz"])]]) with pytest.raises(ValueError): MeasurementTemplate("A measurement", parameters=[[dial_template, IntegerBounds(-3, -1)]])
def test_flatten_bounds(): """Test that flatten works when the objects contain other objects.""" bounds = CategoricalBounds(categories=["foo", "bar"]) template = ProcessTemplate( "spam", conditions=[(ConditionTemplate(name="eggs", bounds=bounds), bounds)] ) spec = ProcessSpec(name="spec", template=template) flat = flatten(spec, 'test-scope') assert len(flat) == 2, "Expected 2 flattened objects"
def test_repeated_objects(): """Test that objects aren't double counted.""" ct = ConditionTemplate(name="color", bounds=CategoricalBounds(categories=["black", "white"])) pt = ProcessTemplate(name="painting", conditions=[ct]) ps = ProcessSpec(name='painting', template=pt, conditions=Condition(name='Paint color', value=NominalCategorical("black"), template=ct ) ) assert len(recursive_flatmap(ps, lambda x: [x])) == 3
def test_flatten(): """Test that gemd utility methods can be applied to citrine-python objects. Citrine-python resources extend the gemd data model, so the gemd operations should work on them. """ bounds = CategoricalBounds(categories=["foo", "bar"]) template = ProcessTemplate( "spam", conditions=[(ConditionTemplate(name="eggs", bounds=bounds), bounds)] ) spec = ProcessSpec(name="spec", template=template) flat = flatten(spec, scope='testing') assert len(flat) == 3, "Expected 3 flattened objects"
def test_fields_from_property(): """Test that several fields of the attribute are derived from the property.""" prop_template = PropertyTemplate(name="cookie eating template", bounds=IntegerBounds(0, 1000)) cond_template = ConditionTemplate(name="Hunger template", bounds=CategoricalBounds(["hungry", "full", "peckish"])) prop = Property(name="number of cookies eaten", template=prop_template, origin='measured', value=NominalInteger(27)) cond = Condition(name="hunger level", template=cond_template, origin='specified', value=NominalCategorical("hungry")) prop_and_conds = PropertyAndConditions(property=prop, conditions=[cond]) assert prop_and_conds.name == prop.name assert prop_and_conds.template == prop.template assert prop_and_conds.origin == prop.origin assert prop_and_conds.value == prop.value
def make_templates(template_scope=DEMO_TEMPLATE_SCOPE): """Build all templates needed for the table.""" tmpl = dict() # Attribute Templates attribute_feed = { "Formula": [PropertyTemplate, CompositionBounds(components=EmpiricalFormula.all_elements())], "Crystallinity": [ConditionTemplate, CategoricalBounds( ['Amorphous', 'Polycrystalline', 'Single crystalline'] )], "Color": [PropertyTemplate, CategoricalBounds( ['Amber', 'Black', 'Blue', 'Bluish', 'Bronze', 'Brown', 'Brown-Black', 'Copper-Red', 'Dark Brown', 'Dark Gray', 'Dark Green', 'Dark Red', 'Gray', 'Light Gray', 'Ocher', 'Orange', 'Orange-Red', 'Pale Yellow', 'Red', 'Red-Yellow', 'Violet', 'White', 'Yellow', 'Yellow-Orange', 'Yellow-White'] )], "Band gap": [PropertyTemplate, RealBounds(lower_bound=0.001, upper_bound=100, default_units='eV')], "Temperature": [ConditionTemplate, RealBounds(lower_bound=1, upper_bound=1000, default_units='K')], "Temperature derivative of band gap": [PropertyTemplate, RealBounds(lower_bound=-0.01, upper_bound=0.01, default_units='eV/K')], "Lasing": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Cathodoluminescence": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Mechanical luminescence": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Photoluminescence": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Electroluminescence": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Thermoluminescence": [PropertyTemplate, CategoricalBounds(['True', 'False'])], "Morphology": [ConditionTemplate, CategoricalBounds(['Thin film', 'Bulk'])], "Electric field polarization": [ConditionTemplate, CategoricalBounds(['Parallel to A axis', 'Parallel to B axis', 'Parallel to C axis', 'Perpendicular to B axis', 'Perpendicular to C axis'])], "Phase": [ConditionTemplate, CategoricalBounds(['A', 'B', 'B1', 'B2', 'Fused quartz', 'Natural diamond', 'Rutile', 'Sapphire', 'Synthetic quartz'])], "Crystal system": [ConditionTemplate, CategoricalBounds(['Cubic', 'Hexagonal', 'Orthorhombic', 'Tetragonal', 'Trigonal'])], "Transition": [ConditionTemplate, CategoricalBounds(['Direct', 'Excitonic', 'Indirect'])], "Bands": [ConditionTemplate, CategoricalBounds(['G1 to X1', 'G15 to G1', 'G15 to X1', 'G25 to G1', 'G25 to G12', 'G25 to G15', 'G6 to G8', 'G8 to G6+', 'L6+ to L6-'])] } for (name, (typ, bounds)) in attribute_feed.items(): assert name not in tmpl tmpl[name] = typ(name=name, bounds=bounds, uids={template_scope: name}, tags=['citrine::demo::template::attribute'] ) # Object Templates object_feed = { "Sample preparation": [ ProcessTemplate, dict() ], "Chemical": [ MaterialTemplate, {"properties": [tmpl["Formula"]]} ], "Band gap measurement": [ MeasurementTemplate, {"properties": [tmpl["Band gap"], tmpl["Temperature derivative of band gap"], tmpl["Color"], tmpl["Lasing"], tmpl["Cathodoluminescence"], tmpl["Mechanical luminescence"], tmpl["Photoluminescence"], tmpl["Electroluminescence"], tmpl["Thermoluminescence"] ], "conditions": [tmpl["Temperature"], tmpl["Crystallinity"], tmpl["Morphology"], tmpl["Electric field polarization"], tmpl["Phase"], tmpl["Crystal system"], tmpl["Transition"], tmpl["Bands"] ] } ], } for (name, (typ, kw_args)) in object_feed.items(): assert name not in tmpl tmpl[name] = typ(name=name, uids={template_scope: name}, tags=['citrine::demo::template::object'], **kw_args) return tmpl
def test_property_template(): """Test creation and serde of condition templates.""" bounds = CategoricalBounds(['solid', 'liquid', 'gas']) template = PropertyTemplate("State", bounds=bounds, uids={'my_id': '0'}) assert PropertyTemplate.build(template.dump()) == template
from gemd.entity.bounds.real_bounds import RealBounds from gemd.entity.value.uniform_real import UniformReal from gemd.entity.template.attribute_template import AttributeTemplate from gemd.entity.template.property_template import PropertyTemplate from gemd.entity.template.condition_template import ConditionTemplate from gemd.entity.template.parameter_template import ParameterTemplate from gemd.json import dumps, loads class SampleAttributeTemplate(AttributeTemplate): """A class to flex the base attribute template.""" typ = "sample_attribute_template" cat_bounds = CategoricalBounds(categories={"a", "b", "c"}) def test_name_is_a_string(): """Test that name is a string.""" with pytest.raises(ValueError) as error: SampleAttributeTemplate(name=42, bounds=cat_bounds) assert "must be a string" in str(error.value) def test_invalid_bounds(): """Test that invalid bounds throw the appropriate error.""" with pytest.raises(ValueError): SampleAttributeTemplate(name="name") # Must have a bounds with pytest.raises(TypeError):
name="kinematic viscosity", bounds=RealBounds(lower_bound=0.0, upper_bound=10.0**40, default_units="m^2 / s") ) } known_conditions = { "temperature": ConditionTemplate( name="temperature", bounds=RealBounds(lower_bound=0.0, upper_bound=1000.0, default_units='K') ) } known_parameters = { "knob_2_setting": ParameterTemplate( name="knob_2_setting", bounds=CategoricalBounds(categories={"low", "medium", "high"}) ) } 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:
def test_contains(): """Test basic contains logic.""" bounds = CategoricalBounds(categories={"spam", "eggs"}) assert bounds.contains(CategoricalBounds(categories={"spam"})) assert not bounds.contains(CategoricalBounds(categories={"spam", "foo"})) assert not bounds.contains(RealBounds(0.0, 2.0, '')) assert not bounds.contains(None) with pytest.raises(TypeError): bounds.contains({"spam", "eggs"}) from gemd.entity.value import NominalCategorical assert bounds.contains(NominalCategorical("spam")) assert not bounds.contains(NominalCategorical("foo"))
def test_json(): """Test that serialization works (categories is encoded as a list).""" bounds = CategoricalBounds(categories={"spam", "eggs"}) copy = loads(dumps(bounds)) assert copy == bounds