Skip to content

Commit 6511376

Browse files
committed
#704 added tests for linear and quadratic and on nonuniform grids
1 parent 86bf852 commit 6511376

File tree

4 files changed

+200
-19
lines changed

4 files changed

+200
-19
lines changed

pybamm/discretisations/discretisation.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,9 @@ def _process_symbol(self, symbol):
707707
mesh = self.mesh[symbol.children[0].domain[0]][0]
708708
if isinstance(mesh, pybamm.SubMesh1D):
709709
symbol.side = mesh.tabs[symbol.side]
710-
return child_spatial_method.boundary_value_or_flux(symbol, disc_child)
710+
return child_spatial_method.boundary_value_or_flux(
711+
symbol, disc_child, symbol.extrapolation
712+
)
711713

712714
else:
713715
return symbol._unary_new_copy(disc_child)

pybamm/expression_tree/unary_operators.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -738,12 +738,15 @@ class BoundaryValue(BoundaryOperator):
738738
The variable whose boundary value to take
739739
side : str
740740
Which side to take the boundary value on ("left" or "right")
741+
extrapolation : str
742+
What extrapolation method to use for this variable ("linear" or "quadratic")
741743
742744
**Extends:** :class:`BoundaryOperator`
743745
"""
744746

745-
def __init__(self, child, side):
747+
def __init__(self, child, side, extrapolation="quadratic"):
746748
super().__init__("boundary value", child, side)
749+
self.extrapolation = extrapolation
747750

748751

749752
class BoundaryGradient(BoundaryOperator):
@@ -755,12 +758,15 @@ class BoundaryGradient(BoundaryOperator):
755758
The variable whose boundary flux to take
756759
side : str
757760
Which side to take the boundary flux on ("left" or "right")
761+
extrapolation : str
762+
What extrapolation method to use on this variable ("linear" or "quadratic")
758763
759764
**Extends:** :class:`BoundaryOperator`
760765
"""
761766

762-
def __init__(self, child, side):
767+
def __init__(self, child, side, extrapolation="linear"):
763768
super().__init__("boundary flux", child, side)
769+
self.extrapolation = extrapolation
764770

765771

766772
#
@@ -850,7 +856,7 @@ def grad_squared(expression):
850856
#
851857

852858

853-
def surf(symbol, set_domain=False):
859+
def surf(symbol, set_domain=False, extrapolation="quadratic"):
854860
"""convenience function for creating a right :class:`BoundaryValue`, usually in the
855861
spherical geometry
856862
@@ -868,10 +874,10 @@ def surf(symbol, set_domain=False):
868874
if symbol.domain in [["negative electrode"], ["positive electrode"]] and isinstance(
869875
symbol, pybamm.PrimaryBroadcast
870876
):
871-
child_surf = boundary_value(symbol.orphans[0], "right")
877+
child_surf = boundary_value(symbol.orphans[0], "right", extrapolation)
872878
out = pybamm.PrimaryBroadcast(child_surf, symbol.domain)
873879
else:
874-
out = boundary_value(symbol, "right")
880+
out = boundary_value(symbol, "right", extrapolation)
875881
if set_domain:
876882
if symbol.domain == ["negative particle"]:
877883
out.domain = ["negative electrode"]
@@ -1015,7 +1021,7 @@ def yz_average(symbol):
10151021
return Integral(symbol, [y, z]) / (l_y * l_z)
10161022

10171023

1018-
def boundary_value(symbol, side):
1024+
def boundary_value(symbol, side, extrapolation="quadratic"):
10191025
"""convenience function for creating a :class:`pybamm.BoundaryValue`
10201026
10211027
Parameters
@@ -1040,7 +1046,7 @@ def boundary_value(symbol, side):
10401046
return symbol.orphans[0]
10411047
# Otherwise, calculate boundary value
10421048
else:
1043-
return BoundaryValue(symbol, side)
1049+
return BoundaryValue(symbol, side, extrapolation)
10441050

10451051

10461052
def r_average(symbol):

pybamm/spatial_methods/finite_volume.py

