def test_complex_and_imaginary_checking(self): A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1) B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=[2, 2]) # TODO: Revisit this when splitting quantity class into non-numerical and numerical C = Symbol('c', ['C'], ['C'], category='object', shape=1) real_float_scalar = Quantity(A, 1.0) real_float_non_scalar = Quantity(B, [[1.0, 1.0], [1.0, 1.0]]) complex_scalar = Quantity(A, complex(1 + 1j)) complex_non_scalar = Quantity( B, [[complex(1.0), complex(1.j)], [complex(1.j), complex(1.0)]]) complex_scalar_zero_imaginary = Quantity(A, complex(1.0)) complex_non_scalar_zero_imaginary = Quantity( B, [[complex(1.0), complex(1.0)], [complex(1.0), complex(1.0)]]) complex_scalar_appx_zero_imaginary = Quantity(A, complex(1.0 + 1e-10j)) complex_non_scalar_appx_zero_imaginary = Quantity( B, [[complex(1.0), complex(1.0 + 1e-10j)], [complex(1.0 + 1e-10j), complex(1.0)]]) non_numerical = Quantity(C, 'test') # Test is_complex_type() with... # ...Quantity objects self.assertFalse(Quantity.is_complex_type(real_float_scalar)) self.assertFalse(Quantity.is_complex_type(real_float_non_scalar)) self.assertTrue(Quantity.is_complex_type(complex_scalar)) self.assertTrue(Quantity.is_complex_type(complex_non_scalar)) self.assertTrue( Quantity.is_complex_type(complex_scalar_zero_imaginary)) self.assertTrue( Quantity.is_complex_type(complex_non_scalar_zero_imaginary)) self.assertTrue( Quantity.is_complex_type(complex_scalar_appx_zero_imaginary)) self.assertTrue( Quantity.is_complex_type(complex_non_scalar_appx_zero_imaginary)) self.assertFalse(Quantity.is_complex_type(non_numerical)) # ...primitive types self.assertFalse(Quantity.is_complex_type(1)) self.assertFalse(Quantity.is_complex_type(1.)) self.assertTrue(Quantity.is_complex_type(1j)) self.assertFalse(Quantity.is_complex_type('test')) # ...np.array types self.assertFalse(Quantity.is_complex_type(np.array([1]))) self.assertFalse(Quantity.is_complex_type(np.array([1.]))) self.assertTrue(Quantity.is_complex_type(np.array([1j]))) self.assertFalse(Quantity.is_complex_type(np.array(['test']))) # ...ureg Quantity objects self.assertFalse(Quantity.is_complex_type(ureg.Quantity(1))) self.assertFalse(Quantity.is_complex_type(ureg.Quantity(1.))) self.assertTrue(Quantity.is_complex_type(ureg.Quantity(1j))) self.assertFalse(Quantity.is_complex_type(ureg.Quantity([1]))) self.assertFalse(Quantity.is_complex_type(ureg.Quantity([1.]))) self.assertTrue(Quantity.is_complex_type(ureg.Quantity([1j]))) # Check member functions self.assertFalse(real_float_scalar.contains_complex_type()) self.assertFalse(real_float_scalar.contains_imaginary_value()) self.assertFalse(real_float_non_scalar.contains_complex_type()) self.assertFalse(real_float_non_scalar.contains_imaginary_value()) self.assertTrue(complex_scalar.contains_complex_type()) self.assertTrue(complex_scalar.contains_imaginary_value()) self.assertTrue(complex_non_scalar.contains_complex_type()) self.assertTrue(complex_non_scalar.contains_imaginary_value()) self.assertTrue(complex_scalar_zero_imaginary.contains_complex_type()) self.assertFalse( complex_scalar_zero_imaginary.contains_imaginary_value()) self.assertTrue( complex_non_scalar_zero_imaginary.contains_complex_type()) self.assertFalse( complex_non_scalar_zero_imaginary.contains_imaginary_value()) self.assertTrue( complex_scalar_appx_zero_imaginary.contains_complex_type()) self.assertFalse( complex_scalar_appx_zero_imaginary.contains_imaginary_value()) self.assertTrue( complex_non_scalar_appx_zero_imaginary.contains_complex_type()) self.assertFalse( complex_non_scalar_appx_zero_imaginary.contains_imaginary_value()) self.assertFalse(non_numerical.contains_complex_type()) self.assertFalse(non_numerical.contains_imaginary_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) for (k, v) in symbol_quantity_dict.items(): replacing = self.symbol_property_map.get(k, k) symbol_quantity_dict[k] = Quantity.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(Quantity.is_complex_type(v) for v in symbol_value_dict.values()) # Plug in and check constraints try: with PrintToLogger(): 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)"} # 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