Direct Solver: Recursive Skeletonization#164
Conversation
39e8347 to
80bb8e1
Compare
8e3731d to
78699ec
Compare
|
@inducer This should be ready for a look. It doesn't do much:
|
7ed33f4 to
ea42c82
Compare
80fe1fb to
b44f4a8
Compare
cc4be24 to
0176d32
Compare
0176d32 to
f71a21b
Compare
inducer
left a comment
There was a problem hiding this comment.
Noticed a pending review. Figured it'd be better to submit it. :) Don't quite know how old this is.
pytential/linalg/cluster.py
Outdated
|
|
||
| Current level that is represented. | ||
|
|
||
| .. attribute:: nlevels |
There was a problem hiding this comment.
Reworked this a while back, so not quite sure if your comment still applies?
pytential/linalg/cluster.py
Outdated
| # {{{ cluster tree | ||
|
|
||
| @dataclass(frozen=True) | ||
| class ClusterTreeLevel: |
There was a problem hiding this comment.
Is this two things rather than one?
There was a problem hiding this comment.
Yeah, separated into a ClusterTree and ClusterLevel with a ClusterTree.levels() iterator to go through them for the recursive skeletonization.
Naming could still be better probably?
| logger.info("\n%s", case) | ||
|
|
||
| run_skeletonize_by_proxy(actx, case, case.resolutions[0], visualize=visualize) | ||
| dd = sym.DOFDescriptor(case.name, discr_stage=case.skel_discr_stage) |
There was a problem hiding this comment.
Update the test docstring to describe precisely what's being tested here.
There was a problem hiding this comment.
Updated the blurb in
https://github.com/inducer/pytential/compare/f71a21b1a54791b02b35712225b9cfaca4be60cf..9a8db7ff410051a6b7d86cd8d5b934d02d094fef
Let me know if it's clear enough!
9a8db7f to
ce00d4e
Compare
c1e986c to
814b8ac
Compare
c479984 to
8aa200f
Compare
9c5bb68 to
a7c5d19
Compare
8bef46c to
ae443aa
Compare
ae443aa to
85033db
Compare
85033db to
15eee49
Compare
15eee49 to
78c496d
Compare
ba99713 to
963f568
Compare
963f568 to
537447a
Compare
537447a to
cf88a78
Compare
cf88a78 to
978a582
Compare
978a582 to
702d44a
Compare
702d44a to
3d8faf1
Compare
7f705e1 to
2df2cc8
Compare
f153ed6 to
8994a24
Compare
8994a24 to
48a5862
Compare
fe8b4f8 to
0490e47
Compare
0490e47 to
50b4825
Compare
There was a problem hiding this comment.
Pull request overview
This PR advances the direct solver work by introducing hierarchical clustering infrastructure and wiring it into proxy-based skeletonization to enable recursive (multi-level) skeletonization, along with the associated test and documentation updates.
Changes:
- Add a new
pytential.linalg.clustermodule providing cluster trees/levels, clustering helpers, andpartition_by_nodes. - Extend skeletonization to produce richer results (including diagonal blocks) and add
rec_skeletonize_by_proxyfor multi-level skeletonization. - Update proxy and skeletonization tests (and docs) to match the new clustering/tree and neighbor-gathering APIs.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
pytential/linalg/cluster.py |
New clustering module (cluster tree/levels, partitioning, visualization utilities). |
pytential/linalg/skeletonization.py |
Adds recursive skeletonization and augments skeletonization results/weighting/logging. |
pytential/linalg/proxy.py |
Removes partition_by_nodes (moved to cluster module) and updates neighbor gathering API. |
pytential/linalg/utils.py |
Documents/exports helper utilities used by skeletonization (make_flat_cluster_diag, interp_decomp). |
test/extra_matrix_data.py |
Updates test fixtures to return (index, tree) and import partitioning from linalg.cluster. |
test/test_linalg_cluster.py |
New tests for cluster tree construction and (un)clustering behavior. |
test/test_linalg_proxy.py |
Adapts tests to updated cluster index return type and neighbor gathering signature. |
test/test_linalg_skeletonization.py |
Updates tests to exercise multilevel skeletonization flow. |
test/test_matrix.py |
Adapts callers to updated get_cluster_index / get_tgt_src_cluster_index return type. |
doc/linalg.rst |
Includes pytential.linalg.cluster in the linalg documentation. |
doc/conf.py |
Adds/adjusts intersphinx mappings for new referenced types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if discr.ambient_dim == 2: | ||
| _visualize_clusters_2d(actx, generator, discr, srcindex, tree, filename, | ||
| dofdesc=dofdesc, overwrite=overwrite) | ||
| elif discr.ambient_dim == 2: |
There was a problem hiding this comment.
The 3D branch in visualize_clusters is unreachable because both the if and elif check discr.ambient_dim == 2. This prevents 3D visualization and will raise NotImplementedError for ambient_dim == 3. The elif condition should check for ambient_dim == 3.
| elif discr.ambient_dim == 2: | |
| elif discr.ambient_dim == 3: |
| ax = fig.gca() | ||
|
|
||
| plotter = TreePlotter(tree._tree) | ||
| plotter.set_bounding_box() | ||
| plotter.draw_tree(fill=False, edgecolor="black", zorder=10) |
There was a problem hiding this comment.
_visualize_clusters_2d unconditionally constructs TreePlotter(tree._tree). When partition_by_nodes(..., tree_kind=None) is used, ClusterTree._tree is None, so visualization will crash. Consider either raising a clearer error if tree._tree is None or skipping tree plotting in that case.
| for i, clevel in enumerate(ctree.levels[:-1]): | ||
| for ibrow, ibcol in product(range(wrangler.nrows), range(wrangler.ncols)): | ||
| skeleton = _skeletonize_block_by_proxy_with_mats( | ||
| actx, ibrow, ibcol, proxy.places, proxy, wrangler, tgt_src_index, | ||
| id_eps=id_eps, | ||
| # NOTE: we probably never want to set the rank here? | ||
| id_rank=None, | ||
| rng=rng, | ||
| max_particles_in_box=max_particles_in_box) |
There was a problem hiding this comment.
rec_skeletonize_by_proxy takes places as an argument, but passes proxy.places into _skeletonize_block_by_proxy_with_mats. If a caller supplies _proxy built from a different GeometryCollection than places, the evaluation will use inconsistent geometries. Consider passing places through (and/or asserting proxy.places is places) to avoid subtle mismatches.
| """Evaluate the proxy to cluster and neighbor to cluster interactions for | ||
| each cluster in *cluster_index*. | ||
| """ |
There was a problem hiding this comment.
Docstring for _evaluate_proxy_skeletonization_interaction still refers to *cluster_index*, but the function now takes source_index/target_index. Updating the wording to match the new parameter names would avoid confusion for future readers.
| assert isinstance(discr, Discretization) | ||
|
|
||
| if tree_kind is not None: | ||
| setup_actx = lpot_source._setup_actx |
There was a problem hiding this comment.
In partition_by_nodes, setup_actx is taken from lpot_source._setup_actx, but places.get_geometry(...) can be a plain meshmode.discretization.Discretization (e.g. when GeometryCollection is built from a discretization), which does not provide _setup_actx. gather_cluster_neighbor_points handles this by using discr._setup_actx instead. Consider using discr._setup_actx here (or branching on the type) to avoid an AttributeError when tree_kind is not None.
| setup_actx = lpot_source._setup_actx | |
| if hasattr(lpot_source, "_setup_actx"): | |
| setup_actx = lpot_source._setup_actx | |
| else: | |
| setup_actx = discr._setup_actx |
Attempting to break up the direct solver MR into smaller, more reviewable pieces. As a rundown
linalg.proxy).The code for this is mostly in the deprecated https://gitlab.tiker.net/inducer/pytential/-/merge_requests/137.