Skip to content

Commit 3a362df

Browse files
committed
Add HeatKernel
1 parent e1048b0 commit 3a362df

File tree

2 files changed

+83
-10
lines changed

2 files changed

+83
-10
lines changed

sumpy/kernel.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,61 @@ def get_pde_as_diff_op(self):
933933
return laplacian(w)
934934

935935

936+
class HeatKernel(ExpressionKernel):
937+
init_arg_names = ("dim",)
938+
939+
def __init__(self, spatial_dims, heat_alpha_name="alpha"):
940+
dim = spatial_dims + 1
941+
d = make_sym_vector("d", dim)
942+
t = d[-1]
943+
r = pymbolic_real_norm_2(d[:-1])
944+
alpha = SpatialConstant(heat_alpha_name)
945+
expr = var("exp")(-r**2/(4 * alpha * t)) / var("sqrt")(t**(dim - 1))
946+
scaling = 1/var("sqrt")((4*var("pi")*alpha)**(dim - 1))
947+
948+
super().__init__(
949+
dim,
950+
expression=expr,
951+
global_scaling_const=scaling,
952+
is_complex_valued=False)
953+
954+
self.heat_alpha_name = heat_alpha_name
955+
956+
def __getinitargs__(self):
957+
return (self.dim, self.heat_alpha_name)
958+
959+
def update_persistent_hash(self, key_hash, key_builder):
960+
key_hash.update(type(self).__name__.encode("utf8"))
961+
key_builder.rec(key_hash, (self.dim, self.heat_alpha_name))
962+
963+
def __repr__(self):
964+
return f"HeatKnl{self.dim - 1}D"
965+
966+
def get_args(self):
967+
return [
968+
KernelArgument(
969+
loopy_arg=lp.ValueArg(self.heat_alpha_name, np.float64),
970+
)]
971+
972+
mapper_method = "map_heat_kernel"
973+
974+
def get_derivative_taker(self, dvec, rscale, sac):
975+
"""Return a :class:`sumpy.derivative_taker.ExprDerivativeTaker` instance
976+
that supports taking derivatives of the base kernel with respect to dvec.
977+
"""
978+
from sumpy.derivative_taker import ExprDerivativeTaker
979+
return ExprDerivativeTaker(self.get_expression(dvec), dvec, rscale,
980+
sac)
981+
982+
def get_pde_as_diff_op(self):
983+
from sumpy.expansion.diff_op import make_identity_diff_op, laplacian, diff
984+
alpha = sym.Symbol(self.heat_alpha_name)
985+
w = make_identity_diff_op(self.dim - 1, time_dependent=True)
986+
time_diff = [0]*self.dim
987+
time_diff[-1] = 1
988+
return diff(w, tuple(time_diff)) - alpha * laplacian(w)
989+
990+
936991
# }}}
937992

938993

@@ -1350,6 +1405,7 @@ def map_expression_kernel(self, kernel):
13501405
map_elasticity_kernel = map_expression_kernel
13511406
map_line_of_compression_kernel = map_expression_kernel
13521407
map_stresslet_kernel = map_expression_kernel
1408+
map_heat_kernel = map_expression_kernel
13531409

13541410
def map_axis_target_derivative(self, kernel):
13551411
return type(kernel)(kernel.axis, self.rec(kernel.inner_kernel))
@@ -1406,6 +1462,7 @@ def map_expression_kernel(self, kernel):
14061462
map_yukawa_kernel = map_expression_kernel
14071463
map_line_of_compression_kernel = map_expression_kernel
14081464
map_stresslet_kernel = map_expression_kernel
1465+
map_heat_kernel = map_expression_kernel
14091466

14101467
def map_axis_target_derivative(self, kernel):
14111468
return 1 + self.rec(kernel.inner_kernel)

test/test_kernels.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
HelmholtzKernel,
4949
BiharmonicKernel,
5050
StokesletKernel,
51+
HeatKernel,
5152
AxisTargetDerivative,
5253
DirectionalSourceDerivative)
5354

