Skip to content

[Repo Assist] perf(gcm): vectorise _remove_constant_columns and optimise cross-covariance in kernel independence test - #1780

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
repo-assist/perf-kernel-independence-test-20260828-9a78d6825883d5a3
Open

github-actions[bot] wants to merge 1 commit into
mainfrom
repo-assist/perf-kernel-independence-test-20260828-9a78d6825883d5a3

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

Two complementary performance improvements to dowhy/gcm/independence_test/kernel.py, both on the hot path of every approx_kernel_based and kernel_based independence test call.

1. Vectorise _remove_constant_columns

Before – per-column np.unique loop: O(p · n log n)

return X[:, [np.unique(X[:, i]).shape[0] > 1 for i in range(X.shape[1])]]

After – single vectorised comparison: O(p · n)

return X[:, ~np.all(X == X[0], axis=0)]

np.all(X == X[0], axis=0) compares all rows against the first row simultaneously in one NumPy broadcast, avoiding the per-column sort inside np.unique. The semantics are identical for all valid inputs (NaN values are rejected downstream at the existing check on line 88–89, so the NaN/NaN inequality edge case never reaches this function in practice).

_remove_constant_columns is called 2–3 times per independence test invocation; with large feature matrices the savings compound.

2. Optimise _estimate_column_wise_covariances

Before – full (dx+dy) × (dx+dy) covariance via np.cov: O(n·(dx+dy)2)

return np.cov(X, Y, rowvar=False)[: X.shape[1], -Y.shape[1] :]

np.cov(X, Y) stacks both matrices and computes the complete (dx+dy)-square covariance matrix, then discards the auto-covariance blocks and returns only the (dx, dy) cross-covariance slice.

After – direct cross-covariance: O(n·dx·dy)

n = X.shape[0]
X_c = X - X.mean(axis=0)
Y_c = Y - Y.mean(axis=0)
return X_c.T @ Y_c / (n - 1)
```

Result is **numerically identical** to the original.  For the default `num_random_features=50` this cuts the covariance work by ~4× (from `1002` to `502` multiplications per call).

`_estimate_column_wise_covariances` is called once per permutation inside `_rit` / `_rcit` (default `num_permutations=100`), plus 3 extra times in `_rcit` for `cov_zz`, `cov_xz`, `cov_zy`.

---

## Test Status

```
tests/gcm/independence_test/test_kernel.py  22 passed in 17.26s ✅

All 22 existing kernel independence-test unit tests (categorical, continuous, conditional, unconditional, determinism, constant-column edge cases) pass unchanged.

Format check: black – no changes needed; isort – no changes needed.
Lint: flake8 – no new errors introduced (pre-existing E501 in docstrings at lines 32–41 are unchanged).

Generated by 🌈 Repo Assist, see workflow run. Learn more.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@11c9a2c442e519ff2b427bf58679f5a525353f76

…riance in kernel independence test

_remove_constant_columns previously called np.unique once per column
(O(p * n log n) total).  Replace with a vectorised comparison against
the first row: np.all(X == X[0], axis=0) is O(n * p) and avoids the
sort step entirely.

_estimate_column_wise_covariances previously called np.cov(X, Y) which
builds the full (dx+dy) × (dx+dy) covariance matrix even though only the
dx × dy cross-covariance block is needed.  Replacing it with the direct
formula X_c.T @ Y_c / (n-1) cuts the computation from O(n*(dx+dy)^2) to
O(n*dx*dy).  For the default num_random_features=50 this is a ~4× speedup
per call.  The result is numerically identical.

Both functions are called inside every RIT/RCIT permutation iteration
(typically 100 iterations per independence test), so the saving compounds.

All 22 existing kernel independence-test unit tests pass unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Optimizes hot paths in GCM kernel independence tests.

Changes:

  • Vectorizes constant-column detection.
  • Computes cross-covariance directly.
  • Adds empty-input handling.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +394 to +395
if X.shape[0] == 0:
return X
Comment on lines +374 to +376
X_c = X - X.mean(axis=0)
Y_c = Y - Y.mean(axis=0)
return X_c.T @ Y_c / (n - 1)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants