Skip to content

Synthetic

vector_search_study.synthetic

Deterministic synthetic embedding corpora for tests and experiments.

SyntheticDataset dataclass

An immutable generated corpus/query pair and its provenance.

Attributes:

Name Type Description
corpus FloatMatrix

Pre-normalized corpus vectors.

queries FloatMatrix

Pre-normalized query vectors.

distribution str

Generator family name.

seed int

PCG64 seed.

objective SearchObjective

Search objective for which vectors were generated.

Source code in src/vector_search_study/synthetic.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@dataclass(frozen=True, slots=True)
class SyntheticDataset:
    """An immutable generated corpus/query pair and its provenance.

    Attributes:
        corpus: Pre-normalized corpus vectors.
        queries: Pre-normalized query vectors.
        distribution: Generator family name.
        seed: PCG64 seed.
        objective: Search objective for which vectors were generated.
    """

    corpus: FloatMatrix
    queries: FloatMatrix
    distribution: str
    seed: int
    objective: SearchObjective = SearchObjective.NORMALIZED_COSINE

make_uniform_sphere_dataset

make_uniform_sphere_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    dtype: object = np.float32,
    seed: int = 20260801,
) -> SyntheticDataset

Generate independent corpus and query vectors on the unit sphere.

Parameters:

Name Type Description Default
corpus_size int

Number of corpus vectors.

required
dimension int

Embedding dimension.

required
query_count int

Number of query vectors.

required
dtype object

Either float32 or float64.

float32
seed int

Non-negative PCG64 seed.

20260801

Returns:

Type Description
SyntheticDataset

A deterministic normalized synthetic dataset.

Source code in src/vector_search_study/synthetic.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def make_uniform_sphere_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    dtype: object = np.float32,
    seed: int = 20_260_801,
) -> SyntheticDataset:
    """Generate independent corpus and query vectors on the unit sphere.

    Args:
        corpus_size: Number of corpus vectors.
        dimension: Embedding dimension.
        query_count: Number of query vectors.
        dtype: Either float32 or float64.
        seed: Non-negative PCG64 seed.

    Returns:
        A deterministic normalized synthetic dataset.
    """
    size, dimensions, queries, resolved_dtype, resolved_seed = _validate_generator_inputs(
        corpus_size,
        dimension,
        query_count,
        dtype=dtype,
        seed=seed,
    )
    generator = np.random.Generator(np.random.PCG64(resolved_seed))
    corpus = _normal_matrix(generator, size, dimensions, resolved_dtype)
    query_matrix = _normal_matrix(generator, queries, dimensions, resolved_dtype)
    return _dataset(
        corpus,
        query_matrix,
        distribution="uniform_sphere",
        seed=resolved_seed,
        objective=SearchObjective.NORMALIZED_COSINE,
    )

make_gaussian_dataset

make_gaussian_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    objective: SearchObjective | str,
    dtype: object = np.float32,
    seed: int = 20260801,
) -> SyntheticDataset

Generate deterministic unnormalized Gaussian embeddings.

Parameters:

Name Type Description Default
corpus_size int

Number of corpus vectors.

required
dimension int

Embedding dimension.

required
query_count int

Number of query vectors.

required
objective SearchObjective | str

Squared L2 or inner-product search.

required
dtype object

Either float32 or float64.

float32
seed int

Non-negative PCG64 seed.

20260801

Returns:

Type Description
SyntheticDataset

A deterministic unnormalized synthetic dataset.

Raises:

Type Description
InvalidSearchParameterError

If normalized cosine is requested.

