def get_aggregated_quantities(self): """ Return mean values for all quantities for each symbol. Returns: (dict<Symbol, weighted_mean) mapping from a Symbol to an aggregated statistic. """ # TODO: proper weighting system, and more flexibility in object handling aggregated = {} for symbol, quantities in self._quantities_by_symbol.items(): if not symbol.category == 'object': aggregated[symbol] = NumQuantity.from_weighted_mean( list(quantities)) return aggregated
def rec_provenance_tree_check(self, q_storage, q_original, from_dict=False): self.assertIsInstance(q_storage, ProvenanceStore) self.assertEqual(q_storage.model, q_original.model) for v in q_storage.inputs or []: self.assertIsInstance(v, ProvenanceStoreQuantity) v_orig = [x for x in q_original.inputs if x._internal_id == v._internal_id] self.assertEqual(len(v_orig), 1) v_orig = v_orig[0] if from_dict: self.assertFalse(v.has_value()) elif NumQuantity.is_acceptable_type(v.value): self.assertTrue(np.isclose(v.value, v_orig.value)) self.assertTrue(v.has_value()) else: self.assertEqual(v.value, v_orig.value) self.assertTrue(v.has_value()) self.assertListEqual(v.tags, v_orig.tags) self.rec_provenance_tree_check(v.provenance, v_orig.provenance, from_dict)
def setUp(self): # Inspiration was taken from the GraphTest class # I tried to construct the dictionaries for comparison # without writing out every one explicity by reusing # information where it was applicable. # If this is too unreadable, can change to writing it # out explicity in a JSON file and importing it. Would # still need to replace some fields dynamically. symbols = StorageTest.generate_symbols() self.custom_syms_as_dicts = { k: {'@module': 'propnet.core.symbols', '@class': 'Symbol', 'name': k, 'display_names': [k], 'display_symbols': [k], 'units': (1, ()), 'shape': 1, 'object_type': None, 'comment': None, 'category': 'property', 'constraint': None, 'default_value': None} for k in ['A', 'B', 'C'] } self.custom_syms_as_dicts['C'].update( {"units": None, "shape": None, "object_type": "str", "category": "object"}) self.custom_symbols_json = copy.deepcopy(self.custom_syms_as_dicts) for k in ['A', 'B']: self.custom_symbols_json[k]['units'] = [1, []] a = [QuantityFactory.create_quantity(symbols['A'], 19), QuantityFactory.create_quantity(symbols['A'], 23)] b = [QuantityFactory.create_quantity(symbols['B'], 38, provenance=ProvenanceElement(model='model1', inputs=[a[0]])), QuantityFactory.create_quantity(symbols['B'], 46, provenance=ProvenanceElement(model='model1', inputs=[a[1]]))] self.quantities_custom_symbol = {"A": a, "B": b} self.sq_custom_sym_as_dicts = { k: [{'@module': 'propnet.dbtools.storage', '@class': 'StorageQuantity', 'internal_id': vv._internal_id, 'data_type': 'NumQuantity', 'symbol_type': symbols[k], 'value': vv.magnitude, 'units': 'dimensionless', 'provenance': ProvenanceStore.from_provenance_element(vv.provenance), 'tags': [], 'uncertainty': None} for vv in v] for k, v in self.quantities_custom_symbol.items() } provenances_json = { "A": [{'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': None, 'inputs': None, 'source': aa.provenance.source} for aa in a]} provenances_json['B'] = [ {'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': 'model1', 'inputs': [{'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStoreQuantity', 'data_type': 'NumQuantity', 'symbol_type': self.custom_symbols_json['A'], 'internal_id': q.provenance.inputs[0]._internal_id, 'tags': [], 'provenance': p}], 'source': q.provenance.source} for q, p in zip(b, provenances_json['A'])] self.sq_custom_sym_json = copy.deepcopy(self.sq_custom_sym_as_dicts) for sym in ['A', 'B']: for q, p in zip(self.sq_custom_sym_json[sym], provenances_json[sym]): q['symbol_type'] = self.custom_symbols_json[sym] q['provenance'] = p band_gaps = [QuantityFactory.create_quantity('band_gap', 3.3, 'eV'), QuantityFactory.create_quantity('band_gap', 2.1, 'eV')] bg_ri_model = DEFAULT_MODEL_DICT['band_gap_refractive_index_moss'] refractive_indices = [bg_ri_model.evaluate({"Eg": bg}).pop('refractive_index') for bg in band_gaps] self.quantities_canonical_symbol = {"band_gaps": band_gaps, "refractive_indices": refractive_indices} self.sq_canonical_sym_as_dicts_no_value = copy.deepcopy(self.sq_custom_sym_as_dicts) self.sq_canonical_sym_as_dicts_no_value['band_gaps'] = self.sq_canonical_sym_as_dicts_no_value.pop('A') self.sq_canonical_sym_as_dicts_no_value['refractive_indices'] = self.sq_canonical_sym_as_dicts_no_value.pop('B') for d, sq in zip(self.sq_canonical_sym_as_dicts_no_value['band_gaps'], band_gaps): d.update({ "internal_id": sq._internal_id, "symbol_type": "band_gap", "units": "electron_volt", "provenance": ProvenanceStore.from_provenance_element(sq.provenance) }) d.pop('value') for d, sq in zip(self.sq_canonical_sym_as_dicts_no_value['refractive_indices'], refractive_indices): d.update({ "internal_id": sq._internal_id, "symbol_type": "refractive_index", "units": "dimensionless", "provenance": ProvenanceStore.from_provenance_element(sq.provenance) }) d.pop('value') self.sq_canonical_sym_values = {"band_gaps": [3.3, 2.1], "refractive_indices": [2.316340583741216, 2.593439239956374]} provenances_json['band_gaps'] = [ {'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': None, 'inputs': None, 'source': bg.provenance.source} for bg in band_gaps ] provenances_json['refractive_indices'] = [{ '@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': 'band_gap_refractive_index_moss', 'inputs': [{'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStoreQuantity', 'data_type': 'NumQuantity', 'symbol_type': 'band_gap', 'internal_id': bg._internal_id, 'tags': [], 'provenance': pj}], 'source': ri.provenance.source} for bg, pj, ri in zip(band_gaps, provenances_json['band_gaps'], refractive_indices) ] self.sq_canonical_sym_json_no_value = copy.deepcopy(self.sq_canonical_sym_as_dicts_no_value) for sym in ["band_gaps", "refractive_indices"]: for q, p in zip(self.sq_canonical_sym_json_no_value[sym], provenances_json[sym]): q['provenance'] = p self.quantity_with_uncertainty = NumQuantity.from_weighted_mean(b) self.sq_with_uncertainty_as_dict_no_numbers = { '@module': 'propnet.dbtools.storage', '@class': 'StorageQuantity', 'internal_id': self.quantity_with_uncertainty._internal_id, 'data_type': 'NumQuantity', 'symbol_type': symbols['B'], 'units': 'dimensionless', 'provenance': ProvenanceStore.from_provenance_element( self.quantity_with_uncertainty.provenance), 'tags': []} provenances_json = { '@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': 'aggregation', 'inputs': [ {'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStoreQuantity', 'data_type': 'NumQuantity', 'symbol_type': self.custom_symbols_json['B'], 'internal_id': b['internal_id'], 'tags': [], 'provenance': b['provenance']} for b in self.sq_custom_sym_json['B']], 'source': self.quantity_with_uncertainty.provenance.source } self.sq_with_uncertainty_json_no_numbers = copy.deepcopy(self.sq_with_uncertainty_as_dict_no_numbers) self.sq_with_uncertainty_json_no_numbers.update({"symbol_type": self.custom_symbols_json['B'], "provenance": provenances_json}) self.sq_with_uncertainty_numbers = {"value": 42.0, "uncertainty": 4.0} obj_symbol = symbols['C'] self.object_quantity = QuantityFactory.create_quantity(obj_symbol, "Test string") self.sq_object_as_dict = copy.deepcopy(self.sq_custom_sym_as_dicts['A'][0]) self.sq_object_as_dict.update({ "data_type": "ObjQuantity", "symbol_type": symbols['C'], "internal_id": self.object_quantity._internal_id, "value": "Test string", "units": None, "provenance": ProvenanceStore.from_provenance_element(self.object_quantity.provenance) }) self.sq_object_json = copy.deepcopy(self.sq_object_as_dict) self.sq_object_json.update( {"symbol_type": self.custom_syms_as_dicts['C'], "provenance": {'@module': 'propnet.dbtools.storage', '@class': 'ProvenanceStore', 'model': None, 'inputs': None, 'source': self.object_quantity.provenance.source}} ) # This setting allows dict differences to be shown in full self.maxDiff = None
def evaluate(self, symbol_quantity_dict_in, allow_failure=True): """ Given a set of property_values, performs error checking to see if the corresponding input symbol_values represents a valid input set based on the self.connections() method. If so, returns a dictionary representing the value of plug_in applied to the input_symbols. The dictionary contains a "successful" key representing if plug_in was successful. The key distinction between evaluate and plug_in is properties in properties out vs. symbols in symbols out. In addition, evaluate also handles any requisite unit_mapping Args: symbol_quantity_dict ({property_name: Quantity}): a mapping of symbol names to quantities to be substituted allow_failure (bool): whether or not to catch errors in model evaluation Returns: dictionary of output properties with associated values generated from the input, along with "successful" if the substitution succeeds """ # Remap symbols and units if symbol map isn't none symbol_quantity_dict = self.map_properties_to_symbols( symbol_quantity_dict_in) input_symbol_quantity_dict = {k: v for k, v in symbol_quantity_dict.items() if not (k in self.constraint_symbols and k not in self.all_input_symbols)} for (k, v) in symbol_quantity_dict.items(): # replacing = self.symbol_property_map.get(k, k) replacing = self.symbol_property_map.get(k) # to_quantity() returns original object if it's already a BaseQuantity # unlike Quantity() which will return a deep copy symbol_quantity_dict[k] = QuantityFactory.to_quantity(replacing, v) # TODO: Is it really necessary to strip these? # TODO: maybe this only applies to pymodels or things with objects? # strip units from input and keep for reassignment symbol_value_dict = {} for symbol, quantity in symbol_quantity_dict.items(): # If unit map convert and then scrub units if self.unit_map.get(symbol): quantity = quantity.to(self.unit_map[symbol]) symbol_value_dict[symbol] = quantity.magnitude # Otherwise use values else: symbol_value_dict[symbol] = quantity.value contains_complex_input = any(NumQuantity.is_complex_type(v) for v in symbol_value_dict.values()) input_symbol_value_dict = {k: symbol_value_dict[k] for k in input_symbol_quantity_dict.keys()} # Plug in and check constraints try: with PrintToLogger(): out = self.plug_in(input_symbol_value_dict) except Exception as err: if allow_failure: return {"successful": False, "message": "{} evaluation failed: {}".format(self, err)} else: raise err if not self.check_constraints({**symbol_value_dict, **out}): return {"successful": False, "message": "Constraints not satisfied"} provenance = ProvenanceElement( model=self.name, inputs=list(input_symbol_quantity_dict.values()), source="propnet") out = self.map_symbols_to_properties(out) unit_map_as_properties = self.map_symbols_to_properties(self.unit_map) for symbol, value in out.items(): try: quantity = QuantityFactory.create_quantity( symbol, value, unit_map_as_properties.get(symbol), provenance=provenance) except SymbolConstraintError as err: if allow_failure: errmsg = "{} symbol constraint failed: {}".format(self, err) return {"successful": False, "message": errmsg} else: raise err if quantity.contains_nan_value(): return {"successful": False, "message": "Evaluation returned invalid values (NaN)"} # TODO: Update when we figure out how we're going to handle complex quantities # Model evaluation will fail if complex values are returned when no complex input was given # Can surely handle this more gracefully, or assume that the users will apply constraints if quantity.contains_imaginary_value() and not contains_complex_input: return {"successful": False, "message": "Evaluation returned invalid values (complex)"} out[symbol] = quantity out['successful'] = True return out