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]) get_area_config = { 'name': 'area', # 'connections': [{'inputs': ['l1', 'l2'], 'outputs': ['a']}], 'equations': ['a = l1 * l2'], # 'unit_map': {'l1': "cm", "l2": "cm", 'a': "cm^2"} 'symbol_property_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 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 validate(args): """Validates test data""" if args.name is not None: model = DEFAULT_MODEL_DICT[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_model_returns_complex(self): # This tests model failure with scalar complex. # 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) get_config = { 'name': 'add_complex_value', # 'connections': [{'inputs': ['b'], 'outputs': ['a']}], 'equations': ['a = b + 1j'], # 'unit_map': {'a': "dimensionless", 'a': "dimensionless"} 'symbol_property_map': { "a": A, "b": B } } model = EquationModel(**get_config) out = model.evaluate({'b': Quantity(B, 5)}, allow_failure=True) self.assertFalse(out['successful']) self.assertEqual(out['message'], 'Evaluation returned invalid values (complex)') out = model.evaluate({'b': Quantity(B, 5j)}, allow_failure=True) self.assertTrue(out['successful']) self.assertTrue(np.isclose(out['a'].magnitude, 6j))
def test_symbol_expansion_cyclic_constraints(self): """ Tests the Symbol Expansion algorithm on a cyclic graph with constraints. The canonical graph and the canonical material are used for this test. """ model4 = EquationModel("model4", ['D=B*C*11'], constraints=["G==0"]) symbols = GraphTest.generate_canonical_symbols() models = GraphTest.generate_canonical_models(constrain_model_4=True) models['model4'] = model4 material = GraphTest.generate_canonical_material(symbols) g = Graph(symbol_types=symbols, models=models, composite_models=dict()) ts = [] ans = [] ts.append(g.calculable_properties({symbols['A']})) ans.append({x for x in symbols.values() if x is not symbols['A']}) ts.append(g.calculable_properties({symbols['B']})) ans.append({symbols['F']}) ts.append(g.calculable_properties({symbols['C']})) ans.append(set()) ts.append(g.calculable_properties({symbols['C'], symbols['G']})) ans.append({symbols['D']}) ts.append(g.calculable_properties({symbols['B'], symbols['C']})) ans.append({symbols['F']}) for i in range(0, len(ts)): self.assertEqual(ts[i], ans[i], "Symbol Expansion failed: test - " + str(i))
def generate_canonical_models(constrain_model_4=False): """ Returns a set of Model objects used in testing. Returns: (dict<str, Model>) """ sym_map = GraphTest.generate_canonical_symbols() # TODO: Resolve the connections issue here model1 = EquationModel(name="model1", equations=['B=2*A', 'C=3*A'], connections=[{"inputs": ["A"], "outputs": ['B', 'C']}], symbol_property_map=sym_map) model2 = EquationModel(name="model2", equations=['G=5*A'], symbol_property_map=sym_map) model3 = EquationModel(name="model3", equations=['F=7*B'], symbol_property_map=sym_map) model5 = EquationModel(name="model5", equations=['D=C*G*13'], symbol_property_map=sym_map) model6 = EquationModel(name="model6", equations=['A=F*D*17'], symbol_property_map=sym_map) if constrain_model_4: model4 = EquationModel(name="model4", equations=['D=B*C*11'], constraints=["G==0"], symbol_property_map=sym_map) else: model4 = EquationModel(name="model4", equations=['D=B*C*11'], symbol_property_map=sym_map) models = [model1, model2, model3, model4, model5, model6] return {x.name : x for x in models}
def add_builtin_models_to_registry(register_symbols=True): _EQUATION_MODEL_NAMES_LIST.clear() # Load equation models equation_model_dir = os.path.join(os.path.dirname(__file__)) equation_module_files = glob(equation_model_dir + '/*.yaml') if register_symbols: from propnet.symbols import add_builtin_symbols_to_registry add_builtin_symbols_to_registry() for filename in equation_module_files: model_path = os.path.join(equation_model_dir, filename) model = EquationModel.from_file(model_path, is_builtin=True, overwrite_registry=True) globals()[model.name] = model _EQUATION_MODEL_NAMES_LIST.append(model.name)
def test_evaluate_constraints(self): """ Tests the evaluation algorithm on a non-cyclic graph involving constraints. The canonical graph and the canonical material are used for this test. """ model4 = EquationModel(name="model4", equations=["D=B*C*11"], constraints=["G==0"]) symbols = GraphTest.generate_canonical_symbols() models = GraphTest.generate_canonical_models() models['model4'] = model4 del models['model6'] material = GraphTest.generate_canonical_material(symbols) g = Graph(symbol_types=symbols, models=models, composite_models=dict()) material_derived = g.evaluate(material) expected_quantities = [ Quantity(symbols['A'], 19), Quantity(symbols['A'], 23), Quantity(symbols['B'], 38), Quantity(symbols['B'], 46), Quantity(symbols['C'], 57), Quantity(symbols['C'], 69), Quantity(symbols['G'], 95), Quantity(symbols['G'], 115), Quantity(symbols['F'], 266), Quantity(symbols['F'], 322), Quantity(symbols['D'], 70395), Quantity(symbols['D'], 85215), Quantity(symbols['D'], 103155) ] self.assertTrue( material == GraphTest.generate_canonical_material(symbols), "evaluate() mutated the original material argument.") derived_quantities = material_derived.get_quantities() self.assertTrue( len(expected_quantities) == len(derived_quantities), "Evaluate did not correctly derive outputs.") for q in expected_quantities: self.assertTrue( q in material_derived._symbol_to_quantity[q.symbol], "Evaluate failed to derive all outputs.") self.assertTrue(q in derived_quantities)
def test_get_path_constraint(self): """ Tests the ability to generate all paths from one symbol to another with constraints. """ model4 = EquationModel("model4", ['D=B*C*11'], constraints=["G==0"]) symbols = GraphTest.generate_canonical_symbols() models = GraphTest.generate_canonical_models(constrain_model_4=True) models['model4'] = model4 del models['model6'] g = Graph(symbol_types=symbols, models=models, composite_models=dict()) paths_1 = g.get_paths(symbols['A'], symbols['F']) paths_2 = g.get_paths(symbols['A'], symbols['D']) ans_1 = [ SymbolPath({symbols['A']}, [models['model1'], models['model3']]) ] ans_2 = [ SymbolPath({symbols['A'], symbols['C']}, [models['model2'], models['model5']]), SymbolPath({symbols['A'], symbols['G']}, [models['model1'], models['model5']]), SymbolPath({symbols['A'], symbols['C'], symbols['B']}, [models['model2'], models['model4']]), SymbolPath({symbols['A'], symbols['G']}, [models['model1'], models['model4']]), SymbolPath({symbols['A']}, [models['model1'], models['model2'], models['model5']]), SymbolPath({symbols['A']}, [models['model2'], models['model1'], models['model5']]), SymbolPath({symbols['A']}, [models['model1'], models['model2'], models['model4']]), SymbolPath({symbols['A']}, [models['model2'], models['model1'], models['model4']]) ] self.assertTrue( len(paths_1) == len(ans_1), "Incorrect paths generated.") self.assertTrue( len(paths_2) == len(ans_2), "Incorrect paths generated.") for i in paths_1: self.assertTrue(i in ans_1, "Incorrect paths generated.") for i in paths_2: self.assertTrue(i in ans_2, "Incorrect paths generated.")
def test_provenance(self): model4 = EquationModel(name="model4", equations=["D=B*C*11"], constraints=["G==0"]) symbols = GraphTest.generate_canonical_symbols() models = GraphTest.generate_canonical_models() models['model4'] = model4 del models['model6'] material = GraphTest.generate_canonical_material(symbols) g = Graph(symbol_types=symbols, models=models, composite_models=dict()) material_derived = g.evaluate(material) expected_quantities = [ Quantity(symbols['A'], 19), Quantity(symbols['A'], 23), Quantity(symbols['B'], 38), Quantity(symbols['B'], 46), Quantity(symbols['C'], 57), Quantity(symbols['C'], 69), Quantity(symbols['G'], 95), Quantity(symbols['G'], 115), Quantity(symbols['F'], 266), Quantity(symbols['F'], 322), Quantity(symbols['D'], 70395), Quantity(symbols['D'], 85215), Quantity(symbols['D'], 103155) ] for q in material_derived._symbol_to_quantity[symbols['A']]: self.assertTrue(q._provenance is None) for q in material_derived._symbol_to_quantity[symbols['B']]: if q.value == 38: self.assertTrue(q._provenance.model is models['model1'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[0] in q._provenance.inputs, "provenance improperly calculated") else: self.assertTrue(q._provenance.model is models['model1'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[1] in q._provenance.inputs, "provenance improperly calculated") for q in material_derived._symbol_to_quantity[symbols['C']]: if q.value == 57: self.assertTrue(q._provenance.model is models['model1'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[0] in q._provenance.inputs, "provenance improperly calculated") else: self.assertTrue(q._provenance.model is models['model1'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[1] in q._provenance.inputs, "provenance improperly calculated") for q in material_derived._symbol_to_quantity[symbols['G']]: if q.value == 95: self.assertTrue(q._provenance.model is models['model2'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[0] in q._provenance.inputs, "provenance improperly calculated") else: self.assertTrue(q._provenance.model is models['model2'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[1] in q._provenance.inputs, "provenance improperly calculated") for q in material_derived._symbol_to_quantity[symbols['D']]: if q.value == 70395: self.assertTrue(q._provenance.model is models['model5'].name, "provenance improperly calculated") self.assertTrue(expected_quantities[4] in q._provenance.inputs, "provenance improperly calculated") self.assertTrue(expected_quantities[6] in q._provenance.inputs, "provenance improperly calculated")
def test_symbol_ancestry_cyclic_constraint(self): """ Tests the Symbol Ancestry algorithm on a cyclic graph with constraints. The canonical graph and the canonical material are used for this test. """ model4 = EquationModel("model4", ['D=B*C*11'], constraints=["G==0"]) symbols = GraphTest.generate_canonical_symbols() models = GraphTest.generate_canonical_models(constrain_model_4=True) models['model4'] = model4 g = Graph(symbol_types=symbols, models=models, composite_models=dict()) out1 = g.required_inputs_for_property(symbols['F']) self.assertTrue(out1.head.m is None and out1.head.parent is None and out1.head.inputs == {symbols['F']} and len(out1.head.children) == 1, "Tree head not properly defined.") self.assertTrue(out1.head.children[0].m == models['model3'] and out1.head.children[0].inputs == {symbols['B']} and out1.head.children[0].parent is out1.head and len(out1.head.children[0].children) == 1, "Tree branch improperly formed.") self.assertTrue(out1.head.children[0].children[0].m == models['model1'] and out1.head.children[0].children[0].inputs == {symbols['A']} and out1.head.children[0].children[0].parent is out1.head.children[0] and len(out1.head.children[0].children) == 1 and len(out1.head.children[0].children[0].children) == 0, "Tree branch improperly formed.") out2 = g.required_inputs_for_property(symbols['D']) self.assertTrue(out2.head.m is None and out2.head.parent is None and out2.head.inputs == {symbols['D']} and len(out2.head.children) == 2, "Tree head not properly defined.") m_map = {x.m: x for x in out2.head.children} self.assertTrue(m_map[models['model4']].inputs == {symbols['B'], symbols['C'], symbols['G']} and m_map[models['model4']].parent is out2.head, "Tree branch improperly formed.") self.assertTrue(m_map[models['model5']].inputs == {symbols['C'], symbols['G']} and m_map[models['model5']].parent is out2.head and len(m_map[models['model5']].children) == 2, "Tree branch improperly formed.") m_map_2 = {x.m: x for x in m_map[models['model5']].children} self.assertTrue(m_map_2[models['model1']].inputs == {symbols['G'], symbols['A']} and m_map_2[models['model1']].parent is m_map[models['model5']] and len(m_map_2[models['model1']].children) == 1 and m_map_2[models['model1']].children[0].parent is m_map_2[models['model1']] and m_map_2[models['model1']].children[0].children == [] and m_map_2[models['model1']].children[0].inputs == {symbols['A']}, "Tree branch improperly formed.") self.assertTrue(m_map_2[models['model2']].inputs == {symbols['C'], symbols['A']} and m_map_2[models['model2']].parent is m_map[models['model5']] and len(m_map_2[models['model2']].children) == 1 and m_map_2[models['model2']].children[0].parent is m_map_2[models['model2']] and m_map_2[models['model2']].children[0].children == [] and m_map_2[models['model2']].children[0].inputs == {symbols['A']}, "Tree branch improperly formed.") m_map_1 = {x.m: x for x in m_map[models['model4']].children} self.assertTrue(m_map_1[models['model1']].inputs == {symbols['G'], symbols['A']} and m_map_1[models['model1']].parent is m_map[models['model4']] and len(m_map_1[models['model1']].children) == 1 and m_map_1[models['model1']].children[0].parent is m_map_1[models['model1']] and m_map_1[models['model1']].children[0].children == [] and m_map_1[models['model1']].children[0].inputs == {symbols['A']}, "Tree branch improperly formed.") self.assertTrue(m_map_1[models['model2']].inputs == {symbols['B'], symbols['C'], symbols['A']} and m_map_1[models['model2']].parent is m_map[models['model4']] and len(m_map_1[models['model2']].children) == 1 and m_map_1[models['model2']].children[0].parent is m_map_1[models['model2']] and m_map_1[models['model2']].children[0].children == [] and m_map_1[models['model2']].children[0].inputs == {symbols['A']}, "Tree branch improperly formed.")
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)
from pkgutil import iter_modules import os from propnet.core.models import EquationModel, PyModuleModel,\ PyModuleCompositeModel from propnet.models import python, composite from glob import glob DEFAULT_MODELS = [] DEFAULT_COMPOSITE_MODELS = [] # Load equation models EQUATION_MODEL_DIR = os.path.join(os.path.dirname(__file__), "serialized") EQUATION_MODULE_FILES = glob(EQUATION_MODEL_DIR + '/*.yaml') for filename in EQUATION_MODULE_FILES: model_path = os.path.join(EQUATION_MODEL_DIR, filename) model = EquationModel.from_file(model_path) DEFAULT_MODELS.append(model) # Load python models MODULE_LIST = iter_modules(python.__path__) for _, module_name, _ in MODULE_LIST: module_path = "propnet.models.python.{}".format(module_name) DEFAULT_MODELS.append(PyModuleModel(module_path)) DEFAULT_MODEL_DICT = {d.name: d for d in DEFAULT_MODELS} DEFAULT_MODEL_NAMES = list(DEFAULT_MODEL_DICT.keys()) # Load composite models COMPOSITE_MODULE_LIST = iter_modules(composite.__path__) for _, module_name, _ in COMPOSITE_MODULE_LIST: module_path = "propnet.models.composite.{}".format(module_name)