Skip to main content

A fast, explicit random generation toolkit

Project description

Fortuna

Fortuna is a fast random-generation toolkit for Python 3.14. It combines an explicit generator model, deterministic stream derivation, native bulk generation, practical probability distributions, and prepared value selectors for games, simulations, procedural generation, and generative systems.

Fortuna 6 is built on the vendored, immutable Storm 5.0.2 engine and is licensed under MIT.

Toolchain-free installs on supported systems: Fortuna 6 ships precompiled CPython 3.14 wheels for macOS arm64 and x86-64, Linux arm64 and x86-64, and Windows x86-64. On those targets, pip and uv install Fortuna without a C++ compiler, platform SDK, or development toolchain. Windows support is notably improved: Visual Studio Build Tools are not required.

Not for cryptography: Fortuna uses MT19937-64. Do not use it for passwords, tokens, keys, secrets, authentication, cryptography, gambling security, or security decisions. Entropy-seeded construction does not make Fortuna cryptographically secure. Python's secrets module is the standard library starting point for security-sensitive randomness.

Installation

Fortuna requires Python 3.14:

python -m pip install Fortuna

With uv:

uv add Fortuna

A source build requires a C++20 toolchain. Contributors working from a checkout should use the reproducible development setup in the development guide.

Five-minute tour

Fortuna's module functions are convenient when a process-local random stream is appropriate:

import Fortuna

initiative = Fortuna.d(20)
fireball_damage = Fortuna.dice(8, 6)
ability_scores = Fortuna.ability_dice(count=6)
morale_adjustment = Fortuna.plus_or_minus_triangular(2)

The corresponding methods on Generator provide explicit ownership and repeatability:

world = Fortuna.Generator(0xD06E_0A5E)

room_count = world.random_int(5, 12)
room_sizes = world.random_int(3, 10, count=room_count)

Two generators with the same seed produce the same stable Fortuna-owned draw schedule:

first = Fortuna.Generator(42)
second = Fortuna.Generator(42)

assert first.dice(3, 6, count=20) == second.dice(3, 6, count=20)

Module defaults and explicit generators

Module-level calls use a Fortuna-owned thread-local generator initialized from process-local entropy. They are appropriate for convenient independent draws:

wand_charges = Fortuna.random_int(1, 20)

Use an explicit Generator when ownership, reproducibility, or worker stream identity matters:

campaign_seed = 8128
encounters = Fortuna.for_stream(campaign_seed, "encounters")
treasure = Fortuna.for_stream(campaign_seed, "treasure")

encounter_roll = encounters.d(100)
treasure_roll = treasure.d(100)

for_stream(root_seed, stream_id) deterministically derives a generator from an unsigned 64-bit root seed and an int, str, or bytes identifier. The stream identifier is an input to seed derivation, not a decorative label, so changing it produces a different deterministic generator sequence. Identifier types are intentionally distinct.

Use from_entropy() when an explicitly owned but nondeterministic generator is required:

generator = Fortuna.from_entropy()

seed(value) is deterministic, including seed(0). It changes only the calling thread's module default. Other threads and existing explicit generators keep their current states.

Scalar and bulk generation

Every numeric generation function and its Generator method accepts the keyword-only argument count:

one_roll = Fortuna.d(20)
many_rolls = Fortuna.d(20, count=100_000)

one_sample = Fortuna.normal_variate(0.0, 1.0)
many_samples = Fortuna.normal_variate(0.0, 1.0, count=100_000)

Bulk generation performs the sampling loop in the native extension without holding the Python GIL and returns a list. count=0 returns an empty list. Arguments are validated before the engine advances.

The public numeric families include:

  • Uniform booleans, integers, ranges, indexes, floats, and canonical draws.
  • Dice, ability dice, and symmetric plus_or_minus distributions.
  • Triangular, normal, log-normal, exponential, gamma, Weibull, beta, Pareto, von Mises, binomial, negative-binomial, geometric, Poisson, extreme-value, chi-squared, Cauchy, Fisher F, and Student's t distributions.
  • Front, center, and back triangular positional profiles.

The complete domains and failure contracts live in the API reference.

Collection operations

Fortuna provides uniform selection, sampling without replacement, and an in-place native Knuth-B shuffle:

monsters = ["goblin", "owlbear", "mimic", "dragon"]

