Skip to content

Commit

Permalink
Merge pull request #745 from suhaklee/issue-744-particledistribution
Browse files Browse the repository at this point in the history
Issue 744 particledistribution
  • Loading branch information
valentinsulzer authored Dec 13, 2019
2 parents 61561b0 + 2162768 commit c75de8c
Show file tree
Hide file tree
Showing 19 changed files with 257 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.png
/local/
*.DS_Store
*.mat

# don't ignore important .txt files
!requirements*
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Features

- Added `InputParameter` node for quickly changing parameter values ([#752](https://github.com/pybamm-team/PyBaMM/pull/752))
- Added optional R(x) distribution in particle models ([#745](https://github.com/pybamm-team/PyBaMM/pull/745))
- Generalized importing of external variables ([#728](https://github.com/pybamm-team/PyBaMM/pull/728))
- Separated active and inactive material volume fractions ([#726](https://github.com/pybamm-team/PyBaMM/pull/726))
- Added submodels for tortuosity ([#726](https://github.com/pybamm-team/PyBaMM/pull/726))
Expand Down
111 changes: 60 additions & 51 deletions examples/scripts/SPMe_SOC.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
w = 0.207 / factor
A = h * w
l_s = 2.5e-5
l1d = (l_n + l_p + l_s)
l1d = l_n + l_p + l_s
vol = h * w * l1d
vol_cm3 = vol * 1e6
tot_cap = 0.0
tot_time = 0.0
fig, axes = plt.subplots(1, 2, sharey=True)
I_mag = 1.01 / factor
print('*' * 30)
print("*" * 30)
for enum, I_app in enumerate([-1.0, 1.0]):
I_app *= I_mag
# load model
Expand All @@ -44,27 +44,33 @@
# load parameter values and process model and geometry
param = model.default_parameter_values
param.update(
{"Electrode height [m]": h,
"Electrode width [m]": w,
"Negative electrode thickness [m]": l_n,
"Positive electrode thickness [m]": l_p,
"Separator thickness [m]": l_s,
"Lower voltage cut-off [V]": 2.8,
"Upper voltage cut-off [V]": 4.7,
"Maximum concentration in negative electrode [mol.m-3]": 25000,
"Maximum concentration in positive electrode [mol.m-3]": 50000,
"Initial concentration in negative electrode [mol.m-3]": 12500,
"Initial concentration in positive electrode [mol.m-3]": 25000,
"Negative electrode surface area density [m-1]": 180000.0,
"Positive electrode surface area density [m-1]": 150000.0,
"Typical current [A]": I_app,
}
{
"Electrode height [m]": h,
"Electrode width [m]": w,
"Negative electrode thickness [m]": l_n,
"Positive electrode thickness [m]": l_p,
"Separator thickness [m]": l_s,
"Lower voltage cut-off [V]": 2.8,
"Upper voltage cut-off [V]": 4.7,
"Maximum concentration in negative electrode [mol.m-3]": 25000,
"Maximum concentration in positive electrode [mol.m-3]": 50000,
"Initial concentration in negative electrode [mol.m-3]": 12500,
"Initial concentration in positive electrode [mol.m-3]": 25000,
"Negative electrode surface area density [m-1]": 180000.0,
"Positive electrode surface area density [m-1]": 150000.0,
"Typical current [A]": I_app,
}
)
param.process_model(model)
param.process_geometry(geometry)
s_var = pybamm.standard_spatial_vars
var_pts = {s_var.x_n: 5, s_var.x_s: 5, s_var.x_p: 5,
s_var.r_n: 5, s_var.r_p: 10}
var_pts = {
s_var.x_n: 5,
s_var.x_s: 5,
s_var.x_p: 5,
s_var.r_n: 5,
s_var.r_p: 10,
}
# set mesh
mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts)
# discretise model
Expand All @@ -74,58 +80,61 @@
t_eval = np.linspace(0, 0.2, 100)
sol = model.default_solver.solve(model, t_eval)
var = "Positive electrode average extent of lithiation"
xpext = pybamm.ProcessedVariable(model.variables[var],
sol.t, sol.y, mesh=mesh)
xpext = pybamm.ProcessedVariable(model.variables[var], sol.t, sol.y, mesh=mesh)
var = "Negative electrode average extent of lithiation"
xnext = pybamm.ProcessedVariable(model.variables[var],
sol.t, sol.y, mesh=mesh)
xnext = pybamm.ProcessedVariable(model.variables[var], sol.t, sol.y, mesh=mesh)
var = "X-averaged positive particle surface concentration"
xpsurf = pybamm.ProcessedVariable(model.variables[var],
sol.t, sol.y, mesh=mesh)
xpsurf = pybamm.ProcessedVariable(model.variables[var], sol.t, sol.y, mesh=mesh)
var = "X-averaged negative particle surface concentration"
xnsurf = pybamm.ProcessedVariable(model.variables[var],
sol.t, sol.y, mesh=mesh)
time = pybamm.ProcessedVariable(model.variables["Time [h]"],
sol.t, sol.y, mesh=mesh)
xnsurf = pybamm.ProcessedVariable(model.variables[var], sol.t, sol.y, mesh=mesh)
time = pybamm.ProcessedVariable(
model.variables["Time [h]"], sol.t, sol.y, mesh=mesh
)
# Coulomb counting
time_hours = time(sol.t)
dc_time = np.around(time_hours[-1], 3)
# Capacity mAh
cap = np.absolute(I_app * 1000 * dc_time)
cap_time = np.absolute(I_app * 1000 * time_hours)
axes[enum].plot(cap_time,
xnext(sol.t), 'r-', label='Average Neg')
axes[enum].plot(cap_time,
xpext(sol.t), 'b-', label='Average Pos')
axes[enum].plot(cap_time,
xnsurf(sol.t), 'r--', label='Surface Neg')
axes[enum].plot(cap_time,
xpsurf(sol.t), 'b--', label='Surface Pos')
axes[enum].set_xlabel('Capacity [mAh]')
axes[enum].plot(cap_time, xnext(sol.t), "r-", label="Average Neg")
axes[enum].plot(cap_time, xpext(sol.t), "b-", label="Average Pos")
axes[enum].plot(cap_time, xnsurf(sol.t), "r--", label="Surface Neg")
axes[enum].plot(cap_time, xpsurf(sol.t), "b--", label="Surface Pos")
axes[enum].set_xlabel("Capacity [mAh]")
handles, labels = axes[enum].get_legend_handles_labels()
axes[enum].legend(handles, labels)
if I_app < 0.0:
axes[enum].set_ylabel('Extent of Lithiation, Elecrode Ratio: '
+ str(e_ratio))
axes[enum].title.set_text('Charge')
axes[enum].set_ylabel(
"Extent of Lithiation, Elecrode Ratio: " + str(e_ratio)
)
axes[enum].title.set_text("Charge")
else:
axes[enum].title.set_text('Discharge')
print('Applied Current', I_app, 'A', 'Time',
dc_time, 'hrs', 'Capacity', cap, 'mAh')
axes[enum].title.set_text("Discharge")
print(
"Applied Current",
I_app,
"A",
"Time",
dc_time,
"hrs",
"Capacity",
cap,
"mAh",
)
tot_cap += cap
tot_time += dc_time

print('Anode : Cathode thickness', e_ratio)
print('Total Charge/Discharge Time', tot_time, 'hrs')
print('Total Capacity', np.around(tot_cap, 3), 'mAh')
print("Anode : Cathode thickness", e_ratio)
print("Total Charge/Discharge Time", tot_time, "hrs")
print("Total Capacity", np.around(tot_cap, 3), "mAh")
specific_cap = np.around(tot_cap, 3) / vol_cm3
print('Total Capacity', specific_cap, 'mAh.cm-3')
print("Total Capacity", specific_cap, "mAh.cm-3")
capacities.append(tot_cap)
specific_capacities.append(specific_cap)

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(thicknesses / l_p, capacities)
ax2.plot(thicknesses / l_p, specific_capacities)
ax1.set_ylabel('Capacity [mAh]')
ax2.set_ylabel('Specific Capacity [mAh.cm-3]')
ax2.set_xlabel('Anode : Cathode thickness')
ax1.set_ylabel("Capacity [mAh]")
ax2.set_ylabel("Specific Capacity [mAh.cm-3]")
ax2.set_xlabel("Anode : Cathode thickness")
1 change: 0 additions & 1 deletion examples/scripts/SPMe_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
# plot
voltage = pybamm.ProcessedVariable(
model.variables["Terminal voltage [V]"], solution.t, solution.y, mesh=mesh

)
step_voltage = pybamm.ProcessedVariable(
model.variables["Terminal voltage [V]"], step_solution.t, step_solution.y, mesh=mesh
Expand Down
1 change: 0 additions & 1 deletion examples/scripts/compare_extrapolations.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,3 @@
solutions = [sim_lin.solution, sim_quad.solution]
plot = pybamm.QuickPlot(models, sim_lin.mesh, solutions)
plot.dynamic_plot()

82 changes: 82 additions & 0 deletions examples/scripts/compare_lithium_ion_particle_distribution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#
# Compare lithium-ion battery models
#
import argparse
import numpy as np
import pybamm

parser = argparse.ArgumentParser()
parser.add_argument(
"--debug", action="store_true", help="Set logging level to 'DEBUG'."
)
args = parser.parse_args()
if args.debug:
pybamm.set_logging_level("DEBUG")
else:
pybamm.set_logging_level("INFO")

# load models
options = {"thermal": "isothermal"}
models = [
pybamm.lithium_ion.DFN(options, name="standard DFN"),
pybamm.lithium_ion.DFN(options, name="particle DFN"),
]


# load parameter values and process models and geometry
params = [models[0].default_parameter_values, models[1].default_parameter_values]
params[0]["Typical current [A]"] = 1.0
params[0].process_model(models[0])


params[1]["Typical current [A]"] = 1.0


def negative_distribution(x):
return 1 + x


def positive_distribution(x):
return 1 + (x - (1 - models[1].param.l_p))


params[1]["Negative particle distribution in x"] = negative_distribution
params[1]["Positive particle distribution in x"] = positive_distribution
params[1].process_model(models[1])

# set mesh
var = pybamm.standard_spatial_vars
var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5}

# discretise models
for param, model in zip(params, models):
# create geometry
geometry = model.default_geometry
param.process_geometry(geometry)
mesh = pybamm.Mesh(geometry, models[-1].default_submesh_types, var_pts)
disc = pybamm.Discretisation(mesh, model.default_spatial_methods)
disc.process_model(model)

# solve model
solutions = [None] * len(models)
t_eval = np.linspace(0, 0.3, 100)
for i, model in enumerate(models):
solutions[i] = model.default_solver.solve(model, t_eval)


output_variables = [
"Negative particle surface concentration",
"Electrolyte concentration",
"Positive particle surface concentration",
"Current [A]",
"Negative electrode potential [V]",
"Electrolyte potential [V]",
"Positive electrode potential [V]",
"Terminal voltage [V]",
"Negative particle distribution in x",
"Positive particle distribution in x",
]

# plot
plot = pybamm.QuickPlot(models, mesh, solutions, output_variables=output_variables)
plot.dynamic_plot()
6 changes: 1 addition & 5 deletions examples/scripts/heat_equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,7 @@ def T_exact(x, t):
label="Numerical" if i == 0 else "",
)
plt.plot(
xx,
T_exact(xx, t),
"-",
color=color,
label="Exact (t={})".format(plot_times[i]),
xx, T_exact(xx, t), "-", color=color, label="Exact (t={})".format(plot_times[i])
)
plt.xlabel("x", fontsize=16)
plt.ylabel("T", fontsize=16)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Negative electrode OCP [V],[function]graphite_mcmb2528_ocp_Dualfoil1998,
Negative electrode porosity,0.3,Scott Moura FastDFN,electrolyte volume fraction
Negative electrode active material volume fraction,0.7,,assuming zero binder volume fraction
Negative particle radius [m],1E-05,Scott Moura FastDFN,
Negative particle distribution in x,1,,
Negative electrode surface area density [m-1],180000,Scott Moura FastDFN,
Negative electrode Bruggeman coefficient (electrolyte),1.5,Scott Moura FastDFN,
Negative electrode Bruggeman coefficient (electrode),1.5,Scott Moura FastDFN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Positive electrode OCP [V],[function]lico2_ocp_Dualfoil1998,
Positive electrode porosity,0.3,Scott Moura FastDFN,electrolyte volume fraction
Positive electrode active material volume fraction,0.7,,assuming zero binder volume fraction
Positive particle radius [m],1E-05,Scott Moura FastDFN,
Positive particle distribution in x,1,,
Positive electrode surface area density [m-1],150000,Scott Moura FastDFN,
Positive electrode Bruggeman coefficient (electrolyte),1.5,Scott Moura FastDFN,
Positive electrode Bruggeman coefficient (electrode),1.5,Scott Moura FastDFN,
Expand Down
7 changes: 6 additions & 1 deletion pybamm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ def version(formatted=False):
from .expression_tree.interpolant import Interpolant
from .expression_tree.input_parameter import InputParameter
from .expression_tree.parameter import Parameter, FunctionParameter
from .expression_tree.broadcasts import Broadcast, PrimaryBroadcast, FullBroadcast
from .expression_tree.broadcasts import (
Broadcast,
PrimaryBroadcast,
FullBroadcast,
ones_like,
)
from .expression_tree.scalar import Scalar
from .expression_tree.variable import Variable
from .expression_tree.independent_variable import (
Expand Down
23 changes: 23 additions & 0 deletions pybamm/expression_tree/broadcasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,26 @@ def evaluate_for_shape(self):
)

return child_eval * vec


def ones_like(*symbols):
"""
Create a symbol with the same shape as the input symbol and with constant value '1',
using `FullBroadcast`.
Parameters
----------
symbols : :class:`Symbol`
Symbols whose shape to copy
"""
# Make a symbol that combines all the children, to get the right domain
# that takes all the child symbols into account
sum_symbol = symbols[0]
for sym in symbols:
sum_symbol += sym

# Just return scalar 1 if symbol has no domain (no broadcasting necessary)
if sum_symbol.domain == []:
return pybamm.Scalar(1)
else:
return FullBroadcast(1, sum_symbol.domain, sum_symbol.auxiliary_domains)
10 changes: 0 additions & 10 deletions pybamm/models/submodels/particle/fickian/base_fickian_particle.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ def _flux_law(self, c, T):
def _unpack(self, variables):
raise NotImplementedError

def set_rhs(self, variables):

c, N, _ = self._unpack(variables)

if self.domain == "Negative":
self.rhs = {c: -(1 / self.param.C_n) * pybamm.div(N)}

elif self.domain == "Positive":
self.rhs = {c: -(1 / self.param.C_p) * pybamm.div(N)}

def set_boundary_conditions(self, variables):

c, _, j = self._unpack(variables)
Expand Down
22 changes: 22 additions & 0 deletions pybamm/models/submodels/particle/fickian/fickian_many_particles.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,30 @@ def get_coupled_variables(self, variables):
N_s = self._flux_law(c_s, T_k)

variables.update(self._get_standard_flux_variables(N_s, N_s))

if self.domain == "Negative":
x = pybamm.standard_spatial_vars.x_n
R = pybamm.FunctionParameter("Negative particle distribution in x", x)
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)
variables.update({"Positive particle distribution in x": R})
return variables

def set_rhs(self, variables):

c, N, _ = self._unpack(variables)

if self.domain == "Negative":
R = variables["Negative particle distribution in x"]
self.rhs = {c: -(1 / (R ** 2 * self.param.C_n)) * pybamm.div(N)}

elif self.domain == "Positive":
R = variables["Positive particle distribution in x"]
self.rhs = {c: -(1 / (R ** 2 * self.param.C_p)) * pybamm.div(N)}

def _unpack(self, variables):
c_s = variables[self.domain + " particle concentration"]
N_s = variables[self.domain + " particle flux"]
Expand Down
Loading

0 comments on commit c75de8c

Please sign in to comment.