Repository: scikit-learn/scikit-learn
File: sklearn/tests/test_kernel_approximation.py (Python)

Code before:
)
def test_nystroem_default_parameters_array_api(array_namespace, device, dtype_name):
    xp = _array_api_for_tests(array_namespace, device)
    rnd = np.random.RandomState(42)
    X_np = rnd.uniform(size=(10, 4)).astype(dtype_name)
    X_xp = xp.asarray(X_np, device=device)

    with config_context(array_api_dispatch=True):
        # rbf kernel should behave as gamma=None by default
        # aka gamma = 1 / n_features
        nystroem = Nystroem(n_components=10)
        X_xp_transformed = nystroem.fit_transform(X_xp)
        X_xp_transformed_np = _convert_to_numpy(X_xp_transformed, xp=xp)
        K = rbf_kernel(X_xp, gamma=None)
        K2 = np.dot(X_xp_transformed_np, X_xp_transformed_np.T)
        assert_allclose(_convert_to_numpy(K, xp=xp), K2)

        # chi2 kernel should behave as gamma=1 by default
        nystroem = Nystroem(kernel="chi2", n_components=10)
        X_xp_transformed = nystroem.fit_transform(X_xp)
        X_xp_transformed_np = _convert_to_numpy(X_xp_transformed, xp=xp)
        K = chi2_kernel(X_xp, gamma=1)
        K2 = np.dot(X_xp_transformed_np, X_xp_transformed_np.T)
        assert_allclose(_convert_to_numpy(K, xp=xp), K2)


def test_nystroem_singular_kernel():
    # test that nystroem works with singular kernel matrix
    rng = np.random.RandomState(0)
    X = rng.rand(10, 20)
    X = np.vstack([X] * 2)  # duplicate samples

    gamma = 100
    N = Nystroem(gamma=gamma, n_components=X.shape[0]).fit(X)
    X_transformed = N.transform(X)

    K = rbf_kernel(X, gamma=gamma)

    assert_array_almost_equal(K, np.dot(X_transformed, X_transformed.T))
    assert np.all(np.isfinite(Y))


@pytest.mark.parametrize(
    "array_namespace, device, dtype_name", yield_namespace_device_dtype_combinations()
)
def test_nystroem_singular_kernel_array_api(array_namespace, device, dtype_name):
    if dtype_name == "float32":
        pytest.xfail(
            "rbf kernel on singular data is not numerically stable with float32"
        )
    xp = _array_api_for_tests(array_namespace, device)

Review comment (performance):
As discussed in other array API pull request and issues, we don't want to duplicate all existing tests to keep the test suite manageable and not too expensive to run.

Instead, we want to add a few generic tests that check that enabling array API dispatch on various array API inputs yields the same results as not enabling array API dispatch on matching NumPy inputs with non-default constructor parameters. `test_nystroem_approximation_array_api` already does that.

Note that adding the `array_api_support` tag also runs common tests on this estimator (with default constructor parameters).

Code after:

    gamma = 100
    N = Nystroem(gamma=gamma, n_components=X.shape[0]).fit(X)
    X_transformed = N.transform(X)

    K = rbf_kernel(X, gamma=gamma)

    assert_array_almost_equal(K, np.dot(X_transformed, X_transformed.T))
    assert np.all(np.isfinite(Y))


def test_nystroem_poly_kernel_params():
    # Non-regression: Nystroem should pass other parameters beside gamma.
    rnd = np.random.RandomState(37)
    X = rnd.uniform(size=(10, 4))

    K = polynomial_kernel(X, degree=3.1, coef0=0.1)
    nystroem = Nystroem(
        kernel="polynomial", n_components=X.shape[0], degree=3.1, coef0=0.1
    )
    X_transformed = nystroem.fit_transform(X)
    assert_array_almost_equal(np.dot(X_transformed, X_transformed.T), K)


def test_nystroem_callable():
    # Test Nystroem on a callable.
    rnd = np.random.RandomState(42)
    n_samples = 10
    X = rnd.uniform(size=(n_samples, 4))

    def logging_histogram_kernel(x, y, log):
        """Histogram kernel that writes to a log."""
        log.append(1)
        return np.minimum(x, y).sum()

    kernel_log = []
    X = list(X)  # test input validation
    Nystroem(
        kernel=logging_histogram_kernel,
        n_components=(n_samples - 1),
        kernel_params={"log": kernel_log},
    ).fit(X)
    assert len(kernel_log) == n_samples * (n_samples - 1) / 2

    # if degree, gamma or coef0 is passed, we raise a ValueError
    msg = "Don't pass gamma, coef0 or degree to Nystroem"
    params = ({"gamma": 1}, {"coef0": 1}, {"degree": 2})
    for param in params:
        ny = Nystroem(kernel=_linear_kernel, n_components=(n_samples - 1), **param)
        with pytest.raises(ValueError, match=msg):
            ny.fit(X)