import torch

def sparse_dot_product_with_defaults(
    x: torch.Tensor,
    y: torch.Tensor,
    x_default: torch.Tensor,
    y_default: torch.Tensor,
) -> torch.Tensor:
    """
    Rowwise dot product between two sparse COO tensors with identical sparsity patterns.
    Only supports 2D inputs and reduction along dim=-1 (rowwise dot).

    Args:
        x, y: Sparse COO tensors of shape (N, M) with the same sparsity pattern.
        x_default, y_default: Dense tensors of shape (N,), giving the default value per row.

    Returns:
        Dense tensor of shape (N,), containing the rowwise dot products.
    """
    assert x.layout == torch.sparse_coo and y.layout == torch.sparse_coo
    x, y = x.coalesce(), y.coalesce()
    assert torch.equal(x.indices(), y.indices()), "Sparsity patterns must match"

    N, M = x.shape
    idx = x.indices()          # shape (2, nnz)
    row_idx, col_idx = idx     # each (nnz,)
    vals_x, vals_y = x.values(), y.values()

    # baseline: every row has M contributions of (default_x * default_y)
    out = (x_default * y_default) * M   # shape (N,)

    # sparse corrections: replace defaults at the actual nonzeros
    corr_vals = vals_x * vals_y - (x_default[row_idx] * y_default[row_idx])
    out.index_add_(0, row_idx, corr_vals)

    return out