Restricted Decision Trees (RDT)
A native C++ implementation of Restricted Decision Trees — a tree-based algorithm that sits between the rigidity of oblivious trees and the flexibility of standard decision trees, exposed to Python via nanobind.
What is a Restricted Decision Tree?
In a Restricted Decision Tree, all nodes at the same depth use the same feature for splitting, but each node can have a different threshold. This constraint provides:
- Structure: more interpretable than standard decision trees — the feature chosen at each depth is a natural, depth-ordered importance ranking.
- Flexibility: more adaptable than oblivious trees (which use the same feature and threshold at every node in a depth).
- Staged decisions: each depth can correspond to one information source (e.g. a KYC vendor check, a medical test panel, a fraud-investigation stage) — useful when a real deployment pipeline is itself staged.
Tree Type Comparison
| Tree Type | Feature Selection | Threshold Selection | Structure |
|---|---|---|---|
| Standard Decision Tree | Different feature per node | Different threshold per node | Unrestricted |
| Restricted Decision Tree | Same feature per depth | Different threshold per node | Semi-structured |
| Oblivious Decision Tree | Same feature per depth | Same threshold per depth | Highly structured |
Features
- ✅ Classification (binary & multi-class) and regression
- ✅ Multiple splitting criteria: Gini, entropy, MSE, MAE
- ✅ Probability predictions (
predict_proba) - ✅ Random Forest and Gradient Boosting ensembles (
rdt.ensemble), with real cross-tree parallelism and a parameter surface matching scikit-learn/XGBoost/LightGBM - ✅ Interactive D3.js tree visualization, plain-text tree printing, and JSON export
- ✅ Native C++ core — validated for exact output parity (predictions, per-depth feature choice, probabilities) against the project's original Cython reference implementation, and substantially faster in practice
Install
pip install restricted-decision-trees
Quick start
import numpy as np
from rdt import RDT
X = np.array([[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]])
y = np.array([0, 0, 0, 1, 1, 1], dtype=float)
model = RDT(task="classification", criterion="gini", max_depth=3)
model.fit(X, y)
model.predict(X) # array([0., 0., 0., 1., 1., 1.])
model.predict_proba(X) # per-class probabilities
model.depth_features_ # {0: 0} -- feature index chosen at each depth
For regression, pass task="regression" (criterion defaults to "mse";
"mae" is also available).
Visualizing a tree
from rdt.visualize import print_tree, visualize_d3_observable, export_tree_dict
print_tree(model, feature_names=["x"])
# Interactive, collapsible D3.js visualization -- writes an HTML file you
# open in a browser (click nodes to expand/collapse, scroll/drag to zoom+pan).
visualize_d3_observable(model, y, feature_names=["x"], filename="tree.html")
# Or get the same tree structure + per-node stats as a plain dict/JSON,
# for a custom visualization of your own.
tree_dict = export_tree_dict(model, y, feature_names=["x"])
See examples/basic_usage.py and examples/d3_visualization_demo.py for
fuller, runnable examples.
Ensembles
from rdt.ensemble import RandomForestRDT, GradientBoostingRDT
rf = RandomForestRDT(n_estimators=100, max_depth=4, oob_score=True, n_jobs=-1)
rf.fit(X_train, y_train)
rf.predict(X_test)
rf.feature_importances_
rf.oob_score_
gb = GradientBoostingRDT(n_estimators=100, learning_rate=0.1, max_depth=3,
validation_fraction=0.15, n_iter_no_change=10) # early stopping
gb.fit(X_train, y_train)
gb.predict(X_test)
# Early stopping on your own validation set (XGBoost's eval_set)
gb = GradientBoostingRDT(n_estimators=5000, learning_rate=0.1, early_stopping_rounds=50,
eval_metric="auc")
gb.fit(X_train, y_train, eval_set=(X_val, y_val))
gb.best_iteration_, gb.best_score_, gb.evals_result_["auc"] # model truncated to best_iteration_ + 1 rounds
Parameter names follow scikit-learn where a concept maps directly to a
scikit-learn parameter; where XGBoost/LightGBM use a different name for the
same concept (colsample_bytree, gamma, min_child_weight,
early_stopping_rounds, ...) that name is accepted as an alias — setting
both names to conflicting values raises. See rdt/ensemble.py's module
docstring for the full mapping, including the two places where RDT's
structural constraint (every node at a depth splits on the same feature)
means a parameter can't work exactly like it does elsewhere:
max_features/colsample_bytreecan't be resampled per node the way scikit-learn/XGBoost do it — each tree gets one random column subset chosen once, before that tree is fit.colsample_bylevel(RandomForestRDT,GradientBoostingRDT, default 1.0) is the per-depth analogue: each depth a tree searches scores only a fresh random subset ofmax(1, floor(colsample_bylevel * m))of the tree'smcolumns (without replacement); the depth's feature is chosen among them. The draws come from a generator seeded from the tree's own seed (so they do not depend onn_jobsand leave the other draws unchanged), and are exposed aslevel_features_(one{depth: [columns of X]}dict per tree, replayed from the seeds each time it is read;fitdoes not store them).reg_alpha/reg_lambda(GradientBoostingRDTonly) are, by default, a real effect (post-hoc leaf-value soft-thresholding/shrinkage) but not XGBoost's exact regularised leaf formula — the default engine is Friedman's gradient boosting as in scikit-learn (trees grown on the gradient with a squared-error criterion, then a per-leaf Newton step forlog_lossandexponential).split_criterion="newton"switches to XGBoost's hessian-weighted tree growth, wherereg_lambda,reg_alpha,gamma,min_child_weightandmax_delta_stephave XGBoost's meaning (see below).
See examples/ensemble_usage.py for a fuller, runnable example.
API reference
rdt.RDT(task="classification", criterion=None, max_depth=5, min_samples_split=2, min_samples_leaf=1, min_impurity_decrease=0.0, presort=True, tree_method="exact", max_bin=256, n_jobs=None, hist_subtraction=True, max_hist_cache_mb=1024)
Parameters
task—"classification"or"regression".criterion— splitting criterion. Defaults to"gini"for classification or"mse"for regression. Classification accepts"gini"or"entropy"; regression accepts"mse"or"mae".max_depth— maximum tree depth.min_samples_split— minimum samples required to split a node.min_samples_leaf— minimum samples required in a leaf node.min_impurity_decrease— a depth is only split if its total weighted impurity decrease (sum over the depth's nodes ofnode_samples / total_samples * node_improvement, scikit-learn's definition and comparison, up to machine epsilon) is at least this value.presort—True(default): presorted split search;False: the original per-node sort (see Performance notes).tree_method—"exact"(default) or"hist": histogram split search on features binned once per fit (see Histogram split search). Not implemented forcriterion="mae"(raisesNotImplementedError).max_bin— maximum bins per feature fortree_method="hist"(2–65536, default 256).n_jobs— threads for thetree_method="hist"search (None = 1).hist_subtraction,max_hist_cache_mb— histogram subtraction fortree_method="hist"and the memory it may keep (see Histogram split search).
Methods
fit(X, y, sample_weight=None)— build the tree from training data.predict(X)— predict target values.predict_proba(X)— predict class probabilities (classification only).
All estimators validate their inputs the way scikit-learn does and raise
ValueError for: X that is not 2D or is empty; NaN or infinity in X,
y or sample_weight (missing values are not supported — impute them
first); y/sample_weight whose length differs from X's rows; negative
or all-zero weights (also after class_weight / scale_pos_weight are
applied; scale_pos_weight must be finite and > 0); and, at predict time, an
X with a different number of features than during fit.
Attributes (after fitting)
depth_features_—dict[int, int], the feature index chosen at each depth.depth_improvements_—dict[int, float], the total weighted impurity decrease achieved at each depth.n_features_— number of features seen duringfit.classes_,n_classes_— unique class labels and their count (classification only).root— the fitted tree's rootNode(.is_leaf,.split_feature,.split_threshold,.value,.class_counts,.left,.right,.n_samples,.indices) for direct programmatic inspection..indices(the training rows that reached the node) is kept by a standaloneRDTand byRandomForestRDT(bootstrap=False)sub-trees; sub-trees fitted on a bootstrap sample drop it after fitting (accessing it raisesAttributeError);.n_samplesis always available.
rdt.ensemble.RandomForestRDT
Bagging ensemble: n_estimators, task, criterion, max_depth,
min_samples_split, min_samples_leaf (alias min_child_weight),
min_impurity_decrease (alias gamma), max_features (alias
colsample_bytree), bootstrap, max_samples, oob_score, class_weight
('balanced' or dict), random_state, n_jobs, presort, tree_method,
max_bin, colsample_bylevel, hist_subtraction, max_hist_cache_mb. max_samples follows
scikit-learn: an int is the number of rows drawn per tree, a float is a
fraction of n_samples, None means n_samples. After fitting:
estimators_ (list of RDT, individually introspectable),
feature_importances_, oob_score_ (if requested), depth_features_ (one
{depth: feature} dict per tree, with features as column indices of the X
passed to fit -- each tree's own indices, as in
estimators_[i].depth_features_, mapped through its column subset) and
level_features_ (each tree's per-depth candidate columns, see
colsample_bylevel).
rdt.ensemble.GradientBoostingRDT
Friedman gradient boosting, matching scikit-learn's
GradientBoostingClassifier/Regressor: each round grows an RDT on the
negative gradient, then sets each leaf to the loss's one-step Newton estimate
(log_loss, exponential; the leaf mean is already exact for
squared_error; absolute_error/huber/quantile keep the leaf mean of the
gradient, a documented simplification). The initial score is the weighted
prior log-odds / log class priors / weighted mean, median or quantile, and
sample_weight (or scale_pos_weight) also weights train_score_ and the
internal early-stopping holdout's loss. Parameters: n_estimators, task, loss (classification:
'log_loss', 'exponential'; regression: 'squared_error',
'absolute_error', 'huber', 'quantile'), learning_rate, max_depth,
min_samples_split, min_samples_leaf (alias min_child_weight),
min_impurity_decrease (alias gamma), subsample, max_features (alias
colsample_bytree), validation_fraction, n_iter_no_change (alias
early_stopping_rounds), tol, eval_metric, reg_alpha, reg_lambda,
scale_pos_weight (binary only), huber_delta, quantile_alpha,
random_state, n_jobs, presort, split_criterion, max_delta_step,
tree_method, max_bin, colsample_bylevel, hist_subtraction,
max_hist_cache_mb. After fitting, depth_improvements_ holds each tree's
per-depth improvement (the total Newton gain with split_criterion="newton").
Classification needs at least two classes in y.
fit(X, y, sample_weight=None, eval_set=None, eval_sample_weight=None).
split_criterion="newton" grows each round's tree the XGBoost way: per-row
gradients g and hessians h (times sample_weight/scale_pos_weight;
log_loss: h = max(p(1-p), 1e-16), multiclass max(2p(1-p)w, 1e-16) per
class as XGBoost's softmax; exponential; squared_error: h = 1), each
node's best split maximising the gain
score(G_L, H_L) + score(G_R, H_R) - score(G, H) with
score(G, H) = T(G)^2 / (H + reg_lambda), T(G) = sign(G)·max(|G| - reg_alpha, 0)
(XGBoost's scale, no ½; evaluated at the clipped weight when
max_delta_step > 0). A node splits only if its gain is >= gamma and > 1e-6
with each child's hessian sum >= min_child_weight; otherwise it becomes a
leaf. Each depth's feature maximises the sum over the nodes it splits of
gain - gamma, and leaves are -T(G) / (H + reg_lambda) (clipped to
±max_delta_step) times learning_rate. In this mode min_child_weight is
a hessian sum (default 1, no longer an alias of min_samples_leaf),
reg_lambda defaults to 1, feature_importances_ are normalised total gains,
and the absolute_error/huber/quantile losses (no hessian) are rejected.
With a single feature this reproduces XGBoost (≥ 3.4) tree_method="exact"
round by round to float32 precision, except that XGBoost applies gamma by
pruning after growth rather than while growing (which can differ at depth ≥ 2),
keeps zero-weight rows as candidate thresholds (so a split can sit elsewhere
between the same weighted rows) and breaks exact gain ties towards the
highest threshold (here the lowest). XGBoost 3.0–3.3 also differ when
max_delta_step > 0 and reg_alpha > 0 (their clipped-weight gain lacks the
2·reg_alpha·|w| term) and give a root leaf whose hessian sum is below
min_child_weight weight 0. After fitting: feature_importances_,
train_score_ (loss per round), n_estimators_ (rounds in the model —
less than n_estimators if early stopping triggered), best_iteration_,
best_score_, evals_result_ (with an eval_set, see below), depth_features_ (one
{depth: feature} dict per tree in fitting order, a multiclass round
contributing one tree per class; features are columns of X), level_features_
(each tree's per-depth candidate columns, see colsample_bylevel).
Methods: predict, predict_proba and, as in scikit-learn,
decision_function(X) (raw scores: shape (n_samples,) for binary
classification, (n_samples, n_classes) for multiclass),
staged_decision_function(X), staged_predict_proba(X) and
staged_predict(X) (generators yielding one new array per fitted round —
n_estimators_ of them, respecting early stopping — whose last stage equals
the non-staged method exactly; the stages are accumulated natively, one round
at a time). decision_function, staged_decision_function and
staged_predict_proba are classification only.
Early stopping
Two ways, both with the patience n_iter_no_change (alias early_stopping_rounds):
- Internal holdout (
fit(X, y), scikit-learn's):validation_fraction(default 0.1) of the rows are held out of training; fitting stops once their (weighted) loss has not improved by more thantol(default 1e-4) forn_iter_no_changerounds. Every fitted round is kept. - Your own validation set (
fit(X, y, eval_set=(X_val, y_val)), XGBoost's sklearn API): every row ofXtrains andvalidation_fractionis ignored (setting it explicitly warns).eval_setis an(X_val, y_val)tuple or a list holding exactly one — a list of several raises: early stopping andevals_result_use one eval set.X_valfollowsX's rules (finite; withtree_method="hist"float32/float64 in any layout, read in place). After each round the eval rows' raw scores are updated with that round's trees only, routed by the raw thresholds exactly aspredictroutes them (hist included), andeval_metricis computed natively (GIL released), weighted byeval_sample_weight(training weights never apply): classification"logloss"(default;predict_proba, clipped to[eps, 1 - eps]like scikit-learn'slog_loss),"auc"(binary only; weighted ROC AUC of the raw score, ties counted half as inroc_auc_score),"error"(weighted fractionpredictmisclassifies); regression"rmse"(default) or"mae". A round improves when it beats the best score so far by more thantol(default 0 here: strictly better — ties do not count; higher is better for AUC). Afterearly_stopping_roundsrounds without improvement fitting stops and the model is truncated to its best round:best_iteration_(0-based, as XGBoost),best_score_,n_estimators_ = best_iteration_ + 1, andpredict,predict_proba,staged_*,train_score_,feature_importances_,depth_features_anddepth_improvements_all describe the truncated model — also whenn_estimatorsruns out first.evals_result_({metric: [score after each round]}) covers every round run, the discarded patience rounds included. The eval set never influences training, so the result is bit-for-bit the modelfit(X, y)withn_estimators=best_iteration_ + 1gives, for anyn_jobs. With aneval_setbut no patience the metric is only recorded (best_iteration_stays None).
rdt.visualize
print_tree(model, feature_names=None)— print an indented text representation of a fitted tree.export_tree_dict(model, y, feature_names=None, class_names=None) -> dict— export the tree as a nested dict with per-node statistics (sample count, impurity, target average, leaf purity).visualize_d3_observable(model, y, feature_names=None, class_names=None, filename="rdt_tree_d3.html", width=1200, height=800) -> str— render an interactive, collapsible D3.js tree to an HTML file (and return the HTML string).
Histogram split search (tree_method="hist")
RDT, RandomForestRDT and GradientBoostingRDT accept tree_method="hist"
and max_bin (default 256, 2–65536):
- Binning, once per
fit, from the training rows with positive weight (for gradient boosting, after the early-stopping split): a feature with at mostmax_bindistinct values gets one bin per value, otherwise approximately equal-count bins. All rows are binned and stored asuint8(every feature asuint16when any feature has more than 256 bins), in row blocks of 16 features (a row's bins of a block side by side).Xmay be float32 or float64, C or Fortran order, a strided view or annp.memmap: it is read in place, a block of columns at a time — never copied — and not read again after binning (boosting updates its training scores, and the forest its OOB score, through the bins). A boostingeval_setis not binned: its rows are routed by the raw thresholds, aspredictdoes. - Search: the tree keeps its rows partitioned by node. At each depth, a
node's histograms are built over its own rows, for the 16 features of a
bin block in one pass (so they stay in cache), and swept with the same
criterion arithmetic as the exact search (Gini, entropy, MSE, and the
gradient boosting criteria
friedman_mseandnewton). Features, and the per-depth row bookkeeping, run in parallel whenn_jobs > 1(a single tree, or a boosting round with one tree); the result does not depend onn_jobs. - Histogram subtraction (
hist_subtraction=True, the default): below the root, each feature's histograms are built only for the smaller child of every split, and its sibling's are derived as parent minus child from the previous depth's histograms, as XGBoost and LightGBM do. This builds roughly a third of the rows at each depth below the root. The histograms are kept for the features scored at both depths, withinmax_hist_cache_mb(default 1024 MiB) for the current and the previous depth together (aboutnodes * sum(bins) * 20bytes per depth fornewton); the other features are built directly. The budget is per tree being fitted: a random forest fits up ton_jobstrees at once, a multiclass boosting round up tomin(n_jobs, n_classes). Derived sums are rounded differently, so split scores can differ fromhist_subtraction=Falsein the last bits and an exact tie between two splits may go the other way; otherwise the trees are the same.hist_subtraction=Falsebuilds every histogram directly, and so does a depth with nothing kept to subtract from (max_hist_cache_mb=0ishist_subtraction=Falsebit for bit). - Thresholds: a split between a node's adjacent non-empty bins
lo < hiuses the midpoint between the largest training value in binloand the smallest in binhi— the exact search's threshold (the midpoint between the node's neighbouring values) whenever every bin holds a single value — moved to a bin boundary only if a row outside the training rows would otherwise be routed differently from its bin, so for every row of theXpassed tofit,x <= thresholdexactly when the row's bin is on the left. With at mostmax_bindistinct values per feature a hist model is therefore the exact model, including with row subsampling and bootstrap, up to how a tie between equally good splits is broken (the two searches round their sums differently, so splits whose scores are equal in exact arithmetic may be ranked differently). - Memory (besides
X; no presorted columns): the bins, 1 byte per row and feature (2 when any feature has more than 256 bins); 40 bytes per bin and feature for the cuts and bin extents; while binning, one sorted column of training values per thread (8 bytes per training row); 32-48 bytes per training row of search state; and the subtraction cache (at mostmax_hist_cache_mbper tree being fitted). A forest growsn_jobstrees at a time, each with its own search state. Gradient boosting's per-row scores and gradients come on top, as with"exact".
Performance notes
- Split search: each feature is sorted once per fit and every depth scores all of its nodes in one pass over the presorted rows, instead of sorting every node's samples for every feature at every depth (see docs/ALGORITHM_DETAILS.md).
- Results: identical to the per-node sort — both searches perform the
same floating-point operations, visiting tied feature values in row order.
presort=False(onRDT,RandomForestRDT,GradientBoostingRDT) selects the per-node sort, e.g. to avoid the presort's memory. - Memory while fitting: the presorted orders take 4 bytes per (row,
sorted column) — Gradient Boosting sorts a column the first time a round
draws it and keeps it until
fitreturns; Random Forest sorts each tree's own sample and column subset — plus about 48 bytes per row of search state, and sorting needs a transient buffer of 16 bytes per row for each of up to 8 columns sorted together (at mostn_features / 4). This is less than v0.2.0's per-tree copies ofXfor the ensembles on wide data, but more than v0.2.0 for a single tree on tall, narrow data (e.g. 2,000,000 x 4: about +190 MB vs +100–130 MB) and for boosting with few rounds and a smallcolsample_bytree;presort=Falseavoids it. - No copies of
X: the ensembles fit and predict through each tree's rows / column subset in place. - GIL:
fit,predictandpredict_probarelease the GIL while the native code runs, so other Python threads (including other fits) keep running. One native model object (rdt._rdt_native.*Model) may serve severalpredictcalls at once, but callingfiton it while it is in use by another thread (or using it while it is being fitted) raisesRuntimeError; therdtestimators fit a fresh native object and publish it only once fitted, so this does not affect them. The nativepredict/predict_probanow return NumPy arrays (they returned lists). n_jobsparallelizes across trees: Random Forest trees, and a multiclass boosting round's per-class trees. Binary / regression boosting fits one tree per round; withtree_method="hist"that tree scores its features in parallel. Gradient boosting also runs each round's per-row passes (gradients, routing rows to the new leaves, score updates, losses, theeval_setrows' scores) and its predictions in row blocks on then_jobsthreads. No result depends onn_jobs: per-row work is independent, and sums are taken in row order.
How it works
For each depth, every candidate feature is scored across all currently active nodes at that depth (summing each node's best achievable impurity improvement for that feature, weighted by the node's share of the training samples); the feature with the highest total wins and is locked in for the whole depth, and the per-node thresholds already found while scoring it are reused directly as the final split points:
For each depth level:
1. For every candidate feature:
for every active node at this depth:
find that node's best threshold for this feature
total_improvement = sum over nodes of
(node samples / total samples) * node improvement
2. Choose the feature with the highest total_improvement; lock it in.
3. Split every node at this depth using the threshold already found
for the winning feature (a node that feature cannot split becomes a leaf).
The weighted per-node term is scikit-learn's weighted impurity decrease, so a
max_depth=1 RDT is the same stump as DecisionTreeClassifier/Regressor,
and depth_improvements_/feature_importances_ are scikit-learn-style
impurity-based (MDI) importances. See docs/ALGORITHM_DETAILS.md for details,
including why a node the depth's feature cannot split is not deferred to a
later depth.
Package structure
restricted-decision-trees/
├── README.md
├── LICENSE
├── pyproject.toml, CMakeLists.txt # nanobind + scikit-build-core build
├── core/include/rdt/ # header-only C++ algorithm core
├── src/bindings.cpp # nanobind bindings
├── python/rdt/
│ ├── __init__.py # RDT (sklearn-style wrapper)
│ ├── ensemble.py # RandomForestRDT / GradientBoostingRDT
│ └── visualize.py # print_tree / D3 viz / JSON export
├── tests/ # pytest unit tests
└── examples/
├── basic_usage.py
├── ensemble_usage.py
└── d3_visualization_demo.py
License
GPL-3.0 — see LICENSE.
Metadata
Release files for restricted-decision-trees 0.3.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 | |
|---|---|---|---|
| restricted_decision_trees-0.3.0.tar.gz | 213.8 kB | Details |
Built distributions (wheels)
Total release size: 3.9 MB
Release files / restricted_decision_trees-0.3.0.tar.gz
| Download URL | restricted_decision_trees-0.3.0.tar.gz |
|---|---|
| Size | 213.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cb6dd0bdace97f10f46532a8966e523e108f130e219d257744d85ed9a46715c8
|
|
BLAKE2b-256 checksum How to use checksums |
d87291745d924e7fb075c1562e9e1998b93c722975d8afa4256baed671fcbce8
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp313-cp313-win_amd64.whl
| Download URL | restricted_decision_trees-0.3.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 284.1 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
64e99c15480935735fb01a269cabe48326cde413b4abf9f744a28dcfa0ae03ec
|
|
BLAKE2b-256 checksum How to use checksums |
61b2d603fbb8e7cfe36f76c2063f7e8af8712b573a8466e22b9e3a8bedfb459e
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | restricted_decision_trees-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 369.4 kB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
64e39130bcd57d3a73a9745173f0a0a5a6c200f6f30cef70fb3d6268d8733f4f
|
|
BLAKE2b-256 checksum How to use checksums |
929ba399e9cdfdf6650ba74f42b2b8cfb1572b4ca6270cc27f5dd28ab85daae3
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | restricted_decision_trees-0.3.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 269.5 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
2c39e7d11a470a3cd34552685ba5e149b66c3205b099a935afa3e1d39b965239
|
|
BLAKE2b-256 checksum How to use checksums |
14cab6ca3a2fe18ce18daa5241acaa0f4fcad51372e9a1b1ebebdb3560718d02
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp312-cp312-win_amd64.whl
| Download URL | restricted_decision_trees-0.3.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 284.2 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7f084ae46eb80ecd2668a727b07f65b93f9ce175d70f7a982f7abf1d547ba40f
|
|
BLAKE2b-256 checksum How to use checksums |
9a0e83a77159981dd53315ded5a19ed2503a5ba14f280225cc6860f4b040dae9
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | restricted_decision_trees-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 369.4 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
d31fc46239b4afbbc42f210da9f9230d219059215b13d3b3eec58bcff71b8007
|
|
BLAKE2b-256 checksum How to use checksums |
e1cb9d1aa02f368ec0edfb9f0396a489455efa08fe2a16366fe5479ec73ffedd
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | restricted_decision_trees-0.3.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 269.5 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
15c58962e8cf15162a7b0026adc1da1abd277482465c07ef4ea53fd19c2edb13
|
|
BLAKE2b-256 checksum How to use checksums |
aded01d6378c52d70bf3253447527bfe9cc34e9937e23fd6a64ea057397e03ae
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp311-cp311-win_amd64.whl
| Download URL | restricted_decision_trees-0.3.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 284.4 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
e345ca7f961ea8e93f90489a1d2d0a6ab1bff95a2b6b8a6deba94dd1ea0f2dc1
|
|
BLAKE2b-256 checksum How to use checksums |
9ebc067be8da571590727bb37ee174b23bbf20fa1a2de1e4817e5e1e9b263843
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | restricted_decision_trees-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 369.9 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
0d13886aa8e7d3c83fd18bf23c86b5ddab14d6c190ced52711518e556ef22580
|
|
BLAKE2b-256 checksum How to use checksums |
e60fd44413fd3e0b88959a749b636ec9304378040433e52e19ddd3ea24712ace
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | restricted_decision_trees-0.3.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 270.2 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
36a5c82a756986a34a21a1af4c6db89f79aa844bffc1dfc6a4772d3fc9073f0b
|
|
BLAKE2b-256 checksum How to use checksums |
50e0e782ac517eaf2e4a824d51b960cf9e1defe48a85da4fb10d232e6fb74534
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp310-cp310-win_amd64.whl
| Download URL | restricted_decision_trees-0.3.0-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 284.1 kB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
f82994df503528a68f28baaf4115b9b3a126a9e162b17fd5650ba04b73eff9d2
|
|
BLAKE2b-256 checksum How to use checksums |
abe35c0973fbec09ebba15b54b6b3b57540c81284546414bb19fc4102544ff23
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | restricted_decision_trees-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 370.1 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
6223fba8072a0682bb7f98b5763cb02d1decc9da29f01a122b9ebafde511def7
|
|
BLAKE2b-256 checksum How to use checksums |
87a0d0ae3a43c7f01620097c5ac0e8a8d6b136faf055646c26b30831162f55bf
|
| 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 Oct 18, 2026.
Transparency logRelease files / restricted_decision_trees-0.3.0-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | restricted_decision_trees-0.3.0-cp310-cp310-macosx_11_0_arm64.whl |
|---|---|
| Size | 270.5 kB |
| Tags | CPython 3.10 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
5d1343861b9f34de817e26bbd833a244148f2b89ee1af5a3e658e14961ec1cef
|
|
BLAKE2b-256 checksum How to use checksums |
faa7dcbef1a4ceb6276c28f6fb7f43b871a381cd5e06876a106b7218279c7f8d
|
| 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 Oct 18, 2026.
Transparency log