monster = Fortuna.random_value(monsters)
patrol = Fortuna.sample(monsters, 2)
Fortuna.shuffle(monsters)

An explicit generator can be supplied to each module helper or used directly:

generator = Fortuna.Generator(7)

monster = Fortuna.random_value(monsters, generator=generator)
patrol = generator.sample(monsters, 2)
generator.shuffle(monsters)

Fortuna selects Knuth-B for its large-workload behavior. In controlled macOS arm64 release-build measurements it holds a substantial advantage at one million elements; at intermediate sizes the two traversals are close and can exchange small wins. Exact ratios remain machine- and build-specific.

Prepared value generation

Prepared value engines materialize their inputs once and remain ordinary Python callables. They are useful when the same table is drawn repeatedly.

RandomValue

RandomValue combines uniform, cyclic, wide, and positional selection behind one small interface:

loot = Fortuna.RandomValue(["copper", "potion", "wand"])

uniform_drop = loot()
same_as_uniform = loot.uniform()
next_in_order = loot.cycle()
front_loaded_drop = loot.front_triangular()
center_loaded_drop = loot.center_triangular()
back_loaded_drop = loot.back_triangular()
wide_drop = loot.truffle_shuffle()

Its methods are normal bound callables. This supports the prepared-generator pattern directly:

loot_gen = Fortuna.RandomValue(["copper", "potion", "wand"]).front_triangular

drop = loot_gen()
more_drops = [loot_gen() for _ in range(20)]

take(count, ...) repeats the engine's default uniform strategy:

loot = Fortuna.RandomValue(["copper", "potion", "wand"])
treasure_pile = loot.take(10)

TruffleShuffle

TruffleShuffle is a stateful wide selector designed for broad uniformity with reduced localized clumping. It shuffles the table once, then moves through that permutation by randomized short distances:

encounter = Fortuna.TruffleShuffle(
    ["goblins", "wolves", "bandits", "skeletons", "giant spiders"]
)

next_encounter = encounter()
encounter_week = encounter.take(7)

TruffleShuffle allows repeats. Its design combines reduced localized repetition with broad long-run coverage. See Algorithm and design notes for the model and its origin.

WeightedChoice

WeightedChoice accepts finite, nonnegative relative weights:

rarity = Fortuna.WeightedChoice(
    [
        (80, "common"),
        (18, "rare"),
        (2, "legendary"),
    ]
)

item_rarity = rarity()

Weights are relative and may total any positive finite value. A weight of zero is allowed, and at least one weight must be positive. Native generators use Storm's prepared cumulative selector with logarithmic lookup for repeated draws.

Callable values

Prepared value engines resolve selected callables after selection. This makes nested procedural tables concise and evaluates only the selected path:

coins = Fortuna.RandomValue(
    [
        lambda: f"{Fortuna.dice(3, 6)} copper pieces",
        lambda: f"{Fortuna.dice(2, 6)} silver pieces",
        lambda: f"{Fortuna.d(6)} gold pieces",
    ]
)

treasure = coins()

Pass resolve_callables=False when callable objects are the intended values. Callable cycles and runaway chains raise RuntimeError.

Positional profiles

The positional profiles use bounded triangular algorithms:

table = ["common", "uncommon", "rare", "very rare", "legendary"]

front = table[Fortuna.front_triangular(len(table))]
center = table[Fortuna.center_triangular(len(table))]
back = table[Fortuna.back_triangular(len(table))]
  • front_triangular uses the smaller of two uniform indexes.
  • center_triangular uses their integer midpoint.
  • back_triangular uses the larger index.

Process and thread behavior

Module defaults are thread-local. A forked child invalidates the inherited module default and reseeds it before its next draw. A generator created through from_entropy() behaves similarly.

Explicit deterministic generators intentionally preserve copied state through fork. Derive a separate stream for each worker that needs a distinct sequence:

worker_generator = Fortuna.for_stream(root_seed, worker_id)

Built-in operations sharing one exact Generator are serialized, but thread scheduling still determines which thread receives each draw. Value engines also contain mutable strategy state, so callers own their synchronization. Use one value-engine instance per worker or synchronize it externally.

Fork only after concurrent use of a shared explicit generator has quiesced; a child could otherwise inherit its native mutex in a locked state.

Reproducibility boundary

