def test_model_returns_nan(self): # This tests model failure with scalar nan. # Quantity class has other more thorough tests. A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1) B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=1) for sym in (B, A): Registry("symbols")[sym] = sym Registry("units")[sym] = sym.units get_config = { 'name': 'equality', # 'connections': [{'inputs': ['b'], 'outputs': ['a']}], 'equations': ['a = b'], # 'unit_map': {'a': "dimensionless", 'a': "dimensionless"} 'variable_symbol_map': { "a": A, "b": B } } model = EquationModel(**get_config) out = model.evaluate( {'b': QuantityFactory.create_quantity(B, float('nan'))}, allow_failure=True) self.assertFalse(out['successful']) self.assertEqual(out['message'], 'Evaluation returned invalid values (NaN)')
def test_clear_registries(self): Registry("to_clear")['entry'] = 'data' self.assertIn('to_clear', Registry.all_instances.keys()) self.assertIn('entry', Registry("to_clear").keys()) self.assertEqual(Registry("to_clear")['entry'], 'data') Registry.clear_all_registries() self.assertNotIn('to_clear', Registry.all_instances.keys())
def as_dict(self): """ Returns object instance as a dictionary. Object can be reconstituted from this dictionary using from_dict(). Returns: (dict) dictionary representation of the object """ symbol = self._symbol_type if symbol.name in Registry("symbols").keys() and symbol == Registry("symbols")[symbol.name] and \ symbol.is_builtin: symbol = self._symbol_type.name return { "@module": self.__class__.__module__, "@class": self.__class__.__name__, "internal_id": self._internal_id, "data_type": self._data_type, "symbol_type": symbol, "value": self._value, "units": self._units, "provenance": self._provenance, "tags": self._tags, "uncertainty": self._uncertainty }
def test_example_code_helper(self): example_model = Registry("models")['semi_empirical_mobility'] # TODO: this is ugly, any way to fix it? example_code = """ from propnet.models import semi_empirical_mobility K = 64 m_e = 0.009 semi_empirical_mobility.plug_in({ \t'K': K, \t'm_e': m_e, }) \"\"\" returns {'mu_e': 8994.92312225673} \"\"\" """ self.assertEqual(example_model.example_code, example_code) for model in Registry("models").values(): # A little weird, but otherwise you don't get the explicit error if model.is_builtin: try: exec(model.example_code) except Exception as e: raise e
def register(self, overwrite_registry=False): """ Registers the symbol with the symbol registry. Args: overwrite_registry (bool): If a symbol with the same name as the current is already registered, `True` will overwrite the old symbol with the current and `False` will raise a KeyError. Raises: KeyError: if `overwrite_registry=False` and a symbol with the same name is already registered, this error is raised. """ if not overwrite_registry and \ (self.name in Registry("symbols").keys() or self.name in Registry("units").keys()): raise KeyError( "Symbol '{}' already exists in the symbol or unit registry". format(self.name)) Registry("symbols")[self.name] = self Registry("units")[ self.name] = self.units.format_babel() if self.units else None if self.default_value is not None: Registry("symbol_values")[self.name] = self.default_value
def symbols_index(): symbol_links = {} for symbol_name in Registry("symbols"): # group by tag symbol_type = Registry("symbols")[symbol_name].category display_name = Registry("symbols")[symbol_name].display_names[0] if symbol_type not in symbol_links: symbol_links[symbol_type] = [] symbol_links[symbol_type].append( html.Div([ dcc.Link("{}".format(display_name), href='/property/{}'.format(symbol_name)), html.Br() ])) symbol_links_grouped = [] for symbol_type, links in symbol_links.items(): symbol_links_grouped += [ html.H6(symbol_type.title()), html.Div(links), html.Br() ] return html.Div([ html.H5('Currently supported symbols:'), html.Div(symbol_links_grouped), #html.Br(), #dcc.Link('< Back', href='/') ])
def test_basic_registry(self): test_reg = Registry("test") test_reg2 = Registry("test") test_reg3 = Registry("test2") self.assertIsInstance(test_reg, dict) self.assertTrue(test_reg is test_reg2) self.assertTrue(test_reg is not test_reg3)
def unregister(self): """ Removes the symbol from all applicable registries. """ Registry("symbols").pop(self.name, None) Registry("units").pop(self.name, None) Registry("symbol_values").pop(self.name, None)
def parse_path(pathname, search=None): """Utility function to parse URL path for routing purposes etc. This function exists because the path has to be parsed in a few places for callbacks. Args: pathname (str): path from url search (str): query string from url Returns: (dict) dictionary containing 'mode' ('property', 'model' etc.), 'value' (name of property etc.) """ if pathname == '/' or pathname is None: return None mode = None # 'property' or 'model' value = None # property name / model name # TODO: get rid of this if pathname == '/model': mode = 'model' elif pathname.startswith('/model'): mode = 'model' for model in Registry("models").keys(): if pathname.startswith('/model/{}'.format(model)): value = model elif pathname == '/property': mode = 'property' elif pathname.startswith('/property'): mode = 'property' for property_ in Registry("symbols").keys(): if pathname.startswith('/property/{}'.format(property_)): value = property_ elif pathname.startswith('/explore'): mode = 'explore' elif pathname.startswith('/plot'): mode = 'plot' if search: q_vals = parse_qs(urlsplit(search).query) value = {k: v[0] for k, v in q_vals.items() if k in ('x', 'y', 'z') and v is not None} elif pathname.startswith('/generate'): mode = 'generate' elif pathname.startswith('/correlate'): mode = 'correlate' elif pathname.startswith('/refs'): mode = 'refs' elif pathname.startswith('/home'): mode = 'home' return { 'mode': mode, 'value': value }
def test_instantiate_all_models(self): models_to_test = [] for model_name in Registry("models").keys(): try: model = Registry("models").get(model_name) self.assertIsNotNone(model) models_to_test.append(model_name) except Exception as e: self.fail('Failed to load model {}: {}'.format(model_name, e))
def tearDownClass(cls): non_builtin_syms = [ k for k, v in Registry("symbols").items() if not v.is_builtin ] for sym in non_builtin_syms: Registry("symbols").pop(sym) Registry("units").pop(sym) non_builtin_models = [ k for k, v in Registry("models").items() if not v.is_builtin ] for model in non_builtin_models: Registry("models").pop(model)
def test_get_symbols(self): self.material.add_quantity(self.q1) self.material.add_quantity(self.q2) self.material.add_quantity(self.q3) out = self.material.get_symbols() self.assertEqual(len(out), 2, "Material did not get Symbol Types correctly.") self.assertTrue( Registry("symbols")['bulk_modulus'] in out, "Material did not get Symbol Types correctly.") self.assertTrue( Registry("symbols")['shear_modulus'] in out, "Material did not get Symbol Types correctly.")
def test_material_remove_quantity(self): self.material.add_quantity(self.q1) self.material.add_quantity(self.q2) self.material.remove_quantity(self.q1) self.assertEqual( len(self.material._quantities_by_symbol[Registry("symbols") ['shear_modulus']]), 1, "Material did not remove the correct quantity.") self.material.remove_quantity(self.q2) self.assertEqual( len(self.material._quantities_by_symbol[Registry("symbols") ['shear_modulus']]), 0, "Material did not remove the quantity correctly.")
def setUpClass(cls): add_builtin_symbols_to_registry() # Create some test properties and a few base objects cls.q1 = QuantityFactory.create_quantity( Registry("symbols")['bulk_modulus'], ureg.Quantity.from_tuple([200, [['gigapascals', 1]]])) cls.q2 = QuantityFactory.create_quantity( Registry("symbols")['shear_modulus'], ureg.Quantity.from_tuple([100, [['gigapascals', 1]]])) cls.q3 = QuantityFactory.create_quantity( Registry("symbols")['bulk_modulus'], ureg.Quantity.from_tuple([300, [['gigapascals', 1]]])) cls.material = None cls.graph = Graph()
def test_model_register_unregister(self): A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1) B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=1) C = Symbol('c', ['C'], ['C'], units='dimensionless', shape=1) D = Symbol('d', ['D'], ['D'], units='dimensionless', shape=1) m = EquationModel('equation_model_to_remove', ['a = b * 3'], variable_symbol_map={ 'a': A, 'b': B }) self.assertIn(m.name, Registry("models")) self.assertTrue(m.registered) m.unregister() self.assertNotIn(m.name, Registry("models")) self.assertFalse(m.registered) m.register() self.assertTrue(m.registered) with self.assertRaises(KeyError): m.register(overwrite_registry=False) m.unregister() m = EquationModel('equation_model_to_remove', ['a = b * 3'], variable_symbol_map={ 'a': A, 'b': B }, register=False) self.assertNotIn(m.name, Registry("models")) self.assertFalse(m.registered) m.register() with self.assertRaises(KeyError): _ = EquationModel('equation_model_to_remove', ['a = b * 3'], variable_symbol_map={ 'a': A, 'b': B }, register=True, overwrite_registry=False) m_replacement = EquationModel('equation_model_to_remove', ['c = d * 3'], variable_symbol_map={ 'c': C, 'd': D }) m_registered = Registry("models")['equation_model_to_remove'] self.assertIs(m_registered, m_replacement) self.assertIsNot(m_registered, m)
def test_material_remove_symbol(self): self.material.add_quantity(self.q1) self.material.add_quantity(self.q2) self.material.add_quantity(self.q3) self.material.remove_symbol(Registry("symbols")['bulk_modulus']) self.assertTrue( Registry("symbols")['shear_modulus'] in self.material._quantities_by_symbol.keys(), "Material did not remove Symbol correctly.") self.assertTrue( self.q2 in self.material._quantities_by_symbol[Registry("symbols") ['shear_modulus']], "Material did not remove Symbol correctly.") self.assertEqual(len(self.material._quantities_by_symbol), 1, "Material did not remove Symbol correctly.")
def validate(args): """Validates test data""" if args.name is not None: model = Registry("models")[args.name] if not args.test_data: test_wrapper(model.validate_from_preset_test, pdb) print("Model validated with test data") return True elif args.file is not None: if args.file.endswith(".yaml"): EquationModel.from_file(args.file) elif args.file.endswith(".py"): # This should define config with open(args.file) as this_file: code = compile(this_file.read(), args.file, 'exec') exec(code, globals()) config = globals().get('config') model = PyModel(**config) if args.test_data is not None: td_data = loadfn(args.test_data) for td_datum in td_data: test_wrapper(model.test, args.pdb, **td_datum) print("{} validated with test data".format(model.name)) return True
def test_validate_all_models(self): for model in Registry("models").values(): if model._test_data is not None: self.assertTrue(model.validate_from_preset_test(), msg="{} model failed".format(model.name)) else: self.assertFalse(model.is_builtin, msg="{} is a built-in model and " "contains no test data".format(model.name))
def as_dict(self): """ Serializes object as a dictionary. Object can be reconstructed with from_dict(). Returns: (dict): representation of object as a dictionary """ symbol = self._symbol_type if symbol.name in Registry("symbols").keys() and symbol == Registry("symbols")[symbol.name] and \ symbol.is_builtin: symbol = self._symbol_type.name else: symbol = symbol.as_dict() return {"symbol_type": symbol, "provenance": self.provenance.as_dict() if self.provenance else None, "tags": self.tags, "internal_id": self._internal_id}
def get_symbol_from_string(name): """ Looks up Symbol from name in Registry("symbols") registry. Args: name: (str) the name of the Symbol object Returns: (Symbol) the Symbol object associated with the name """ # Invoke default symbol if symbol is a string if not isinstance(name, str): raise TypeError("Expected str, encountered {}".format(type(name))) if name not in Registry("symbols").keys(): raise ValueError("Symbol type {} not recognized".format(name)) return Registry("symbols")[name]
def registered(self): """ Indicates if a symbol is registered with the symbol registry. Returns: bool: True if the symbol is registered. False otherwise. """ return self.name in Registry("symbols").keys()
def test_unit_handling(self): """ Tests unit handling with a simple model that calculates the area of a rectangle as the product of two lengths. In this case the input lengths are provided in centimeters and meters. Tests whether the input units are properly coerced into canonical types. Tests whether the output units are properly set. Tests whether the model returns as predicted. Returns: None """ L = Symbol('l', ['L'], ['L'], units=[1.0, [['centimeter', 1.0]]], shape=[1]) A = Symbol('a', ['A'], ['A'], units=[1.0, [['centimeter', 2.0]]], shape=[1]) for sym in (L, A): Registry("symbols")[sym] = sym Registry("units")[sym] = sym.units get_area_config = { 'name': 'area', # 'connections': [{'inputs': ['l1', 'l2'], 'outputs': ['a']}], 'equations': ['a = l1 * l2'], # 'unit_map': {'l1': "cm", "l2": "cm", 'a': "cm^2"} 'variable_symbol_map': { "a": A, "l1": L, "l2": L } } model = EquationModel(**get_area_config) out = model.evaluate( { 'l1': QuantityFactory.create_quantity(L, 1, 'meter'), 'l2': QuantityFactory.create_quantity(L, 2) }, allow_failure=False) self.assertTrue(math.isclose(out['a'].magnitude, 200.0)) self.assertTrue(out['a'].units == A.units)
def process_item(self, item): quantities = [] material = item.copy() containers = [c + '.quantities' for c in self.props if pydash.get(material, c)] + ['inputs'] for container in containers: for q in pydash.get(material, container) or []: this_q = q.copy() this_q['material_key'] = material['task_id'] prov_inputs = pydash.get(this_q, 'provenance.inputs') if prov_inputs: new_prov_inputs = [qq['internal_id'] for qq in prov_inputs] else: new_prov_inputs = None pydash.set_(this_q, 'provenance.inputs', new_prov_inputs) quantities.append(this_q) pydash.set_(material, container, [q['internal_id'] for q in pydash.get(material, container) or []]) if container != 'inputs': prop = container.split(".")[0] units = Registry("units").get(prop) if units != pydash.get(material, [prop, 'units']): pq_mean = ureg.Quantity(material[prop]['mean'], material[prop]['units']).to(units) pq_std = ureg.Quantity(material[prop]['std'], material[prop]['units']).to(units) material[prop]['mean'] = pq_mean.magnitude material[prop]['std'] = pq_std.magnitude material[prop]['units'] = pq_mean.units.format_babel() for q in quantities: units = Registry("units").get(q['symbol_type']) if q['units'] != units: pq = ureg.Quantity(q['value'], q['units']).to(units) q['value'] = pq.magnitude q['units'] = pq.units.format_babel() return quantities, material
def test_model_formatting(self): # TODO: clean up tests (self.assertNotNone), test reference format too for model in Registry("models").values(): if not model.is_builtin: continue self.assertIsNotNone(model.name) msg = "{} has invalid property".format(model.name) self.assertIsNotNone(model.categories, msg=msg) self.assertIsNotNone(model.description, msg=msg) self.assertIsNotNone(model.variable_symbol_map, msg=msg) self.assertIsNotNone(model.implemented_by, msg=msg) self.assertNotEqual(model.implemented_by, [], msg=msg) self.assertTrue(isinstance(model.variable_symbol_map, dict), msg=msg) self.assertTrue(len(model.variable_symbol_map.keys()) > 0, msg=msg) for key in model.variable_symbol_map.keys(): self.assertTrue(isinstance(key, str), 'Invalid variable_symbol_map key: ' + str(key)) self.assertTrue( isinstance(model.variable_symbol_map[key], str) and model.variable_symbol_map[key] in Registry("symbols").keys(), msg=msg) self.assertTrue( model.connections is not None and isinstance(model.connections, list) and len(model.connections) > 0, msg=msg) for reference in model.references: self.assertTrue(reference.startswith('@'), msg=msg) for item in model.connections: self.assertIsNotNone(item, msg=msg) self.assertTrue(isinstance(item, dict), msg=msg) self.assertTrue('inputs' in item.keys(), msg=msg) self.assertTrue('outputs' in item.keys(), msg=msg) self.assertIsNotNone(item['inputs'], msg=msg) self.assertIsNotNone(item['outputs'], msg=msg) self.assertTrue(isinstance(item['inputs'], list), msg=msg) self.assertTrue(isinstance(item['outputs'], list), msg=msg) self.assertTrue(len(item['inputs']) > 0, msg=msg) self.assertTrue(len(item['outputs']) > 0, msg=msg) for in_symb in item['inputs']: self.assertIsNotNone(in_symb, msg=msg) self.assertTrue(isinstance(in_symb, str), msg=msg) self.assertTrue(in_symb in model.variable_symbol_map.keys(), msg=msg) for out_symb in item['outputs']: self.assertIsNotNone(out_symb, msg=msg) self.assertIsNotNone(isinstance(out_symb, str), msg=msg) self.assertTrue(out_symb in model.variable_symbol_map.keys(), msg=msg)
def add_builtin_symbols_to_registry(): for f in _DEFAULT_SYMBOL_TYPE_FILES: d = loadfn(f) d['is_builtin'] = True d['overwrite_registry'] = True symbol_type = Symbol.from_dict(d) if "{}.yaml".format(symbol_type.name) not in f: raise ValueError('Name/filename mismatch in {}'.format(f)) # This is just to enable importing this module for name, symbol in Registry("symbols").items(): if symbol.is_builtin: globals()[name] = symbol
def as_dict(self): """ Gives the dictionary representation of this object, excluding value, units, and uncertainty. Returns: (dict) dictionary representation of this object for serialization """ symbol = self._symbol_type if symbol.name in Registry("symbols").keys() and symbol == Registry("symbols")[symbol.name] and \ symbol.is_builtin: symbol = self._symbol_type.name return { "@module": self.__class__.__module__, "@class": self.__class__.__name__, "data_type": self._data_type, "symbol_type": symbol, "internal_id": self._internal_id, "tags": self._tags, "provenance": self._provenance }
def fit_model_scores(materials, benchmarks, models=None, init_scores=None, constrain_sum=False): """ Fits a set of model scores to a set of benchmark data Args: materials ([Material]): list of evaluated materials containing symbols for benchmarking benchmarks ([{Symbol or str: float}]): list of dicts, keyed by Symbol or symbol name containing benchmark data for each material in ``materials``. models ([Model or str]): list of models which should have their scores adjusted in the aggregation weighting scheme init_scores ({str: float}): initial scores for minimization procedure. If unspecified, all scores are equal. Scores are normalized to sum of scores. constrain_sum (bool): True constrains the sum of weights to 1, False removes this constraint. Default: False (no constraint) Returns: {str: float} scores corresponding to those which minimize SSE for the benchmarked dataset """ # Probably not smart to have ALL available models in the list. That's a lot of DOF. # TODO: Perhaps write a method to produce a list of models in the provenance trees # of the symbols to be benchmarked. Should be easy with the caching we have for provenance. model_list = models or list(Registry("models").keys()) def f(f_scores): model_score_dict = {m: s for m, s in zip(model_list, f_scores)} return get_sse(materials, benchmarks, model_score_dict) scores = OrderedDict((m, 1) for m in model_list) scores.update(init_scores or {}) x0 = np.array(list(scores.values())) x0 = x0 / np.sum(x0) bounds = Bounds([0] * len(x0), [1] * len(x0)) if constrain_sum: constraint = [LinearConstraint([1] * len(x0), [1], [1])] else: constraint = [] result = minimize(f, x0=x0, method='trust-constr', bounds=bounds, constraints=constraint) vec = [s for s in result.x] return OrderedDict(zip(model_list, vec))
def add_default_quantities(self): """ Adds any default symbols which are not present in the graph Returns: None """ new_syms = set(Registry("symbol_values").keys()) new_syms -= set(self._quantities_by_symbol.keys()) for sym in new_syms: quantity = QuantityFactory.from_default(sym) logger.warning("Adding default {} quantity with value {}".format( sym, quantity)) self.add_quantity(quantity)
def test_symbol_register_unregister(self): A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1) self.assertIn(A.name, Registry("symbols")) self.assertTrue(A.registered) A.unregister() self.assertNotIn(A.name, Registry("symbols")) self.assertFalse(A.registered) A.register() self.assertTrue(A.registered) with self.assertRaises(KeyError): A.register(overwrite_registry=False) A.unregister() A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1, register=False) self.assertNotIn(A.name, Registry("symbols")) self.assertFalse(A.registered) A.register() with self.assertRaises(KeyError): _ = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1, register=True, overwrite_registry=False) A_replacement = Symbol('a', ['A^*'], ['A^*'], units='kilogram', shape=1) A_registered = Registry("symbols")['a'] self.assertIs(A_registered, A_replacement) self.assertIsNot(A_registered, A)
def from_default(symbol): """ Method to invoke a default quantity from a symbol name Args: symbol (Symbol or str): symbol or string corresponding to the symbol name Returns: BaseQuantity corresponding to default quantity from default """ val = Registry("symbol_values").get(symbol) if val is None: raise ValueError("No default value for {}".format(symbol)) prov = ProvenanceElement(model='default') return QuantityFactory.create_quantity(symbol, val, provenance=prov)