@@ -278,6 +279,8 @@ def test_p2e2p(actx_factory, base_knl, expn_class, order, with_source_derivative
278279
extra_kwargs["k"] = 0.2
279280
if isinstance(base_knl, StokesletKernel):
280281
extra_kwargs["mu"] = 0.2
282+
if isinstance(base_knl, HeatKernel):
283+
extra_kwargs["alpha"] = 1.0
281284

282285
if with_source_derivative:
283286
knl = DirectionalSourceDerivative(base_knl, "dir_vec")
@@ -306,12 +309,21 @@ def test_p2e2p(actx_factory, base_knl, expn_class, order, with_source_derivative
306309
h_values = [1/2, 1/3, 1/5]
307310

308311
rng = np.random.default_rng(19)
309-
center = np.array([2, 1, 0][:knl.dim], np.float64)
310-
sources = actx.from_numpy(
311-
0.7 * (-0.5 + rng.random((knl.dim, nsources), dtype=np.float64))
312-
+ center[:, np.newaxis])
312+
313+
if isinstance(base_knl, HeatKernel):
314+
# Setup sources so that there are no negative time intervals
315+
center = np.array([0, 0, 0, 0.5][-knl.dim:], np.float64)
316+
sources = (-0.5 + rng.random((knl.dim, nsources), dtype=np.float64)
317+
+ center[:, np.newaxis])
318+
loc_center = np.array([0.0, 0.0, 0.0, 6.0][-knl.dim:]) + center
319+
else:
320+
center = np.array([2, 1, 0][-knl.dim:], np.float64)
321+
sources = 0.7 * (-0.5 + rng.random((knl.dim, nsources), dtype=np.float64)
322+
+ center[:, np.newaxis])
323+
loc_center = np.array([5.5, 0.0, 0.0][-knl.dim:]) + center
313324

314325
strengths = actx.from_numpy(np.ones(nsources, dtype=np.float64) / nsources)
326+
sources = actx.from_numpy(sources)
315327

316328
source_boxes = actx.from_numpy(np.array([0], dtype=np.int32))
317329
box_source_starts = actx.from_numpy(np.array([0], dtype=np.int32))
@@ -328,15 +340,12 @@ def test_p2e2p(actx_factory, base_knl, expn_class, order, with_source_derivative
328340

329341
for h in h_values:
330342
if issubclass(expn_class, LocalExpansionBase):
331-
loc_center = np.array([5.5, 0.0, 0.0][:knl.dim]) + center
332343
centers = np.array(loc_center, dtype=np.float64).reshape(knl.dim, 1)
333344
fp = FieldPlotter(loc_center, extent=h, npoints=res)
334345
else:
335-
eval_center = np.array([1/h, 0.0, 0.0][:knl.dim]) + center
346+
eval_center = np.array([0.0, 0.0, 0.0, 1/h][-knl.dim:]) + center
336347
fp = FieldPlotter(eval_center, extent=0.1, npoints=res)
337-
centers = (np.array([0.0, 0.0, 0.0][:knl.dim],
338-
dtype=np.float64).reshape(knl.dim, 1)
339-
+ center[:, np.newaxis])
348+
centers = center[:, np.newaxis]
340349

341350
centers = actx.from_numpy(centers)
342351
targets = actx.from_numpy(make_obj_array(fp.points))
@@ -434,7 +443,10 @@ def test_p2e2p(actx_factory, base_knl, expn_class, order, with_source_derivative
434443
if issubclass(expn_class, LocalExpansionBase):
435444
tgt_order_grad = tgt_order - 1
436445
slack = 0.7
437-
grad_slack = 0.5
446+
if isinstance(base_knl, HeatKernel):
447+
grad_slack = 0.8
448+
else:
449+
grad_slack = 0.5
438450
else:
439451
tgt_order_grad = tgt_order + 1
440452

@@ -445,6 +457,10 @@ def test_p2e2p(actx_factory, base_knl, expn_class, order, with_source_derivative
445457
slack += 1
446458
grad_slack += 1
447459

460+
if isinstance(base_knl, HeatKernel):
461+
slack += 0.5
462+
grad_slack += 1.5
463+
448464
if isinstance(knl, DirectionalSourceDerivative):
449465
slack += 1
450466
grad_slack += 2

0 commit comments

Comments
 (0)