def test_basic_symbols(self): a = pybamm.Scalar(1) unpacker = pybamm.SymbolUnpacker(pybamm.Scalar) unpacked = unpacker.unpack_symbol(a) self.assertEqual(unpacked, {a.id: a}) b = pybamm.Parameter("b") unpacker_param = pybamm.SymbolUnpacker(pybamm.Parameter) unpacked = unpacker_param.unpack_symbol(a) self.assertEqual(unpacked, {}) unpacked = unpacker_param.unpack_symbol(b) self.assertEqual(unpacked, {b.id: b})
def test_binary(self): a = pybamm.Scalar(1) b = pybamm.Parameter("b") unpacker = pybamm.SymbolUnpacker(pybamm.Scalar) unpacked = unpacker.unpack_symbol(a + b) # Can't check dictionary directly so check ids self.assertEqual(unpacked.keys(), {a.id: a}.keys()) self.assertEqual(unpacked[a.id].id, a.id) unpacker_param = pybamm.SymbolUnpacker(pybamm.Parameter) unpacked = unpacker_param.unpack_symbol(a + b) # Can't check dictionary directly so check ids self.assertEqual(unpacked.keys(), {b.id: b}.keys()) self.assertEqual(unpacked[b.id].id, b.id)
def check_and_convert_equations(self, equations): """ Convert any scalar equations in dict to 'pybamm.Scalar' and check that domains are consistent """ # Convert any numbers to a pybamm.Scalar for var, eqn in equations.items(): if isinstance(eqn, numbers.Number): equations[var] = pybamm.Scalar(eqn) if not all([ variable.domain == equation.domain or variable.domain == [] or equation.domain == [] for variable, equation in equations.items() ]): raise pybamm.DomainError( "variable and equation in '{}' must have the same domain". format(self.name)) # For initial conditions, check that the equation doesn't contain any # Variable objects # skip this if the dictionary has no "name" attribute (which will be the case # after pickling) if hasattr(self, "name") and self.name == "initial_conditions": for var, eqn in equations.items(): if eqn.has_symbol_of_classes(pybamm.Variable): unpacker = pybamm.SymbolUnpacker(pybamm.Variable) variable_in_equation = list( unpacker.unpack_symbol(eqn).values())[0] raise TypeError( "Initial conditions cannot contain 'Variable' objects, " "but '{!r}' found in initial conditions for '{}'". format(variable_in_equation, var)) return equations
def check_variables(self): # Create list of all Variable nodes that appear in the model's list of variables unpacker = pybamm.SymbolUnpacker(pybamm.Variable) all_vars = unpacker.unpack_list_of_symbols(self.variables.values()) var_ids_in_keys = set() model_and_external_variables = (list(self.rhs.keys()) + list(self.algebraic.keys()) + self.external_variables) for var in model_and_external_variables: if isinstance(var, pybamm.Variable): var_ids_in_keys.add(var.id) # Key can be a concatenation elif isinstance(var, pybamm.Concatenation): var_ids_in_keys.update([child.id for child in var.children]) for var_id, var in all_vars.items(): if var_id not in var_ids_in_keys: raise pybamm.ModelError(""" No key set for variable '{}'. Make sure it is included in either model.rhs, model.algebraic, or model.external_variables in an unmodified form (e.g. not Broadcasted) """.format(var))
def check_algebraic_equations(self, post_discretisation): """ Check that the algebraic equations are well-posed. Before discretisation, each algebraic equation key must appear in the equation After discretisation, there must be at least one StateVector in each algebraic equation """ vars_in_bcs = set() unpacker = pybamm.SymbolUnpacker(pybamm.Variable) for side_eqn in self.boundary_conditions.values(): all_vars = unpacker.unpack_list_of_symbols( [eqn for eqn, _ in side_eqn.values()]) vars_in_bcs.update(all_vars.keys()) if not post_discretisation: # After the model has been defined, each algebraic equation key should # appear in that algebraic equation, or in the boundary conditions # this has been relaxed for concatenations for now for var, eqn in self.algebraic.items(): if not (any(x.id == var.id for x in eqn.pre_order()) or var.id in vars_in_bcs or isinstance(var, pybamm.Concatenation)): raise pybamm.ModelError( "each variable in the algebraic eqn keys must appear in the eqn" ) else: # variables in keys don't get discretised so they will no longer match # with the state vectors in the algebraic equations. Instead, we check # that each algebraic equation contains some StateVector for eqn in self.algebraic.values(): if not eqn.has_symbol_of_classes(pybamm.StateVector): raise pybamm.ModelError( "each algebraic equation must contain at least one StateVector" )
def shape(self): """ Shape of an object, found by evaluating it with appropriate t and y. """ try: return self._saved_shape except AttributeError: # Default behaviour is to try to evaluate the object directly # Try with some large y, to avoid having to unpack (slow) try: y = np.nan * np.ones((1000, 1)) evaluated_self = self.evaluate(0, y, y, inputs="shape test") # If that fails, fall back to calculating how big y should really be except ValueError: unpacker = pybamm.SymbolUnpacker(pybamm.StateVector) state_vectors_in_node = unpacker.unpack_symbol(self).values() min_y_size = max( max( len(x._evaluation_array) for x in state_vectors_in_node), 1) # Pick a y that won't cause RuntimeWarnings y = np.nan * np.ones((min_y_size, 1)) evaluated_self = self.evaluate(0, y, y, inputs="shape test") # Return shape of evaluated object if isinstance(evaluated_self, numbers.Number): self._saved_shape = () else: self._saved_shape = evaluated_self.shape return self._saved_shape
def shape(self): """ Shape of an object, found by evaluating it with appropriate t and y. """ # Default behaviour is to try to evaluate the object directly # Try with some large y, to avoid having to unpack (slow) try: y = np.linspace(0.1, 0.9, int(1e4)) evaluated_self = self.evaluate(0, y, y, inputs="shape test") # If that fails, fall back to calculating how big y should really be except ValueError: unpacker = pybamm.SymbolUnpacker(pybamm.StateVector) state_vectors_in_node = unpacker.unpack_symbol(self).values() if state_vectors_in_node == []: y = None else: min_y_size = max( len(x._evaluation_array) for x in state_vectors_in_node) # Pick a y that won't cause RuntimeWarnings y = np.linspace(0.1, 0.9, min_y_size) evaluated_self = self.evaluate(0, y, y, inputs="shape test") # Return shape of evaluated object if isinstance(evaluated_self, numbers.Number): return () else: return evaluated_self.shape
def _find_input_parameters(self): "Find all the input parameters in the model" unpacker = pybamm.SymbolUnpacker(pybamm.InputParameter) all_input_parameters = unpacker.unpack_list_of_symbols( list(self.rhs.values()) + list(self.algebraic.values()) + list(self.initial_conditions.values()) + list(self.variables.values()) + [event.expression for event in self.events]) return list(all_input_parameters.values())
def test_unpack_list_of_symbols(self): a = pybamm.Scalar(1) b = pybamm.Parameter("b") c = pybamm.Parameter("c") unpacker = pybamm.SymbolUnpacker(pybamm.Parameter) unpacked = unpacker.unpack_list_of_symbols([a + b, a - c, b + c]) # Can't check dictionary directly so check ids self.assertEqual(unpacked.keys(), {b.id: b, c.id: c}.keys()) self.assertEqual(unpacked[b.id].id, b.id) self.assertEqual(unpacked[c.id].id, c.id)
def _find_symbols(self, typ): """Find all the instances of `typ` in the model""" unpacker = pybamm.SymbolUnpacker(typ) all_input_parameters = unpacker.unpack_list_of_symbols( list(self.rhs.values()) + list(self.algebraic.values()) + list(self.initial_conditions.values()) + [ x[side][0] for x in self.boundary_conditions.values() for side in x.keys() ] + list(self.variables.values()) + [event.expression for event in self.events] + [self.timescale] + list(self.length_scales.values())) return list(all_input_parameters.values())
def _find_parameters(self): "Find all the parameters in the model" unpacker = pybamm.SymbolUnpacker( (pybamm.Parameter, pybamm.InputParameter)) def NestedDictValues(d): "Get all the values from a nested dict" for v in d.values(): if isinstance(v, dict): yield from NestedDictValues(v) else: yield v all_parameters = unpacker.unpack_list_of_symbols( list(NestedDictValues(self))) return list(all_parameters.values())
def check_well_determined(self, post_discretisation): """ Check that the model is not under- or over-determined. """ # Equations (differential and algebraic) # Get all the variables from differential and algebraic equations vars_in_rhs_keys = set() vars_in_algebraic_keys = set() vars_in_eqns = set() # Get all variables ids from rhs and algebraic keys and equations, and # from boundary conditions # For equations we look through the whole expression tree. # "Variables" can be Concatenations so we also have to look in the whole # expression tree unpacker = pybamm.SymbolUnpacker((pybamm.Variable, pybamm.VariableDot)) for var, eqn in self.rhs.items(): # Find all variables and variabledot objects vars_in_rhs_keys_dict = unpacker.unpack_symbol(var) vars_in_eqns_dict = unpacker.unpack_symbol(eqn) # Store ids only # Look only for Variable (not VariableDot) in rhs keys vars_in_rhs_keys.update([ var_id for var_id, var in vars_in_rhs_keys_dict.items() if isinstance(var, pybamm.Variable) ]) vars_in_eqns.update(vars_in_eqns_dict.keys()) for var, eqn in self.algebraic.items(): # Find all variables and variabledot objects vars_in_algebraic_keys_dict = unpacker.unpack_symbol(var) vars_in_eqns_dict = unpacker.unpack_symbol(eqn) # Store ids only # Look only for Variable (not VariableDot) in algebraic keys vars_in_algebraic_keys.update([ var_id for var_id, var in vars_in_algebraic_keys_dict.items() if isinstance(var, pybamm.Variable) ]) vars_in_eqns.update(vars_in_eqns_dict.keys()) for var, side_eqn in self.boundary_conditions.items(): for side, (eqn, typ) in side_eqn.items(): vars_in_eqns_dict = unpacker.unpack_symbol(eqn) vars_in_eqns.update(vars_in_eqns_dict.keys()) # If any keys are repeated between rhs and algebraic then the model is # overdetermined if not set(vars_in_rhs_keys).isdisjoint(vars_in_algebraic_keys): raise pybamm.ModelError("model is overdetermined (repeated keys)") # If any algebraic keys don't appear in the eqns (or bcs) then the model is # overdetermined (but rhs keys can be absent from the eqns, e.g. dcdt = -1 is # fine) # Skip this step after discretisation, as any variables in the equations will # have been discretised to slices but keys will still be variables extra_algebraic_keys = vars_in_algebraic_keys.difference(vars_in_eqns) if extra_algebraic_keys and not post_discretisation: raise pybamm.ModelError( "model is overdetermined (extra algebraic keys)") # If any variables in the equations don't appear in the keys then the model is # underdetermined vars_in_keys = vars_in_rhs_keys.union(vars_in_algebraic_keys) extra_variables_in_equations = vars_in_eqns.difference(vars_in_keys) # get ids of external variables external_ids = {var.id for var in self.external_variables} for var in self.external_variables: if isinstance(var, pybamm.Concatenation): child_ids = {child.id for child in var.children} external_ids = external_ids.union(child_ids) extra_variables = extra_variables_in_equations.difference(external_ids) if extra_variables: raise pybamm.ModelError( "model is underdetermined (too many variables)")