Fortuna's bounded integer, index, range, dice, canonical, bounded-triangular, stream-derivation, uniform value-selection, sampling, and shuffle schedules are stable across supported platforms throughout the Fortuna 6 line.

Standard-library probability distributions, random_float, custom floating transforms, TruffleShuffle's Poisson movement, and WeightedChoice's real draw depend partly on C++ standard-library implementations. They are repeatable within one platform and toolchain build. Their exact seeded sequences are platform-and-toolchain-specific. See Algorithm and design notes for the complete boundary.

Documentation

Development

uv sync --frozen --group dev --reinstall-package Fortuna
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run python -m benchmarks
uv build

Correctness and statistical validation live under tests/. Performance measurement lives under benchmarks/; ordinary CI gates correctness, while timing provides evidence for engineering decisions.

License

Fortuna is available under the MIT License. Its vendored Storm header is also MIT licensed; provenance and checksums are recorded in the vendoring record.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fortuna-6.0.4.tar.gz (114.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fortuna-6.0.4-cp314-cp314-win_amd64.whl (349.1 kB view details)

Uploaded CPython 3.14Windows x86-64

fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (202.4 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl (187.0 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.24+ ARM64manylinux: glibc 2.28+ ARM64

fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl (159.9 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl (181.3 kB view details)

Uploaded CPython 3.14macOS 10.15+ x86-64

File details

Details for the file fortuna-6.0.4.tar.gz.

File metadata

  • Download URL: fortuna-6.0.4.tar.gz
  • Upload date:
  • Size: 114.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4.tar.gz
Algorithm Hash digest
SHA256 f9000ca2b56a547bf3e0f104f662a51ed160920569dee155f6caceb3a5918551
MD5 13d1b7c23f01fbd68cf9b20febe031ed
BLAKE2b-256 22197a9167725280c904af46b155d34c347bbcc896d33d72ae88cbf830cf13d5

See more details on using hashes here.

File details

Details for the file fortuna-6.0.4-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: fortuna-6.0.4-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 349.1 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 971f70a75f85c01da9de94f60ae040cbb0cc100ae543eda961a8ccf209511f09
MD5 22582309abe11067c0901bd4db2972b3
BLAKE2b-256 c76a09d32010324ceec92c47a659b7831dcd35a6272c020637ec295eb5fa7bfb

See more details on using hashes here.

File details

Details for the file fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

  • Download URL: fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
  • Upload date:
  • Size: 202.4 kB
  • Tags: CPython 3.14, manylinux: glibc 2.24+ x86-64, manylinux: glibc 2.28+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 acb574cea4cdf13da9682bfc99ab8a7f95f88ab74f4238569f86a71e1a794846
MD5 ff98ae609d7f05ccbe61a0d9823ffb7e
BLAKE2b-256 2d55ab00c86821863a181a3d8374d5d9408565cd820a65b0a962044fb68337a1

See more details on using hashes here.

File details

Details for the file fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl.

File metadata

  • Download URL: fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
  • Upload date:
  • Size: 187.0 kB
  • Tags: CPython 3.14, manylinux: glibc 2.24+ ARM64, manylinux: glibc 2.28+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 0b00c207f9921982551dbac90286d285d962c61ee82d499cccfe09d077d3a4a5
MD5 5b01ae4e301252a846d052157efd5190
BLAKE2b-256 edb753f163192e01ef87198ba86314c4a8565453f3ce349f0b6b1ead3c32d23d

See more details on using hashes here.

File details

Details for the file fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

  • Download URL: fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 159.9 kB
  • Tags: CPython 3.14, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3e9d309a59f67457a6992b46d9a8251b1853241d910454f9c41c2bd4de5bc318
MD5 bb15d600db25bf5e389b779daa8cee59
BLAKE2b-256 b61f60a591ffd8c8c15b568018e7a7fead289fa006dd462e34e44db568638f62

See more details on using hashes here.

File details

Details for the file fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl.

File metadata

  • Download URL: fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl
  • Upload date:
  • Size: 181.3 kB
  • Tags: CPython 3.14, macOS 10.15+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 2ad2c4dfc74a8e505f7652deae5110f6f5607e19b7211ca733480c28c16cb3c0
MD5 b7c1d6318365e3991d442a7cfd9582f8
BLAKE2b-256 ba999bb6bbeab82a7b4736fc2a10bf02981fdee7ff1e10976ab85c2727fbc109

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page