XiaPL
A fast poker hand-evaluation and equity library for Texas Hold'em and
Pot-Limit Omaha, used from Python. The public surface is a typed Python
package (xiapl); underneath it is a zero-dependency C++20 core, which is
also usable directly from C++.
Version 0.1.0 | MIT License
Three things set XiaPL apart:
- Speed — bitwise 52-card mask arithmetic throughout, exact enumeration where it's tractable and reproducible Monte Carlo elsewhere, and threaded evaluation paths that are bit-identical at every thread count. The C++ core is the speed story, not a second product to learn — Python calls straight into it. Single-threaded 7-card evaluation costs 10.6 ns/eval in batch through the public entry point on an Apple M1 Pro; the head-to-head against PH Evaluator and OMPEval on two machines — method, correctness cross-check, and the contexts where XiaPL loses — is in docs/benchmarks.md.
- PLO as a first-class citizen — Hold'em and PLO are not two libraries
glued together. Every public entry point (
evaluate_hand,judge,calculate_equity,Range.from_string, ...) takes a single trailinggameparameter instead of a_holdem/_plosplit, and PLO gets its own weighted range grammar rather than an awkward reuse of Hold'em notation. - Usability — a typed Python API (
py.typedplus hand-written.pyistubs verified against the binding with mypy stubtest), reproducible seeded Monte Carlo end-to-end, and parser error messages that teach the correct syntax instead of just rejecting input.
XiaPL is 0.x / alpha: the Python API is the supported surface and the one this README documents, but it may still evolve (rename, reshape, regroup) before 1.0 — see Pre-1.0 API notes.
Also: best-of-5-to-7 hand evaluation with suit-isomorphism canonicalization,
exact (non-sampled) top-% starting-hand ranking, weighted range set algebra
(| / & / -), PHH hand-history read/write (xiapl.phh), and a
zero-external-dependency C++20 core that's source-portable across
macOS / Linux / Windows and arm64 / x86_64.
Installation
pip install xiapl
is the planned route once a wheel is published to PyPI. Today, install from a source checkout:
pip install .
This builds the C++ extension via pybind11 as part of the install (macOS
deployment target is handled automatically, no shell environment variable
needed) and installs xiapl, including a py.typed marker and hand-written
.pyi stubs for mypy / IDE support, and exposes xiapl.__version__.
Requires Python >= 3.11 and a C++20 compiler (Apple clang, GCC, clang,
or MSVC — see Platform support). No other build
dependency is required beyond pybind11, which pip install . pulls in
automatically via pyproject.toml.
For iterative development (editable install — the flow used by the test suite; Python-side edits are picked up immediately, C++-side edits need a re-run of the command below):
pip install -e .
Every pip install -e . fully recompiles the C++ extension (setuptools'
editable-install hook builds in a fresh temp directory each time, so there is
no incremental object-file cache across invocations) — re-run it after
touching anything under src/, include/, or binding/. If you already
have pybind11 and setuptools installed, skip pip's per-invocation build
sandbox with:
pip install -e . --no-build-isolation
(this does not make individual rebuilds faster, since every recompile is still a full rebuild — it only skips reinstalling the build toolchain into a throwaway environment on every call).
Quick Start
Cards & Deck
from xiapl.card import Card
from xiapl.deck import Deck
ace_spades = Card.from_string("As")
print(ace_spades) # As
print(ace_spades.rank, ace_spades.suit, ace_spades.id) # 14 s 51
deck = Deck()
deck.shuffle(seed=7) # reproducible; omit for a random shuffle
hole = deck.deal(2)
flop = deck.deal(3)
print([str(c) for c in hole]) # ['6c', '6h']
print([str(c) for c in flop]) # ['Qh', '8h', '3d']
Hand Evaluation — evaluate_cards / evaluate_hand / judge
evaluate_* return a HandValue (category + tie-breaker ranks). The
surface is never split by game: one entry point per concept, with a
trailing keyword-only game that defaults to GameType.Holdem.
from xiapl.card import Card
from xiapl.eval import evaluate_cards, evaluate_hand, judge, describe_hand
from xiapl.simulation import GameType
from xiapl.utils import cards_to_mask
# Best five out of 5..7 loose cards (no hole/board split)
cards = [Card.from_string(s) for s in ("As", "Ks", "Qs", "Js", "Ts", "2c", "7h")]
print(describe_hand(evaluate_cards(cards))) # Straight Flush [14]
# Mask form: Hold'em (board + 2 hole)
board = cards_to_mask([Card.from_string(s) for s in ("As", "Kd", "2c", "7h", "9s")])
hole_a = cards_to_mask([Card.from_string("Ah"), Card.from_string("Ac")])
hole_b = cards_to_mask([Card.from_string("Ks"), Card.from_string("Kh")])
print(describe_hand(evaluate_hand(board, hole_a))) # Three of a Kind [14 13 9]
# Multiway showdown: indices of every player holding the best hand
# (more than one index means a chop)
print(judge([hole_a, hole_b], board)) # [0]
# PLO: board + 4 hole, same functions, game=GameType.Plo
plo_board = cards_to_mask([Card.from_string(s) for s in ("2h", "7d", "Jc", "4s", "9h")])
plo_hole = cards_to_mask([Card.from_string(s) for s in ("As", "Ks", "Qd", "Jd")])
plo_value = evaluate_hand(plo_board, plo_hole, game=GameType.Plo)
print(describe_hand(plo_value)) # One Pair [11 14 9 7]
Reproducible Equity — calculate_equity + SimulationOptions
SimulationOptions.exact() enumerates exhaustively; .mc_seeded(n, seed)
runs deterministic Monte Carlo — repeated calls with the same options are
bit-exact.
from xiapl.card import Card
from xiapl.simulation import calculate_equity, SimulationOptions, GameType
from xiapl.utils import cards_to_mask
hole_a = cards_to_mask([Card.from_string("As"), Card.from_string("Ks")])
hole_b = cards_to_mask([Card.from_string("Qd"), Card.from_string("Qc")])
# 1) Exact preflop enumeration
r = calculate_equity([hole_a, hole_b], 0, SimulationOptions.exact())
print(r.exact, r.trials, [round(p.equity, 4) for p in r.players])
# True 1712304 [0.4621, 0.5379]
# 2) Reproducible Monte Carlo — bit-exact rerun
mc = SimulationOptions.mc_seeded(iterations=20_000, seed=42)
a = calculate_equity([hole_a, hole_b], 0, mc)
b = calculate_equity([hole_a, hole_b], 0, mc)
print(a.players[0].equity == b.players[0].equity) # True
# 3) Multiway — up to 10 players for Hold'em (32 for PLO), same call
hole_c = cards_to_mask([Card.from_string("7h"), Card.from_string("6h")])
r3 = calculate_equity([hole_a, hole_b, hole_c], 0, SimulationOptions.exact())
print([round(p.equity, 4) for p in r3.players], round(r3.chop_rate, 4))
# [0.3779, 0.3957, 0.2263] 0.0015
# 4) PLO equity — same API, game=
plo_a = cards_to_mask([Card.from_string(s) for s in ("As", "Ks", "Qd", "Jd")])
plo_b = cards_to_mask([Card.from_string(s) for s in ("2c", "7c", "Jh", "9d")])
plo_r = calculate_equity([plo_a, plo_b], 0,
SimulationOptions.mc_seeded(50_000, 7), game=GameType.Plo)
print([round(p.equity, 4) for p in plo_r.players]) # [0.6371, 0.3629]
Ranges — Range.from_string, set algebra, range-vs-range equity
from xiapl.range import Range, rank_starting_hands, generate_top_percent_range
from xiapl.simulation import calculate_range_equity, SimulationOptions, GameType
# Standard notation, weighted combos, and Range.all(game=)
hero = Range.from_string("JJ+, AKs, AKo")
print(hero.size(), hero.total_weight()) # 40 40.0
full_holdem = Range.all(game=GameType.Holdem)
print(full_holdem.size()) # 1326 (all combos, weight 1.0 each)
full_plo = Range.all(game=GameType.Plo)
print(full_plo.size()) # 270725
# Range vs range equity — per-combo (sorted by input order) + weighted aggregates
h, v = Range.from_string("AA"), Range.from_string("KK")
res = calculate_range_equity(h, v, 0, SimulationOptions.exact())
print(round(res.hero_aggregate_equity, 4), round(res.villain_aggregate_equity, 4))
# 0.8195 0.1805
print(res.hero[0].combo_mask, round(res.hero[0].equity, 4)) # first hero combo
# Weighted set algebra: union w=max(wa,wb), intersection w=min(wa,wb),
# difference w=max(0, wa-wb) — both operators and named methods
a = Range.from_string("AA, KK")
b = Range.from_string("KK, QQ")
print((a | b).size(), (a & b).size(), (a - b).size()) # 18 6 6
assert (a | b).size() == a.union(b).size()
# Exact top-% starting-hand ranking (no sampling noise)
print(rank_starting_hands(0.10))
# ['AA', 'KK', 'QQ', 'JJ', 'TT', '99', '88', 'AKs', '77', 'AQs', 'AJs',
# 'AKo', 'ATs', 'AQo', 'AJo', 'KQs']
top10 = generate_top_percent_range(0.10)
print(top10.size(), top10.total_weight()) # 104 104.0
Pass mode=RangeEquityMode.AggregateOnly to calculate_range_equity when
only the weighted aggregate is needed — it skips the per-combo bookkeeping
and, for Monte Carlo, uses a sampled-pair estimator instead of the
enumerate-every-pair engine (see the PLO example below). Use PerCombo
(the default) for the per-combo breakdown or exact reproducibility parity
with earlier calls.
Both ranges must carry the same game (Range.game) — Hold'em vs Hold'em
and PLO vs PLO are supported; a mismatch raises ValueError. Multi-way
calculate_equity accepts up to 10 players for Hold'em / 32 for PLO, but
calculate_range_equity is heads-up only (both Hold'em and PLO — see
Limitations).
PLO Range Notation
PLO ranges use a separate, frozen v0.1 grammar:
Range.from_string(text, game=GameType.Plo) — comma-separated items, each
one of a 4-symbol rank pattern, an exact 4-card hand, or a
progression, with an optional :weight suffix.
Pattern cheat-sheet
| Notation | Meaning | Count |
|---|---|---|
**** |
any 4-card hand | 270,725 |
AA** |
at least two aces (containment — includes trip/quad aces) | 6,961 |
AK** |
at least one ace and one king (containment, not "exactly") | 17,316 |
AAKKds |
AA + KK, double-suited (exact suit histogram {2,2}) |
6 |
AAKKss |
AA + KK, single-suited ({2,1,1}) |
24 |
AAKKr |
AA + KK, rainbow ({1,1,1,1}) |
6 |
AsKsQhJd |
exact 4-card hand | 1 |
JJ**+ |
pair-pattern progression: JJ**, QQ**, KK**, AA** |
27,628 |
JT98-8765 |
closed rundown progression: JT98, T987, 9876, 8765 |
1,024 |
8765- |
open (trailing -) downward progression: 8765, 7654, 6543, 5432 — shifts every rank down until one would leave [2,14] |
— |
AKQJds:0.5 |
weight suffix, applied to every combo the item expands to | — |
Matching is multiset containment, not exact-slot equality: AA** reads
as "at least two aces", not "exactly two aces plus two unconstrained cards"
— AAKQ and AAAK both match. AK** = 17,316 (not the smaller "exactly
one ace, exactly one king, two others" count) is the standard worked
example. The suit qualifiers ds / ss / r are exact suit-histogram
constraints and do not partition the space in general — AKQJds + AKQJss + AKQJr covers 204 of AKQJ's 256 hands; the missing 52 (monotone +
three-one) have no v0.1 qualifier.
Items are unioned; a combo reached by two items with equal parsed weight
merges silently, conflicting weights raise ValueError naming both items
and both weights. A well-formed pattern that no hand can satisfy is not
an error — it expands to zero combos, so check .size() / .empty() if
that matters (e.g. "AAAKds": three aces need three suits, which rules out
the double-suited {2,2} histogram):
from xiapl.range import Range
from xiapl.simulation import GameType
z = Range.from_string("AAAKds", game=GameType.Plo)
print(z.size(), z.empty()) # 0 True
Four teaching errors (the parser's messages spell these out):
| Input | Why it's rejected | Fix |
|---|---|---|
"AA" |
2 rank symbols — that's Hold'em notation; a PLO pattern needs exactly 4 | Pad with wildcards: "AA**" |
"AAxx" |
x is not part of the v0.1 alphabet |
Use * for "any rank": "AA**" |
"AKs**" |
a suit letter after only 3 rank symbols isn't a valid ds/ss/r suffix position |
Use a suffix after exactly 4 rank symbols ("AKQJds") or an exact hand ("AsKsQhJd") |
"15%" (e.g. a top-15% percentile range) |
% percentile ranges are not supported in v0.1 — there is no ranking table to define "top X%" against |
Spell the hands out as explicit patterns instead (e.g. "AA**, AKQJds, KQJTds"); percentile ranges are planned once a versioned PLO hand-strength ordering exists to make "top X%" well-defined |
>>> Range.from_string("AA", game=GameType.Plo)
ValueError: PLO range item "AA": "AA" has 2 rank symbol(s); a rank pattern
is exactly 4 symbols; pad the unknown cards with '*' (e.g. "AA" -> "AA**")
Equity example — AggregateOnly Monte Carlo is the primary PLO equity
path for wide ranges (exact mode's evaluation cache fills faster on 4-card
combos than on 2-card ones — see Limitations):
from xiapl.range import Range
from xiapl.simulation import calculate_range_equity, SimulationOptions, GameType, RangeEquityMode
hero = Range.from_string("AAKKds", game=GameType.Plo) # 6 combos
villain = Range.from_string("QQJJds,JT98ds", game=GameType.Plo) # 42 combos
mc = SimulationOptions.mc_seeded(200_000, 7)
agg = calculate_range_equity(hero, villain, 0, mc, RangeEquityMode.AggregateOnly)
print(round(agg.hero_aggregate_equity, 4), round(agg.aggregate_std_error, 5))
# 0.6442 0.00107
print(len(agg.hero)) # 0 -- per-combo breakdown is not computed in AggregateOnly
PHH — reading real hand histories
xiapl.phh is a pure-Python addition for the Poker Hand History format. It
reads and writes the two variant codes the rest of the library models —
NT (no-limit hold'em) and PO (pot-limit Omaha) — and rejects the other
nine with a dedicated UnsupportedVariantError; more variants follow demand.
Parsing a real transcribed hand and feeding its hole/board masks straight
into calculate_equity (the "Ivey vs Dwan" televised million-dollar pot):
from xiapl import phh
from xiapl.simulation import calculate_equity, SimulationOptions
hand = phh.read_phh("tests/fixtures/phh/dwan-ivey-2009.phh")
print(hand.variant, hand.players)
# NT ['Phil Ivey', 'Patrik Antonius', 'Tom Dwan']
ivey_hole = hand.hole_mask(1) # player 1 = Ivey
dwan_hole = hand.hole_mask(3) # player 3 = Dwan
flop = hand.board_mask(max_cards=3) # board as dealt through the flop
r = calculate_equity([ivey_hole, dwan_hole], flop, SimulationOptions.exact())
print([round(p.equity, 4) for p in r.players]) # [0.6283, 0.3717]
format_phh / write_phh serialize a PhhHand back to PHH text or a file
(the writer guarantees well-formed PHH syntax, not game-legal action
sequences — see docs/phh.md for the full format
documentation: supported variant subset, data model, action grammar, writer
contract, and spec divergences).
The reader and writer are audited against the whole public
phh-dataset corpus — 31,870
files / 21,616,107 hands of real online cash-game logs, all 10,000
Pluribus hands, and a televised 2023 WSOP final table. Every file parses,
every hand survives a semantic round-trip through format_phh unchanged,
and the 68 files in variants outside the supported subset are rejected with
UnsupportedVariantError rather than failing some other way
(details).
Determinism & threads
SimulationOptions has four fields: iterations (0 = exact enumeration,
> 0 = Monte Carlo), seed, deterministic, and threads. The factory
methods set the sensible combinations: .exact(), .mc_random(n)
(non-reproducible), .mc_seeded(n, seed) (deterministic=True + the given
seed).
threads is a pure speed knob — it never changes results on any equity
path. 0 (the default) auto-parallelizes once a workload is large enough;
1 is serial; a positive N runs exactly N workers. On every Monte Carlo
sampling path (calculate_equity, and calculate_range_equity's
AggregateOnly mode), trials run in fixed 65536-trial chunks, each with its
own seed-derived RNG substream, reduced in a fixed chunk-index order — a
given seed therefore produces bit-identical results at every threads
value, serial or parallel. calculate_range_equity's PerCombo path
parallelizes differently (splitting the evaluation cache and the hero row
loop across workers) but is bit-identical across threads values by the
same fixed-partition-and-reduce discipline. The one exception:
calculate_equity's board enumeration in exact mode always runs
single-threaded regardless of threads.
calculate_equity and calculate_range_equity release the GIL for the
duration of the C++ call, so other Python threads keep running during a
long Monte Carlo call. Because the GIL is released, mutating a Range /
Combo object (e.g. Combo.weight via Range.combos()) from another
Python thread while a calculate_range_equity call is reading it is a data
race — don't do it.
API overview
| Module | Key names |
|---|---|
xiapl.card |
Card, Card.from_string, Card.from_id, .rank / .suit / .id |
xiapl.deck |
Deck, .shuffle() / .shuffle(seed), .deal(n), .deal_one(), .burn(n), .remove_cards(), .reset(), .cards, .card_ids |
xiapl.eval |
HandCategory, HandValue, describe_hand, evaluate_cards, evaluate_mask, evaluate_hand(board, hole, *, game=), judge(hole_masks, board, *, game=) |
xiapl.simulation |
GameType, SimulationMode, SimulationOptions (.exact() / .mc_random(n) / .mc_seeded(n, seed)), EquityResult, PlayerEquity, RangeEquityMode (PerCombo / AggregateOnly), RangeEquityResult, calculate_equity, calculate_range_equity |
xiapl.range |
Range, Combo, Range.from_string, Range.all(game=), .combos(), .valid_combos(dead_mask), .total_weight(), .union / .intersection / .difference (| / & / -), try_parse_range, rank_starting_hands, generate_top_percent_range |
xiapl.canonicalize |
canonicalize_hero_and_board, canonicalize_hero_and_board_masks, canonicalize_board, canonicalize_board_mask, canonicalize_hand, canonicalize_hand_mask, generate_canonical_situations |
xiapl.utils |
card_to_mask, cards_to_mask, mask_to_cards, mask_to_ids |
xiapl.phh |
PhhHand, parse_phh / parse_phh_all, read_phh / read_phh_all, format_phh / format_phh_all, write_phh / write_phh_all, UnsupportedVariantError |
Every extension module ships a hand-written .pyi stub under
python/xiapl/, verified against the compiled _xiapl extension with mypy
stubtest; xiapl installs py.typed so downstream mypy/IDE type-checking
picks them up automatically. xiapl.phh is the one pure-Python module (not
part of the compiled extension) and carries its annotations inline, so it
needs no stub.
Pre-1.0 API notes
Breaking changes recorded here are exempt from a deprecation cycle while XiaPL is at 0.x:
- Starting-hand ranking is now exact: preflop top-% ranking is backed by
a compile-time table of exact vs-random equities (exhaustive enumeration
over every (hero, villain, board) configuration — no RNG, no seed, no
sampling noise), replacing an earlier Monte Carlo scoring approach. The
label set returned for a given
top_percentchanges accordingly for the same input. Exposed to Python asxiapl.range.rank_starting_hands/xiapl.range.generate_top_percent_range, bothgamekeyword-only. - Board-only canonicalization is now a true canonical form:
canonicalize_board/canonicalize_board_maskorder the four suits by their full 13-bit rank pattern, where they previously ordered them by the single highest rank present in each suit. The old rule was not a canonical form —Ah Kh AdandAd Kd Ahare the same board up to relabelling, but both have two ace-topped suits, and the suit-index tie-break sent them to different representatives. It split the 22,100 flops into 1,833 classes; the new one produces exactly the 1,755 suit-isomorphism classes (turn: 16,432, river: 134,459). Boards on which no two suits share their top rank — the large majority — canonicalize exactly as before. Any cache or table keyed on the old representative is still self-consistent but redundant, and will merge further if rebuilt.canonicalize_hero_and_board/canonicalize_hero_and_board_masksare unchanged and still use the top-rank rule. generate_canonical_situationsnow enumerates the strict (hero+board) canonical form, so the flop population is 1,286,792 where it used to be 1,420,796. Same reason as the board-only change one entry up, applied to the pair: the old rule ordered suits by their top board rank, and its tie-break split situations that are the same up to relabelling. 1,286,792 is the exact Burnside orbit count for the symmetric group on the four suits, so the new population has no redundancy left in it (turn: 13,960,050; river: 123,156,254). The board halves of the result are exactly the 1,755 canonical flops.canonicalize_hero_and_board/canonicalize_hero_and_board_maskskeep their old, frozen behaviour and are therefore no longer the map this enumeration uses; a cache keyed by the old population must be rebuilt.- PLO full-range enumeration order changed from lowest-card-outermost
lexicographic to colexicographic (ascending-mask) order, matching what
Range.from_string("****", game=GameType.Plo)already produced —Range.allno longer disagrees with the parser on element order depending on how the caller spelled the same range. A pinned seeded MC stream built overRange.all(GameType.Plo)will change; one built over a parser-constructed full-range string was already in this order and is unaffected. - Python enum casing: all four
py::enum_bindings (HandCategory,GameType,SimulationMode,RangeEquityMode) dropped.export_values(), so enum members no longer leak onto the enclosing submodule (e.g.xiapl.simulation.Holdemalongsidexiapl.simulation.GameType.Holdem) — always go through the enum type.RangeEquityModeis now PascalCase-canonical (PerCombo/AggregateOnly); the oldPER_COMBO/AGGREGATE_ONLYspellings are kept as deprecated aliases of the same values, to be removed at 1.0. Range's combo-size validation error now spells the offending combo as card names ("Range: combo KdAs has 2 cards, expected 4 for this GameType") instead of a raw decimal mask value, matching the PLO parser's own diagnostic style; a mask bit outside the 52-card deck falls back to a"#<decimal>"rendering.- Game-unified API: the public surface is never split by game — one
entry point per concept takes a
gameparameter defaulting toGameType.Holdem, dispatched in the C++ library so Python sees the same behavior as C++.gameis keyword-only onevaluate_hand,judge, andcalculate_equity(judge(masks, board, game=GameType.Plo)); onRange.all/Range.from_stringit stays positional-or-keyword. Older per-game C++ function pairs (evaluate_holdem/evaluate_plo, etc.) were deleted with no aliases, pre-publication. Deck.shuffle(seed)always reproduces the same permutation for the same seed, including seed0.
Design notes
Card ID encoding
id = suit * 13 + (rank - 2)
rank: 2, 3, 4, ..., 9, T(10), J(11), Q(12), K(13), A(14)
suit: 0=clubs, 1=diamonds, 2=hearts, 3=spades
52-bit masks — cards are represented as bits in a 64-bit integer; bit
i corresponds to card ID i. This is what every mask-taking function
(evaluate_hand, judge, calculate_equity, cards_to_mask, ...) accepts
and returns, and it's what makes set operations (union, intersection,
population count) for deck manipulation and collision detection cheap.
Iteration modes — every simulation entry point takes an iterations
knob: 0 means exact full enumeration (deterministic by construction);
> 0 means Monte Carlo sampling (pair with a fixed seed for
reproducibility).
Range grammar, summarized — Hold'em notation is the familiar
"AKs" / "AKo" / "JJ+" / "TT-88" / "AQs+" / "76s-54s" /
"AdAh:0.5" form, comma-separated, with an optional :weight suffix per
item. PLO notation (see above) is a distinct,
frozen v0.1 grammar built from 4-symbol rank patterns, suit qualifiers
(ds/ss/r), and progressions — it is not a reuse of Hold'em notation
padded out to four cards.
Using the C++ core directly
The Python package is a thin binding over xiapl_core (reached through the C
ABI adapter described in The C ABI); the C++ API mirrors
the Python surface (same type and function names — Card, Deck, Range,
SimulationOptions, evaluate_hand, judge, calculate_equity, ... —
modulo :: vs . and PascalCase-vs-snake_case call conventions). If
you're building a C++ application directly on top of the core rather than
calling it from Python, this section is your entry point.
cmake -S . -B build \
-DXIAPL_BUILD_CORE=ON \
-DXIAPL_BUILD_TESTS=ON \
-DXIAPL_BUILD_EXAMPLES=ON
cmake --build build -j
cmake --install build --prefix /usr/local
Use from an external CMake project:
find_package(xiapl REQUIRED)
target_link_libraries(myapp PRIVATE xiapl::xiapl_core)
# C ABI / FFI adapter (see "The C ABI" below)
target_link_libraries(myffi PRIVATE xiapl::xiapl_c_api)
Build options:
| Option | Default | Effect |
|---|---|---|
XIAPL_BUILD_CORE |
ON | Core library (xiapl_core) |
XIAPL_BUILD_FFI |
ON | C ABI library (xiapl_c_api) for FFI |
XIAPL_BUILD_SHARED |
OFF | Additionally build the C ABI as a shared library |
XIAPL_BUILD_TESTS |
OFF | Doctest-based C++ unit tests |
XIAPL_BUILD_EXAMPLES |
OFF | Runnable examples under examples/ |
XIAPL_BUILD_BENCHMARKS |
OFF | Chrono-based microbenchmarks |
The umbrella header pulls in the whole installed surface:
#include <xiapl/xiapl.h>
using namespace xiapl;
Runnable, self-contained examples for every Quick Start topic above
(cards/deck, evaluation, equity, weighted ranges, range-vs-range equity)
live under examples/ (evaluate_hand.cpp, calc_equity.cpp,
weighted_range.cpp, range_equity.cpp), built with
-DXIAPL_BUILD_EXAMPLES=ON. Stable public headers install under
${prefix}/include/xiapl/; internals live under
include/xiapl/detail/ and are not installed.
benchmarks/ holds two chrono-based microbenchmarks (bench_eval.cpp for
raw hand evaluation, bench_equity.cpp for equity simulation) so the speed
claims at the top of this README are something you can measure on your own
machine rather than take on faith:
cmake -S . -B build -DXIAPL_BUILD_BENCHMARKS=ON
cmake --build build -j
./build/benchmarks/bench_eval && ./build/benchmarks/bench_equity
(both are compiled -O3 -DNDEBUG regardless of CMAKE_BUILD_TYPE, so the
numbers stay meaningful in a Debug configure.)
docs/benchmarks.md has the published numbers: the 7-card evaluator head-to-head against PH Evaluator and OMPEval on an Apple M1 Pro and an Intel Xeon, single-threaded, with the measurement method, the correctness cross-check between the three libraries, and a note on which x86 compiler flags matter.
apps/gen_preflop_rank.cpp is the offline generator for the exact
preflop ranking table checked in at src/core/preflop_rank_table.inc
(the data behind rank_starting_hands / generate_top_percent_range).
The table is a committed artifact, so the generator exists for
auditability: anyone can re-derive it and byte-compare, and --verify
re-checks sample entries against a brute-force enumeration. It is not part
of the default build:
cmake --build build --target gen_preflop_rank
./build/gen_preflop_rank --threads 6 --verify --out src/core/preflop_rank_table.inc
The output is deterministic (integer counting, no RNG), so a regeneration on any platform or thread count must reproduce the committed file exactly.
The C ABI
XiaPL is an hourglass: one C++ core (xiapl_core), one narrow neck, many
bindings. The neck is include/xiapl/c_api.h — 77 XIAPL_API functions at
XIAPL_C_ABI_VERSION 4 — and it IS the contract every binding is written
against. The Python package in this repository is the first consumer: all
seven of its modules (card, utils, eval, canonicalize, deck,
range, simulation) are implemented against this header and nothing else,
which tests/check_binding_includes.sh enforces mechanically.
The C ABI is a mechanical, id/mask-level projection of the C++ public API — a strict subset that invents no representation and performs no presentation logic of its own (no sorting or formatting the C++ API does not already do). That makes it the natural target for a Rust / Node / Go / WASM binding, in the same way libraries such as SQLite or llama.cpp use their own C ABI as the one thing every downstream binding is written against. The header's top-of-file comment states the full frozen contract: the boundary conventions (ownership, query-then-fill sizing, error propagation, thread contract, determinism) and the ABI stability policy.
Stability: unstable until 1.0. The C ABI ships because it is the
substrate the Python binding is built on, and you are welcome to read and
use it — but during 0.x its functions and semantics may change between
releases without a deprecation cycle. xiapl_c_abi_version() /
XIAPL_C_ABI_VERSION exist so a consumer fails loudly instead of silently
when that happens. It is planned to graduate into a first-class,
stability-committed contract once the first non-Python binding ships; until
then, the Python package is the supported public surface.
It is built when XIAPL_BUILD_FFI=ON (the default) and exposed as
xiapl::xiapl_c_api. XIAPL_BUILD_SHARED=ON additionally builds it as a
shared library exporting the xiapl_* symbols and nothing else;
tests/check_exports.sh is the machine check for that (registered as the
xiapl_c_api_shared_exports ctest on macOS and Linux — the two platforms
whose link-time export filter is configured).
#include <xiapl/c_api.h>
/* Once at binding init: catch a stale FFI struct declaration loudly. */
if (xiapl_c_abi_version() != XIAPL_C_ABI_VERSION) { /* refuse to run */ }
xiapl_sim_options_t options; /* xiapl_sim_options_exact fully
* populates every field, so no
* {0} initializer is needed. */
if (xiapl_sim_options_exact(&options) != XIAPL_OK) { /* handle error */ }
const uint64_t holes[2] = { hero_mask, villain_mask };
xiapl_equity_summary_t summary = {0};
double equity[2] = {0};
int rc = xiapl_calculate_equity(holes, /*num_players=*/2, board_mask, &options,
XIAPL_GAME_HOLDEM, /*out_winrate=*/NULL, equity,
/*out_std_error=*/NULL, /*players_capacity=*/2,
&summary);
The C API mirrors the public equity surface: fixed-hand equity
(xiapl_calculate_equity), range-vs-range equity over opaque xiapl_range_t
handles (xiapl_calculate_range_equity, Hold'em and PLO alike — a range
carries its own game tag), and top-percent range generation
(xiapl_generate_top_percent_range), plus the card / deck / eval /
canonicalize primitives. See the header for the complete function list and
per-function contracts.
Platform support
| OS | Arch | Compiler | Status |
|---|---|---|---|
| macOS | arm64 | Apple clang | Verified (local CI) |
| Linux | x86_64 / arm64 | clang / gcc | Source-portable (C++20 + portable bit-intrinsic wrappers); the core builds with clang on x86_64 and agrees with two independent evaluators over a million hands there (see docs/benchmarks.md) |
| Windows | x86_64 | MSVC | Verified on real hardware (Ryzen 7 3700X, MSVC 19.51 / Visual Studio 2026, Python 3.12): pip install ., the full pytest suite, and ctest all pass (4/4), zero warnings |
Both pip install . and the CMake build compile the C++ core from source
on the host, so platform support is compiler support: all bit operations
route through portable wrappers (ctz64, clz64, clz32, popcount64)
that expand to GCC/Clang __builtin_*, MSVC _BitScan* / __popcnt64, or
a scalar fallback. SIMD is left to the compiler's auto-vectorizer (no
explicit NEON / SSE / AVX intrinsics). Any binary data the library produces
is little-endian, matching every supported target. On x86, building for a
baseline newer than generic x86-64 is worth it — the flush check's
popcounts only become a single instruction from x86-64-v2 / -mpopcnt
onwards (docs/benchmarks.md quantifies it).
Limitations
calculate_range_equitywithiterations == 0(exact mode) caps the per-hand evaluation cache at ~512 MB. Wide preflop ranges (no board) easily exceed this — the call raisesRuntimeErrorwith a remediation hint instead of allocating multiple gigabytes silently. Useiterations > 0(Monte Carlo) for those cases. Exact mode on a flop / turn / river is unaffected.- Multi-way
calculate_equityaccepts up to 10 players for Hold'em / 32 for PLO (hole_masks.size()past either raisesValueError), butcalculate_range_equityis heads-up only (both Hold'em and PLO — multiway range-vs-range is not supported today, and is planned). - XiaPL is 0.x / alpha:
Card,Deck,Range,SimulationOptions,calculate_equity,calculate_range_equity, and the rest of the surface in the API overview are exposed today and exercised by the test suite, but signatures may still change before 1.0 (see Pre-1.0 API notes).
Testing
python -m pytest python/test/ -v
For contributors touching the C++ core, the corresponding C++ test suite:
cmake -S . -B build -DXIAPL_BUILD_TESTS=ON
cmake --build build --target xiapl_tests
./build/xiapl_tests
./build/xiapl_tests --test-suite-exclude=slow # quick tier (or: ctest -LE slow)
ctest runs a little more than that binary. With
-DXIAPL_BUILD_TESTS=ON -DXIAPL_BUILD_SHARED=ON the full lane set is:
| ctest name | What it checks |
|---|---|
xiapl_tests_fast / xiapl_tests_slow |
the core C++ suite, split by doctest's slow suite |
xiapl_c_api_tests |
every C ABI entry point, its error classification and its ownership rules |
xiapl_c_api_abi_check |
that <xiapl/c_api.h> compiles as plain C11 under -Wpedantic |
xiapl_binding_include_purity |
that binding/ reaches the library only through <xiapl/c_api.h> |
xiapl_c_api_shared_exports |
that the shared C ABI exports the xiapl_* functions and nothing else (macOS / Linux) |
The last two are shell-script guards rather than compiled test binaries, and
neither is gated on XIAPL_BUILD_TESTS: the include-purity check is
registered by every configure of this project, and the export check by every
configure with XIAPL_BUILD_SHARED=ON on a platform whose export filter is
configured. Both are skipped with a status message if no bash is found.
They guard architectural properties that have no compile-time enforcement of
their own.
License
MIT — see LICENSE for details.
Release files for xiapl 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xiapl-0.1.0.tar.gz | 449.2 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| xiapl-0.1.0-cp313-cp313-win_amd64.whl | CPython 3.13 | CPython 3.13 | Windows x86-64 | Details |
| xiapl-0.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| xiapl-0.1.0-cp313-cp313-macosx_11_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 11.0+ ARM64 | Details |
| xiapl-0.1.0-cp313-cp313-macosx_10_13_x86_64.whl | CPython 3.13 | CPython 3.13 | macOS 10.13+ x86-64 | Details |
| xiapl-0.1.0-cp312-cp312-win_amd64.whl | CPython 3.12 | CPython 3.12 | Windows x86-64 | Details |
| xiapl-0.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| xiapl-0.1.0-cp312-cp312-macosx_11_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 11.0+ ARM64 | Details |
| xiapl-0.1.0-cp312-cp312-macosx_10_13_x86_64.whl | CPython 3.12 | CPython 3.12 | macOS 10.13+ x86-64 | Details |
| xiapl-0.1.0-cp311-cp311-win_amd64.whl | CPython 3.11 | CPython 3.11 | Windows x86-64 | Details |
| xiapl-0.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| xiapl-0.1.0-cp311-cp311-macosx_11_0_arm64.whl | CPython 3.11 | CPython 3.11 | macOS 11.0+ ARM64 | Details |
| xiapl-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl | CPython 3.11 | CPython 3.11 | macOS 10.9+ x86-64 | Details |
Total release size: 5.8 MB
Release files / xiapl-0.1.0.tar.gz
| Download URL | xiapl-0.1.0.tar.gz |
|---|---|
| Size | 449.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
839de54aeb4b7afb07db86a7d18acf8603503aaf45d2e02d04df219df1ef5822
|
|
BLAKE2b-256 checksum How to use checksums |
56d9ba40c94bbf04ab374e47d9256690febdb168dbf5bf3ff9a9236541a1e012
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp313-cp313-win_amd64.whl
| Download URL | xiapl-0.1.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 494.9 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
60f3a30652bf64c8433cbafb62acc3927b95af90b4f3bae8e977494ed131d1c3
|
|
BLAKE2b-256 checksum How to use checksums |
5506510f4ac351f6e6e2abbfb8dfbecdc6822cb24226504476449b8b6b5eef29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | xiapl-0.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 526.9 kB |
| Tags | CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
7d48f836eca39a26c9fb242f1928aa35925b05b791bf7bdd2675b4f79622a5e9
|
|
BLAKE2b-256 checksum How to use checksums |
872b98f3dbb3c474c9677db5ec87b0c5bed7e98229371524087585189267667e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | xiapl-0.1.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 373.7 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9a257b9d217afbefa7a942e2647b50e0d659862bedd76c13a80ae2607bf10429
|
|
BLAKE2b-256 checksum How to use checksums |
9d7b72482eb4ad666d31ae06f80b59ab857c8458b82dc8d791cdffba24272ba1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | xiapl-0.1.0-cp313-cp313-macosx_10_13_x86_64.whl |
|---|---|
| Size | 391.4 kB |
| Tags | CPython 3.13 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
eecf4eac285519a0897dd13ece17993954a5c3480e1c448b4ec9fd7b6a48597e
|
|
BLAKE2b-256 checksum How to use checksums |
dc1dbf586be19aa664031efcf27ee14b7f16171ddf8379b2501c68d7c8a8bc97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp312-cp312-win_amd64.whl
| Download URL | xiapl-0.1.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 494.9 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
0651f9b6a3b864311e845569b59488e3694e406b1bb60c1b6a8747754a621a5b
|
|
BLAKE2b-256 checksum How to use checksums |
a11cac85bbff12b0392d624bbc55dd05052a9e35e517d83c7bd7e4dfd2cbc282
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | xiapl-0.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 527.3 kB |
| Tags | CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
78483aeb81416bc8b2f2a9d873d4187bce79c6ad74140b14b9d21a37743eef76
|
|
BLAKE2b-256 checksum How to use checksums |
9b4fb6e62e099b493d4f9b8094904a1ac5951c8160a562790c5315e58ff23cb5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | xiapl-0.1.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 373.7 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
7f6d64441eec1de3b399d4f6f49a767c71cd5b3684aa5d9ecf8091fe1ab73344
|
|
BLAKE2b-256 checksum How to use checksums |
8cc95e6698164da56870d862a47c7368d80dbb3160f7e4cf0693438580798c46
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | xiapl-0.1.0-cp312-cp312-macosx_10_13_x86_64.whl |
|---|---|
| Size | 391.3 kB |
| Tags | CPython 3.12 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
9ab16bfbdc31736fd51b0571338c3b953d594c02ce0942dac37a8667e6427971
|
|
BLAKE2b-256 checksum How to use checksums |
802ab9f4af4f2f9df182f72284c4369d816e9365c84211f874b444f99d4afd7c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp311-cp311-win_amd64.whl
| Download URL | xiapl-0.1.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 492.4 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
eb61aeba00c02763ad9be47464a412bb404309682b6c13d353c807ec69fdac89
|
|
BLAKE2b-256 checksum How to use checksums |
88113e9344367dc74457d71be05a83cffa57bc4a1951ef8334124afe70013eac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | xiapl-0.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 524.5 kB |
| Tags | CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
d3265424f03e31f1c41c097f050dd2ea756d809fb2a2b6fa72b281744e1b207c
|
|
BLAKE2b-256 checksum How to use checksums |
b66b8dc72a1b213418b23ad13b8266b5da9421827c791a0542d60bceaa20e4aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | xiapl-0.1.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 371.6 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
0c8844505b84ea7e6f88b88a86fcdff4bd3c765fd2e1dbaabab226b5c5b2f033
|
|
BLAKE2b-256 checksum How to use checksums |
b802d495919a7a38a3b53b32f50c9b8e390d45700d15bb5e287f523100f7ca80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / xiapl-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | xiapl-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl |
|---|---|
| Size | 388.1 kB |
| Tags | CPython 3.11 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
9d3d8f6d79cbd9f0526b2f52d3117325eb6f4df67cd4aef6392ce5940e9a8bbe
|
|
BLAKE2b-256 checksum How to use checksums |
3f60360ee1d5ee26828d220d4cfddfd8eadcdd2ccd08b61b2612a9a506b56577
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log