+49
Original file line numberDiff line numberDiff line change
@@ -596,26 +596,75 @@ def boundary_value_or_flux(self, symbol, discretised_child, extrapolation="linea
596596
if symbol.side == "left":
597597
dx0 = nodes[0] - edges[0]
598598
dx1 = submesh_list[0].d_nodes[0]
599+
dx2 = submesh_list[0].d_nodes[1]
599600

600601
if extrapolation == "linear":
602+
# to find value at x* use formula:
603+
# f(x*) = f_1 - (dx0 / dx1) (f_2 - f_1)
604+
# where
605+
# dx0 is distance between edge and first node
606+
# dx1 is distance between first and second nodes
607+
601608
sub_matrix = csr_matrix(
602609
([1 + (dx0 / dx1), -(dx0 / dx1)], ([0, 0], [0, 1])),
603610
shape=(1, prim_pts),
604611
)
612+
elif extrapolation == "quadratic":
613+
# to find value at x* use formula:
614+
# see mathematica notebook at:
615+
# https://github.com/Scottmar93/extrapolation-coefficents/tree/master
616+
# f(x*) = a f_1 + b f_2 + c f_3
617+
618+
a = (dx0 + dx1) * (dx0 + dx1 + dx2) / (dx1 * (dx1 + dx2))
619+
b = -dx0 * (dx0 + dx1 + dx2) / (dx1 * dx2)
620+
c = dx0 * (dx0 + dx1) / (dx2 * (dx1 + dx2))
621+
622+
sub_matrix = csr_matrix(
623+
([a, b, c], ([0, 0, 0], [0, 1, 2])), shape=(1, prim_pts),
624+
)
605625
else:
606626
raise NotImplementedError
627+
607628
elif symbol.side == "right":
608629
dxN = edges[-1] - nodes[-1]
609630
dxNm1 = submesh_list[0].d_nodes[-1]
631+
dxNm2 = submesh_list[0].d_nodes[-2]
610632

611633
if extrapolation == "linear":
634+
# to find value at x* use formula:
635+
# f(x*) = f_N - (dxN / dxNm1) (f_N - f_Nm1)
636+
# where
637+
# dxN is distance between edge and last node
638+
# dxNm1 is distance between second lase and last nodes
639+
612640
sub_matrix = csr_matrix(
613641
(
614642
[-(dxN / dxNm1), 1 + (dxN / dxNm1)],
615643
([0, 0], [prim_pts - 2, prim_pts - 1]),
616644
),
617645
shape=(1, prim_pts),
618646
)
647+
elif extrapolation == "quadratic":
648+
# to find value at x* use formula:
649+
# see mathematica notebook at:
650+
# https://github.com/Scottmar93/extrapolation-coefficents/tree/master
651+
# f(x*) = a f_N + b f_Nm1 + c f_Nm2
652+
653+
a = (
654+
(dxN + dxNm1)
655+
* (dxN + dxNm1 + dxNm2)
656+
/ (dxNm1 * (dxNm1 + dxNm2))
657+
)
658+
b = -dxN * (dxN + dxNm1 + dxNm2) / (dxNm1 * dxNm2)
659+
c = dxN * (dxN + dxNm1) / (dxNm2 * (dxNm1 + dxNm2))
660+
661+
sub_matrix = csr_matrix(
662+
(
663+
[c, b, a],
664+
([0, 0, 0], [prim_pts - 3, prim_pts - 2, prim_pts - 1]),
665+
),
666+
shape=(1, prim_pts),
667+
)
619668
else:
620669
raise NotImplementedError
621670

tests/unit/test_spatial_methods/test_finite_volume.py

+135-11
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_concatenation(self):
5656
with self.assertRaisesRegex(pybamm.ShapeError, "child must have size n_nodes"):
5757
fin_vol.concatenation(edges)
5858

59-
def test_extrapolate_left_right(self):
59+
def test_linear_extrapolate_left_right(self):
6060
# create discretisation
6161
mesh = get_mesh_for_testing()
6262
spatial_methods = {
@@ -74,8 +74,8 @@ def test_extrapolate_left_right(self):
7474
# create variable
7575
var = pybamm.Variable("var", domain=whole_cell)
7676
# boundary value should work with something more complicated than a variable
77-
extrap_left = pybamm.BoundaryValue(2 * var, "left")
78-
extrap_right = pybamm.BoundaryValue(4 - var, "right")
77+
extrap_left = pybamm.BoundaryValue(2 * var, "left", "linear")
78+
extrap_right = pybamm.BoundaryValue(4 - var, "right", "linear")
7979
disc.set_variable_slices([var])
8080
extrap_left_disc = disc.process_symbol(extrap_left)
8181
extrap_right_disc = disc.process_symbol(extrap_right)
@@ -104,6 +104,80 @@ def test_extrapolate_left_right(self):
104104
self.assertEqual(extrap_flux_left_disc.evaluate(None, linear_y), 2)
105105
self.assertEqual(extrap_flux_right_disc.evaluate(None, linear_y), -1)
106106

107+
# Microscale
108+
# create variable
109+
var = pybamm.Variable("var", domain="negative particle")
110+
surf_eqn = pybamm.surf(var, extrapolation="linear")
111+
disc.set_variable_slices([var])
112+
surf_eqn_disc = disc.process_symbol(surf_eqn)
113+
114+
# check constant extrapolates to constant
115+
constant_y = np.ones_like(micro_submesh[0].nodes[:, np.newaxis])
116+
self.assertEqual(surf_eqn_disc.evaluate(None, constant_y), 1.0)
117+
118+
# check linear variable extrapolates correctly
119+
linear_y = micro_submesh[0].nodes
120+
y_surf = micro_submesh[0].edges[-1]
121+
np.testing.assert_array_almost_equal(
122+
surf_eqn_disc.evaluate(None, linear_y), y_surf
123+
)
124+
125+
def test_quadratic_extrapolate_left_right(self):
126+
# create discretisation
127+
mesh = get_mesh_for_testing()
128+
spatial_methods = {
129+
"macroscale": pybamm.FiniteVolume,
130+
"negative particle": pybamm.FiniteVolume,
131+
"current collector": pybamm.ZeroDimensionalMethod,
132+
}
133+
disc = pybamm.Discretisation(mesh, spatial_methods)
134+
135+
whole_cell = ["negative electrode", "separator", "positive electrode"]
136+
macro_submesh = mesh.combine_submeshes(*whole_cell)
137+
micro_submesh = mesh["negative particle"]
138+
139+
# Macroscale
140+
# create variable
141+
var = pybamm.Variable("var", domain=whole_cell)
142+
# boundary value should work with something more complicated than a variable
143+
extrap_left = pybamm.BoundaryValue(2 * var, "left", "quadratic")
144+
extrap_right = pybamm.BoundaryValue(4 - var, "right", "quadratic")
145+
disc.set_variable_slices([var])
146+
extrap_left_disc = disc.process_symbol(extrap_left)
147+
extrap_right_disc = disc.process_symbol(extrap_right)
148+
149+
# check constant extrapolates to constant
150+
constant_y = np.ones_like(macro_submesh[0].nodes[:, np.newaxis])
151+
np.testing.assert_array_almost_equal(
152+
extrap_left_disc.evaluate(None, constant_y), 2.0
153+
)
154+
np.testing.assert_array_almost_equal(
155+
extrap_right_disc.evaluate(None, constant_y), 3.0
156+
)
157+
158+
# check linear variable extrapolates correctly
159+
linear_y = macro_submesh[0].nodes
160+
np.testing.assert_array_almost_equal(
161+
extrap_left_disc.evaluate(None, linear_y), 0
162+
)
163+
np.testing.assert_array_almost_equal(
164+
extrap_right_disc.evaluate(None, linear_y), 3
165+
)
166+
167+
# Fluxes
168+
extrap_flux_left = pybamm.BoundaryGradient(2 * var, "left")
169+
extrap_flux_right = pybamm.BoundaryGradient(1 - var, "right")
170+
extrap_flux_left_disc = disc.process_symbol(extrap_flux_left)
171+
extrap_flux_right_disc = disc.process_symbol(extrap_flux_right)
172+
173+
# check constant extrapolates to constant
174+
self.assertEqual(extrap_flux_left_disc.evaluate(None, constant_y), 0)
175+
self.assertEqual(extrap_flux_right_disc.evaluate(None, constant_y), 0)
176+
177+
# check linear variable extrapolates correctly
178+
self.assertEqual(extrap_flux_left_disc.evaluate(None, linear_y), 2)
179+
self.assertEqual(extrap_flux_right_disc.evaluate(None, linear_y), -1)
180+
107181
# Microscale
108182
# create variable
109183
var = pybamm.Variable("var", domain="negative particle")
@@ -113,11 +187,53 @@ def test_extrapolate_left_right(self):
113187

114188
# check constant extrapolates to constant
115189
constant_y = np.ones_like(micro_submesh[0].nodes[:, np.newaxis])
116-
self.assertEqual(surf_eqn_disc.evaluate(None, constant_y), 1)
190+
np.testing.assert_array_almost_equal(
191+
surf_eqn_disc.evaluate(None, constant_y), 1
192+
)
117193

118194
# check linear variable extrapolates correctly
119195
linear_y = micro_submesh[0].nodes
120-
y_surf = micro_submesh[0].nodes[-1] + micro_submesh[0].d_nodes[-1] / 2
196+
y_surf = micro_submesh[0].edges[-1]
197+
np.testing.assert_array_almost_equal(
198+
surf_eqn_disc.evaluate(None, linear_y), y_surf
199+
)
200+
201+
def test_extrapolate_on_nonuniform_grid(self):
202+
geometry = pybamm.Geometry("1D micro")
203+
204+
submesh_types = {
205+
"negative particle": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh),
206+
"positive particle": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh),
207+
}
208+
209+
var = pybamm.standard_spatial_vars
210+
rpts = 10
211+
var_pts = {
212+
var.r_n: rpts,
213+
var.r_p: rpts,
214+
}
215+
mesh = pybamm.Mesh(geometry, submesh_types, var_pts)
216+
spatial_methods = {
217+
"negative particle": pybamm.FiniteVolume,
218+
}
219+
disc = pybamm.Discretisation(mesh, spatial_methods)
220+
221+
var = pybamm.Variable("var", domain="negative particle")
222+
surf_eqn = pybamm.surf(var)
223+
disc.set_variable_slices([var])
224+
surf_eqn_disc = disc.process_symbol(surf_eqn)
225+
226+
micro_submesh = mesh["negative particle"]
227+
228+
# check constant extrapolates to constant
229+
constant_y = np.ones_like(micro_submesh[0].nodes[:, np.newaxis])
230+
np.testing.assert_array_almost_equal(
231+
surf_eqn_disc.evaluate(None, constant_y), 1
232+
)
233+
234+
# check linear variable extrapolates correctly
235+
linear_y = micro_submesh[0].nodes
236+
y_surf = micro_submesh[0].edges[-1]
121237
np.testing.assert_array_almost_equal(
122238
surf_eqn_disc.evaluate(None, linear_y), y_surf
123239
)
@@ -1033,7 +1149,7 @@ def test_indefinite_integral(self):
10331149
}
10341150
}
10351151
int_grad_phi_disc = disc.process_symbol(int_grad_phi)
1036-
left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left")
1152+
left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left", "linear")
10371153
left_boundary_value_disc = disc.process_symbol(left_boundary_value)
10381154

