Source code for sgtlearn.datasets

"""Synthetic datasets for SGT demos."""

from __future__ import annotations

from typing import Optional

import numpy as np

__all__ = ["make_plus"]


[docs] def make_plus( n_samples: int = 1500, *, grid: int = 3, margin: float = 0.05, random_state: Optional[int] = None, ) -> tuple[np.ndarray, np.ndarray]: """Generate the "Plus Sign" dataset. A ``grid x grid`` arrangement of cells where the center column and center row form a plus (+) of one class, and the remaining cells are the other class. The boundary is non-monotone in each feature -- the pattern an SGT's shape function captures with a Left/Right/Left split. Parameters ---------- n_samples : int, default=1500 Number of points to return. grid : int, default=3 Number of cells per axis. The plus is the center row/column, so ``grid`` should be odd for a centered cross. margin : float, default=0.05 Width of the empty gap kept around each cell border, so the classes are visually separated. Must be in ``[0, 0.5)``. random_state : int, optional Seed for reproducibility. Returns ------- X : ndarray of shape (n_samples, 2) Feature matrix, with both features in ``[0, grid)``. y : ndarray of shape (n_samples,) Labels: ``1`` for the plus sign, ``0`` for the remaining cells. """ if not 0.0 <= margin < 0.5: raise ValueError(f"margin must be in [0, 0.5); got {margin}") rng = np.random.default_rng(random_state) center = grid // 2 X = np.empty((0, 2), dtype=float) # rejection-sample in batches until we have enough points off the borders while X.shape[0] < n_samples: batch = rng.uniform(0.0, grid, size=(n_samples * 2, 2)) frac = batch - np.floor(batch) keep = ((frac > margin) & (frac < 1 - margin)).all(axis=1) X = np.vstack([X, batch[keep]]) X = X[:n_samples] col = np.floor(X[:, 0]).astype(int) row = np.floor(X[:, 1]).astype(int) y = ((col == center) | (row == center)).astype(int) return X, y