Skip to content

Commit 5724362

Browse files
ShawnL00inducer
authored andcommitted
l2l coeffs test
1 parent 118ae83 commit 5724362

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

sumpy/test/test_l2l_coeffs.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""
2+
Verification for the difference between compressed L2L coefficients and
3+
full L2L coefficients.
4+
"""
5+
import os
6+
os.environ['PYOPENCL_CTX'] = '0'
7+
8+
import math
9+
import numpy as np
10+
import scipy.special as spsp
11+
import sympy as sp
12+
import pytest
13+
import pyopencl as cl
14+
import sumpy.toys as t
15+
from sumpy.kernel import (
16+
YukawaKernel,
17+
HelmholtzKernel,
18+
LaplaceKernel,
19+
BiharmonicKernel
20+
)
21+
from sumpy.expansion.local import (
22+
LinearPDEConformingVolumeTaylorLocalExpansion,
23+
VolumeTaylorLocalExpansion
24+
)
25+
from sumpy.array_context import _acf
26+
from sumpy.tools import build_matrix
27+
28+
29+
VERBOSE = False
30+
31+
def to_scalar(val):
32+
"""Convert symbolic or array value to scalar."""
33+
if hasattr(val, 'evalf'):
34+
val = val.evalf()
35+
if hasattr(val, 'item'):
36+
val = val.item()
37+
return complex(val)
38+
39+
40+
class NumericMatVecOperator:
41+
"""Wrapper for symbolic matrix-vector operator with numeric
42+
substitution."""
43+
44+
def __init__(self, symbolic_op, repl_dict):
45+
self.symbolic_op = symbolic_op
46+
self.repl_dict = repl_dict
47+
self.shape = symbolic_op.shape
48+
49+
def matvec(self, vec):
50+
result = self.symbolic_op.matvec(vec)
51+
numeric_result = []
52+
for expr in result:
53+
if hasattr(expr, 'xreplace'):
54+
numeric_result.append(
55+
complex(expr.xreplace(self.repl_dict).evalf())
56+
)
57+
else:
58+
numeric_result.append(complex(expr))
59+
return np.array(numeric_result)
60+
61+
62+
def get_repl_dict(kernel, extra_kwargs):
63+
"""Numeric substitution for symbolic kernel parameters."""
64+
repl_dict = {}
65+
if 'lam' in extra_kwargs:
66+
repl_dict[sp.Symbol('lam')] = extra_kwargs['lam']
67+
if 'k' in extra_kwargs:
68+
repl_dict[sp.Symbol('k')] = extra_kwargs['k']
69+
return repl_dict
70+
71+
72+
@pytest.mark.parametrize("knl,extra_kwargs", [
73+
(LaplaceKernel(2), {}),
74+
(YukawaKernel(2), {'lam': 0.1}),
75+
(HelmholtzKernel(2), {'k': 0.5}),
76+
(BiharmonicKernel(2), {}),
77+
])
78+
79+
def test_l2l_coefficient_differences(knl, extra_kwargs):
80+
"""
81+
Test L2L coefficient differences between formula and direct
82+
computation.
83+
"""
84+
order = 7
85+
dim = 2
86+
repl_dict = get_repl_dict(knl, extra_kwargs)
87+
88+
# Setup sources and centers
89+
source = np.array([[5.0], [5.0]])
90+
c1 = np.array([0.0, 0.0])
91+
c2 = c1 + np.array([-0.5, 1.0])
92+
strength = np.array([1.0])
93+
94+
actx = _acf()
95+
toy_ctx = t.ToyContext(
96+
knl,
97+
local_expn_class=LinearPDEConformingVolumeTaylorLocalExpansion,
98+
extra_kernel_kwargs=extra_kwargs
99+
)
100+
toy_ctx_full = t.ToyContext(
101+
knl,
102+
local_expn_class=VolumeTaylorLocalExpansion,
103+
extra_kernel_kwargs=extra_kwargs
104+
)
105+
106+
# Compute expansions
107+
p = t.PointSources(toy_ctx, source, weights=strength)
108+
p_full = t.PointSources(toy_ctx_full, source, weights=strength)
109+
110+
p2l = t.local_expand(actx, p, c1, order=order, rscale=1.0)
111+
p2l2l = t.local_expand(actx, p2l, c2, order=order, rscale=1.0)
112+
p2l_full = t.local_expand(actx, p_full, c1, order=order, rscale=1.0)
113+
p2l2l_full = t.local_expand(actx, p2l_full, c2, order=order, rscale=1.0)
114+
115+
# Build matrix M
116+
p2l2l_expn = LinearPDEConformingVolumeTaylorLocalExpansion(knl, order)
117+
wrangler = p2l2l_expn.expansion_terms_wrangler
118+
M_symbolic = wrangler.get_projection_matrix(rscale=1.0)
119+
numeric_op = NumericMatVecOperator(M_symbolic, repl_dict)
120+
M = build_matrix(numeric_op, dtype=np.complex128)
121+
122+
# Get compressed coefficients
123+
mu_c_symbolic = wrangler.get_full_kernel_derivatives_from_stored(
124+
p2l2l.coeffs, rscale=1.0
125+
)
126+
mu_c = []
127+
for coeff in mu_c_symbolic:
128+
if hasattr(coeff, 'xreplace'):
129+
mu_c.append(to_scalar(coeff.xreplace(repl_dict)))
130+
else:
131+
mu_c.append(to_scalar(coeff))
132+
133+
# Get identifiers
134+
stored_identifiers = p2l2l_expn.get_coefficient_identifiers()
135+
full_identifiers = p2l2l_expn.get_full_coefficient_identifiers()
136+
lexpn_idx = VolumeTaylorLocalExpansion(knl, order).get_full_coefficient_identifiers()
137+
138+
h = c2 - c1
139+
global_const = to_scalar(knl.get_global_scaling_const())
140+
141+
if VERBOSE:
142+
print(f'\n{"="*104}')
143+
print(f'L2L Verification: {type(knl).__name__} (order={order})')
144+
print(f'{"="*104}')
145+
print(f'c1 = {c1}, c2 = {c2}, h = {h}')
146+
print()
147+
print(f"{'i':>3s} | {'ν(i)':>15s} | {'|ν|':4s} | "
148+
f"{'formula':>31s} | {'direct':>31s} | {'abs err':>10s}")
149+
print("-" * 104)
150+
151+
max_abs_error = 0.0
152+
153+
for i, nu_i in enumerate(full_identifiers):
154+
i_card = sum(np.array(nu_i))
155+
156+
# Compute error by formula
157+
error = 0.0 + 0.0j
158+
for k, nu_jk in enumerate(stored_identifiers):
159+
jk_card = sum(np.array(nu_jk))
160+
if jk_card >= i_card:
161+
continue
162+
163+
start_idx = math.comb(order - i_card + dim, dim)
164+
end_idx = math.comb(order - jk_card + dim, dim)
165+
166+
for q_idx in range(start_idx, end_idx):
167+
nu_q = full_identifiers[q_idx]
168+
nu_sum = tuple(map(sum, zip(nu_q, nu_jk)))
169+
if nu_sum not in full_identifiers:
170+
continue
171+
172+
deriv_idx = full_identifiers.index(nu_sum)
173+
gamma_deriv = to_scalar(p2l_full.coeffs[deriv_idx])
174+
h_pow = np.prod(h ** np.array(nu_q))
175+
fact_nu_q = np.prod(spsp.factorial(nu_q))
176+
177+
error += -M[i, k] * gamma_deriv * h_pow / fact_nu_q
178+
179+
error /= np.prod(spsp.factorial(nu_i))
180+
error *= global_const
181+
182+
# Compute direct difference
183+
true_i_idx = lexpn_idx.index(nu_i)
184+
mu_full = to_scalar(p2l2l_full.coeffs[true_i_idx])
185+
direct_diff = (mu_full - mu_c[i]) / np.prod(spsp.factorial(nu_i))
186+
direct_diff *= global_const
187+
188+
# Compute errors
189+
abs_err = abs(error - direct_diff)
190+
max_abs_error = max(max_abs_error, abs_err)
191+
192+
if VERBOSE:
193+
print(f"{i:3d} | {str(nu_i):>15s} | {i_card:4d} | "
194+
f"{error.real: .8e}{error.imag:+.8e}j | "
195+
f"{direct_diff.real: .8e}{direct_diff.imag:+.8e}j | "
196+
f"{abs_err:9.2e}")
197+
198+
if VERBOSE:
199+
print(f"\nMaximum absolute error: {max_abs_error:.2e}")
200+
print(f'{"="*104}\n')
201+
202+
assert max_abs_error < 1e-10, \
203+
f"{type(knl).__name__}: error {max_abs_error:.2e}"
204+
205+
206+
if __name__ == "__main__":
207+
pytest.main([__file__, "-v", "-s"])

0 commit comments

Comments
 (0)