Source code in src/vector_search_study/synthetic.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def make_gaussian_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    objective: SearchObjective | str,
    dtype: object = np.float32,
    seed: int = 20_260_801,
) -> SyntheticDataset:
    """Generate deterministic unnormalized Gaussian embeddings.

    Args:
        corpus_size: Number of corpus vectors.
        dimension: Embedding dimension.
        query_count: Number of query vectors.
        objective: Squared L2 or inner-product search.
        dtype: Either float32 or float64.
        seed: Non-negative PCG64 seed.

    Returns:
        A deterministic unnormalized synthetic dataset.

    Raises:
        InvalidSearchParameterError: If normalized cosine is requested.
    """
    resolved_objective = resolve_search_objective(objective)
    if resolved_objective.requires_normalization:
        raise InvalidSearchParameterError("use make_uniform_sphere_dataset for normalized cosine")
    size, dimensions, queries, resolved_dtype, resolved_seed = _validate_generator_inputs(
        corpus_size,
        dimension,
        query_count,
        dtype=dtype,
        seed=seed,
    )
    generator = np.random.Generator(np.random.PCG64(resolved_seed))
    corpus = generator.standard_normal((size, dimensions)).astype(resolved_dtype, copy=False)
    query_matrix = generator.standard_normal((queries, dimensions)).astype(resolved_dtype, copy=False)
    return _dataset(
        corpus,
        query_matrix,
        distribution="gaussian",
        seed=resolved_seed,
        objective=resolved_objective,
    )

make_clustered_dataset

make_clustered_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    cluster_count: int = 8,
    noise: float = 0.15,
    dtype: object = np.float32,
    seed: int = 20260801,
    objective: SearchObjective
    | str = SearchObjective.NORMALIZED_COSINE,
) -> SyntheticDataset

Generate normalized vectors around shared random cluster centroids.

Parameters:

Name Type Description Default
corpus_size int

Number of corpus vectors.

required
dimension int

Embedding dimension.

required
query_count int

Number of query vectors.

required
cluster_count int

Number of latent centroids.

8
noise float

Positive standard deviation around each centroid.

0.15
dtype object

Either float32 or float64.

float32
seed int

Non-negative PCG64 seed.

20260801
objective SearchObjective | str

Exact-search score convention. Cosine output is normalized; L2 and inner-product output is not.

NORMALIZED_COSINE

Returns:

Type Description
SyntheticDataset

A deterministic normalized clustered dataset.

Source code in src/vector_search_study/synthetic.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def make_clustered_dataset(
    corpus_size: int,
    dimension: int,
    query_count: int,
    *,
    cluster_count: int = 8,
    noise: float = 0.15,
    dtype: object = np.float32,
    seed: int = 20_260_801,
    objective: SearchObjective | str = SearchObjective.NORMALIZED_COSINE,
) -> SyntheticDataset:
    """Generate normalized vectors around shared random cluster centroids.

    Args:
        corpus_size: Number of corpus vectors.
        dimension: Embedding dimension.
        query_count: Number of query vectors.
        cluster_count: Number of latent centroids.
        noise: Positive standard deviation around each centroid.
        dtype: Either float32 or float64.
        seed: Non-negative PCG64 seed.
        objective: Exact-search score convention. Cosine output is normalized;
            L2 and inner-product output is not.

    Returns:
        A deterministic normalized clustered dataset.
    """
    size, dimensions, queries, resolved_dtype, resolved_seed = _validate_generator_inputs(
        corpus_size,
        dimension,
        query_count,
        dtype=dtype,
        seed=seed,
    )
    clusters = validate_positive_int(cluster_count, name="cluster_count")
    if isinstance(noise, bool) or not isinstance(noise, (int, float)) or not np.isfinite(noise) or noise <= 0:
        raise InvalidSearchParameterError("noise must be a positive finite number")

    resolved_objective = resolve_search_objective(objective)
    generator = np.random.Generator(np.random.PCG64(resolved_seed))
    centroids = generator.standard_normal((clusters, dimensions)).astype(resolved_dtype, copy=False)
    corpus_assignments = generator.integers(0, clusters, size=size)
    query_assignments = generator.integers(0, clusters, size=queries)
    corpus_noise = generator.standard_normal((size, dimensions)).astype(resolved_dtype, copy=False)
    query_noise = generator.standard_normal((queries, dimensions)).astype(resolved_dtype, copy=False)
    corpus = np.asarray(centroids[corpus_assignments] + noise * corpus_noise, dtype=resolved_dtype, order="C")
    query_matrix = np.asarray(
        centroids[query_assignments] + noise * query_noise,
        dtype=resolved_dtype,
        order="C",
    )
    if resolved_objective.requires_normalization:
        corpus = normalize_rows(corpus)
        query_matrix = normalize_rows(query_matrix)
    return _dataset(
        corpus,
        query_matrix,
        distribution="clustered",
        seed=resolved_seed,
        objective=resolved_objective,
    )