Skip to content

Commit

Permalink
#1575 add discharge energy
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinsulzer committed Mar 8, 2022
1 parent 5473db5 commit 3fea944
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 65 deletions.
2 changes: 1 addition & 1 deletion examples/scripts/compare_lithium_ion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
sims.append(sim)

# plot
pybamm.dynamic_plot(sims)
pybamm.dynamic_plot(sims, ["Discharge energy [W.h]"])
21 changes: 13 additions & 8 deletions pybamm/models/full_battery_models/lead_acid/basic_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def __init__(self, name="Basic full model"):
# Variables
######################
# Variables that depend on time only are created without a domain
Q = pybamm.Variable("Discharge capacity [A.h]")
Q_Ah = pybamm.Variable("Discharge capacity [A.h]")
Q_Wh = pybamm.Variable("Discharge energy [W.h]")
# Variables that vary spatially are created with a domain
c_e_n = pybamm.Variable(
"Negative electrolyte concentration", domain="negative electrode"
Expand Down Expand Up @@ -121,15 +122,21 @@ def __init__(self, name="Basic full model"):
)
j = pybamm.concatenation(j_n, j_s, j_p)

V = pybamm.boundary_value(phi_s_p, "right")
pot = param.potential_scale
V_dim = param.U_p_ref - param.U_n_ref + pot * V

######################
# State of Charge
######################
I = param.dimensional_current_with_time
# The `rhs` dictionary contains differential equations, with the key being the
# variable in the d/dt
self.rhs[Q] = I * param.timescale / 3600
self.rhs[Q_Ah] = I * param.timescale / 3600
self.rhs[Q_Wh] = I * V_dim * param.timescale / 3600
# Initial conditions must be provided for the ODEs
self.initial_conditions[Q] = pybamm.Scalar(0)
self.initial_conditions[Q_Ah] = pybamm.Scalar(0)
self.initial_conditions[Q_Wh] = pybamm.Scalar(0)

######################
# Convection
Expand Down Expand Up @@ -270,10 +277,8 @@ def __init__(self, name="Basic full model"):
######################
# (Some) variables
######################
voltage = pybamm.boundary_value(phi_s_p, "right")
# The `variables` dictionary contains all variables that might be useful for
# visualising the solution of the model
pot = param.potential_scale

self.variables = {
"Electrolyte concentration": c_e,
Expand All @@ -283,15 +288,15 @@ def __init__(self, name="Basic full model"):
"Positive electrode potential [V]": param.U_p_ref
- param.U_n_ref
+ pot * phi_s_p,
"Terminal voltage [V]": param.U_p_ref - param.U_n_ref + pot * voltage,
"Terminal voltage [V]": V_dim,
"Porosity": eps,
"Volume-averaged velocity": v,
"X-averaged separator transverse volume-averaged velocity": div_V_s,
}
self.events.extend(
[
pybamm.Event("Minimum voltage", voltage - param.voltage_low_cut),
pybamm.Event("Maximum voltage", voltage - param.voltage_high_cut),
pybamm.Event("Minimum voltage", V - param.voltage_low_cut),
pybamm.Event("Maximum voltage", V - param.voltage_high_cut),
]
)

