Skip to content

Commit 6303f9e

Browse files
committed
implement hierachical matrix solver
1 parent 3d8faf1 commit 6303f9e

File tree

12 files changed

+1592
-32
lines changed

12 files changed

+1592
-32
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

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
}
3636

3737
nitpick_ignore_regex = [
38+
["py:class", r".*_ProxyNeighborEvaluationResult"],
3839
# Sphinx started complaining about these in 8.2.1(-ish)
3940
# -AK, 2025-02-24
4041
["py:class", r"TypeAliasForwardRef"],

examples/scaling-study-hmatrix.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
import logging
24+
from dataclasses import dataclass
25+
26+
import numpy as np
27+
28+
from meshmode.array_context import PyOpenCLArrayContext
29+
from pytools.convergence import EOCRecorder
30+
31+
from pytential import GeometryCollection, sym
32+
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)
108+
109+
eoc_build = EOCRecorder()
110+
eoc_matvec = EOCRecorder()
111+
112+
import meshmode.discretization.poly_element as mpoly
113+
import meshmode.mesh.generation as mgen
114+
115+
resolutions = [64, 128, 256, 512, 1024, 1536, 2048, 2560, 3072]
116+
117+
for n in resolutions:
118+
mesh = mgen.make_curve_mesh(
119+
mgen.NArmedStarfish(5, 0.25),
120+
np.linspace(0, 1, n),
121+
order=target_order)
122+
123+
from meshmode.discretization import Discretization
124+
pre_density_discr = Discretization(actx, mesh,
125+
mpoly.InterpolatoryQuadratureGroupFactory(target_order))
126+
127+
from pytential.qbx import QBXLayerPotentialSource
128+
qbx = QBXLayerPotentialSource(
129+
pre_density_discr,
130+
fine_order=source_ovsmp * target_order,
131+
qbx_order=qbx_order,
132+
fmm_order=False, fmm_backend=None,
133+
)
134+
places = GeometryCollection(qbx, auto_where=dd.geometry)
135+
density_discr = places.get_discretization(dd.geometry, dd.discr_stage)
136+
137+
logger.info("ndofs: %d", density_discr.ndofs)
138+
logger.info("nelements: %d", density_discr.mesh.nelements)
139+
140+
timings = run_hmatrix_matvec(actx, places, dofdesc=dd)
141+
eoc_build.add_data_point(density_discr.ndofs, timings.build)
142+
eoc_matvec.add_data_point(density_discr.ndofs, timings.matvec)
143+
144+
for name, eoc in [("build", eoc_build), ("matvec", eoc_matvec)]:
145+
logger.info("%s\n%s",
146+
name, eoc.pretty_print(
147+
abscissa_label="dofs",
148+
error_label=f"{name} (s)",
149+
abscissa_format="%d",
150+
error_format="%.3fs",
151+
eoc_format="%.2f",
152+
)
153+
)
154+
visualize_eoc(f"scaling-study-hmatrix-{name}", eoc, 1)
155+
156+
157+
def visualize_eoc(
158+
filename: str, eoc: EOCRecorder, order: int,
159+
overwrite: bool = False) -> None:
160+
try:
161+
import matplotlib.pyplot as plt
162+
except ImportError:
163+
logger.info("matplotlib not available for plotting")
164+
return
165+
166+
fig = plt.figure(figsize=(10, 10), dpi=300)
167+
ax = fig.gca()
168+
169+
h, error = np.array(eoc.history).T # type: ignore[no-untyped-call]
170+
ax.loglog(h, error, "o-")
171+
172+
max_h = np.max(h)
173+
min_e = np.min(error)
174+
max_e = np.max(error)
175+
min_h = np.exp(np.log(max_h) + np.log(min_e / max_e) / order)
176+
177+
ax.loglog(
178+
[max_h, min_h], [max_e, min_e], "k-", label=rf"$\mathcal{{O}}(h^{order})$"
179+
)
180+
181+
# }}}
182+
183+
ax.grid(True, which="major", linestyle="-", alpha=0.75)
184+
ax.grid(True, which="minor", linestyle="--", alpha=0.5)
185+
186+
ax.set_xlabel("$N$")
187+
ax.set_ylabel("$T~(s)$")
188+
189+
import pathlib
190+
filename = pathlib.Path(filename)
191+
if not overwrite and filename.exists():
192+
raise FileExistsError(f"output file '{filename}' already exists")
193+
194+
fig.savefig(filename)
195+
plt.close(fig)
196+
197+
198+
if __name__ == "__main__":
199+
run_scaling_study(ambient_dim=2)

pytential/linalg/direct_solver_symbolic.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,14 @@ def prepare_proxy_expr(
7575
places: GeometryCollection,
7676
exprs: Iterable[ArithmeticExpression],
7777
auto_where: tuple[DOFDescriptorLike, DOFDescriptorLike],
78+
remove_transforms: bool = True,
7879
) -> obj_array.ObjectArray1D[ArithmeticExpression]:
7980
def _prepare_expr(expr: ArithmeticExpression) -> ArithmeticExpression:
8081
# remove all diagonal / non-operator terms in the expression
8182
expr = IntGTermCollector()(expr)
8283
# ensure all IntGs remove all the kernel derivatives
83-
expr = KernelTransformationRemover()(expr)
84+
if remove_transforms:
85+
expr = KernelTransformationRemover()(expr)
8486
# ensure all IntGs have their source and targets set
8587
expr = DOFDescriptorReplacer(auto_where[0], auto_where[1])(expr)
8688

0 commit comments

Comments
 (0)