Skip to content

Commit 47c06b1

Browse files
committed
implement hierachical matrix solver
1 parent 8bef46c commit 47c06b1

File tree

10 files changed

+1547
-27
lines changed

10 files changed

+1547
-27
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ examples/*.pdf
2121

2222
*.vtu
2323
*.vts
24+
*.png
25+
*.gif
2426

2527
.cache
2628

examples/scaling-study-hmatrix.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
__copyright__ = "Copyright (C) 2022 Alexandru Fikl"
2+
3+
__license__ = """
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
"""
22+
23+
from dataclasses import dataclass
24+
25+
import numpy as np
26+
27+
from meshmode.array_context import PyOpenCLArrayContext
28+
from pytools.convergence import EOCRecorder
29+
30+
from pytential import GeometryCollection, sym
31+
32+
import logging
33+
34+
logging.basicConfig(level=logging.INFO)
35+
logger = logging.getLogger(__name__)
36+
37+
38+
@dataclass(frozen=True)
39+
class Timings:
40+
build: float
41+
matvec: float
42+
43+
44+
def run_hmatrix_matvec(
45+
actx: PyOpenCLArrayContext,
46+
places: GeometryCollection, *,
47+
dofdesc: sym.DOFDescriptor) -> None:
48+
from sumpy.kernel import LaplaceKernel
49+
kernel = LaplaceKernel(places.ambient_dim)
50+
sym_u = sym.var("u")
51+
sym_op = -0.5 * sym_u + sym.D(kernel, sym_u, qbx_forced_limit="avg")
52+
53+
density_discr = places.get_discretization(dofdesc.geometry, dofdesc.discr_stage)
54+
u = actx.thaw(density_discr.nodes()[0])
55+
56+
def build_hmat():
57+
from pytential.linalg.hmatrix import build_hmatrix_by_proxy
58+
return build_hmatrix_by_proxy(
59+
actx, places, sym_op, sym_u,
60+
domains=[dofdesc],
61+
context={},
62+
auto_where=dofdesc,
63+
id_eps=1.0e-10,
64+
_tree_kind="adaptive-level-restricted",
65+
_approx_nproxy=64,
66+
_proxy_radius_factor=1.15).get_forward()
67+
68+
# warmup
69+
from pytools import ProcessTimer
70+
with ProcessTimer() as pt:
71+
hmat = build_hmat()
72+
actx.queue.finish()
73+
74+
logger.info("build(warmup): %s", pt)
75+
76+
# build
77+
with ProcessTimer() as pt:
78+
hmat = build_hmat()
79+
actx.queue.finish()
80+
81+
t_build = pt.wall_elapsed
82+
logger.info("build: %s", pt)
83+
84+
# matvec
85+
with ProcessTimer() as pt:
86+
du = hmat @ u
87+
assert du is not None
88+
actx.queue.finish()
89+
90+
t_matvec = pt.wall_elapsed
91+
logger.info("matvec: %s", pt)
92+
93+
return Timings(t_build, t_matvec)
94+
95+
96+
def run_scaling_study(
97+
ambient_dim: int, *,
98+
target_order: int = 4,
99+
source_ovsmp: int = 4,
100+
qbx_order: int = 4,
101+
) -> None:
102+
dd = sym.DOFDescriptor(f"d{ambient_dim}", discr_stage=sym.QBX_SOURCE_STAGE2)
103+
104+
import pyopencl as cl
105+
ctx = cl.create_some_context()
106+
queue = cl.CommandQueue(ctx)
107+
actx = PyOpenCLArrayContext(queue, force_device_scalars=True)
108+
109+
eoc_build = EOCRecorder()
110+
eoc_matvec = EOCRecorder()
111+
112+
import meshmode.mesh.generation as mgen
113+
import meshmode.discretization.poly_element as mpoly
114+
resolutions = [64, 128, 256, 512, 1024, 1536, 2048, 2560, 3072]
115+
116+
for n in resolutions:
117+
mesh = mgen.make_curve_mesh(
118+
mgen.NArmedStarfish(5, 0.25),
119+
np.linspace(0, 1, n),
120+
order=target_order)
121+
122+
from meshmode.discretization import Discretization
123+
pre_density_discr = Discretization(actx, mesh,
124+
mpoly.InterpolatoryQuadratureGroupFactory(target_order))
125+
126+
from pytential.qbx import QBXLayerPotentialSource
127+
qbx = QBXLayerPotentialSource(
128+
pre_density_discr,
129+
fine_order=source_ovsmp * target_order,
130+
qbx_order=qbx_order,
131+
fmm_order=False, fmm_backend=None,
132+
)
133+
places = GeometryCollection(qbx, auto_where=dd.geometry)
134+
density_discr = places.get_discretization(dd.geometry, dd.discr_stage)
135+
136+
logger.info("ndofs: %d", density_discr.ndofs)
137+
logger.info("nelements: %d", density_discr.mesh.nelements)
138+
139+
timings = run_hmatrix_matvec(actx, places, dofdesc=dd)
140+
eoc_build.add_data_point(density_discr.ndofs, timings.build)
141+
eoc_matvec.add_data_point(density_discr.ndofs, timings.matvec)
142+
143+
for name, eoc in [("build", eoc_build), ("matvec", eoc_matvec)]:
144+
logger.info("%s\n%s",
145+
name, eoc.pretty_print(
146+
abscissa_label="dofs",
147+
error_label=f"{name} (s)",
148+
abscissa_format="%d",
149+
error_format="%.3fs",
150+
eoc_format="%.2f",
151+
)
152+
)
153+
visualize_eoc(f"scaling-study-hmatrix-{name}", eoc, 1)
154+
155+
156+
def visualize_eoc(
157+
filename: str, eoc: EOCRecorder, order: int,
158+
overwrite: bool = False) -> None:
159+
try:
160+
import matplotlib.pyplot as plt
161+
except ImportError:
162+
logger.info("matplotlib not available for plotting")
163+
return
164+
165+
fig = plt.figure(figsize=(10, 10), dpi=300)
166+
ax = fig.gca()
167+
168+
h, error = np.array(eoc.history).T # type: ignore[no-untyped-call]
169+
ax.loglog(h, error, "o-")
170+
171+
max_h = np.max(h)
172+
min_e = np.min(error)
173+
max_e = np.max(error)
174+
min_h = np.exp(np.log(max_h) + np.log(min_e / max_e) / order)
175+
176+
ax.loglog(
177+
[max_h, min_h], [max_e, min_e], "k-", label=rf"$\mathcal{{O}}(h^{order})$"
178+
)
179+
180+
# }}}
181+
182+
ax.grid(True, which="major", linestyle="-", alpha=0.75)
183+
ax.grid(True, which="minor", linestyle="--", alpha=0.5)
184+
185+
ax.set_xlabel("$N$")
186+
ax.set_ylabel("$T~(s)$")
187+
188+
import pathlib
189+
filename = pathlib.Path(filename)
190+
if not overwrite and filename.exists():
191+
raise FileExistsError(f"output file '{filename}' already exists")
192+
193+
fig.savefig(filename)
194+
plt.close(fig)
195+
196+
197+
if __name__ == "__main__":
198+
run_scaling_study(ambient_dim=2)

pytential/linalg/direct_solver_symbolic.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,16 @@ def prepare_expr(places, exprs, auto_where=None):
5454
return _prepare_expr(places, exprs, auto_where=auto_where)
5555

5656

57-
def prepare_proxy_expr(places, exprs, auto_where=None):
57+
def prepare_proxy_expr(
58+
places, exprs,
59+
auto_where=None,
60+
remove_transforms: bool = True):
5861
def _prepare_expr(expr):
5962
# remove all diagonal / non-operator terms in the expression
6063
expr = IntGTermCollector()(expr)
6164
# ensure all IntGs remove all the kernel derivatives
62-
expr = KernelTransformationRemover()(expr)
65+
if remove_transforms:
66+
expr = KernelTransformationRemover()(expr)
6367
# ensure all IntGs have their source and targets set
6468
expr = DOFDescriptorReplacer(auto_where[0], auto_where[1])(expr)
6569

0 commit comments

Comments
 (0)