Expand Down
24 changes: 17 additions & 7 deletions pybamm/models/full_battery_models/lithium_ion/basic_dfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def __init__(self, name="Doyle-Fuller-Newman model"):
# Variables
######################
# Variables that depend on time only are created without a domain
Q = pybamm.Variable("Discharge capacity [A.h]")
Q_Ah = pybamm.Variable("Discharge capacity [A.h]")
Q_Wh = pybamm.Variable("Discharge energy [W.h]")
# Variables that vary spatially are created with a domain
c_e_n = pybamm.Variable(
"Negative electrolyte concentration", domain="negative electrode"
Expand Down Expand Up @@ -71,6 +72,11 @@ def __init__(self, name="Doyle-Fuller-Newman model"):
phi_s_p = pybamm.Variable(
"Positive electrode potential", domain="positive electrode"
)
V = pybamm.boundary_value(phi_s_p, "right")
pot_scale = self.param.potential_scale
U_ref = self.param.U_p_ref - self.param.U_n_ref
V_dim = U_ref + pot_scale * V

# Particle concentrations are variables on the particle domain, but also vary in
# the x-direction (electrode domain) and so must be provided with auxiliary
# domains
Expand Down Expand Up @@ -149,9 +155,11 @@ def __init__(self, name="Doyle-Fuller-Newman model"):
I = param.dimensional_current_with_time
# The `rhs` dictionary contains differential equations, with the key being the
# variable in the d/dt
self.rhs[Q] = I * param.timescale / 3600
self.rhs[Q_Ah] = I * param.timescale / 3600
self.rhs[Q_Wh] = I * V_dim * param.timescale / 3600
# Initial conditions must be provided for the ODEs
self.initial_conditions[Q] = pybamm.Scalar(0)
self.initial_conditions[Q_Ah] = pybamm.Scalar(0)
self.initial_conditions[Q_Wh] = pybamm.Scalar(0)

######################
# Particles
Expand Down Expand Up @@ -269,22 +277,24 @@ def __init__(self, name="Doyle-Fuller-Newman model"):
######################
# (Some) variables
######################
voltage = pybamm.boundary_value(phi_s_p, "right")
# The `variables` dictionary contains all variables that might be useful for
# visualising the solution of the model
self.variables = {
"Discharge capacity [A.h]": Q_Ah,
"Discharge energy [W.h]": Q_Wh,
"Negative particle surface concentration": c_s_surf_n,
"Electrolyte concentration": c_e,
"Positive particle surface concentration": c_s_surf_p,
"Current [A]": I,
"Negative electrode potential": phi_s_n,
"Electrolyte potential": phi_e,
"Positive electrode potential": phi_s_p,
"Terminal voltage": voltage,
"Terminal voltage": V,
"Terminal voltage [V]": V_dim,
}
self.events += [
pybamm.Event("Minimum voltage", voltage - param.voltage_low_cut),
pybamm.Event("Maximum voltage", voltage - param.voltage_high_cut),
pybamm.Event("Minimum voltage", V - param.voltage_low_cut),
pybamm.Event("Maximum voltage", V - param.voltage_high_cut),
]

def new_empty_copy(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"):
# Variables
######################
# Variables that depend on time only are created without a domain
Q = pybamm.Variable("Discharge capacity [A.h]")
Q_Ah = pybamm.Variable("Discharge capacity [A.h]")
Q_Wh = pybamm.Variable("Discharge energy [W.h]")

# Define some useful scalings
pot_scale = param.potential_scale
Expand Down Expand Up @@ -159,15 +160,30 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"):
j_s = pybamm.PrimaryBroadcast(0, "separator")
j = pybamm.concatenation(j_s, j_w)

######################
# (Some) variables
######################
vdrop_cell = pybamm.boundary_value(phi_s_w, "right") - ref_potential
vdrop_Li = -eta_Li - delta_phis_Li
voltage = vdrop_cell + vdrop_Li
voltage_dim = U_w_ref - U_Li_ref + pot_scale * voltage
c_e_total = pybamm.x_average(eps * c_e)
c_s_surf_w_av = pybamm.x_average(c_s_surf_w)

c_s_rav = pybamm.r_average(c_s_w)
c_s_vol_av = pybamm.x_average(eps_s_w * c_s_rav)

######################
# State of Charge
######################
I = param.dimensional_current_with_time
# The `rhs` dictionary contains differential equations, with the key being the
# variable in the d/dt
self.rhs[Q] = I * self.timescale / 3600
self.rhs[Q_Ah] = I * self.timescale / 3600
self.rhs[Q_Wh] = I * voltage_dim * self.timescale / 3600
# Initial conditions must be provided for the ODEs
self.initial_conditions[Q] = pybamm.Scalar(0)
self.initial_conditions[Q_Ah] = pybamm.Scalar(0)
self.initial_conditions[Q_Wh] = pybamm.Scalar(0)

######################
# Particles
Expand Down Expand Up @@ -271,19 +287,6 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"):

self.initial_conditions[phi_e] = param.U_n_ref / pot_scale

######################
# (Some) variables
######################
vdrop_cell = pybamm.boundary_value(phi_s_w, "right") - ref_potential
vdrop_Li = -eta_Li - delta_phis_Li
voltage = vdrop_cell + vdrop_Li
voltage_dim = U_w_ref - U_Li_ref + pot_scale * voltage
c_e_total = pybamm.x_average(eps * c_e)
c_s_surf_w_av = pybamm.x_average(c_s_surf_w)

c_s_rav = pybamm.r_average(c_s_w)
c_s_vol_av = pybamm.x_average(eps_s_w * c_s_rav)

# Cut-off voltage
self.events.append(
pybamm.Event(
Expand Down Expand Up @@ -322,6 +325,8 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"):
# visualising the solution of the model
self.variables = {
"Time [s]": self.timescale * pybamm.t,
"Discharge capacity [A.h]": Q_Ah,
"Discharge capacity [W.h]": Q_Wh,
"Positive particle surface concentration": c_s_surf_w,
"X-averaged positive particle surface concentration": c_s_surf_w_av,
"Positive particle concentration": c_s_w,
Expand Down
51 changes: 28 additions & 23 deletions pybamm/models/full_battery_models/lithium_ion/basic_spm.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@ def __init__(self, name="Single Particle Model"):
# Variables
######################
# Variables that depend on time only are created without a domain
Q = pybamm.Variable("Discharge capacity [A.h]")
Q_Ah = pybamm.Variable("Discharge capacity [A.h]")
Q_Wh = pybamm.Variable("Discharge energy [W.h]")
# Variables that vary spatially are created with a domain
c_s_n = pybamm.Variable(
"X-averaged negative particle concentration", domain="negative particle"
)
c_s_p = pybamm.Variable(
"X-averaged positive particle concentration", domain="positive particle"
)
# Surf takes the surface value of a variable, i.e. its boundary value on the
# right side. This is also accessible via `boundary_value(x, "right")`, with
# "left" providing the boundary value of the left side
c_s_surf_n = pybamm.surf(c_s_n)
c_s_surf_p = pybamm.surf(c_s_p)

# Constant temperature
T = param.T_init
Expand All @@ -60,15 +66,31 @@ def __init__(self, name="Single Particle Model"):
j_n = i_cell / param.l_n
j_p = -i_cell / param.l_p

# Interfacial reactions
j0_n = param.gamma_n * param.j0_n(1, c_s_surf_n, T) / param.C_r_n
j0_p = param.gamma_p * param.j0_p(1, c_s_surf_p, T) / param.C_r_p
eta_n = (2 / param.ne_n) * pybamm.arcsinh(j_n / (2 * j0_n))
eta_p = (2 / param.ne_p) * pybamm.arcsinh(j_p / (2 * j0_p))
phi_s_n = 0
phi_e = -eta_n - param.U_n(c_s_surf_n, T)
phi_s_p = eta_p + phi_e + param.U_p(c_s_surf_p, T)
V = phi_s_p

pot_scale = self.param.potential_scale
U_ref = self.param.U_p_ref - self.param.U_n_ref
V_dim = U_ref + pot_scale * V

######################
# State of Charge
######################
I = param.dimensional_current_with_time
# The `rhs` dictionary contains differential equations, with the key being the
# variable in the d/dt
self.rhs[Q] = I * param.timescale / 3600
self.rhs[Q_Ah] = I * param.timescale / 3600
self.rhs[Q_Wh] = I * V_dim * param.timescale / 3600
# Initial conditions must be provided for the ODEs
self.initial_conditions[Q] = pybamm.Scalar(0)
self.initial_conditions[Q_Ah] = pybamm.Scalar(0)
self.initial_conditions[Q_Wh] = pybamm.Scalar(0)

######################
# Particles
Expand All @@ -80,11 +102,7 @@ def __init__(self, name="Single Particle Model"):
N_s_p = -param.D_p(c_s_p, T) * pybamm.grad(c_s_p)
self.rhs[c_s_n] = -(1 / param.C_n) * pybamm.div(N_s_n)
self.rhs[c_s_p] = -(1 / param.C_p) * pybamm.div(N_s_p)
# Surf takes the surface value of a variable, i.e. its boundary value on the
# right side. This is also accessible via `boundary_value(x, "right")`, with
# "left" providing the boundary value of the left side
c_s_surf_n = pybamm.surf(c_s_n)
c_s_surf_p = pybamm.surf(c_s_p)

# Boundary conditions must be provided for equations with spatial derivatives
self.boundary_conditions[c_s_n] = {
"left": (pybamm.Scalar(0), "Neumann"),
Expand Down Expand Up @@ -138,27 +156,14 @@ def __init__(self, name="Single Particle Model"):
######################
# (Some) variables
######################
# Interfacial reactions
j0_n = param.gamma_n * param.j0_n(1, c_s_surf_n, T) / param.C_r_n
j0_p = param.gamma_p * param.j0_p(1, c_s_surf_p, T) / param.C_r_p
eta_n = (2 / param.ne_n) * pybamm.arcsinh(j_n / (2 * j0_n))
eta_p = (2 / param.ne_p) * pybamm.arcsinh(j_p / (2 * j0_p))
phi_s_n = 0
phi_e = -eta_n - param.U_n(c_s_surf_n, T)
phi_s_p = eta_p + phi_e + param.U_p(c_s_surf_p, T)
V = phi_s_p

pot_scale = self.param.potential_scale
U_ref = self.param.U_p_ref - self.param.U_n_ref
V_dim = U_ref + pot_scale * V

whole_cell = ["negative electrode", "separator", "positive electrode"]
# The `variables` dictionary contains all variables that might be useful for
# visualising the solution of the model
# Primary broadcasts are used to broadcast scalar quantities across a domain
# into a vector of the right shape, for multiplying with other vectors
self.variables = {
"Discharge capacity [A.h]": Q,
"Discharge capacity [A.h]": Q_Ah,
"Discharge energy [W.h]": Q_Wh,
"Negative particle surface concentration": pybamm.PrimaryBroadcast(
c_s_surf_n, "negative electrode"
),
Expand Down
5 changes: 3 additions & 2 deletions pybamm/models/standard_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

class StandardVariables:
def __init__(self):
# Discharge capacity
self.Q = pybamm.Variable("Discharge capacity [A.h]")
# Discharge capacity and energy
self.Q_Ah = pybamm.Variable("Discharge capacity [A.h]")
self.Q_Wh = pybamm.Variable("Discharge energy [W.h]")

# Electrolyte concentration
self.c_e_n = pybamm.Variable(
Expand Down
24 changes: 16 additions & 8 deletions pybamm/models/submodels/external_circuit/base_external_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@ def __init__(self, param):
super().__init__(param)

def get_fundamental_variables(self):
Q = pybamm.standard_variables.Q
variables = {"Discharge capacity [A.h]": Q}
Q_Ah = pybamm.standard_variables.Q_Ah
Q_Wh = pybamm.standard_variables.Q_Wh
variables = {"Discharge capacity [A.h]": Q_Ah, "Discharge energy [W.h]": Q_Wh}
return variables

def set_initial_conditions(self, variables):
Q = variables["Discharge capacity [A.h]"]
self.initial_conditions[Q] = pybamm.Scalar(0)
Q_Ah = variables["Discharge capacity [A.h]"]
Q_Wh = variables["Discharge energy [W.h]"]
self.initial_conditions[Q_Ah] = pybamm.Scalar(0)
self.initial_conditions[Q_Wh] = pybamm.Scalar(0)

def set_rhs(self, variables):
# ODE for discharge capacity
Q = variables["Discharge capacity [A.h]"]
Q_Ah = variables["Discharge capacity [A.h]"]
Q_Wh = variables["Discharge energy [W.h]"]
I = variables["Current [A]"]
self.rhs[Q] = I * self.param.timescale / 3600
V = variables["Terminal voltage [V]"]

self.rhs[Q_Ah] = I * self.param.timescale / 3600
self.rhs[Q_Wh] = I * V * self.param.timescale / 3600


class LeadingOrderBaseModel(BaseModel):
Expand All @@ -33,6 +40,7 @@ def __init__(self, param):
super().__init__(param)

def get_fundamental_variables(self):
Q = pybamm.Variable("Leading-order discharge capacity [A.h]")
variables = {"Discharge capacity [A.h]": Q}
Q_Ah = pybamm.Variable("Leading-order discharge capacity [A.h]")
Q_Wh = pybamm.Variable("Leading-order discharge energy [W.h]")
variables = {"Discharge capacity [A.h]": Q_Ah, "Discharge energy [W.h]": Q_Wh}
return variables

0 comments on commit 3fea944

Please sign in to comment.