> ## Documentation Index
> Fetch the complete documentation index at: https://ahvn.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Random

> Reproducible random data helpers with StableRNG.

<Note>
  *Never roll a dice if you don't already know the answer.*
</Note>

Use Random utilities when generated rows, samples, or vectors should stay stable across runs.

`StableRNG` gives you six stability guarantees: same seed → same output; new pipeline steps stay isolated; batch and loop calls agree; growing a dataset does not reshuffle old picks; other RNG libraries stay untouched; sample results follow item content, not list order.

<br />

## 1. Why Randomness Needs Stability

Without a deliberate design for stability, "reproducible" random data is fragile. A test that sets `seed=42` can still break when someone adds a random call earlier in the pipeline, when the dataset grows by one row, when you switch from a batch call to a loop, or when input order changes. Each change can rewrite every value that came after it.

HeavenBase's `StableRNG` guarantees six orthogonal stability properties. Each one closes a concrete failure mode you would otherwise hit as an application grows.

### 1.1. Seed Stability — same seed, same output

**What it means:** Run the same draw twice with the same seed and you get the same value.

Without seed stability, `seed=42` might produce different strings, floats, or labels across runs, versions, or machines — so a failing test is hard to replay.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

with StableRNG(seed=42) as rng:
    a = rng.rnd_str(8)
with StableRNG(seed=42) as rng:
    b = rng.rnd_str(8)

assert a == b
# a and b are both '7dffoji5'
```

Same seed and same draw order always reproduce the same sequence. That is the baseline for debugging fixtures and benchmarks.

<br />

### 1.2. Process Stability — new steps do not rewrite old ones

**What it means:** Add a new random step anywhere in your pipeline; values from steps you already rely on stay the same.

Without process stability, inserting a "generate metrics" step between "generate rows" and "generate samples" would change both downstream outputs — even though those steps did not change.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

# Original pipeline
rows = StableRNG(seed=42).step('rows').rnd_str(4)      # '5yqh'
samples = StableRNG(seed=42).step('samples').rnd_str(4) # '2qpl'

# After inserting a new step — existing values stay the same
rows2 = StableRNG(seed=42).step('rows').rnd_str(4)       # '5yqh'
metrics = StableRNG(seed=42).step('metrics').rnd_str(4)  # 'zkqs' (new)
samples2 = StableRNG(seed=42).step('samples').rnd_str(4) # '2qpl'

assert rows == rows2 and samples == samples2
```

The `step(...)` method derives a named child stream from the base seed without mutating the parent. Two different step names never share state.

<br />

### 1.3. Batch Stability — one batch call matches a loop

**What it means:** `rng.method(..., n=10)` returns the same values as calling `rng.method(...)` ten times in a loop.

Without batch stability, refactoring between batched and incremental code changes your fixtures — even with the same seed and method.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

with StableRNG(seed=42) as rng:
    batch = rng.rnd_float(0.0, 1.0, n=3)  # one call

with StableRNG(seed=42) as rng:
    seq = [rng.rnd_float(0.0, 1.0) for _ in range(3)]  # three calls

assert batch == seq
# Both: [0.086, 0.142, 0.270]
```

This holds for every generation method that accepts `n=`: `rnd_int`, `rnd_str`, `rnd_vec`, `choice`, and so on. You can pick the style that reads best without changing results.

<br />

### 1.4. Data Stability — new records do not reshuffle old picks

**What it means:** Sample or split today; add more items tomorrow; decisions for items that were already in the pool stay the sam (minor changes are expected but not drastic).

Without data stability, sampling 2 items from `[p1, p2, p3, p4]` with `seed=42` might select `[p3, p4]`, but adding `p5` could change the selection to `[p2, p5]`. Benchmarks and evaluation splits become brittle.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

items = ['p1', 'p2', 'p3', 'p4']
sample1 = StableRNG(seed=42).hash_sample(items, k=2)
# sample1: ['p3', 'p4']

items2 = ['p1', 'p2', 'p3', 'p4', 'p5']  # one new item
sample2 = StableRNG(seed=42).hash_sample(items2, k=2)
# sample2: ['p3', 'p4'] — existing items not changed drastically

assert all(x in sample2 for x in sample1)
```

`hash_sample` and `hash_split` base each item's selection on its own digest (seeded by the `StableRNG` seed), not on list position. Adding items only changes the decision for the added items.

The core idea of achieving data stability is to use a hash of the item to determine the selection, rather than the position of the item in the list. When adding a single new item to the pool, depending on whether its hash value falls within the threshold, it will be added to the selected or remaining group, kicking at most one item out of the selected group.

<br />

### 1.5. Ecosystem Stability — other RNG libraries stay untouched

**What it means:** Using `StableRNG` never seeds or resets Python's `random`, NumPy's global RNG, PyTorch, or any other RNG in your process.

Without ecosystem stability, heavy `StableRNG` use could silently re-seed global RNG state and break reproducibility elsewhere in the codebase.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import random
from heavenbase.utils import StableRNG

