def test_nan_checking(self): A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1) B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=[2, 2]) C = Symbol('c', ['C'], ['C'], units='dimensionless', shape=1) D = Symbol('d', ['D'], ['D'], units='dimensionless', shape=[2, 2]) E = Symbol('e', ['E'], ['E'], category='object', shape=1) scalar_quantity = Quantity(A, float('nan')) non_scalar_quantity = Quantity( B, [[1.0, float('nan')], [float('nan'), 1.0]]) complex_scalar_quantity = Quantity(C, complex('nan+nanj')) complex_non_scalar_quantity = Quantity( D, [[complex(1.0), complex('nanj')], [complex('nan'), complex(1.0)]]) self.assertTrue(scalar_quantity.contains_nan_value()) self.assertTrue(non_scalar_quantity.contains_nan_value()) self.assertTrue(complex_scalar_quantity.contains_nan_value()) self.assertTrue(complex_non_scalar_quantity.contains_nan_value()) scalar_quantity = Quantity(A, 1.0) non_scalar_quantity = Quantity(B, [[1.0, 2.0], [2.0, 1.0]]) complex_scalar_quantity = Quantity(C, complex('1+1j')) complex_non_scalar_quantity = Quantity( D, [[complex(1.0), complex('5j')], [complex('5'), complex(1.0)]]) self.assertFalse(scalar_quantity.contains_nan_value()) self.assertFalse(non_scalar_quantity.contains_nan_value()) self.assertFalse(complex_scalar_quantity.contains_nan_value()) self.assertFalse(complex_non_scalar_quantity.contains_nan_value()) non_numerical = Quantity(E, 'test') self.assertFalse(non_numerical.contains_nan_value())
def evaluate(self, symbol_quantity_dict, 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) # 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 # Plug in and check constraints try: out = self.plug_in(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( symbol_quantity_dict.values())) out = self.map_symbols_to_properties(out) for symbol, value in out.items(): try: quantity = Quantity(symbol, value, self.unit_map.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)" } out[symbol] = quantity out['successful'] = True return out