Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cda104c
Add single-rank test for lsq_pseudoinv factory
msimberg Mar 9, 2026
0c2ec7d
Only run some tests in ci
msimberg Mar 9, 2026
37f70ab
modified np strict references with broader array_ns
nfarabullini Mar 4, 2026
95a75d8
Update interpolation_fields.py
nfarabullini Mar 5, 2026
2e3c5b6
ran pre-commit
nfarabullini Mar 5, 2026
663ed91
removed additional but unused return val
nfarabullini Mar 5, 2026
173fcf0
Update interpolation_fields.py
nfarabullini Mar 5, 2026
a623b48
ran pre-commit
nfarabullini Mar 5, 2026
925e7cf
small fix to tuple
nfarabullini Mar 6, 2026
903a56d
Add custom dimensions for lsq coeffs field
msimberg Mar 11, 2026
f93312f
Skip Lsq dims in test that checks connectivities
msimberg Mar 16, 2026
821c5ef
Update dimensions for lsq fields from serialized data
msimberg Mar 16, 2026
3190933
Merge remote-tracking branch 'origin/main' into lsq-pseudoinv-factory…
msimberg Mar 16, 2026
81b68af
Fix lsq_pseudoinv computation in distributed setups and add test
msimberg Mar 16, 2026
cbd1495
Remove todo
msimberg Mar 17, 2026
957f10d
Uncomment testing
msimberg Mar 17, 2026
e452029
Update model/common/tests/common/grid/unit_tests/test_icon.py
msimberg Mar 17, 2026
908d971
Merge branch 'main' into lsq-pseudoinv-factory-test
nfarabullini Mar 17, 2026
e069401
Update model/common/tests/common/interpolation/unit_tests/test_interp…
msimberg Mar 17, 2026
fd0c55c
Simplify test
msimberg Mar 17, 2026
d1168ea
Move lsq dimensions
msimberg Mar 17, 2026
10974eb
Merge branch 'main' into lsq-pseudoinv-factory-test
nfarabullini Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions model/common/src/icon4py/model/common/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
C2E2CDim = gtx.Dimension("C2E2C", gtx.DimensionKind.LOCAL)
C2E2C2EDim = gtx.Dimension("C2E2C2E", gtx.DimensionKind.LOCAL)
C2E2C2E2CDim = gtx.Dimension("C2E2C2E2C", gtx.DimensionKind.LOCAL)
LsqCDim = gtx.Dimension("LsqC", gtx.DimensionKind.LOCAL)
LsqUnkDim = gtx.Dimension("LsqUnk", gtx.DimensionKind.LOCAL)
E2C = gtx.FieldOffset("E2C", source=CellDim, target=(EdgeDim, E2CDim))
C2E = gtx.FieldOffset("C2E", source=EdgeDim, target=(CellDim, C2EDim))
V2C = gtx.FieldOffset("V2C", source=CellDim, target=(VertexDim, V2CDim))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def _register_computed_fields(self) -> None:
array_ns=self._xp,
),
fields=(attrs.LSQ_PSEUDOINV,),
domain=(),
domain=(dims.CellDim, dims.LsqUnkDim, dims.LsqCDim),
deps={
"cell_center_x": geometry_attrs.CELL_CENTER_X,
"cell_center_y": geometry_attrs.CELL_CENTER_Y,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,8 +1167,8 @@ def compute_lsq_pseudoinv(
for jjb in range(lsq_dim_c):
for jjk in range(lsq_dim_unk):
for jc in range(start_idx, min_rlcell_int):
u, s, v_t = array_ns.linalg.svd(z_lsq_mat_c[jc, :, :])
if cell_owner_mask[jc]:
u, s, v_t = array_ns.linalg.svd(z_lsq_mat_c[jc, :, :])
lsq_pseudoinv[jc, :lsq_dim_unk, jjb] = (
lsq_pseudoinv[jc, :lsq_dim_unk, jjb]
+ v_t[jjk, :lsq_dim_unk] / s[jjk] * u[jjb, jjk] * lsq_weights_c[jc, jjb]
Expand Down Expand Up @@ -1237,7 +1237,11 @@ def compute_lsq_coeffs(
for js in range(lsq_dim_stencil):
z_dist_g[:, js, :] = array_ns.asarray(
gnomonic_proj(
cell_lon, cell_lat, cell_lon[c2e2c[:, js]], cell_lat[c2e2c[:, js]]
cell_lon,
cell_lat,
cell_lon[c2e2c[:, js]],
cell_lat[c2e2c[:, js]],
array_ns,
)
).T

Expand All @@ -1252,15 +1256,17 @@ def compute_lsq_coeffs(
ilc_s = c2e2c[jc, :lsq_dim_stencil]
cc_cell = array_ns.zeros((lsq_dim_stencil, 2))

cc_cv = (cell_center_x[jc], cell_center_y[jc])
cc_cv = array_ns.asarray((cell_center_x[jc], cell_center_y[jc]))
for js in range(lsq_dim_stencil):
cc_cell[js, :] = diff_on_edges_torus_numpy(
cell_center_x[jc],
cell_center_y[jc],
cell_center_x[ilc_s][js],
cell_center_y[ilc_s][js],
domain_length,
domain_height,
cc_cell[js, :] = array_ns.asarray(
diff_on_edges_torus_numpy(
cell_center_x[jc],
cell_center_y[jc],
cell_center_x[ilc_s][js],
cell_center_y[ilc_s][js],
domain_length,
domain_height,
)
)
z_dist_g[jc, :, :] = cc_cell - cc_cv

Expand Down
14 changes: 10 additions & 4 deletions model/common/src/icon4py/model/common/math/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause

from types import ModuleType

import numpy as np

Expand All @@ -17,6 +17,7 @@ def gnomonic_proj(
lat_c: data_alloc.NDArray,
lon: data_alloc.NDArray,
lat: data_alloc.NDArray,
array_ns: ModuleType = np,
) -> tuple[data_alloc.NDArray, data_alloc.NDArray]:
"""
Compute gnomonic projection.
Expand All @@ -38,11 +39,16 @@ def gnomonic_proj(
TODO:
replace this with a suitable library call
Comment on lines 39 to 40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect it's still as valid as before (this PR doesn't change the validity).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but do we need it at all? I'm just confused of what kinf of library call should be used here instead of this function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fair question though I'll leave that consideration out of this PR.

It looks like @ajocksch added this functionality and the TODO, maybe you can comment if you think the TODO is still useful? If not, we can remove it separately.

"""
cosc = np.sin(lat_c) * np.sin(lat) + np.cos(lat_c) * np.cos(lat) * np.cos(lon - lon_c)
cosc = array_ns.sin(lat_c) * array_ns.sin(lat) + array_ns.cos(lat_c) * array_ns.cos(
lat
) * array_ns.cos(lon - lon_c)
zk = 1.0 / cosc

x = zk * np.cos(lat) * np.sin(lon - lon_c)
y = zk * (np.cos(lat_c) * np.sin(lat) - np.sin(lat_c) * np.cos(lat) * np.cos(lon - lon_c))
x = zk * array_ns.cos(lat) * array_ns.sin(lon - lon_c)
y = zk * (
array_ns.cos(lat_c) * array_ns.sin(lat)
- array_ns.sin(lat_c) * array_ns.cos(lat) * array_ns.cos(lon - lon_c)
)

return x, y

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def _get_neighbor_tables(grid: base.Grid) -> dict:
def gather_field(field: np.ndarray, props: decomp_defs.ProcessProperties) -> tuple:
constant_dims = tuple(field.shape[1:])
_log.info(f"gather_field on rank={props.rank} - gathering field of local shape {field.shape}")
# Because of sparse indexing the field may have a non-contigous layout,
# which Gatherv doesn't support. Make sure the field is contiguous.
field = np.ascontiguousarray(field)
constant_length = functools.reduce(operator.mul, constant_dims, 1)
local_sizes = np.array(props.comm.gather(field.size, root=0))
if props.rank == 0:
Expand Down Expand Up @@ -337,6 +340,7 @@ def test_geometry_fields_compare_single_multi_rank(
interpolation_attributes.GEOFAC_GRG_Y,
interpolation_attributes.GEOFAC_N2S,
interpolation_attributes.GEOFAC_ROT,
interpolation_attributes.LSQ_PSEUDOINV,
interpolation_attributes.NUDGECOEFFS_E,
interpolation_attributes.POS_ON_TPLANE_E_X,
interpolation_attributes.POS_ON_TPLANE_E_Y,
Expand Down
2 changes: 2 additions & 0 deletions model/common/tests/common/grid/unit_tests/test_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def test_when_replace_skip_values_then_only_pentagon_points_remain(
) -> None:
if dim == dims.V2E2VDim:
pytest.skip("V2E2VDim is not supported in the current grid configuration.")
if dim in (dims.LsqCDim, dims.LsqUnkDim):
pytest.skip("LsqCDim and LsqUnkDim are not offset dimensions.")
grid = utils.run_grid_manager(grid_descriptor, keep_skip_values=False, backend=backend).grid
connectivity = grid.get_connectivity(dim.value)
if dim in icon.CONNECTIVITIES_ON_PENTAGONS and not grid.limited_area:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,10 @@ def test_distributed_interpolation_lsq_pseudoinv(
parallel_helpers.log_process_properties(processor_props)
parallel_helpers.log_local_field_size(decomposition_info)
factory = interpolation_factory_from_savepoint
field_ref_1 = interpolation_savepoint.__getattribute__("lsq_pseudoinv_1")().asnumpy()
field_ref_2 = interpolation_savepoint.__getattribute__("lsq_pseudoinv_2")().asnumpy()
field_1 = factory.get(attrs.LSQ_PSEUDOINV)[:, 0, :]
field_2 = factory.get(attrs.LSQ_PSEUDOINV)[:, 1, :]
assert test_utils.dallclose(field_1, field_ref_1, atol=1e-15) # type: ignore[arg-type] # mypy does not recognize sliced array as still an array
assert test_utils.dallclose(field_2, field_ref_2, atol=1e-15) # type: ignore[arg-type] # mypy does not recognize sliced array as still an array
field_ref_1 = interpolation_savepoint.lsq_pseudoinv_1().asnumpy()
field_ref_2 = interpolation_savepoint.lsq_pseudoinv_2().asnumpy()
field = factory.get(attrs.LSQ_PSEUDOINV).asnumpy()
field_1 = field[:, 0, :]
field_2 = field[:, 1, :]
assert test_utils.dallclose(field_1, field_ref_1, atol=1e-15)
assert test_utils.dallclose(field_2, field_ref_2, atol=1e-15)
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,20 @@ def test_rbf_interpolation_coeffs_vertex(
field_v2[horizontal_start:],
atol=RBF_TOLERANCES[dims.VertexDim][experiment.name],
)


@pytest.mark.level("integration")
@pytest.mark.datatest
def test_lsq_pseudoinv(
interpolation_savepoint: serialbox.InterpolationSavepoint,
experiment: definitions.Experiment,
backend: gtx_typing.Backend | None,
) -> None:
field_ref_1 = interpolation_savepoint.lsq_pseudoinv_1().asnumpy()
field_ref_2 = interpolation_savepoint.lsq_pseudoinv_2().asnumpy()
factory = _get_interpolation_factory(backend, experiment)
field = factory.get(attrs.LSQ_PSEUDOINV).asnumpy()
field_1 = field[:, 0, :]
field_2 = field[:, 1, :]
assert test_helpers.dallclose(field_ref_1, field_1, atol=1e-15)
assert test_helpers.dallclose(field_ref_2, field_2, atol=1e-15)
4 changes: 2 additions & 2 deletions model/testing/src/icon4py/model/testing/serialbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,10 @@ def rbf_vec_idx_v(self):
return self._get_field("rbf_vec_idx_v", dims.VertexDim, dims.V2EDim)

def lsq_pseudoinv_1(self):
return self._get_field("lsq_pseudoinv_1", dims.CellDim, dims.C2E2CDim)
return self._get_field("lsq_pseudoinv_1", dims.CellDim, dims.LsqCDim)

def lsq_pseudoinv_2(self):
return self._get_field("lsq_pseudoinv_2", dims.CellDim, dims.C2E2CDim)
return self._get_field("lsq_pseudoinv_2", dims.CellDim, dims.LsqCDim)


class MetricSavepoint(IconSavepoint):
Expand Down
Loading