random.seed(999)
expected = [random.random() for _ in range(5)]

random.seed(999)
with StableRNG(seed=42) as rng:
    _ = rng.rnd_str(1000)  # heavy use of StableRNG
actual = [random.random() for _ in range(5)]

assert expected == actual
# StableRNG never calls random.seed, numpy.random.seed, or torch.manual_seed
```

Each `StableRNG` creates its own independent NumPy `Generator` instance. It never touches the global `random` module, `numpy.random` state, or any other RNG system.

<br />

### 1.6. Hash Stability — content decides, not list order

**What it means:** Put the same items in a different order; the sample or split for those items stays the same.

Without hash stability, sampling from `['p1', 'p2', 'p3']` might give `['p3', 'p1']`, but reordering the input to `['p3', 'p1', 'p2']` could give `['p1', 'p2']`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

items_a = ['p1', 'p2', 'p3']
items_b = ['p3', 'p1', 'p2']  # same items, different order

s_a = StableRNG(seed=42).hash_sample(items_a, k=2)
s_b = StableRNG(seed=42).hash_sample(items_b, k=2)

assert sorted(s_a) == sorted(s_b)
# Both: ['p1', 'p3']
```

`hash_sample` and `hash_split` compute each item's digest with the generator's configured hash function (CRC32 by default, optionally `md5` or `sha256`), salt it with the seed, then select by digest value. Input order never enters the decision.

<br />

<Note>
  These six properties are built into `StableRNG`. You get all of them by passing a seed — no extra flags or setup.
</Note>

<br />

## 2. Core Idea

`StableRNG` is for tests, demos, benchmarks, and LLM-application fixtures where "random" data still needs to be debuggable. If a failure happens with seed `42`, you should be able to regenerate the same rows, vectors, and samples later.

<br />

## 3. Generate a Reproducible Sequence

Create a generator with a seed and use it as a context when multiple draws belong to one sequence.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

with StableRNG(seed=42) as rng:
    row_id = rng.rnd_str(8)
    score = rng.rnd_float(0.0, 1.0)
    label = rng.choice(["open", "closed", "review"])
```

The same seed and draw order produce the same sequence:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

with StableRNG(seed=7) as left:
    left_values = [left.rnd_str(6), left.rnd_int(0, 10)]

with StableRNG(seed=7) as right:
    right_values = [right.rnd_str(6), right.rnd_int(0, 10)]

assert left_values == right_values
```

Outside a context, a single method call is stable for the current seed. That is useful when one value should be reproducible without coupling it to previous draws.

<br />

## 4. Derive Child Streams

Use `step(...)` to split one base seed into named streams without mutating the parent generator.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

base = StableRNG(seed=42)
rows_rng = base.step("rows")
sample_rng = base.step("samples")

rows = rows_rng.rnd_str(6, n=3)
sample = sample_rng.choice(["p1", "p2", "p3", "p4"], k=2, replace=False)
```

This keeps unrelated parts of a test from changing each other when one workflow adds a new random draw.

<br />

## 5. Generate Batches and Vectors

Most generators accept `n` for a count or shape. Use this for fixtures that need many values at once.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

rng = StableRNG(seed=42)

ids = rng.rnd_str(4, n=5)
grid = rng.rnd_int(0, 10, n=(2, 3))
vecs = rng.rnd_vec(dim=8, n=3)
```

`rnd_vec(...)` returns unit-length vectors. That makes it useful for LLM and vector-search development, where you often need embeddings-shaped data before wiring a real embedding provider into the example.

<br />

## 6. Sample Without Rewriting the World

Use `hash_sample(...)` and `hash_split(...)` when a stable sample should not depend on input order or on unrelated new records.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from heavenbase.utils import StableRNG

items = ["p1", "p2", "p3", "p4"]

sample = StableRNG(seed=11).hash_sample(items, k=2)
selected, remaining = StableRNG(seed=11).hash_split(items, r=0.5)
wide_sample = StableRNG(seed=11, hash_function="sha256").hash_sample(items, k=2)
```

If you later add `"p5"`, the decision for `"p1"` through `"p4"` is still based on each item's hash and the seed, not on the original list position.

CRC32 is the default because sampling collisions only change deterministic sample choice or tie handling. The default reads `heavenbase.rng.hash_function` and `heavenbase.rng.hash_modulo` from configuration, and each `hash_sample(...)` / `hash_split(...)` call can override the hash function.

<Check>
  HeavenBase benchmarks use seeded generation so row content, samples, and vectors can be reproduced.
</Check>

<br />

## Further Exploration

<Tip>
  **Related resources:**

  * [Hash](/features/utilities/hash) - deterministic hashes used by stable sampling.
  * [File System](/features/utilities/file-system) - write generated fixtures to local artifacts.
  * [Query](/features/query) - where synthetic vector fixtures are often used.
</Tip>

<br />
