From 2a8cc85b9dd47287c0db195eaae4a73351eea8a8 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 11 Mar 2020 16:42:36 +0000 Subject: [PATCH 01/18] added some helper functions to find information on function parameters --- examples/scripts/quick_test.py | 5 + pybamm/expression_tree/parameter.py | 33 +- pybamm/models/base_model.py | 60 +++ .../function_control_external_circuit.py | 8 +- .../fickian/fickian_many_particles.py | 12 +- pybamm/parameters/electrical_parameters.py | 2 +- .../standard_parameters_lead_acid.py | 42 ++- .../standard_parameters_lithium_ion.py | 110 +++++- testing.ipynb | 347 ++++++++++++++++++ 9 files changed, 587 insertions(+), 32 deletions(-) create mode 100644 examples/scripts/quick_test.py create mode 100644 testing.ipynb diff --git a/examples/scripts/quick_test.py b/examples/scripts/quick_test.py new file mode 100644 index 0000000000..4c84d04284 --- /dev/null +++ b/examples/scripts/quick_test.py @@ -0,0 +1,5 @@ +import pybamm + +model = pybamm.lithium_ion.SPMe() +model.info("Negative electrode diffusivity [m2.s-1]") +model.info("Electrolyte conductivity [S.m-1]") diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index 9c2049ac8c..e9ebd17416 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -54,10 +54,17 @@ class FunctionParameter(pybamm.Symbol): if diff_variable is specified, the FunctionParameter node will be replaced by a :class:`pybamm.Function` and then differentiated with respect to diff_variable. Default is None. - + description: str + A description of the function. + inputs: list + A list of strings describing the inputs. + outputs: list + A list of string describing the outputs. """ - def __init__(self, name, *children, diff_variable=None): + def __init__( + self, name, *children, diff_variable=None, inputs=None, + ): # assign diff variable self.diff_variable = diff_variable children_list = list(children) @@ -76,6 +83,28 @@ def __init__(self, name, *children, diff_variable=None): auxiliary_domains=auxiliary_domains, ) + self.inputs = inputs + + @property + def inputs(self): + if self._inputs: + for inp in self._inputs: + print(inp) + + @inputs.setter + def inputs(self, inp=None): + if inp: + if inp.__class__ is list: + for i in inp: + if i.__class__ is not str: + raise TypeError( + "Inputs must be a provided as a list of strings" + ) + else: + raise TypeError("Inputs must be a provided as a list of strings") + + self._inputs = inp + def set_id(self): """See :meth:`pybamm.Symbol.set_id` """ self._id = hash( diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index f8ae8c5c17..72d7f4d7bf 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -572,6 +572,31 @@ def check_variables(self): ) ) + def info(self, symbol_name): + """ + Provides helpful summary information for a symbol. + + Parameters + ---------- + parameter_name : str + """ + + div = "-----------------------------------------" + symbol = find_symbol_in_model(self, symbol_name) + + if not symbol: + return None + + print(div) + print(symbol_name, "\n") + print(type(symbol), "\n") + + if isinstance(symbol, pybamm.FunctionParameter): + print("Inputs:") + symbol.inputs + + print(div) + @property def default_solver(self): "Return default solver based on whether model is ODE model or DAE model" @@ -582,3 +607,38 @@ def default_solver(self): return pybamm.IDAKLUSolver() else: return pybamm.CasadiSolver(mode="safe") + + +# helper functions for finding symbols +def find_symbol_in_tree(tree, name): + if name == tree.name: + return tree + elif len(tree.children) > 0: + for child in tree.children: + child_return = find_symbol_in_tree(child, name) + if child_return: + return child_return + else: + return None + + +def find_symbol_in_dict(dic, name): + for tree in dic.values(): + tree_return = find_symbol_in_tree(tree, name) + if tree_return: + return tree_return + return None + + +def find_symbol_in_model(model, name): + dics = [ + model.rhs, + model.algebraic, + model.variables, + ] + for dic in dics: + dic_return = find_symbol_in_dict(dic, name) + if dic_return: + return dic_return + print(name + " not found") + return None diff --git a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py index 6e3812f99a..525f7fc0d0 100644 --- a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py @@ -50,7 +50,9 @@ def __init__(self, param): def constant_voltage(variables): V = variables["Terminal voltage [V]"] - return V - pybamm.FunctionParameter("Voltage function [V]", pybamm.t) + return V - pybamm.FunctionParameter( + "Voltage function [V]", pybamm.t, inputs=["Time [s]"] + ) class PowerFunctionControl(FunctionControl): @@ -63,7 +65,9 @@ def __init__(self, param): def constant_power(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] - return I * V - pybamm.FunctionParameter("Power function [W]", pybamm.t) + return I * V - pybamm.FunctionParameter( + "Power function [W]", pybamm.t, inputs=["Time [s]"] + ) class LeadingOrderFunctionControl(FunctionControl, LeadingOrderBaseModel): diff --git a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py b/pybamm/models/submodels/particle/fickian/fickian_many_particles.py index 87677fde30..cd81d88642 100644 --- a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py +++ b/pybamm/models/submodels/particle/fickian/fickian_many_particles.py @@ -52,12 +52,20 @@ def get_coupled_variables(self, variables): if self.domain == "Negative": x = pybamm.standard_spatial_vars.x_n - R = pybamm.FunctionParameter("Negative particle distribution in x", x) + R = pybamm.FunctionParameter( + "Negative particle distribution in x", + x, + inputs=["Dimensionless through-cell position (x_n)"], + ) variables.update({"Negative particle distribution in x": R}) elif self.domain == "Positive": x = pybamm.standard_spatial_vars.x_p - R = pybamm.FunctionParameter("Positive particle distribution in x", x) + R = pybamm.FunctionParameter( + "Positive particle distribution in x", + x, + inputs=["Dimensionless through-cell position (x_p)"], + ) variables.update({"Positive particle distribution in x": R}) return variables diff --git a/pybamm/parameters/electrical_parameters.py b/pybamm/parameters/electrical_parameters.py index c83405ca5a..e2f0de71d9 100644 --- a/pybamm/parameters/electrical_parameters.py +++ b/pybamm/parameters/electrical_parameters.py @@ -26,7 +26,7 @@ # the user may provide the typical timescale as a parameter. timescale = pybamm.Parameter("Typical timescale [s]") dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale + "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc diff --git a/pybamm/parameters/standard_parameters_lead_acid.py b/pybamm/parameters/standard_parameters_lead_acid.py index e48faefcb1..16f8532ea0 100644 --- a/pybamm/parameters/standard_parameters_lead_acid.py +++ b/pybamm/parameters/standard_parameters_lead_acid.py @@ -174,16 +174,28 @@ def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" - return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", c_e) + return pybamm.FunctionParameter( + "Electrolyte diffusivity [m2.s-1]", + c_e, + inputs=["Electrolyte concentration [mol.m-3"], + ) def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" - return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", c_e) + return pybamm.FunctionParameter( + "Electrolyte conductivity [S.m-1]", + c_e, + inputs=["Electrolyte concentration [mol.m-3]"], + ) def chi_dimensional(c_e): - return pybamm.FunctionParameter("Darken thermodynamic factor", c_e) + return pybamm.FunctionParameter( + "Darken thermodynamic factor", + c_e, + inputs=["Electrolyte concentration [mol.m-3]"], + ) def c_w_dimensional(c_e, c_ox=0, c_hy=0): @@ -224,20 +236,28 @@ def mu_dimensional(c_e): """ Dimensional viscosity of electrolyte [kg.m-1.s-1]. """ - return pybamm.FunctionParameter("Electrolyte viscosity [kg.m-1.s-1]", c_e) + return pybamm.FunctionParameter( + "Electrolyte viscosity [kg.m-1.s-1]", + c_e, + inputs=["Electrolyte concentration [mol.m-3]"], + ) def U_n_dimensional(c_e, T): "Dimensional open-circuit voltage in the negative electrode [V]" return pybamm.FunctionParameter( - "Negative electrode open-circuit potential [V]", m_dimensional(c_e) + "Negative electrode open-circuit potential [V]", + m_dimensional(c_e), + inputs=["Electrolyte concentration [mol.m-3]"], ) def U_p_dimensional(c_e, T): "Dimensional open-circuit voltage in the positive electrode [V]" return pybamm.FunctionParameter( - "Positive electrode open-circuit potential [V]", m_dimensional(c_e) + "Positive electrode open-circuit potential [V]", + m_dimensional(c_e), + inputs=["Electrolyte concentration [mol.m-3]"], ) @@ -245,10 +265,14 @@ def U_p_dimensional(c_e, T): rho_typ = rho_dimensional(c_e_typ) mu_typ = mu_dimensional(c_e_typ) U_n_ref = pybamm.FunctionParameter( - "Negative electrode open-circuit potential [V]", pybamm.Scalar(1) + "Negative electrode open-circuit potential [V]", + pybamm.Scalar(1), + inputs=["Electrolyte concentration [mol.m-3]"], ) U_p_ref = pybamm.FunctionParameter( - "Positive electrode open-circuit potential [V]", pybamm.Scalar(1) + "Positive electrode open-circuit potential [V]", + pybamm.Scalar(1), + inputs=["Electrolyte concentration [mol.m-3]"], ) @@ -491,7 +515,7 @@ def U_p(c_e_p, T): # 6. Input current and voltage dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale + "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc diff --git a/pybamm/parameters/standard_parameters_lithium_ion.py b/pybamm/parameters/standard_parameters_lithium_ion.py index a841dfe713..9e8f1ed266 100644 --- a/pybamm/parameters/standard_parameters_lithium_ion.py +++ b/pybamm/parameters/standard_parameters_lithium_ion.py @@ -109,14 +109,18 @@ def c_n_init_dimensional(x): "Initial concentration as a function of dimensionless position x" return pybamm.FunctionParameter( - "Initial concentration in negative electrode [mol.m-3]", x + "Initial concentration in negative electrode [mol.m-3]", + x, + inputs=["Dimensionless through-cell position (x_n)"], ) def c_p_init_dimensional(x): "Initial concentration as a function of dimensionless position x" return pybamm.FunctionParameter( - "Initial concentration in positive electrode [mol.m-3]", x + "Initial concentration in positive electrode [mol.m-3]", + x, + inputs=["Dimensionless through-cell position (x_p)"], ) @@ -140,45 +144,99 @@ def c_p_init_dimensional(x): def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" + inputs = [ + "Electrolyte concentration [mol.m-3]", + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] return pybamm.FunctionParameter( - "Electrolyte diffusivity [m2.s-1]", c_e, T, T_ref, E_D_e, R + "Electrolyte diffusivity [m2.s-1]", c_e, T, T_ref, E_D_e, R, inputs=inputs ) def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" + inputs = [ + "Electrolyte concentration [mol.m-3]", + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] return pybamm.FunctionParameter( - "Electrolyte conductivity [S.m-1]", c_e, T, T_ref, E_k_e, R + "Electrolyte conductivity [S.m-1]", c_e, T, T_ref, E_k_e, R, inputs=inputs ) def D_n_dimensional(sto, T): """Dimensional diffusivity in negative particle. Note this is defined as a function of stochiometry""" + + inputs = [ + "Negative particle stoichiometry", + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] + return pybamm.FunctionParameter( - "Negative electrode diffusivity [m2.s-1]", sto, T, T_ref, E_D_s_n, R + "Negative electrode diffusivity [m2.s-1]", + sto, + T, + T_ref, + E_D_s_n, + R, + inputs=inputs, ) def D_p_dimensional(sto, T): """Dimensional diffusivity in positive particle. Note this is defined as a function of stochiometry""" + inputs = [ + "Positive particle stoichiometry", + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] return pybamm.FunctionParameter( - "Positive electrode diffusivity [m2.s-1]", sto, T, T_ref, E_D_s_p, R + "Positive electrode diffusivity [m2.s-1]", + sto, + T, + T_ref, + E_D_s_p, + R, + inputs=inputs, ) def m_n_dimensional(T): "Dimensional negative reaction rate" + inputs = [ + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] return pybamm.FunctionParameter( - "Negative electrode reaction rate", T, T_ref, E_r_n, R + "Negative electrode reaction rate", T, T_ref, E_r_n, R, inputs=inputs ) def m_p_dimensional(T): "Dimensional negative reaction rate" + inputs = [ + "Temperature [K]", + "Reference temperature [K]", + "Activation energy [J.mol-1]", + "Ideal gas constant [J.mol-1.K-1]", + ] return pybamm.FunctionParameter( - "Positive electrode reaction rate", T, T_ref, E_r_p, R + "Positive electrode reaction rate", T, T_ref, E_r_p, R, inputs=inputs ) @@ -186,8 +244,12 @@ def dUdT_n_dimensional(sto): """ Dimensional entropic change of the negative electrode open-circuit potential [V.K-1] """ + inputs = [ + "Negative particle stoichiometry", + "Max negative particle concentration [mol.m-3]", + ] return pybamm.FunctionParameter( - "Negative electrode OCP entropic change [V.K-1]", sto, c_n_max + "Negative electrode OCP entropic change [V.K-1]", sto, c_n_max, inputs=inputs ) @@ -195,20 +257,30 @@ def dUdT_p_dimensional(sto): """ Dimensional entropic change of the positive electrode open-circuit potential [V.K-1] """ + inputs = [ + "Positive particle stoichiometry", + "Max positive particle concentration [mol.m-3]", + ] return pybamm.FunctionParameter( - "Positive electrode OCP entropic change [V.K-1]", sto, c_p_max + "Positive electrode OCP entropic change [V.K-1]", sto, c_p_max, inputs=inputs ) def U_n_dimensional(sto, T): "Dimensional open-circuit potential in the negative electrode [V]" - u_ref = pybamm.FunctionParameter("Negative electrode OCP [V]", sto) + inputs = [ + "Negative particle stoichiometry", + ] + u_ref = pybamm.FunctionParameter("Negative electrode OCP [V]", sto, inputs=inputs) return u_ref + (T - T_ref) * dUdT_n_dimensional(sto) def U_p_dimensional(sto, T): "Dimensional open-circuit potential in the positive electrode [V]" - u_ref = pybamm.FunctionParameter("Positive electrode OCP [V]", sto) + inputs = [ + "Positive particle stoichiometry", + ] + u_ref = pybamm.FunctionParameter("Positive electrode OCP [V]", sto, inputs=inputs) return u_ref + (T - T_ref) * dUdT_p_dimensional(sto) @@ -291,13 +363,19 @@ def U_p_dimensional(sto, T): # Microscale geometry epsilon_n = pybamm.FunctionParameter( - "Negative electrode porosity", pybamm.standard_spatial_vars.x_n + "Negative electrode porosity", + pybamm.standard_spatial_vars.x_n, + inputs=["Through-cell distance (x_n) [m]"], ) epsilon_s = pybamm.FunctionParameter( - "Separator porosity", pybamm.standard_spatial_vars.x_s + "Separator porosity", + pybamm.standard_spatial_vars.x_s, + inputs=["Through-cell distance (x_s) [m]"], ) epsilon_p = pybamm.FunctionParameter( - "Positive electrode porosity", pybamm.standard_spatial_vars.x_p + "Positive electrode porosity", + pybamm.standard_spatial_vars.x_p, + inputs=["Through-cell distance (x_p) [m]"], ) epsilon = pybamm.Concatenation(epsilon_n, epsilon_s, epsilon_p) epsilon_s_n = pybamm.Parameter("Negative electrode active material volume fraction") @@ -469,7 +547,7 @@ def dUdT_p(c_s_p): # 6. Input current and voltage dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale + "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc diff --git a/testing.ipynb b/testing.ipynb new file mode 100644 index 0000000000..4040a1a27f --- /dev/null +++ b/testing.ipynb @@ -0,0 +1,347 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pybamm" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "def find_symbol_in_tree(tree, name):\n", + " \"Finds on instance of a symbol in a tree\"\n", + " \n", + " if name == tree.name:\n", + " return tree\n", + " elif len(tree.children) > 0:\n", + " for child in tree.children:\n", + " child_return = find_symbol_in_tree(child, name)\n", + " if child_return:\n", + " return child_return\n", + " else:\n", + " return None\n", + " \n", + "def find_symbol_in_dict(dic, name):\n", + " \n", + " for tree in dic.values(): \n", + " tree_return = find_symbol_in_tree(tree, name)\n", + " \n", + " if tree_return: \n", + " return tree_return\n", + " \n", + " return tree_return \n", + "\n", + "def find_symbol_in_model(model, name):\n", + " \n", + " dics = [model.rhs, model.algebraic, model.boundary_conditions, model.initial_conditions]\n", + " \n", + " for dic in dics: \n", + " dic_return = find_symbol_in_dict(dic, name)\n", + " \n", + " if dic_return: \n", + " return dic_return\n", + " \n", + " print(\"Symbol not found\")\n", + " return dic_return\n", + "\n", + "def info(model, name):\n", + " \n", + " div = \"-----------------------------------------\"\n", + " symbol = find_symbol_in_model(model, name)\n", + " \n", + " print(div)\n", + " print(name, \"\\n\")\n", + " print(type(symbol), \"\\n\")\n", + " \n", + " if isinstance(symbol, pybamm.FunctionParameter): \n", + " print(\"Inputs:\") \n", + " symbol.inputs\n", + " \n", + " \n", + " print(div)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "model = pybamm.lithium_ion.DFN()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(list(model.rhs.values())[0].children)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "symbol = list(model.rhs.values())[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "function = find_symbol_in_tree(symbol, \"Negative electrode diffusivity [m2.s-1]\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative particle stoichiometry\n", + "Temperature [K]\n", + "Reference temperature [K]\n", + "Activation energy [J.mol-1]\n", + "Ideal gas constant [J.mol-1.K-1]\n" + ] + } + ], + "source": [ + "function.inputs" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "fun = find_symbol_in_model(model, \"Negative electrode diffusivity [m2.s-1]\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative particle stoichiometry\n", + "Temperature [K]\n", + "Reference temperature [K]\n", + "Activation energy [J.mol-1]\n", + "Ideal gas constant [J.mol-1.K-1]\n" + ] + } + ], + "source": [ + "fun.inputs" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pybamm.expression_tree.parameter.FunctionParameter" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(fun)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------------------------------------\n", + "Negative electrode diffusivity [m2.s-1] \n", + "\n", + " \n", + "\n", + "Inputs:\n", + "Negative particle stoichiometry\n", + "Temperature [K]\n", + "Reference temperature [K]\n", + "Activation energy [J.mol-1]\n", + "Ideal gas constant [J.mol-1.K-1]\n", + "-----------------------------------------\n" + ] + } + ], + "source": [ + "info(model, \"Negative electrode diffusivity [m2.s-1]\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pybamm" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "model = pybamm.lithium_ion.DFN()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------------------------------------\n", + "Electrolyte conductivity [S.m-1] \n", + "\n", + " \n", + "\n", + "Inputs:\n", + "Electrolyte concentration [mol.m-3]\n", + "Temperature [K]\n", + "Reference temperature [K]\n", + "Activation energy [J.mol-1]\n", + "Ideal gas constant [J.mol-1.K-1]\n", + "-----------------------------------------\n" + ] + } + ], + "source": [ + "model.info(\"Electrolyte conductivity [S.m-1]\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------------------------------------\n", + "Electrolyte diffusivity [m2.s-1] \n", + "\n", + " \n", + "\n", + "Inputs:\n", + "Electrolyte concentration [mol.m-3]\n", + "Temperature [K]\n", + "Reference temperature [K]\n", + "Activation energy [J.mol-1]\n", + "Ideal gas constant [J.mol-1.K-1]\n", + "-----------------------------------------\n" + ] + } + ], + "source": [ + "model.info(\"Electrolyte diffusivity [m2.s-1]\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "\"'Electrolyte con' not found. Best matches are ['Electrolyte conductivity [S.m-1]', 'Electrode width [m]', 'Typical electrolyte concentration [mol.m-3]']\"", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/Projects/PyBaMM/pybamm/util.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 89\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 90\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mKeyError\u001b[0m: 'Electrolyte con'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mpara\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Electrolyte con\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Projects/PyBaMM/pybamm/parameters/parameter_values.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 85\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_dict_items\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 86\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__setitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Projects/PyBaMM/pybamm/util.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 90\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0mbest_matches\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_best_matches\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 92\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{key}' not found. Best matches are {best_matches}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 93\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mKeyError\u001b[0m: \"'Electrolyte con' not found. Best matches are ['Electrolyte conductivity [S.m-1]', 'Electrode width [m]', 'Typical electrolyte concentration [mol.m-3]']\"" + ] + } + ], + "source": [ + "para[\"Electrolyte condy\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4b989142595f365bd50abf8a351ab21d3395685d Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 11 Mar 2020 17:01:19 +0000 Subject: [PATCH 02/18] removed-example-files --- examples/scripts/quick_test.py | 5 - pybamm/expression_tree/parameter.py | 6 +- pybamm/models/base_model.py | 3 +- testing.ipynb | 347 ---------------------------- 4 files changed, 6 insertions(+), 355 deletions(-) delete mode 100644 examples/scripts/quick_test.py delete mode 100644 testing.ipynb diff --git a/examples/scripts/quick_test.py b/examples/scripts/quick_test.py deleted file mode 100644 index 4c84d04284..0000000000 --- a/examples/scripts/quick_test.py +++ /dev/null @@ -1,5 +0,0 @@ -import pybamm - -model = pybamm.lithium_ion.SPMe() -model.info("Negative electrode diffusivity [m2.s-1]") -model.info("Electrolyte conductivity [S.m-1]") diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index e9ebd17416..3f5db26c34 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -136,7 +136,9 @@ def diff(self, variable): """ See :meth:`pybamm.Symbol.diff()`. """ # return a new FunctionParameter, that knows it will need to be differentiated # when the parameters are set - return FunctionParameter(self.name, *self.orphans, diff_variable=variable) + return FunctionParameter( + self.name, *self.orphans, diff_variable=variable, inputs=self._inputs + ) def new_copy(self): """ See :meth:`pybamm.Symbol.new_copy()`. """ @@ -155,7 +157,7 @@ def _function_parameter_new_copy(self, children): : :pybamm.FunctionParameter A new copy of the function parameter """ - return FunctionParameter(self.name, *children, diff_variable=self.diff_variable) + return FunctionParameter(self.name, *children, diff_variable=self.diff_variable, inputs=self._inputs) def _evaluate_for_shape(self): """ diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 72d7f4d7bf..7ae6198b6e 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -589,9 +589,10 @@ def info(self, symbol_name): print(div) print(symbol_name, "\n") - print(type(symbol), "\n") + print(type(symbol)) if isinstance(symbol, pybamm.FunctionParameter): + print("") print("Inputs:") symbol.inputs diff --git a/testing.ipynb b/testing.ipynb deleted file mode 100644 index 4040a1a27f..0000000000 --- a/testing.ipynb +++ /dev/null @@ -1,347 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import pybamm" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [], - "source": [ - "def find_symbol_in_tree(tree, name):\n", - " \"Finds on instance of a symbol in a tree\"\n", - " \n", - " if name == tree.name:\n", - " return tree\n", - " elif len(tree.children) > 0:\n", - " for child in tree.children:\n", - " child_return = find_symbol_in_tree(child, name)\n", - " if child_return:\n", - " return child_return\n", - " else:\n", - " return None\n", - " \n", - "def find_symbol_in_dict(dic, name):\n", - " \n", - " for tree in dic.values(): \n", - " tree_return = find_symbol_in_tree(tree, name)\n", - " \n", - " if tree_return: \n", - " return tree_return\n", - " \n", - " return tree_return \n", - "\n", - "def find_symbol_in_model(model, name):\n", - " \n", - " dics = [model.rhs, model.algebraic, model.boundary_conditions, model.initial_conditions]\n", - " \n", - " for dic in dics: \n", - " dic_return = find_symbol_in_dict(dic, name)\n", - " \n", - " if dic_return: \n", - " return dic_return\n", - " \n", - " print(\"Symbol not found\")\n", - " return dic_return\n", - "\n", - "def info(model, name):\n", - " \n", - " div = \"-----------------------------------------\"\n", - " symbol = find_symbol_in_model(model, name)\n", - " \n", - " print(div)\n", - " print(name, \"\\n\")\n", - " print(type(symbol), \"\\n\")\n", - " \n", - " if isinstance(symbol, pybamm.FunctionParameter): \n", - " print(\"Inputs:\") \n", - " symbol.inputs\n", - " \n", - " \n", - " print(div)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "model = pybamm.lithium_ion.DFN()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(list(model.rhs.values())[0].children)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "symbol = list(model.rhs.values())[1]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "function = find_symbol_in_tree(symbol, \"Negative electrode diffusivity [m2.s-1]\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Negative particle stoichiometry\n", - "Temperature [K]\n", - "Reference temperature [K]\n", - "Activation energy [J.mol-1]\n", - "Ideal gas constant [J.mol-1.K-1]\n" - ] - } - ], - "source": [ - "function.inputs" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "fun = find_symbol_in_model(model, \"Negative electrode diffusivity [m2.s-1]\")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Negative particle stoichiometry\n", - "Temperature [K]\n", - "Reference temperature [K]\n", - "Activation energy [J.mol-1]\n", - "Ideal gas constant [J.mol-1.K-1]\n" - ] - } - ], - "source": [ - "fun.inputs" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "pybamm.expression_tree.parameter.FunctionParameter" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "type(fun)" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-----------------------------------------\n", - "Negative electrode diffusivity [m2.s-1] \n", - "\n", - " \n", - "\n", - "Inputs:\n", - "Negative particle stoichiometry\n", - "Temperature [K]\n", - "Reference temperature [K]\n", - "Activation energy [J.mol-1]\n", - "Ideal gas constant [J.mol-1.K-1]\n", - "-----------------------------------------\n" - ] - } - ], - "source": [ - "info(model, \"Negative electrode diffusivity [m2.s-1]\")" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import pybamm" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "model = pybamm.lithium_ion.DFN()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-----------------------------------------\n", - "Electrolyte conductivity [S.m-1] \n", - "\n", - " \n", - "\n", - "Inputs:\n", - "Electrolyte concentration [mol.m-3]\n", - "Temperature [K]\n", - "Reference temperature [K]\n", - "Activation energy [J.mol-1]\n", - "Ideal gas constant [J.mol-1.K-1]\n", - "-----------------------------------------\n" - ] - } - ], - "source": [ - "model.info(\"Electrolyte conductivity [S.m-1]\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-----------------------------------------\n", - "Electrolyte diffusivity [m2.s-1] \n", - "\n", - " \n", - "\n", - "Inputs:\n", - "Electrolyte concentration [mol.m-3]\n", - "Temperature [K]\n", - "Reference temperature [K]\n", - "Activation energy [J.mol-1]\n", - "Ideal gas constant [J.mol-1.K-1]\n", - "-----------------------------------------\n" - ] - } - ], - "source": [ - "model.info(\"Electrolyte diffusivity [m2.s-1]\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "\"'Electrolyte con' not found. Best matches are ['Electrolyte conductivity [S.m-1]', 'Electrode width [m]', 'Typical electrolyte concentration [mol.m-3]']\"", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m~/Projects/PyBaMM/pybamm/util.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 89\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 90\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyError\u001b[0m: 'Electrolyte con'", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mpara\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Electrolyte con\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/Projects/PyBaMM/pybamm/parameters/parameter_values.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 85\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_dict_items\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 86\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__setitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/Projects/PyBaMM/pybamm/util.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 90\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0mbest_matches\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_best_matches\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 92\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{key}' not found. Best matches are {best_matches}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 93\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyError\u001b[0m: \"'Electrolyte con' not found. Best matches are ['Electrolyte conductivity [S.m-1]', 'Electrode width [m]', 'Typical electrolyte concentration [mol.m-3]']\"" - ] - } - ], - "source": [ - "para[\"Electrolyte condy\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 47b4a8f42694f904a406e5e1caff08ce9eff759d Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 11 Mar 2020 17:05:56 +0000 Subject: [PATCH 03/18] added small test --- tests/unit/test_models/test_model_info.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/unit/test_models/test_model_info.py diff --git a/tests/unit/test_models/test_model_info.py b/tests/unit/test_models/test_model_info.py new file mode 100644 index 0000000000..c9f2cfbc75 --- /dev/null +++ b/tests/unit/test_models/test_model_info.py @@ -0,0 +1,25 @@ +# +# Tests getting model info +# +import pybamm +import unittest + + +class TestModelInfo(unittest.TestCase): + def test_find_parameter_info(self): + model = pybamm.lithium_ion.SPM() + model.info("Negative electrode diffusivity [m2.s-1]") + model = pybamm.lithium_ion.SPMe() + model.info("Negative electrode diffusivity [m2.s-1]") + model = pybamm.lithium_ion.DFN() + model.info("Negative electrode diffusivity [m2.s-1]") + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + pybamm.settings.debug_mode = True + unittest.main() From fd5497be6018d24ed31a24ea5cfe0983f35b8365 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 11 Mar 2020 17:12:35 +0000 Subject: [PATCH 04/18] flake8 --- pybamm/expression_tree/parameter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index 3f5db26c34..805861e374 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -157,7 +157,9 @@ def _function_parameter_new_copy(self, children): : :pybamm.FunctionParameter A new copy of the function parameter """ - return FunctionParameter(self.name, *children, diff_variable=self.diff_variable, inputs=self._inputs) + return FunctionParameter( + self.name, *children, diff_variable=self.diff_variable, inputs=self._inputs + ) def _evaluate_for_shape(self): """ From be026d708845ffb2d1662771df678a1853942735 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 11 Mar 2020 17:14:26 +0000 Subject: [PATCH 05/18] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c0d13c98..3c244ebb52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Added a helper function for info on function parameters ([#881](https://github.com/pybamm-team/PyBaMM/pull/881)) - Added additional notebooks showing how to create and compare models ([#877](https://github.com/pybamm-team/PyBaMM/pull/877)) - Added `Minimum`, `Maximum` and `Sign` operators ([#876](https://github.com/pybamm-team/PyBaMM/pull/876)) - Added a search feature to `FuzzyDict` ([#875](https://github.com/pybamm-team/PyBaMM/pull/875)) From 7de5ed7ed6942dbd0f0eecf22999ef715544ae77 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sat, 14 Mar 2020 12:30:44 +0000 Subject: [PATCH 06/18] #880 converted funciton args to dictionary --- pybamm/expression_tree/parameter.py | 47 ++--- pybamm/parameters/electrical_parameters.py | 2 +- .../standard_parameters_lead_acid.py | 51 ++--- .../standard_parameters_lithium_ion.py | 186 +++++++----------- 4 files changed, 120 insertions(+), 166 deletions(-) diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index 805861e374..daab7ddf85 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -48,26 +48,21 @@ class FunctionParameter(pybamm.Symbol): name : str name of the node - child : :class:`Symbol` - child node + inputs : dict + A dictionary with string keys and :class:`pybamm.Symbol` values representing + the function inputs. diff_variable : :class:`pybamm.Symbol`, optional if diff_variable is specified, the FunctionParameter node will be replaced by a :class:`pybamm.Function` and then differentiated with respect to diff_variable. Default is None. - description: str - A description of the function. - inputs: list - A list of strings describing the inputs. - outputs: list - A list of string describing the outputs. """ def __init__( - self, name, *children, diff_variable=None, inputs=None, + self, name, inputs, diff_variable=None, ): # assign diff variable self.diff_variable = diff_variable - children_list = list(children) + children_list = list(inputs.values()) # Turn numbers into scalars for idx, child in enumerate(children_list): @@ -83,16 +78,16 @@ def __init__( auxiliary_domains=auxiliary_domains, ) - self.inputs = inputs + self.input_names = list(inputs.keys()) @property - def inputs(self): + def input_names(self): if self._inputs: - for inp in self._inputs: + for inp in self._input_names: print(inp) - @inputs.setter - def inputs(self, inp=None): + @input_names.setter + def inputs_names(self, inp=None): if inp: if inp.__class__ is list: for i in inp: @@ -103,7 +98,7 @@ def inputs(self, inp=None): else: raise TypeError("Inputs must be a provided as a list of strings") - self._inputs = inp + self._input_names = inp def set_id(self): """See :meth:`pybamm.Symbol.set_id` """ @@ -136,19 +131,24 @@ def diff(self, variable): """ See :meth:`pybamm.Symbol.diff()`. """ # return a new FunctionParameter, that knows it will need to be differentiated # when the parameters are set - return FunctionParameter( - self.name, *self.orphans, diff_variable=variable, inputs=self._inputs - ) + children_list = self.orphans + input_names = self._input_names + + input_dict = {input_names[i]: children_list[i] for i in range(len(input_names))} + + return FunctionParameter(self.name, input_dict, diff_variable=variable) def new_copy(self): """ See :meth:`pybamm.Symbol.new_copy()`. """ - return self._function_parameter_new_copy(self.orphans) + return self._function_parameter_new_copy(self.input_names, self.orphans) - def _function_parameter_new_copy(self, children): + def _function_parameter_new_copy(self, input_names, children): """Returns a new copy of the function parameter. Inputs ------ + input_names : : list + A list of str of the names of the children/function inputs children : : list A list of the children of the function @@ -157,8 +157,11 @@ def _function_parameter_new_copy(self, children): : :pybamm.FunctionParameter A new copy of the function parameter """ + + input_dict = {input_names[i]: children[i] for i in range(len(input_names))} + return FunctionParameter( - self.name, *children, diff_variable=self.diff_variable, inputs=self._inputs + self.name, input_dict, diff_variable=self.diff_variable ) def _evaluate_for_shape(self): diff --git a/pybamm/parameters/electrical_parameters.py b/pybamm/parameters/electrical_parameters.py index e2f0de71d9..b98c0d2e7f 100644 --- a/pybamm/parameters/electrical_parameters.py +++ b/pybamm/parameters/electrical_parameters.py @@ -26,7 +26,7 @@ # the user may provide the typical timescale as a parameter. timescale = pybamm.Parameter("Typical timescale [s]") dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] + "Current function [A]", {"Time[s]": pybamm.t * timescale} ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc diff --git a/pybamm/parameters/standard_parameters_lead_acid.py b/pybamm/parameters/standard_parameters_lead_acid.py index 55e7c933c2..0d290005f2 100644 --- a/pybamm/parameters/standard_parameters_lead_acid.py +++ b/pybamm/parameters/standard_parameters_lead_acid.py @@ -174,28 +174,19 @@ def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" - return pybamm.FunctionParameter( - "Electrolyte diffusivity [m2.s-1]", - c_e, - inputs=["Electrolyte concentration [mol.m-3"], - ) + inputs = {"Electrolyte concentration [mol.m-3": c_e} + return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", inputs) def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" - return pybamm.FunctionParameter( - "Electrolyte conductivity [S.m-1]", - c_e, - inputs=["Electrolyte concentration [mol.m-3]"], - ) + inputs = {"Electrolyte concentration [mol.m-3]": c_e} + return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", inputs) def chi_dimensional(c_e): - return pybamm.FunctionParameter( - "Darken thermodynamic factor", - c_e, - inputs=["Electrolyte concentration [mol.m-3]"], - ) + inputs = {"Electrolyte concentration [mol.m-3]": c_e} + return pybamm.FunctionParameter("Darken thermodynamic factor", inputs) def c_w_dimensional(c_e, c_ox=0, c_hy=0): @@ -236,43 +227,37 @@ def mu_dimensional(c_e): """ Dimensional viscosity of electrolyte [kg.m-1.s-1]. """ - return pybamm.FunctionParameter( - "Electrolyte viscosity [kg.m-1.s-1]", - c_e, - inputs=["Electrolyte concentration [mol.m-3]"], - ) + inputs = {"Electrolyte concentration [mol.m-3]": c_e} + return pybamm.FunctionParameter("Electrolyte viscosity [kg.m-1.s-1]", inputs) def U_n_dimensional(c_e, T): "Dimensional open-circuit voltage in the negative electrode [V]" + inputs = {"Electrolyte molar mass [mol.kg-1]": m_dimensional(c_e)} return pybamm.FunctionParameter( - "Negative electrode open-circuit potential [V]", - m_dimensional(c_e), - inputs=["Electrolyte concentration [mol.m-3]"], + "Negative electrode open-circuit potential [V]", inputs ) def U_p_dimensional(c_e, T): "Dimensional open-circuit voltage in the positive electrode [V]" + inputs = {"Electrolyte molar mass [mol.kg-1]": m_dimensional(c_e)} return pybamm.FunctionParameter( - "Positive electrode open-circuit potential [V]", - m_dimensional(c_e), - inputs=["Electrolyte concentration [mol.m-3]"], + "Positive electrode open-circuit potential [V]", inputs ) D_e_typ = D_e_dimensional(c_e_typ, T_ref) rho_typ = rho_dimensional(c_e_typ) mu_typ = mu_dimensional(c_e_typ) + +inputs = {"Electrolyte concentration [mol.m-3]": pybamm.Scalar(1)} U_n_ref = pybamm.FunctionParameter( - "Negative electrode open-circuit potential [V]", - pybamm.Scalar(1), - inputs=["Electrolyte concentration [mol.m-3]"], + "Negative electrode open-circuit potential [V]", inputs ) +inputs = {"Electrolyte concentration [mol.m-3]": pybamm.Scalar(1)} U_p_ref = pybamm.FunctionParameter( - "Positive electrode open-circuit potential [V]", - pybamm.Scalar(1), - inputs=["Electrolyte concentration [mol.m-3]"], + "Positive electrode open-circuit potential [V]", inputs ) @@ -516,7 +501,7 @@ def U_p(c_e_p, T): # 6. Input current and voltage dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] + "Current function [A]", {"Time [s]": pybamm.t * timescale} ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc diff --git a/pybamm/parameters/standard_parameters_lithium_ion.py b/pybamm/parameters/standard_parameters_lithium_ion.py index 08589da4a5..4e68ca6709 100644 --- a/pybamm/parameters/standard_parameters_lithium_ion.py +++ b/pybamm/parameters/standard_parameters_lithium_ion.py @@ -108,19 +108,17 @@ def c_n_init_dimensional(x): "Initial concentration as a function of dimensionless position x" + inputs = {"Dimensionless through-cell position (x_n)": x} return pybamm.FunctionParameter( - "Initial concentration in negative electrode [mol.m-3]", - x, - inputs=["Dimensionless through-cell position (x_n)"], + "Initial concentration in negative electrode [mol.m-3]", inputs ) def c_p_init_dimensional(x): "Initial concentration as a function of dimensionless position x" + inputs = {"Dimensionless through-cell position (x_p)": x} return pybamm.FunctionParameter( - "Initial concentration in positive electrode [mol.m-3]", - x, - inputs=["Dimensionless through-cell position (x_p)"], + "Initial concentration in positive electrode [mol.m-3]", inputs ) @@ -144,112 +142,88 @@ def c_p_init_dimensional(x): def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" - inputs = [ - "Electrolyte concentration [mol.m-3]", - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] - return pybamm.FunctionParameter( - "Electrolyte diffusivity [m2.s-1]", c_e, T, T_ref, E_D_e, R, inputs=inputs - ) + inputs = { + "Electrolyte concentration [mol.m-3]": c_e, + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_D_e, + "Ideal gas constant [J.mol-1.K-1]": R, + } + return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", inputs) def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" - inputs = [ - "Electrolyte concentration [mol.m-3]", - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] - return pybamm.FunctionParameter( - "Electrolyte conductivity [S.m-1]", c_e, T, T_ref, E_k_e, R, inputs=inputs - ) + inputs = { + "Electrolyte concentration [mol.m-3]": c_e, + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_k_e, + "Ideal gas constant [J.mol-1.K-1]": R, + } + return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", inputs) def D_n_dimensional(sto, T): """Dimensional diffusivity in negative particle. Note this is defined as a function of stochiometry""" - inputs = [ - "Negative particle stoichiometry", - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] + inputs = { + "Negative particle stoichiometry": sto, + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_D_s_n, + "Ideal gas constant [J.mol-1.K-1]": R, + } - return pybamm.FunctionParameter( - "Negative electrode diffusivity [m2.s-1]", - sto, - T, - T_ref, - E_D_s_n, - R, - inputs=inputs, - ) + return pybamm.FunctionParameter("Negative electrode diffusivity [m2.s-1]", inputs) def D_p_dimensional(sto, T): """Dimensional diffusivity in positive particle. Note this is defined as a function of stochiometry""" - inputs = [ - "Positive particle stoichiometry", - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] - return pybamm.FunctionParameter( - "Positive electrode diffusivity [m2.s-1]", - sto, - T, - T_ref, - E_D_s_p, - R, - inputs=inputs, - ) + inputs = { + "Positive particle stoichiometry": sto, + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_D_s_p, + "Ideal gas constant [J.mol-1.K-1]": R, + } + return pybamm.FunctionParameter("Positive electrode diffusivity [m2.s-1]", inputs) def m_n_dimensional(T): "Dimensional negative reaction rate" - inputs = [ - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] - return pybamm.FunctionParameter( - "Negative electrode reaction rate", T, T_ref, E_r_n, R, inputs=inputs - ) + inputs = { + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_r_n, + "Ideal gas constant [J.mol-1.K-1]": R, + } + return pybamm.FunctionParameter("Negative electrode reaction rate", inputs) def m_p_dimensional(T): "Dimensional negative reaction rate" - inputs = [ - "Temperature [K]", - "Reference temperature [K]", - "Activation energy [J.mol-1]", - "Ideal gas constant [J.mol-1.K-1]", - ] - return pybamm.FunctionParameter( - "Positive electrode reaction rate", T, T_ref, E_r_p, R, inputs=inputs - ) + inputs = { + "Temperature [K]": T, + "Reference temperature [K]": T_ref, + "Activation energy [J.mol-1]": E_r_p, + "Ideal gas constant [J.mol-1.K-1]": R, + } + return pybamm.FunctionParameter("Positive electrode reaction rate", inputs) def dUdT_n_dimensional(sto): """ Dimensional entropic change of the negative electrode open-circuit potential [V.K-1] """ - inputs = [ - "Negative particle stoichiometry", - "Max negative particle concentration [mol.m-3]", - ] + inputs = { + "Negative particle stoichiometry": sto, + "Max negative particle concentration [mol.m-3]": c_n_max, + } return pybamm.FunctionParameter( - "Negative electrode OCP entropic change [V.K-1]", sto, c_n_max, inputs=inputs + "Negative electrode OCP entropic change [V.K-1]", inputs ) @@ -257,30 +231,26 @@ def dUdT_p_dimensional(sto): """ Dimensional entropic change of the positive electrode open-circuit potential [V.K-1] """ - inputs = [ - "Positive particle stoichiometry", - "Max positive particle concentration [mol.m-3]", - ] + inputs = { + "Positive particle stoichiometry": sto, + "Max positive particle concentration [mol.m-3]": c_p_max, + } return pybamm.FunctionParameter( - "Positive electrode OCP entropic change [V.K-1]", sto, c_p_max, inputs=inputs + "Positive electrode OCP entropic change [V.K-1]", inputs ) def U_n_dimensional(sto, T): "Dimensional open-circuit potential in the negative electrode [V]" - inputs = [ - "Negative particle stoichiometry", - ] - u_ref = pybamm.FunctionParameter("Negative electrode OCP [V]", sto, inputs=inputs) + inputs = {"Negative particle stoichiometry": sto} + u_ref = pybamm.FunctionParameter("Negative electrode OCP [V]", inputs) return u_ref + (T - T_ref) * dUdT_n_dimensional(sto) def U_p_dimensional(sto, T): "Dimensional open-circuit potential in the positive electrode [V]" - inputs = [ - "Positive particle stoichiometry", - ] - u_ref = pybamm.FunctionParameter("Positive electrode OCP [V]", sto, inputs=inputs) + inputs = {"Positive particle stoichiometry": sto} + u_ref = pybamm.FunctionParameter("Positive electrode OCP [V]", inputs) return u_ref + (T - T_ref) * dUdT_p_dimensional(sto) @@ -364,22 +334,18 @@ def U_p_dimensional(sto, T): centre_z_tab_p = pybamm.geometric_parameters.centre_z_tab_p # Microscale geometry -epsilon_n = pybamm.FunctionParameter( - "Negative electrode porosity", - pybamm.standard_spatial_vars.x_n, - inputs=["Through-cell distance (x_n) [m]"], -) -epsilon_s = pybamm.FunctionParameter( - "Separator porosity", - pybamm.standard_spatial_vars.x_s, - inputs=["Through-cell distance (x_s) [m]"], -) -epsilon_p = pybamm.FunctionParameter( - "Positive electrode porosity", - pybamm.standard_spatial_vars.x_p, - inputs=["Through-cell distance (x_p) [m]"], -) + +inputs = {"Through-cell distance (x_n) [m]": pybamm.standard_spatial_vars.x_n} +epsilon_n = pybamm.FunctionParameter("Negative electrode porosity", inputs) + +inputs = {"Through-cell distance (x_s) [m]": pybamm.standard_spatial_vars.x_s} +epsilon_s = pybamm.FunctionParameter("Separator porosity", inputs) + +inputs = {"Through-cell distance (x_p) [m]": pybamm.standard_spatial_vars.x_p} +epsilon_p = pybamm.FunctionParameter("Positive electrode porosity", inputs) + epsilon = pybamm.Concatenation(epsilon_n, epsilon_s, epsilon_p) + epsilon_s_n = pybamm.Parameter("Negative electrode active material volume fraction") epsilon_s_p = pybamm.Parameter("Positive electrode active material volume fraction") epsilon_inactive_n = 1 - epsilon_n - epsilon_s_n @@ -552,7 +518,7 @@ def dUdT_p(c_s_p): # 6. Input current and voltage dimensional_current_with_time = pybamm.FunctionParameter( - "Current function [A]", pybamm.t * timescale, inputs=["Time [s]"] + "Current function [A]", {"Time [s]": pybamm.t * timescale} ) dimensional_current_density_with_time = dimensional_current_with_time / ( n_electrodes_parallel * pybamm.geometric_parameters.A_cc From b1128191ed17e21ea5cd5c44e29c3345c9be3593 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sat, 14 Mar 2020 12:52:02 +0000 Subject: [PATCH 07/18] #880 fixed model.info tests --- pybamm/expression_tree/parameter.py | 6 +++--- pybamm/models/base_model.py | 2 +- .../submodels/particle/fickian/fickian_many_particles.py | 6 ++---- pybamm/parameters/thermal_parameters.py | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index daab7ddf85..f214683c98 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -82,12 +82,12 @@ def __init__( @property def input_names(self): - if self._inputs: + if self._input_names: for inp in self._input_names: print(inp) @input_names.setter - def inputs_names(self, inp=None): + def input_names(self, inp=None): if inp: if inp.__class__ is list: for i in inp: @@ -140,7 +140,7 @@ def diff(self, variable): def new_copy(self): """ See :meth:`pybamm.Symbol.new_copy()`. """ - return self._function_parameter_new_copy(self.input_names, self.orphans) + return self._function_parameter_new_copy(self._input_names, self.orphans) def _function_parameter_new_copy(self, input_names, children): """Returns a new copy of the function parameter. diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 7ae6198b6e..1ae9807871 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -594,7 +594,7 @@ def info(self, symbol_name): if isinstance(symbol, pybamm.FunctionParameter): print("") print("Inputs:") - symbol.inputs + symbol.input_names print(div) diff --git a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py b/pybamm/models/submodels/particle/fickian/fickian_many_particles.py index cd81d88642..700d15e768 100644 --- a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py +++ b/pybamm/models/submodels/particle/fickian/fickian_many_particles.py @@ -54,8 +54,7 @@ def get_coupled_variables(self, variables): x = pybamm.standard_spatial_vars.x_n R = pybamm.FunctionParameter( "Negative particle distribution in x", - x, - inputs=["Dimensionless through-cell position (x_n)"], + {"Dimensionless through-cell position (x_n)": x}, ) variables.update({"Negative particle distribution in x": R}) @@ -63,8 +62,7 @@ def get_coupled_variables(self, variables): x = pybamm.standard_spatial_vars.x_p R = pybamm.FunctionParameter( "Positive particle distribution in x", - x, - inputs=["Dimensionless through-cell position (x_p)"], + {"Dimensionless through-cell position (x_p)": x}, ) variables.update({"Positive particle distribution in x": R}) diff --git a/pybamm/parameters/thermal_parameters.py b/pybamm/parameters/thermal_parameters.py index 5273d369f3..15f0f9ee8f 100644 --- a/pybamm/parameters/thermal_parameters.py +++ b/pybamm/parameters/thermal_parameters.py @@ -116,7 +116,7 @@ def T_amb_dim(t): - return pybamm.FunctionParameter("Ambient temperature [K]", t) + return pybamm.FunctionParameter("Ambient temperature [K]", {"Times [s]": t}) def T_amb(t): From 0f445a7236c4a680a99a0bf160cb8f19169d58a7 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sat, 14 Mar 2020 14:12:28 +0000 Subject: [PATCH 08/18] #880 fixed tests for new function parameter format --- .../function_control_external_circuit.py | 6 ++---- .../test_operations/test_copy.py | 2 +- .../test_operations/test_simplify.py | 4 ++-- .../unit/test_expression_tree/test_parameter.py | 6 +++--- tests/unit/test_expression_tree/test_symbol.py | 2 +- .../test_lead_acid/test_loqs.py | 2 +- .../test_lithium_ion/test_spm.py | 2 +- .../test_function_control.py | 8 +++++++- .../test_parameters/test_parameter_values.py | 16 ++++++++-------- 9 files changed, 26 insertions(+), 22 deletions(-) diff --git a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py index 2aba6dd271..e89fdda0b0 100644 --- a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py @@ -58,9 +58,7 @@ def __init__(self, param): def constant_voltage(variables): V = variables["Terminal voltage [V]"] - return V - pybamm.FunctionParameter( - "Voltage function [V]", pybamm.t, inputs=["Time [s]"] - ) + return V - pybamm.FunctionParameter("Voltage function [V]", {"Times [s]": pybamm.t}) class PowerFunctionControl(FunctionControl): @@ -74,7 +72,7 @@ def constant_power(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] return I * V - pybamm.FunctionParameter( - "Power function [W]", pybamm.t, inputs=["Time [s]"] + "Power function [W]", {"Time [s]": pybamm.t} ) diff --git a/tests/unit/test_expression_tree/test_operations/test_copy.py b/tests/unit/test_expression_tree/test_operations/test_copy.py index 32afe6c273..e3fdb43c7d 100644 --- a/tests/unit/test_expression_tree/test_operations/test_copy.py +++ b/tests/unit/test_expression_tree/test_operations/test_copy.py @@ -25,7 +25,7 @@ def test_symbol_new_copy(self): -a, abs(a), pybamm.Function(np.sin, a), - pybamm.FunctionParameter("function", a), + pybamm.FunctionParameter("function", {"a": a}), pybamm.grad(a), pybamm.div(a), pybamm.Integral(a, pybamm.t), diff --git a/tests/unit/test_expression_tree/test_operations/test_simplify.py b/tests/unit/test_expression_tree/test_operations/test_simplify.py index 129aef5e98..25b7d1c03a 100644 --- a/tests/unit/test_expression_tree/test_operations/test_simplify.py +++ b/tests/unit/test_expression_tree/test_operations/test_simplify.py @@ -45,11 +45,11 @@ def myfunction(x, y): self.assertEqual((f).simplify().evaluate(), 0) # FunctionParameter - f = pybamm.FunctionParameter("function", b) + f = pybamm.FunctionParameter("function", {"b": b}) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, b.id) - f = pybamm.FunctionParameter("function", a, b) + f = pybamm.FunctionParameter("function", {"a": a, "b": b}) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, a.id) self.assertEqual((f).simplify().children[1].id, b.id) diff --git a/tests/unit/test_expression_tree/test_parameter.py b/tests/unit/test_expression_tree/test_parameter.py index fc03e73f45..39b54c1289 100644 --- a/tests/unit/test_expression_tree/test_parameter.py +++ b/tests/unit/test_expression_tree/test_parameter.py @@ -22,7 +22,7 @@ def test_evaluate_for_shape(self): class TestFunctionParameter(unittest.TestCase): def test_function_parameter_init(self): var = pybamm.Variable("var") - func = pybamm.FunctionParameter("func", var) + func = pybamm.FunctionParameter("func", {"var": var}) self.assertEqual(func.name, "func") self.assertEqual(func.children[0].id, var.id) self.assertEqual(func.domain, []) @@ -30,12 +30,12 @@ def test_function_parameter_init(self): def test_function_parameter_diff(self): var = pybamm.Variable("var") - func = pybamm.FunctionParameter("a", var).diff(var) + func = pybamm.FunctionParameter("a", {"var": var}).diff(var) self.assertEqual(func.diff_variable, var) def test_evaluate_for_shape(self): a = pybamm.Parameter("a") - func = pybamm.FunctionParameter("func", 2 * a) + func = pybamm.FunctionParameter("func", {"2a": 2 * a}) self.assertIsInstance(func.evaluate_for_shape(), numbers.Number) diff --git a/tests/unit/test_expression_tree/test_symbol.py b/tests/unit/test_expression_tree/test_symbol.py index dbff68dd3a..b2fe817db6 100644 --- a/tests/unit/test_expression_tree/test_symbol.py +++ b/tests/unit/test_expression_tree/test_symbol.py @@ -374,7 +374,7 @@ def test_shape_and_size_for_testing(self): param = pybamm.Parameter("a") self.assertEqual(param.shape_for_testing, ()) - func = pybamm.FunctionParameter("func", state) + func = pybamm.FunctionParameter("func", {"state": state}) self.assertEqual(func.shape_for_testing, state.shape_for_testing) concat = pybamm.Concatenation() diff --git a/tests/unit/test_models/test_full_battery_models/test_lead_acid/test_loqs.py b/tests/unit/test_models/test_full_battery_models/test_lead_acid/test_loqs.py index 0c7757d17f..cb5675944b 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lead_acid/test_loqs.py +++ b/tests/unit/test_models/test_full_battery_models/test_lead_acid/test_loqs.py @@ -170,7 +170,7 @@ def test_well_posed_function(self): def external_circuit_function(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] - return V + I - pybamm.FunctionParameter("Function", pybamm.t) + return V + I - pybamm.FunctionParameter("Function", {"Time [s]": pybamm.t}) options = {"operating mode": external_circuit_function} model = pybamm.lead_acid.LOQS(options) diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py index 83bd6e5cd7..9fd9fd09a7 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py @@ -174,7 +174,7 @@ def test_well_posed_function(self): def external_circuit_function(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] - return V + I - pybamm.FunctionParameter("Function", pybamm.t) + return V + I - pybamm.FunctionParameter("Function", {"Time [s]": pybamm.t}) options = {"operating mode": external_circuit_function} model = pybamm.lithium_ion.SPM(options) diff --git a/tests/unit/test_models/test_submodels/test_external_circuit/test_function_control.py b/tests/unit/test_models/test_submodels/test_external_circuit/test_function_control.py index 4a06002baa..eb25f4b12a 100644 --- a/tests/unit/test_models/test_submodels/test_external_circuit/test_function_control.py +++ b/tests/unit/test_models/test_submodels/test_external_circuit/test_function_control.py @@ -9,7 +9,13 @@ def external_circuit_function(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] - return V + I - pybamm.FunctionParameter("Current plus voltage function", pybamm.t) + return ( + V + + I + - pybamm.FunctionParameter( + "Current plus voltage function", {"Time [s]": pybamm.t} + ) + ) class TestFunctionControl(unittest.TestCase): diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py index 30a75ddd47..e37885a54b 100644 --- a/tests/unit/test_parameters/test_parameter_values.py +++ b/tests/unit/test_parameters/test_parameter_values.py @@ -305,12 +305,12 @@ def test_process_function_parameter(self): a = pybamm.InputParameter("a") # process function - func = pybamm.FunctionParameter("func", a) + func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(u={"a": 3}), 369) # process constant function - const = pybamm.FunctionParameter("const", a) + const = pybamm.FunctionParameter("const", {"a": a}) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) @@ -323,7 +323,7 @@ def test_process_function_parameter(self): # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues({"func": "[input]"}) a = pybamm.Scalar(3) - func = pybamm.FunctionParameter("func", a) + func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(u={"func": 13}), 13) @@ -334,7 +334,7 @@ def D(c): parameter_values = pybamm.ParameterValues({"Diffusivity": D}) a = pybamm.InputParameter("a") - func = pybamm.FunctionParameter("Diffusivity", a) + func = pybamm.FunctionParameter("Diffusivity", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(u={"a": 3}), 9) @@ -365,7 +365,7 @@ def D(a, b): a = pybamm.Parameter("a") b = pybamm.Parameter("b") - func = pybamm.FunctionParameter("Diffusivity", a, b) + func = pybamm.FunctionParameter("Diffusivity", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 3) @@ -378,7 +378,7 @@ def test_process_interpolant(self): ) a = pybamm.Parameter("a") - func = pybamm.FunctionParameter("Diffusivity", a) + func = pybamm.FunctionParameter("Diffusivity", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertIsInstance(processed_func, pybamm.Interpolant) @@ -403,8 +403,8 @@ def test_interpolant_against_function(self): ) a = pybamm.InputParameter("a") - func = pybamm.FunctionParameter("function", a) - interp = pybamm.FunctionParameter("interpolation", a) + func = pybamm.FunctionParameter("function", {"a": a}) + interp = pybamm.FunctionParameter("interpolation", {"a": a}) processed_func = parameter_values.process_symbol(func) processed_interp = parameter_values.process_symbol(interp) From bac6b845733cf2526e11d723ab411357c993a879 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Tue, 17 Mar 2020 10:02:11 +0000 Subject: [PATCH 09/18] #880 fixed tests --- pybamm/parameters/standard_parameters_lead_acid.py | 4 ++++ pybamm/parameters/standard_parameters_lithium_ion.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pybamm/parameters/standard_parameters_lead_acid.py b/pybamm/parameters/standard_parameters_lead_acid.py index 0d290005f2..ce88acf339 100644 --- a/pybamm/parameters/standard_parameters_lead_acid.py +++ b/pybamm/parameters/standard_parameters_lead_acid.py @@ -509,3 +509,7 @@ def U_p(c_e_p, T): current_with_time = ( dimensional_current_with_time / I_typ * pybamm.Function(np.sign, I_typ) ) + + +"Remove any temporary variables" +del inputs diff --git a/pybamm/parameters/standard_parameters_lithium_ion.py b/pybamm/parameters/standard_parameters_lithium_ion.py index 4e68ca6709..0a26f13334 100644 --- a/pybamm/parameters/standard_parameters_lithium_ion.py +++ b/pybamm/parameters/standard_parameters_lithium_ion.py @@ -526,3 +526,7 @@ def dUdT_p(c_s_p): current_with_time = ( dimensional_current_with_time / I_typ * pybamm.Function(np.sign, I_typ) ) + + +"Remove any temporary variables" +del inputs From 63f68c0ba231d1c8def5cf1b5c2abde79652ab2e Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Wed, 18 Mar 2020 09:46:27 +0000 Subject: [PATCH 10/18] #880 updated notebooks --- .../Creating Models/1-an-ode-model.ipynb | 2 +- .../Creating Models/2-a-pde-model.ipynb | 2 +- .../3-negative-particle-problem.ipynb | 2 +- ...mparing-full-and-reduced-order-models.ipynb | 2 +- .../Creating Models/5-a-simple-SEI-model.ipynb | 6 +++--- examples/notebooks/parameter-values.ipynb | 18 +++++++++--------- examples/scripts/create-model.py | 2 +- squared.py | 3 +++ 8 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 squared.py diff --git a/examples/notebooks/Creating Models/1-an-ode-model.ipynb b/examples/notebooks/Creating Models/1-an-ode-model.ipynb index 56228af093..00dbabe62c 100644 --- a/examples/notebooks/Creating Models/1-an-ode-model.ipynb +++ b/examples/notebooks/Creating Models/1-an-ode-model.ipynb @@ -271,7 +271,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/notebooks/Creating Models/2-a-pde-model.ipynb b/examples/notebooks/Creating Models/2-a-pde-model.ipynb index 384f016072..09acd1e1c5 100644 --- a/examples/notebooks/Creating Models/2-a-pde-model.ipynb +++ b/examples/notebooks/Creating Models/2-a-pde-model.ipynb @@ -298,7 +298,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/notebooks/Creating Models/3-negative-particle-problem.ipynb b/examples/notebooks/Creating Models/3-negative-particle-problem.ipynb index faba0f70aa..de365eab89 100644 --- a/examples/notebooks/Creating Models/3-negative-particle-problem.ipynb +++ b/examples/notebooks/Creating Models/3-negative-particle-problem.ipynb @@ -316,7 +316,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/notebooks/Creating Models/4-comparing-full-and-reduced-order-models.ipynb b/examples/notebooks/Creating Models/4-comparing-full-and-reduced-order-models.ipynb index b9c3e1ab02..c7d9dccbbe 100644 --- a/examples/notebooks/Creating Models/4-comparing-full-and-reduced-order-models.ipynb +++ b/examples/notebooks/Creating Models/4-comparing-full-and-reduced-order-models.ipynb @@ -375,7 +375,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/notebooks/Creating Models/5-a-simple-SEI-model.ipynb b/examples/notebooks/Creating Models/5-a-simple-SEI-model.ipynb index 53388008bf..d9ae44aa71 100644 --- a/examples/notebooks/Creating Models/5-a-simple-SEI-model.ipynb +++ b/examples/notebooks/Creating Models/5-a-simple-SEI-model.ipynb @@ -249,7 +249,7 @@ "c_inf_dim = pybamm.Parameter(\"Bulk electrolyte solvent concentration\")\n", "\n", "def D_dim(cc):\n", - " return pybamm.FunctionParameter(\"Diffusivity\", cc)\n", + " return pybamm.FunctionParameter(\"Diffusivity\", {\"Solvent concentration [mol.m-3]\": cc})\n", "\n", "# dimensionless parameters\n", "k = k_dim * L_0_dim / D_dim(c_inf_dim)\n", @@ -591,7 +591,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b2a5f649a3b64685ad9510649f130829", + "model_id": "efe1fe18458a42d88056baf689f6da80", "version_major": 2, "version_minor": 0 }, @@ -653,7 +653,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/notebooks/parameter-values.ipynb b/examples/notebooks/parameter-values.ipynb index e8b21c0965..29d6f69b27 100644 --- a/examples/notebooks/parameter-values.ipynb +++ b/examples/notebooks/parameter-values.ipynb @@ -47,7 +47,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "parameter values are \n" + "parameter values are \n" ] } ], @@ -73,7 +73,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "parameter values are \n" + "parameter values are \n" ] } ], @@ -109,7 +109,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Marquis2019 chemistry set is {'chemistry': 'lithium-ion', 'cell': 'kokam_Marquis2019', 'anode': 'graphite_mcmb2528_Marquis2019', 'separator': 'separator_Marquis2019', 'cathode': 'lico2_Marquis2019', 'electrolyte': 'lipf6_Marquis2019', 'experiment': '1C_discharge_from_full_Marquis2019'}\n", + "Marquis2019 chemistry set is {'chemistry': 'lithium-ion', 'cell': 'kokam_Marquis2019', 'anode': 'graphite_mcmb2528_Marquis2019', 'separator': 'separator_Marquis2019', 'cathode': 'lico2_Marquis2019', 'electrolyte': 'lipf6_Marquis2019', 'experiment': '1C_discharge_from_full_Marquis2019', 'citation': 'marquis2019asymptotic'}\n", "Negative current collector thickness is 2.5e-05 m\n" ] } @@ -138,7 +138,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "parameter values are \n" + "parameter values are \n" ] } ], @@ -165,7 +165,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "parameter values are \n" + "parameter values are \n" ] } ], @@ -218,7 +218,7 @@ "a = pybamm.Parameter(\"a\")\n", "b = pybamm.Parameter(\"b\")\n", "c = pybamm.Parameter(\"c\")\n", - "func = pybamm.FunctionParameter(\"square function\", a)\n", + "func = pybamm.FunctionParameter(\"square function\", {\"a\": a})\n", "\n", "expr = a + b * c\n", "try:\n", @@ -329,7 +329,7 @@ "outputs": [ { "data": { - "image/png": "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fTfXXnttuf5WsUrH0I7H0JgxY8jIyOChhx76dV/p6el88sknDB+uzwYiUjM99tEi/u/HLqT0uImhax4PDbtNSQ0lnxWYN5WnCu5hwB+Ab8xsdrjtBqAtgLs/BrwFDAEWAluA88N968zsNuCr8PPGuPu66IW/E2nDIW04z36+mFve+Jb39jmSjpWyYxGRqmnTpk2cdtppPPjggyWuMkLo6tWECRP46KOPgFDyUPrK47vvvssbb7zBfffdB4SWBlm6dCldunTh6aefJi0tjYsvvpjDDjvs1+ecdNJJJCcnk5yczIABA/jf//5H8+bN2dVUjDvuuIM77riDu+66i4ceeohbb721RP9tt92GmTFr1ixGjx5drvmGzZs3jzifVMqvKh1Der9FpCZ765uV/N+0+Qzr2YohZw6BHVfarDC7TEDd/VNgpzXJPXRmv7yMvgnAhD2KLgoGd2/B6Knf8p9vVnLVPvWDCkNEpFzKe5Up2goKCjjttNM455xzOPXUUyNus33YYlncnVdffZVOnTrt0LdgwQLq1au3wwf+0gmImZGcnFxiHcbzzz+fWbNm0apVqxLz+ADOOecchgwZskMCuv11txchKs/SGnl5eTsMCa2KdAzteAxFUl3ebxGR3TVzyTquenk2B7ZtyL2np1X68lO7VQW3KmreIImD2zXmrW9WBh2KiEhMcndGjBhBly5d+Mtf/rLHr3P88cfzz3/+89erjbNmzQIgJyeHkSNH8vHHH7N27VomTZr063OmTJlCXl4ea9eu5cMPP+Tggw/mgAMOYPHixb9u89RTT5UoIrNgwYISz+/cufNux1q/fn02btxYou2HH36ge/fuu/1aEvvHkN5vEZGQRdmbuPCZDFo3TOaJcw8mKTG+0mOo9gkowNAeLflh9SYWrN64641FRGqYzz77jGeffZb333+fXr160atXrx2uNJbHzTffTEFBAWlpaXTr1o2bb74ZgKuvvprLL7+cAw44gCeffJLrrruONWvWAJCWlsaAAQM45JBDuPnmm2nVqhV169alQ4cOLFy4MOJ+rrvuOrp3705aWhrvvvtumUt+7ExaWhrx8fH07Nnz1yJEH3zwAUOHDt3t15LYP4YGDBjAvHnzfi1CBHq/RaTm+XlTPuc99RVxZjx9/sE0rltr10+qAFbR63DtifT0dM/IyIja663ZkEffu6Zz5cCO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QKF3vwpYyyiclJ1nH6Zl3f8IsNy7PwHY4PWl0rs/IWzxe8sdSuYEcgvbNvCefxa5T+w/j54nlD8blvU/iD7MIHMO2sm9po8DSrPuPw2cHcL4p3a/dsgknbe87ZHX6y8MffDml5M5/2Vc2LaBtz7vBG7sZ5lIvBeGcZ1EOkcOog+hzZN5xh/aHJlvH7zllCdHL/7Q58g8+qA8OfKxhypPjsoGHMaVeS6ZUZzeABZ505aQ+RUUoAj4vffGfBaYndV2kdfuNeCckMb/KLAZeNG7LfWmnwys8t6Iq4DPh3gb/A/wshfrE8ChWW0/522bdcBnwxi/9/9i4Pu92oViG5D5la0B6CRzTP7ngcuAy7z5BvzU698q4Pgwrf88+3AbsD3rfbDSmz7bW/8vea+xRSGN/6tZ74HlZH1J8Hv9hbEP3jKfITMwTXa7sGyDU8mcY/OvrNfJuVF7L4zAeol0jsyzD6HOk3nEH+ocmU8fvP8Xozw5WvGHOkfm2QflyZGPP1R5svvXBhEREREREZERFeVzQEVERERERCRCVICKiIiIiIhIIFSAioiIiIiISCBUgIqIiIiIiEggVICKiIiIiIhIIFSAioxhZlZhZl8e7ThERETCSHlSJHgqQEXGtgpAiVVERMSf8qRIwFSAioxt3wcONLMXzeyHox2MiIhIyChPigTMnHOjHYOIjBAzmwXc75ybO8qhiIiIhI7ypEjwtAdUREREREREAqECVERERERERAKhAlRkbNsBlI52ECIiIiGlPCkSMBWgImOYc64R+IeZrdbgCiIiIj0pT4oET4MQiYiIiIiISCC0B1REREREREQCoQJUREREREREAqECVERERERERAKhAlREREREREQCoQJUREREREREAqECVERERERERAKhAlREREREREQC8f/tjayVNCsiOgAAAABJRU5ErkJggg==\n", + "image/png": "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fTfXXnttuf5WsUrH0I7H0JgxY8jIyOChhx76dV/p6el88sknDB+uzwYiUjM99tEi/u/HLqT0uImhax4PDbtNSQ0lnxWYN5WnCu5hwB+Ab8xsdrjtBqAtgLs/BrwFDAEWAluA88N968zsNuCr8PPGuPu66IW/E2nDIW04z36+mFve+Jb39jmSjpWyYxGRqmnTpk2cdtppPPjggyWuMkLo6tWECRP46KOPgFDyUPrK47vvvssbb7zBfffdB4SWBlm6dCldunTh6aefJi0tjYsvvpjDDjvs1+ecdNJJJCcnk5yczIABA/jf//5H8+bN2dVUjDvuuIM77riDu+66i4ceeohbb721RP9tt92GmTFr1ixGjx5drvmGzZs3jzifVMqvKh1Der9FpCZ765uV/N+0+Qzr2YohZw6BHVfarDC7TEDd/VNgpzXJPXRmv7yMvgnAhD2KLgoGd2/B6Knf8p9vVnLVPvWDCkNEpFzKe5Up2goKCjjttNM455xzOPXUUyNus33YYlncnVdffZVOnTrt0LdgwQLq1au3wwf+0gmImZGcnFxiHcbzzz+fWbNm0apVqxLz+ADOOecchgwZskMCuv11txchKs/SGnl5eTsMCa2KdAzteAxFUl3ebxGR3TVzyTquenk2B7ZtyL2np1X68lO7VQW3KmreIImD2zXmrW9WBh2KiEhMcndGjBhBly5d+Mtf/rLHr3P88cfzz3/+89erjbNmzQIgJyeHkSNH8vHHH7N27VomTZr063OmTJlCXl4ea9eu5cMPP+Tggw/mgAMOYPHixb9u89RTT5UoIrNgwYISz+/cufNux1q/fn02btxYou2HH36ge/fuu/1aEvvHkN5vEZGQRdmbuPCZDFo3TOaJcw8mKTG+0mOo9gkowNAeLflh9SYWrN64641FRGqYzz77jGeffZb333+fXr160atXrx2uNJbHzTffTEFBAWlpaXTr1o2bb74ZgKuvvprLL7+cAw44gCeffJLrrruONWvWAJCWlsaAAQM45JBDuPnmm2nVqhV169alQ4cOLFy4MOJ+rrvuOrp3705aWhrvvvtumUt+7ExaWhrx8fH07Nnz1yJEH3zwAUOHDt3t15LYP4YGDBjAvHnzfi1CBHq/RaTm+XlTPuc99RVxZjx9/sE0rltr10+qAFbR63DtifT0dM/IyIja663ZkEffu6Zz5cCO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QKF3vwpYyyiclJ1nH6Zl3f8IsNy7PwHY4PWl0rs/IWzxe8sdSuYEcgvbNvCefxa5T+w/j54nlD8blvU/iD7MIHMO2sm9po8DSrPuPw2cHcL4p3a/dsgknbe87ZHX6y8MffDml5M5/2Vc2LaBtz7vBG7sZ5lIvBeGcZ1EOkcOog+hzZN5xh/aHJlvH7zllCdHL/7Q58g8+qA8OfKxhypPjsoGHMaVeS6ZUZzeABZ505aQ+RUUoAj4vffGfBaYndV2kdfuNeCckMb/KLAZeNG7LfWmnwys8t6Iq4DPh3gb/A/wshfrE8ChWW0/522bdcBnwxi/9/9i4Pu92oViG5D5la0B6CRzTP7ngcuAy7z5BvzU698q4Pgwrf88+3AbsD3rfbDSmz7bW/8vea+xRSGN/6tZ74HlZH1J8Hv9hbEP3jKfITMwTXa7sGyDU8mcY/OvrNfJuVF7L4zAeol0jsyzD6HOk3nEH+ocmU8fvP8Xozw5WvGHOkfm2QflyZGPP1R5svvXBhEREREREZERFeVzQEVERERERCRCVICKiIiIiIhIIFSAioiIiIiISCBUgIqIiIiIiEggVICKiIiIiIhIIFSAioxhZlZhZl8e7ThERETCSHlSJHgqQEXGtgpAiVVERMSf8qRIwFSAioxt3wcONLMXzeyHox2MiIhIyChPigTMnHOjHYOIjBAzmwXc75ybO8qhiIiIhI7ypEjwtAdUREREREREAqECVERERERERAKhAlRkbNsBlI52ECIiIiGlPCkSMBWgImOYc64R+IeZrdbgCiIiIj0pT4oET4MQiYiIiIiISCC0B1REREREREQCoQJUREREREREAqECVERERERERAKhAlREREREREQCoQJUREREREREAqECVERERERERAKhAlREREREREQC8f/tjayVNCsiOgAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -401,7 +401,7 @@ { "data": { "text/plain": [ - "{Variable(-0x4a013e7c3cccb9f1, u, children=[], domain=[], auxiliary_domains={}): Multiplication(0x786762aded2a40a5, *, children=['-a', 'y[0:1]'], domain=[], auxiliary_domains={})}" + "{Variable(-0x2cdf9e1c15ea083, u, children=[], domain=[], auxiliary_domains={}): Multiplication(0x1d65a94f24de5058, *, children=['-a', 'y[0:1]'], domain=[], auxiliary_domains={})}" ] }, "execution_count": 12, @@ -481,7 +481,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/scripts/create-model.py b/examples/scripts/create-model.py index 8c2d702835..73b9161351 100644 --- a/examples/scripts/create-model.py +++ b/examples/scripts/create-model.py @@ -18,7 +18,7 @@ def D_dim(cc): - return pybamm.FunctionParameter("Diffusivity", cc) + return pybamm.FunctionParameter("Diffusivity", {"Concentration [mol.m-3]": cc}) # dimensionless parameters diff --git a/squared.py b/squared.py new file mode 100644 index 0000000000..9784968d2b --- /dev/null +++ b/squared.py @@ -0,0 +1,3 @@ + +def squared(x): + return x ** 2 From 1e682fb13142c808d3bcee04d4cd932baba91702 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Fri, 20 Mar 2020 16:47:54 +0000 Subject: [PATCH 11/18] #880 implemented robs comments --- pybamm/expression_tree/parameter.py | 8 +++-- pybamm/models/base_model.py | 32 ++++++++++++------- .../function_control_external_circuit.py | 2 +- squared.py | 3 -- 4 files changed, 28 insertions(+), 17 deletions(-) delete mode 100644 squared.py diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index f214683c98..f249dd64d2 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -50,7 +50,9 @@ class FunctionParameter(pybamm.Symbol): name of the node inputs : dict A dictionary with string keys and :class:`pybamm.Symbol` values representing - the function inputs. + the function inputs. The string keys should provide a reasonable description + of what the input to the function is + (e.g. "Electrolyte concentration [mol.m-3]") diff_variable : :class:`pybamm.Symbol`, optional if diff_variable is specified, the FunctionParameter node will be replaced by a :class:`pybamm.Function` and then differentiated with respect to diff_variable. @@ -93,7 +95,9 @@ def input_names(self, inp=None): for i in inp: if i.__class__ is not str: raise TypeError( - "Inputs must be a provided as a list of strings" + "Inputs must be a provided as" + + "a dictionary of the form:" + + "{{str: :class:`pybamm.Symbol`}}" ) else: raise TypeError("Inputs must be a provided as a list of strings") diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index f7efa40dfa..013a17c31d 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -398,13 +398,15 @@ def check_for_time_derivatives(self): for node in eq.pre_order(): if isinstance(node, pybamm.VariableDot): raise pybamm.ModelError( - "time derivative of variable found ({}) in rhs equation {}" - .format(node, key) + "time derivative of variable found ({}) in rhs equation {}".format( + node, key + ) ) if isinstance(node, pybamm.StateVectorDot): raise pybamm.ModelError( - "time derivative of state vector found ({}) in rhs equation {}" - .format(node, key) + "time derivative of state vector found ({}) in rhs equation {}".format( + node, key + ) ) # Check that no variable time derivatives exist in the algebraic equations @@ -441,8 +443,11 @@ def check_well_determined(self, post_discretisation): [x.id for x in eqn.pre_order() if isinstance(x, pybamm.Variable)] ) vars_in_eqns.update( - [x.get_variable().id for x in eqn.pre_order() - if isinstance(x, pybamm.VariableDot)] + [ + x.get_variable().id + for x in eqn.pre_order() + if isinstance(x, pybamm.VariableDot) + ] ) for var, eqn in self.algebraic.items(): vars_in_algebraic_keys.update( @@ -452,8 +457,11 @@ def check_well_determined(self, post_discretisation): [x.id for x in eqn.pre_order() if isinstance(x, pybamm.Variable)] ) vars_in_eqns.update( - [x.get_variable().id for x in eqn.pre_order() - if isinstance(x, pybamm.VariableDot)] + [ + x.get_variable().id + for x in eqn.pre_order() + if isinstance(x, pybamm.VariableDot) + ] ) for var, side_eqn in self.boundary_conditions.items(): for side, (eqn, typ) in side_eqn.items(): @@ -461,8 +469,11 @@ def check_well_determined(self, post_discretisation): [x.id for x in eqn.pre_order() if isinstance(x, pybamm.Variable)] ) vars_in_eqns.update( - [x.get_variable().id for x in eqn.pre_order() - if isinstance(x, pybamm.VariableDot)] + [ + x.get_variable().id + for x in eqn.pre_order() + if isinstance(x, pybamm.VariableDot) + ] ) # If any keys are repeated between rhs and algebraic then the model is # overdetermined @@ -683,5 +694,4 @@ def find_symbol_in_model(model, name): dic_return = find_symbol_in_dict(dic, name) if dic_return: return dic_return - print(name + " not found") return None diff --git a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py index e89fdda0b0..079b0fb4c5 100644 --- a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py @@ -58,7 +58,7 @@ def __init__(self, param): def constant_voltage(variables): V = variables["Terminal voltage [V]"] - return V - pybamm.FunctionParameter("Voltage function [V]", {"Times [s]": pybamm.t}) + return V - pybamm.FunctionParameter("Voltage function [V]", {"Time [s]": pybamm.t}) class PowerFunctionControl(FunctionControl): diff --git a/squared.py b/squared.py deleted file mode 100644 index 9784968d2b..0000000000 --- a/squared.py +++ /dev/null @@ -1,3 +0,0 @@ - -def squared(x): - return x ** 2 From 4a230486ba66c0bc9ac10337b9c50a59998843bd Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sun, 22 Mar 2020 11:07:31 +0000 Subject: [PATCH 12/18] #881 updated tests --- pybamm/expression_tree/parameter.py | 12 ++++++---- .../test_expression_tree/test_parameter.py | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index f249dd64d2..a5f8ab78ca 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -95,12 +95,16 @@ def input_names(self, inp=None): for i in inp: if i.__class__ is not str: raise TypeError( - "Inputs must be a provided as" + - "a dictionary of the form:" + - "{{str: :class:`pybamm.Symbol`}}" + "Inputs must be a provided as" + + "a dictionary of the form:" + + "{{str: :class:`pybamm.Symbol`}}" ) else: - raise TypeError("Inputs must be a provided as a list of strings") + raise TypeError( + "Inputs must be a provided as" + + " a dictionary of the form:" + + "{{str: :class:`pybamm.Symbol`}}" + ) self._input_names = inp diff --git a/tests/unit/test_expression_tree/test_parameter.py b/tests/unit/test_expression_tree/test_parameter.py index 39b54c1289..349411438b 100644 --- a/tests/unit/test_expression_tree/test_parameter.py +++ b/tests/unit/test_expression_tree/test_parameter.py @@ -38,6 +38,29 @@ def test_evaluate_for_shape(self): func = pybamm.FunctionParameter("func", {"2a": 2 * a}) self.assertIsInstance(func.evaluate_for_shape(), numbers.Number) + def test_print_input_names(self): + var = pybamm.Variable("var") + func = pybamm.FunctionParameter("a", {"var": var}) + func.input_names + + def test_set_input_names(self): + + var = pybamm.Variable("var") + func = pybamm.FunctionParameter("a", {"var": var}) + + new_input_names = ["first", "second"] + func.input_names = new_input_names + + self.assertEqual(func._input_names, new_input_names) + + with self.assertRaises(TypeError): + new_input_names = {"wrong": "input type"} + func.input_names = new_input_names + + with self.assertRaises(TypeError): + new_input_names = [var] + func.input_names = new_input_names + if __name__ == "__main__": print("Add -v for more debug output") From 34c46d598b7b27422aa184a3421c02e41370bd75 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sun, 22 Mar 2020 12:25:17 +0000 Subject: [PATCH 13/18] #880 improve covereage --- pybamm/models/base_model.py | 4 ---- .../test_expression_tree/test_parameter.py | 13 +++++++++++++ tests/unit/test_models/test_base_model.py | 18 +++++++----------- tests/unit/test_models/test_model_info.py | 2 ++ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 013a17c31d..ece7c0d1ae 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -672,8 +672,6 @@ def find_symbol_in_tree(tree, name): child_return = find_symbol_in_tree(child, name) if child_return: return child_return - else: - return None def find_symbol_in_dict(dic, name): @@ -681,7 +679,6 @@ def find_symbol_in_dict(dic, name): tree_return = find_symbol_in_tree(tree, name) if tree_return: return tree_return - return None def find_symbol_in_model(model, name): @@ -694,4 +691,3 @@ def find_symbol_in_model(model, name): dic_return = find_symbol_in_dict(dic, name) if dic_return: return dic_return - return None diff --git a/tests/unit/test_expression_tree/test_parameter.py b/tests/unit/test_expression_tree/test_parameter.py index 349411438b..64e3fa6c4e 100644 --- a/tests/unit/test_expression_tree/test_parameter.py +++ b/tests/unit/test_expression_tree/test_parameter.py @@ -38,11 +38,24 @@ def test_evaluate_for_shape(self): func = pybamm.FunctionParameter("func", {"2a": 2 * a}) self.assertIsInstance(func.evaluate_for_shape(), numbers.Number) + def test_copy(self): + a = pybamm.Parameter("a") + func = pybamm.FunctionParameter("func", {"2a": 2 * a}) + + new_func = func.new_copy() + self.assertEqual(func._input_names, new_func._input_names) + def test_print_input_names(self): var = pybamm.Variable("var") func = pybamm.FunctionParameter("a", {"var": var}) func.input_names + def test_get_children_domains(self): + var = pybamm.Variable("var", domain=["negative electrode"]) + var_2 = pybamm.Variable("var", domain=["positive electrode"]) + with self.assertRaises(pybamm.DomainError): + pybamm.FunctionParameter("a", {"var": var, "var 2": var_2}) + def test_set_input_names(self): var = pybamm.Variable("var") diff --git a/tests/unit/test_models/test_base_model.py b/tests/unit/test_models/test_base_model.py index 2f2b37d05a..c1189c2f22 100644 --- a/tests/unit/test_models/test_base_model.py +++ b/tests/unit/test_models/test_base_model.py @@ -92,6 +92,7 @@ def test_variables_set_get(self): variables = {"c": "alpha", "d": "beta"} model.variables = variables self.assertEqual(variables, model.variables) + self.assertEqual(model.variable_names, list(variables.keys())) def test_jac_set_get(self): model = pybamm.BaseModel() @@ -253,8 +254,7 @@ def test_check_well_posedness_variables(self): model.rhs = {c: d.diff(pybamm.t), d: -1} model.initial_conditions = {c: 1, d: 1} with self.assertRaisesRegex( - pybamm.ModelError, - "time derivative of variable found", + pybamm.ModelError, "time derivative of variable found", ): model.check_well_posedness() @@ -266,8 +266,7 @@ def test_check_well_posedness_variables(self): } model.initial_conditions = {c: 1, d: 1} with self.assertRaisesRegex( - pybamm.ModelError, - "time derivative of variable found", + pybamm.ModelError, "time derivative of variable found", ): model.check_well_posedness() @@ -276,8 +275,7 @@ def test_check_well_posedness_variables(self): model.rhs = {c: d.diff(pybamm.t), d: -1} model.initial_conditions = {c: 1, d: 1} with self.assertRaisesRegex( - pybamm.ModelError, - "time derivative of variable found", + pybamm.ModelError, "time derivative of variable found", ): model.check_well_posedness() @@ -285,11 +283,10 @@ def test_check_well_posedness_variables(self): model = pybamm.BaseModel() model.algebraic = { d: 5 * pybamm.StateVector(slice(0, 15)) - 1, - c: 5 * pybamm.StateVectorDot(slice(0, 15)) - 1 + c: 5 * pybamm.StateVectorDot(slice(0, 15)) - 1, } with self.assertRaisesRegex( - pybamm.ModelError, - "time derivative of state vector found", + pybamm.ModelError, "time derivative of state vector found", ): model.check_well_posedness(post_discretisation=True) @@ -298,8 +295,7 @@ def test_check_well_posedness_variables(self): model.rhs = {c: 5 * pybamm.StateVectorDot(slice(0, 15)) - 1} model.initial_conditions = {c: 1} with self.assertRaisesRegex( - pybamm.ModelError, - "time derivative of state vector found", + pybamm.ModelError, "time derivative of state vector found", ): model.check_well_posedness(post_discretisation=True) diff --git a/tests/unit/test_models/test_model_info.py b/tests/unit/test_models/test_model_info.py index c9f2cfbc75..58c5c67c52 100644 --- a/tests/unit/test_models/test_model_info.py +++ b/tests/unit/test_models/test_model_info.py @@ -14,6 +14,8 @@ def test_find_parameter_info(self): model = pybamm.lithium_ion.DFN() model.info("Negative electrode diffusivity [m2.s-1]") + model.info("Not a parameter") + if __name__ == "__main__": print("Add -v for more debug output") From 5668b7633d4c31290c934cf9bc445d12716f45ee Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sun, 22 Mar 2020 12:29:14 +0000 Subject: [PATCH 14/18] #880 fixed flake8 in base_model --- pybamm/models/base_model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index ece7c0d1ae..5d0efe1029 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -398,15 +398,13 @@ def check_for_time_derivatives(self): for node in eq.pre_order(): if isinstance(node, pybamm.VariableDot): raise pybamm.ModelError( - "time derivative of variable found ({}) in rhs equation {}".format( - node, key - ) + "time derivative of variable" + + "found ({}) in rhs equation {}".format(node, key) ) if isinstance(node, pybamm.StateVectorDot): raise pybamm.ModelError( - "time derivative of state vector found ({}) in rhs equation {}".format( - node, key - ) + "time derivative of state vector" + + "found ({}) in rhs equation {}".format(node, key) ) # Check that no variable time derivatives exist in the algebraic equations From 35e1fdb0d6a2b722d4a2dee38740a376d01940a3 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Sun, 22 Mar 2020 14:08:24 +0000 Subject: [PATCH 15/18] #880 fixed tests --- pybamm/models/base_model.py | 4 ++-- tests/unit/test_models/test_base_model.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 5d0efe1029..718c4bb348 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -399,12 +399,12 @@ def check_for_time_derivatives(self): if isinstance(node, pybamm.VariableDot): raise pybamm.ModelError( "time derivative of variable" - + "found ({}) in rhs equation {}".format(node, key) + + " found ({}) in rhs equation {}".format(node, key) ) if isinstance(node, pybamm.StateVectorDot): raise pybamm.ModelError( "time derivative of state vector" - + "found ({}) in rhs equation {}".format(node, key) + + " found ({}) in rhs equation {}".format(node, key) ) # Check that no variable time derivatives exist in the algebraic equations diff --git a/tests/unit/test_models/test_base_model.py b/tests/unit/test_models/test_base_model.py index c1189c2f22..fe642ec57b 100644 --- a/tests/unit/test_models/test_base_model.py +++ b/tests/unit/test_models/test_base_model.py @@ -92,7 +92,7 @@ def test_variables_set_get(self): variables = {"c": "alpha", "d": "beta"} model.variables = variables self.assertEqual(variables, model.variables) - self.assertEqual(model.variable_names, list(variables.keys())) + self.assertEqual(model.variable_names(), list(variables.keys())) def test_jac_set_get(self): model = pybamm.BaseModel() From 2a4f7925081ca7eab1972aae935b31401998a933 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Mon, 30 Mar 2020 09:56:59 +0100 Subject: [PATCH 16/18] #880 fix tino comment --- pybamm/expression_tree/parameter.py | 3 +++ pybamm/models/base_model.py | 2 +- tests/unit/test_expression_tree/test_parameter.py | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index a5f8ab78ca..eca440ebd2 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -84,6 +84,9 @@ def __init__( @property def input_names(self): + return self._input_names + + def print_input_names(self): if self._input_names: for inp in self._input_names: print(inp) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 718c4bb348..410d5a9a68 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -645,7 +645,7 @@ def info(self, symbol_name): if isinstance(symbol, pybamm.FunctionParameter): print("") print("Inputs:") - symbol.input_names + symbol.print_input_names() print(div) diff --git a/tests/unit/test_expression_tree/test_parameter.py b/tests/unit/test_expression_tree/test_parameter.py index 64e3fa6c4e..ee3231a0d8 100644 --- a/tests/unit/test_expression_tree/test_parameter.py +++ b/tests/unit/test_expression_tree/test_parameter.py @@ -43,12 +43,12 @@ def test_copy(self): func = pybamm.FunctionParameter("func", {"2a": 2 * a}) new_func = func.new_copy() - self.assertEqual(func._input_names, new_func._input_names) + self.assertEqual(func.input_names, new_func.input_names) def test_print_input_names(self): var = pybamm.Variable("var") func = pybamm.FunctionParameter("a", {"var": var}) - func.input_names + func.print_input_names() def test_get_children_domains(self): var = pybamm.Variable("var", domain=["negative electrode"]) @@ -64,7 +64,7 @@ def test_set_input_names(self): new_input_names = ["first", "second"] func.input_names = new_input_names - self.assertEqual(func._input_names, new_input_names) + self.assertEqual(func.input_names, new_input_names) with self.assertRaises(TypeError): new_input_names = {"wrong": "input type"} From f858ce4604cacc7b31032a0d40872384a3dc97d0 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Mon, 30 Mar 2020 11:02:08 +0100 Subject: [PATCH 17/18] #801 fixed small bug --- .../test_full_battery_models/test_lithium_ion/test_spme.py | 4 ++++ tests/unit/test_parameters/test_parameter_values.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py index 0709b96686..b5f5a873ba 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py @@ -8,6 +8,10 @@ import unittest +pybamm.set_logging_level("DEBUG") +pybamm.settings.debug_mode = True + + class TestSPMe(unittest.TestCase): def test_basic_processing(self): options = {"thermal": "isothermal"} diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py index 01fce225fb..761630a59c 100644 --- a/tests/unit/test_parameters/test_parameter_values.py +++ b/tests/unit/test_parameters/test_parameter_values.py @@ -322,7 +322,7 @@ def test_process_function_parameter(self): self.assertEqual(processed_diff_func.evaluate(u={"a": 3}), 123) # function parameter that returns a python float - func = pybamm.FunctionParameter("float_func", a) + func = pybamm.FunctionParameter("float_func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 42) From 7e66610c1c1a5cb7a5f9caf69e4a41d133e48601 Mon Sep 17 00:00:00 2001 From: Scott Marquis Date: Mon, 30 Mar 2020 11:54:20 +0100 Subject: [PATCH 18/18] #880 fixed tests --- pybamm/parameters/standard_parameters_lead_acid.py | 7 ++++--- pybamm/parameters/standard_parameters_lithium_ion.py | 7 +++++-- .../test_full_battery_models/test_lithium_ion/test_spme.py | 4 ---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pybamm/parameters/standard_parameters_lead_acid.py b/pybamm/parameters/standard_parameters_lead_acid.py index b85cc9bf0c..15e45d3fcd 100644 --- a/pybamm/parameters/standard_parameters_lead_acid.py +++ b/pybamm/parameters/standard_parameters_lead_acid.py @@ -171,13 +171,14 @@ def t_plus(c_e): - "Dimensionless transference number (i.e. c_e is dimensional)" - return pybamm.FunctionParameter("Cation transference number", c_e * c_e_typ) + "Dimensionless transference number (i.e. c_e is dimensionless)" + inputs = {"Electrolyte concentration [mol.m-3]": c_e * c_e_typ} + return pybamm.FunctionParameter("Cation transference number", inputs) def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" - inputs = {"Electrolyte concentration [mol.m-3": c_e} + inputs = {"Electrolyte concentration [mol.m-3]": c_e} return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", inputs) diff --git a/pybamm/parameters/standard_parameters_lithium_ion.py b/pybamm/parameters/standard_parameters_lithium_ion.py index ee5c322105..2b3a8323bc 100644 --- a/pybamm/parameters/standard_parameters_lithium_ion.py +++ b/pybamm/parameters/standard_parameters_lithium_ion.py @@ -373,11 +373,14 @@ def U_p_dimensional(sto, T): def t_plus(c_e): - return pybamm.FunctionParameter("Cation transference number", c_e) + "Dimensionless transference number (i.e. c_e is dimensionless)" + inputs = {"Electrolyte concentration [mol.m-3]": c_e * c_e_typ} + return pybamm.FunctionParameter("Cation transference number", inputs) def one_plus_dlnf_dlnc(c_e): - return pybamm.FunctionParameter("1 + dlnf/dlnc", c_e) + inputs = {"Electrolyte concentration [mol.m-3]": c_e * c_e_typ} + return pybamm.FunctionParameter("1 + dlnf/dlnc", inputs) beta_surf = pybamm.Scalar(0) diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py index b5f5a873ba..0709b96686 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_spme.py @@ -8,10 +8,6 @@ import unittest -pybamm.set_logging_level("DEBUG") -pybamm.settings.debug_mode = True - - class TestSPMe(unittest.TestCase): def test_basic_processing(self): options = {"thermal": "isothermal"}