Reference
JellyBaMM: a package for simulating Li-ion electrochemistry on jellyroll structures with PyBaMM
__definitions__
special
Created on Wed Oct 21 14:00:26 2020
@author: tom
__funcs__
special
Created on Wed Oct 30 14:11:13 2019
@author: thomas
__postprocess__
special
Created on Wed Mar 4 13:32:23 2020
@author: Tom
weighted_avg_and_std(values, weights)
Return the weighted average and standard deviation. values, weights -- Numpy ndarrays with the same shape.
Source code in jellybamm/__postprocess__.py
def weighted_avg_and_std(values, weights):
"""
Return the weighted average and standard deviation.
values, weights -- Numpy ndarrays with the same shape.
"""
average = np.average(values, weights=weights)
# Fast and numerically precise:
variance = np.average((values - average) ** 2, weights=weights)
return (average, math.sqrt(variance))
__segment_jellyroll__
special
Created on Wed Oct 28 10:09:24 2020
@author: Tom
__topology__
special
make_1D_net(Nunit, spacing, pos_tabs, neg_tabs)
Generate a 1D network of batteries connected in parallel
Parameters
Nunit : TYPE DESCRIPTION. spacing : TYPE DESCRIPTION. pos_tabs : TYPE DESCRIPTION. neg_tabs : TYPE DESCRIPTION.
Returns
TYPE DESCRIPTION.
Source code in jellybamm/__topology__.py
def make_1D_net(Nunit, spacing, pos_tabs, neg_tabs):
r"""
Generate a 1D network of batteries connected in parallel
Parameters
----------
Nunit : TYPE
DESCRIPTION.
spacing : TYPE
DESCRIPTION.
pos_tabs : TYPE
DESCRIPTION.
neg_tabs : TYPE
DESCRIPTION.
Returns
-------
TYPE
DESCRIPTION.
"""
net = op.network.Cubic([Nunit + 2, 2, 1], spacing)
net["pore.pos_cc"] = net["pore.front"]
net["pore.neg_cc"] = net["pore.back"]
T = net.find_neighbor_throats(net.pores("left"), mode="xnor")
tt.trim(net, throats=T)
T = net.find_neighbor_throats(net.pores("right"), mode="xnor")
tt.trim(net, throats=T)
pos_cc_Ts = net.find_neighbor_throats(net.pores("pos_cc"), mode="xnor")
neg_cc_Ts = net.find_neighbor_throats(net.pores("neg_cc"), mode="xnor")
net.add_boundary_pores(labels=["front", "back"])
net["pore.free_stream"] = np.logical_or(
net["pore.front_boundary"], net["pore.back_boundary"]
)
net["throat.free_stream"] = np.logical_or(
net["throat.front_boundary"], net["throat.back_boundary"]
)
pos_tab_nodes = net.pores()[net["pore.pos_cc"]][pos_tabs]
neg_tab_nodes = net.pores()[net["pore.neg_cc"]][neg_tabs]
net["pore.pos_tab"] = False
net["pore.neg_tab"] = False
net["pore.pos_tab"][pos_tab_nodes] = True
net["pore.neg_tab"][neg_tab_nodes] = True
net["throat.pos_cc"] = False
net["throat.neg_cc"] = False
net["throat.pos_cc"][pos_cc_Ts] = True
net["throat.neg_cc"][neg_cc_Ts] = True
net["throat.spm_resistor"] = True
net["throat.spm_resistor"][pos_cc_Ts] = False
net["throat.spm_resistor"][neg_cc_Ts] = False
net["throat.spm_resistor"][net["throat.free_stream"]] = False
net["throat.spm_resistor_order"] = -1
net["throat.spm_resistor_order"][net["throat.spm_resistor"]] = np.arange(Nunit)
net["throat.spm_neg_inner"] = net["throat.spm_resistor"]
del net["pore.left"]
del net["pore.right"]
del net["pore.front"]
del net["pore.front_boundary"]
del net["pore.back"]
del net["pore.back_boundary"]
del net["pore.internal"]
del net["pore.surface"]
del net["throat.internal"]
del net["throat.surface"]
phase = op.phases.GenericPhase(network=net)
geo = op.geometry.GenericGeometry(network=net, pores=net.Ps, throats=net.Ts)
op.physics.GenericPhysics(network=net, phase=phase, geometry=geo)
net["pore.radial_position"] = net["pore.coords"][:, 0]
net["pore.arc_index"] = np.indices([Nunit + 2, 4, 1])[0].flatten()
net["pore.region_id"] = -1
net["pore.cell_id"] = -1
net["throat.arc_length"] = spacing
net["throat.electrode_height"] = spacing
# placeholder
net["pore.volume"] = 1.0
net["throat.area"] = 1.0
net["throat.length"] = 1.0
return net.project, np.cumsum(net["throat.arc_length"])
make_spiral_net(Nlayers, dtheta, spacing, inner_r, pos_tabs, neg_tabs, length_3d, tesla_tabs)
Generate a perfect spiral network
Parameters
Nlayers : TYPE DESCRIPTION. dtheta : TYPE DESCRIPTION. spacing : TYPE DESCRIPTION. pos_tabs : TYPE DESCRIPTION. neg_tabs : TYPE DESCRIPTION. length_3d : TYPE DESCRIPTION. tesla_tabs : TYPE DESCRIPTION.
Returns
prj : TYPE DESCRIPTION. arc_edges : TYPE DESCRIPTION.
Source code in jellybamm/__topology__.py
def make_spiral_net(
Nlayers, dtheta, spacing, inner_r, pos_tabs, neg_tabs, length_3d, tesla_tabs
):
r"""
Generate a perfect spiral network
Parameters
----------
Nlayers : TYPE
DESCRIPTION.
dtheta : TYPE
DESCRIPTION.
spacing : TYPE
DESCRIPTION.
pos_tabs : TYPE
DESCRIPTION.
neg_tabs : TYPE
DESCRIPTION.
length_3d : TYPE
DESCRIPTION.
tesla_tabs : TYPE
DESCRIPTION.
Returns
-------
prj : TYPE
DESCRIPTION.
arc_edges : TYPE
DESCRIPTION.
"""
Narc = int(360 / dtheta) # number of nodes in a wind/layer
Nunit = int(Nlayers * Narc) # total number of unit cells
N1d = 2
# 2D assembly
assembly = np.zeros([Nunit, N1d], dtype=int)
assembly[:, 0] = 0
assembly[:, 1] = 1
unit_id = np.tile(np.arange(0, Nunit), (N1d, 1)).T
prj = op.Project()
net = op.network.Cubic(shape=[Nunit, N1d, 1], spacing=spacing, project=prj)
net["pore.pos_cc"] = net["pore.back"]
net["pore.neg_cc"] = net["pore.front"]
net["pore.region_id"] = assembly.flatten()
net["pore.cell_id"] = unit_id.flatten()
# Extend the connections in the cell repetition direction
net["pore.coords"][:, 0] *= 10
# Update coords
net["pore.radial_position"] = 0.0
net["pore.arc_index"] = 0
r_start = net["pore.coords"][net["pore.cell_id"] == 0][:, 1]
dr = spacing * N1d
for i in range(N1d):
(x, y, rad, pos) = spiral(r_start[i] + inner_r, dr, ntheta=Narc, n=Nlayers)
mask = net["pore.coords"][:, 1] == r_start[i]
coords = net["pore.coords"][mask]
coords[:, 0] = x[:-1]
coords[:, 1] = y[:-1]
net["pore.coords"][mask] = coords
net["pore.radial_position"][mask] = rad[:-1]
net["pore.arc_index"][mask] = pos[:-1]
if i == 0:
arc_edges = np.cumsum(np.deg2rad(dtheta) * rad)
arc_edges -= arc_edges[0]
# Make interlayer connections after rolling
Ps_neg_cc = net.pores("neg_cc")
Ps_pos_cc = net.pores("pos_cc")
no_tab = np.array([4, 5, 13, 14, 22, 23, 31, 32])
if tesla_tabs:
pos_tabs = net["pore.arc_index"][Ps_pos_cc]
neg_tabs = net["pore.arc_index"][Ps_neg_cc]
pos_tabs = ~np.in1d(pos_tabs, no_tab)
neg_tabs = ~np.in1d(neg_tabs, no_tab)
coords_left = net["pore.coords"][Ps_neg_cc]
coords_right = net["pore.coords"][Ps_pos_cc]
pos_cc_Ts = net.find_neighbor_throats(net.pores("pos_cc"), mode="xnor")
neg_cc_Ts = net.find_neighbor_throats(net.pores("neg_cc"), mode="xnor")
pos_tab_nodes = net.pores()[net["pore.pos_cc"]][pos_tabs]
neg_tab_nodes = net.pores()[net["pore.neg_cc"]][neg_tabs]
net["pore.pos_tab"] = False
net["pore.neg_tab"] = False
net["pore.pos_tab"][pos_tab_nodes] = True
net["pore.neg_tab"][neg_tab_nodes] = True
conns = []
# Identify pores in rolled layers that need new connections
# This represents the separator layer which is not explicitly resolved
for i_left, cl in enumerate(coords_left):
vec = coords_right - cl
dist = np.linalg.norm(vec, axis=1)
if np.any(dist < 2 * spacing):
i_right = np.argwhere(dist < 1.5 * spacing)[0][0]
conns.append([Ps_neg_cc[i_left], Ps_pos_cc[i_right]])
# Create new throats
op.topotools.extend(network=net, throat_conns=conns, labels=["separator"])
h = net.check_network_health()
if len(h["duplicate_throats"]) > 0:
trim_Ts = np.asarray(h["duplicate_throats"])[:, 1]
op.topotools.trim(network=net, throats=trim_Ts)
Ts = net.find_neighbor_throats(pores=net.pores("pos_cc"), mode="xor")
net["throat.pos_cc"] = False
net["throat.neg_cc"] = False
net["throat.pos_cc"][pos_cc_Ts] = True
net["throat.neg_cc"][neg_cc_Ts] = True
net["throat.spm_resistor"] = True
net["throat.spm_resistor"][pos_cc_Ts] = False
net["throat.spm_resistor"][neg_cc_Ts] = False
net["throat.spm_resistor_order"] = -1
spm_res = net["throat.spm_resistor"]
net["throat.spm_resistor_order"][spm_res] = np.arange(np.sum(spm_res))
p1 = net["throat.conns"][:, 0]
p1_neg = net["pore.neg_cc"][p1]
net["throat.spm_neg_inner"] = p1_neg * net["throat.spm_resistor"]
net["throat.spm_pos_inner"] = (~p1_neg) * net["throat.spm_resistor"]
Ps = net["throat.conns"][Ts].flatten()
Ps, counts = np.unique(Ps.flatten(), return_counts=True)
boundary = Ps[counts == 1]
net["pore.inner"] = False
net["pore.outer"] = False
net["pore.inner"][boundary] = True
net["pore.outer"][boundary] = True
net["pore.inner"][net.pores("pos_cc")] = False
net["pore.outer"][net.pores("neg_cc")] = False
# Free stream convection boundary nodes
free_rad = inner_r + (Nlayers + 0.5) * dr
(x, y, rad, pos) = spiral(free_rad, dr, ntheta=Narc, n=1)
net_free = op.network.Cubic(shape=[Narc, 1, 1], spacing=spacing)
net_free["throat.trimmers"] = True
net_free["pore.free_stream"] = True
net_free["pore.coords"][:, 0] = x[:-1]
net_free["pore.coords"][:, 1] = y[:-1]
op.topotools.stitch(
network=net,
donor=net_free,
P_network=net.pores(),
P_donor=net_free.Ps,
len_max=1.0 * dr,
method="nearest",
)
net["throat.free_stream"] = net["throat.stitched"]
del net["throat.stitched"]
free_pores = net.pores("free_stream")
net["pore.radial_position"][free_pores] = rad[:-1]
net["pore.arc_index"][free_pores] = pos[:-1]
op.topotools.trim(network=net, throats=net.throats("trimmers"))
net["pore.region_id"][net["pore.free_stream"]] = -1
net["pore.cell_id"][net["pore.free_stream"]] = -1
# Inner boundary nodes
inner_rad = inner_r - 0.5 * dr
(x, y, rad, pos) = spiral(inner_rad, dr, ntheta=Narc, n=1)
net_inner = op.network.Cubic(shape=[Narc, 1, 1], spacing=spacing)
net_inner["throat.trimmers"] = True
net_inner["pore.inner_boundary"] = True
net_inner["pore.coords"][:, 0] = x[:-1]
net_inner["pore.coords"][:, 1] = y[:-1]
op.topotools.stitch(
network=net,
donor=net_inner,
P_network=net.pores(),
P_donor=net_inner.Ps,
len_max=0.95 * dr,
method="nearest",
)
inner_pores = net.pores("inner_boundary")
net["pore.radial_position"][inner_pores] = rad[:-1]
net["pore.arc_index"][inner_pores] = pos[:-1]
net["pore.region_id"][net["pore.inner_boundary"]] = -1
net["pore.cell_id"][net["pore.inner_boundary"]] = -1
P1 = net["throat.conns"][:, 0]
P2 = net["throat.conns"][:, 1]
same_arc = net["pore.arc_index"][P1] == net["pore.arc_index"][P2]
cross_stitch = np.logical_and(net["throat.stitched"], ~same_arc)
net["throat.inner_boundary"] = net["throat.stitched"]
net["throat.trimmers"][cross_stitch] = True
del net["throat.stitched"]
del net["pore.left"]
del net["pore.right"]
del net["pore.front"]
del net["pore.back"]
del net["pore.internal"]
del net["pore.surface"]
del net["throat.internal"]
del net["throat.surface"]
op.topotools.trim(network=net, throats=net.throats("trimmers"))
# print("N SPM", net.num_throats("spm_resistor"))
geo = jellybamm.setup_geometry(net, dtheta, spacing, length_3d=length_3d)
net["throat.arc_length"] = np.deg2rad(dtheta) * net["throat.radial_position"]
phase = op.phases.GenericPhase(network=net)
op.physics.GenericPhysics(network=net, phase=phase, geometry=geo)
return prj, arc_edges
network_to_netlist(network, Rs=1e-05, Ri=60, V=3.6, I_app=-5.0)
Make a liionpack netlist from a network
Parameters
network : TYPE DESCRIPTION. Rs : TYPE, optional DESCRIPTION. The default is 1e-5. Ri : TYPE, optional DESCRIPTION. The default is 60. V : TYPE, optional DESCRIPTION. The default is 3.6. I_app : TYPE, optional DESCRIPTION. The default is -5.0.
Returns
netlist : TYPE DESCRIPTION.
Source code in jellybamm/__topology__.py
def network_to_netlist(network, Rs=1e-5, Ri=60, V=3.6, I_app=-5.0):
r"""
Make a liionpack netlist from a network
Parameters
----------
network : TYPE
DESCRIPTION.
Rs : TYPE, optional
DESCRIPTION. The default is 1e-5.
Ri : TYPE, optional
DESCRIPTION. The default is 60.
V : TYPE, optional
DESCRIPTION. The default is 3.6.
I_app : TYPE, optional
DESCRIPTION. The default is -5.0.
Returns
-------
netlist : TYPE
DESCRIPTION.
"""
desc = []
node1 = []
node2 = []
value = []
node1_x = []
node1_y = []
node2_x = []
node2_y = []
xs = network["pore.coords"][:, 0]
ys = network["pore.coords"][:, 1]
Tid = []
# Negative current collector
for t in network.throats("neg_cc"):
desc.append("Rbn" + str(t))
n1, n2 = network["throat.conns"][t]
node1.append(n1)
node2.append(n2)
value.append(1 / network["throat.electrical_conductance"][t])
node1_x.append(xs[n1])
node1_y.append(ys[n1])
node2_x.append(xs[n2])
node2_y.append(ys[n2])
Tid.append(t)
# Positive current collector
for t in network.throats("pos_cc"):
desc.append("Rbp" + str(t))
n1, n2 = network["throat.conns"][t]
node1.append(n1)
node2.append(n2)
value.append(1 / network["throat.electrical_conductance"][t])
node1_x.append(xs[n1])
node1_y.append(ys[n1])
node2_x.append(xs[n2])
node2_y.append(ys[n2])
Tid.append(t)
# check contiguous
node_max = max((max(node1), max(node2)))
for i in range(node_max):
if i not in node1:
if i not in node2:
print("Missing", i)
add_res = True
nn = node_max
# Battery Segment
for t in network.throats("throat.spm_resistor"):
n1, n2 = network["throat.conns"][t]
# swap node if n1 is negative
n1_neg = network["pore.neg_cc"][n1]
if n1_neg:
n1, n2 = network["throat.conns"][t][::-1]
vx = xs[n2] - xs[n1]
vy = ys[n2] - ys[n1]
vax = xs[n1] + vx / 3
vbx = xs[n1] + vx * 2 / 3
vay = ys[n1] + vy / 3
vby = ys[n1] + vy * 2 / 3
if add_res:
# Make a new connection resistor from neg to V
nn += 1
desc.append("Rs" + str(t))
node1.append(n1)
node2.append(nn)
value.append(Rs)
node1_x.append(xs[n1])
node1_y.append(ys[n1])
node2_x.append(vax)
node2_y.append(vay)
Tid.append(t)
# Make a battery node Va to Vb
nn += 1
desc.append("V" + str(t))
node1.append(nn - 1)
node2.append(nn)
value.append(V)
node1_x.append(vax)
node1_y.append(vay)
node2_x.append(vbx)
node2_y.append(vby)
Tid.append(t)
# Make an intenal resistor from Vb to pos
desc.append("Ri" + str(t))
node1.append(nn)
node2.append(n2)
value.append(Ri)
node1_x.append(vbx)
node1_y.append(vby)
node2_x.append(xs[n2])
node2_y.append(ys[n2])
Tid.append(t)
else:
desc.append("V" + str(t))
node1.append(n1)
node2.append(n2)
value.append(V)
node1_x.append(xs[n1])
node1_y.append(ys[n1])
node2_x.append(xs[n2])
node2_y.append(ys[n2])
Tid.append(t)
# Terminals
n1 = network.pores("pos_cc")[-1]
n2 = network.pores("neg_cc")[0]
desc.append("I0")
node1.append(n1)
node2.append(n2)
value.append(I_app)
node1_x.append(xs[n1])
node1_y.append(ys[n1])
node2_x.append(xs[n2])
node2_y.append(ys[n2])
Tid.append(-1)
netlist_data = {
"desc": desc,
"node1": node1,
"node2": node2,
"value": value,
"node1_x": node1_x,
"node1_y": node1_y,
"node2_x": node2_x,
"node2_y": node2_y,
"pnm_throat_id": Tid,
}
# add internal resistors
netlist = pd.DataFrame(netlist_data)
return netlist