10391155
combined_submesh = mesh.combine_submeshes("negative electrode", "separator")
@@ -1087,15 +1203,19 @@ def test_indefinite_integral(self):
10871203
)
10881204
phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
10891205
np.testing.assert_array_almost_equal(phi_exact, phi_approx)
1090-
self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)
1206+
np.testing.assert_array_almost_equal(
1207+
left_boundary_value_disc.evaluate(y=phi_exact), 0
1208+
)
10911209

10921210
# sine case
10931211
phi_exact = np.sin(
10941212
combined_submesh[0].nodes[:, np.newaxis] - combined_submesh[0].edges[0]
10951213
)
10961214
phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
10971215
np.testing.assert_array_almost_equal(phi_exact, phi_approx)
1098-
self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)
1216+
np.testing.assert_array_almost_equal(
1217+
left_boundary_value_disc.evaluate(y=phi_exact), 0
1218+
)
10991219

11001220
# --------------------------------------------------------------------
11011221
# micrsoscale case
@@ -1112,7 +1232,7 @@ def test_indefinite_integral(self):
11121232
}
11131233

11141234
c_integral_disc = disc.process_symbol(c_integral)
1115-
left_boundary_value = pybamm.BoundaryValue(c_integral, "left")
1235+
left_boundary_value = pybamm.BoundaryValue(c_integral, "left", "linear")
11161236
left_boundary_value_disc = disc.process_symbol(left_boundary_value)
11171237
combined_submesh = mesh["negative particle"]
11181238

@@ -1127,13 +1247,17 @@ def test_indefinite_integral(self):
11271247
c_exact = combined_submesh[0].nodes[:, np.newaxis]
11281248
c_approx = c_integral_disc.evaluate(None, c_exact)
11291249
np.testing.assert_array_almost_equal(c_exact, c_approx)
1130-
self.assertEqual(left_boundary_value_disc.evaluate(y=c_exact), 0)
1250+
np.testing.assert_array_almost_equal(
1251+
left_boundary_value_disc.evaluate(y=c_exact), 0
1252+
)
11311253

11321254
# sine case
11331255
c_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis])
11341256
c_approx = c_integral_disc.evaluate(None, c_exact)
11351257
np.testing.assert_array_almost_equal(c_exact, c_approx, decimal=3)
1136-
self.assertEqual(left_boundary_value_disc.evaluate(y=c_exact), 0)
1258+
np.testing.assert_array_almost_equal(
1259+
left_boundary_value_disc.evaluate(y=c_exact), 0
1260+
)
11371261

11381262
def test_indefinite_integral_on_nodes(self):
11391263
mesh = get_mesh_for_testing()

0 commit comments

Comments
 (0)