gboostrs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ testpypi:
9
+ description: "Upload to TestPyPI instead of PyPI"
10
+ type: choice
11
+ options: ["false", "true"]
12
+ default: "false"
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: release-${{ github.ref }}
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build:
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ include:
27
+ - { label: linux-x86_64, runner: ubuntu-latest, python: "3.11" }
28
+ - { label: linux-x86_64, runner: ubuntu-latest, python: "3.12" }
29
+ - { label: linux-x86_64, runner: ubuntu-latest, python: "3.13" }
30
+ - { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.11" }
31
+ - { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.12" }
32
+ - { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.13" }
33
+ - { label: macos-arm64, runner: macos-latest, python: "3.11" }
34
+ - { label: macos-arm64, runner: macos-latest, python: "3.12" }
35
+ - { label: macos-arm64, runner: macos-latest, python: "3.13" }
36
+ - { label: windows-x86_64, runner: windows-latest, python: "3.11" }
37
+ - { label: windows-x86_64, runner: windows-latest, python: "3.12" }
38
+ - { label: windows-x86_64, runner: windows-latest, python: "3.13" }
39
+ runs-on: ${{ matrix.runner }}
40
+ env:
41
+ CARGO_TERM_COLOR: always
42
+ steps:
43
+ - uses: actions/checkout@v7
44
+
45
+ - uses: actions/setup-python@v7
46
+ with:
47
+ python-version: ${{ matrix.python }}
48
+
49
+ - uses: dtolnay/rust-toolchain@stable
50
+
51
+ - name: Cache cargo
52
+ uses: actions/cache@v6
53
+ with:
54
+ path: |
55
+ ~/.cargo/registry
56
+ ~/.cargo/git
57
+ target
58
+ key: ${{ runner.os }}-${{ matrix.label }}-cargo-${{ hashFiles('**/Cargo.lock') }}
59
+ restore-keys: |
60
+ ${{ runner.os }}-${{ matrix.label }}-cargo-
61
+
62
+ - name: Install maturin
63
+ run: python -m pip install -U pip maturin
64
+
65
+ - name: Build wheel
66
+ shell: bash
67
+ run: |
68
+ maturin build --release --manifest-path py-gboost/Cargo.toml -o wheels
69
+
70
+ - uses: actions/upload-artifact@v7
71
+ with:
72
+ name: wheels-${{ matrix.label }}-py${{ matrix.python }}
73
+ path: wheels/*.whl
74
+ if-no-files-found: error
75
+
76
+ sdist:
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v7
80
+
81
+ - uses: dtolnay/rust-toolchain@stable
82
+
83
+ - name: Install maturin
84
+ run: python3 -m pip install -U pip maturin
85
+
86
+ - name: Build sdist
87
+ run: maturin sdist --manifest-path py-gboost/Cargo.toml --out dist
88
+
89
+ - uses: actions/upload-artifact@v7
90
+ with:
91
+ name: sdist
92
+ path: dist/*.tar.gz
93
+ if-no-files-found: error
94
+
95
+ publish:
96
+ needs: [build, sdist]
97
+ runs-on: ubuntu-latest
98
+ if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
99
+ steps:
100
+ - uses: actions/download-artifact@v8
101
+ with:
102
+ pattern: "wheels-*"
103
+ path: dist
104
+ merge-multiple: true
105
+
106
+ - uses: actions/download-artifact@v8
107
+ with:
108
+ name: sdist
109
+ path: dist
110
+ merge-multiple: true
111
+
112
+ - name: List artifacts
113
+ run: ls -la dist
114
+
115
+ - uses: actions/setup-python@v7
116
+ with:
117
+ python-version: "3.13"
118
+
119
+ - name: Install twine
120
+ run: python -m pip install -U pip twine
121
+
122
+ - name: Check with twine
123
+ run: twine check dist/*
124
+
125
+ - name: Upload to TestPyPI
126
+ if: inputs.testpypi == 'true'
127
+ env:
128
+ TWINE_USERNAME: __token__
129
+ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
130
+ run: twine upload --repository-url https://test.pypi.org/legacy/ dist/*
131
+
132
+ - name: Upload to PyPI
133
+ if: inputs.testpypi != 'true'
134
+ env:
135
+ TWINE_USERNAME: __token__
136
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
137
+ run: twine upload dist/*
@@ -0,0 +1,4 @@
1
+ Cargo.lock
2
+ target
3
+ .venv
4
+ __pycache__
@@ -0,0 +1,34 @@
1
+ [package]
2
+ name = "gboost"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ rust-version = "1.85"
6
+ description = "Gradient boosting library in Rust"
7
+ license = "MIT"
8
+ repository = "https://github.com/rseko/gboost"
9
+ keywords = ["gradient-boosting", "machine-learning", "decision-tree"]
10
+ categories = ["algorithms", "science"]
11
+ readme = "README.md"
12
+
13
+ [features]
14
+ large-index = []
15
+
16
+ [dependencies]
17
+ anyhow = "1.0"
18
+ itertools = "0.15"
19
+ num-traits = "0.2"
20
+ rand = "0.10"
21
+ rayon = "1.12"
22
+ serde = { version = "1.0", features = ["derive"] }
23
+ serde_json = { version = "1.0", features = ["preserve_order"] }
24
+ thiserror = "2.0"
25
+ zerocopy = { version = "0.8", features = ["std"] }
26
+
27
+ [dev-dependencies]
28
+ criterion = "0.8"
29
+ half = { version = "2.7", features = ["num-traits"] }
30
+ tempfile = "3"
31
+
32
+ [[bench]]
33
+ name = "benchmark"
34
+ harness = false
gboostrs-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey Kozub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: gboostrs
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Requires-Dist: numpy
8
+ Summary: Python bindings for the gboost gradient boosting library
9
+ Keywords: gradient-boosting,machine-learning,decision-tree
10
+ Author: rseko
11
+ License-Expression: MIT
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
+ Project-URL: Repository, https://github.com/rseko/gboost
15
+
16
+ # py-gboost
17
+
18
+ Python bindings for the [`gboost`](../README.md) gradient-boosting library.
19
+
20
+ It exposes a single `GradientBooster` class backed by the Rust core, with a
21
+ sklearn-like `fit(x, y)` / `predict(x)` API over NumPy `float32` arrays. All
22
+ training work (histogram binning, split search, per-batch update) runs in Rust;
23
+ `fit` and `fit_next` release the GIL while the booster grows.
24
+
25
+ ## Installation
26
+
27
+ `py-gboost` is a [maturin](https://www.maturin.rs) package; it builds the Rust
28
+ extension and installs it as the importable module `gboostrs`.
29
+
30
+ ```bash
31
+ # From a release build
32
+ pip install .
33
+
34
+ # Or, for in-place development
35
+ pip install maturin
36
+ maturin develop
37
+ ```
38
+
39
+ Requires Python 3.9+. The import name is `gboostrs`:
40
+
41
+ ```python
42
+ import gboostrs
43
+ gboostrs.__version__
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```python
49
+ import numpy as np
50
+ from gboostrs import GradientBooster
51
+
52
+ rng = np.random.default_rng(1)
53
+ x = rng.standard_normal((100, 5)).astype(np.float32) # (rows, cols)
54
+ y = rng.standard_normal(100).astype(np.float32) # one value per row
55
+
56
+ # Regression
57
+ gb = GradientBooster(objective="squaredloss", iterations=100)
58
+ gb.fit(x, y)
59
+ scores = gb.predict(x) # np.ndarray of float32, one score per row
60
+ gb.loss() # mean loss of the current predictions vs. the labels
61
+
62
+ # Binary classification (labels in {0, 1})
63
+ y = rng.integers(0, 2, 100).astype(np.float32)
64
+ gb = GradientBooster(objective="logloss", iterations=100)
65
+ gb.fit(x, y)
66
+ ```
67
+
68
+ ### Growing round by round
69
+
70
+ `fit(x, y)` trains up to `iterations` rounds and replaces any previously
71
+ trained model. `fit_next()` grows one additional round on the existing model
72
+ and returns `False` when a round produced no tree (no gain) — the natural
73
+ early-stopping signal.
74
+
75
+ ```python
76
+ gb = GradientBooster(objective="squaredloss", iterations=0)
77
+ gb.fit(x, y) # fit(0) builds the booster without growing trees
78
+
79
+ while gb.fit_next():
80
+ print(gb.loss())
81
+ ```
82
+
83
+ ## `GradientBooster`
84
+
85
+ All parameters are keyword-only and have defaults, so `GradientBooster()` is a
86
+ valid, fully-configured instance.
87
+
88
+ | Parameter | Default | Description |
89
+ | -------------------- | :-----------: | ------------------------------------------------------------------------------- |
90
+ | `iterations` | `100` | Maximum boosting rounds run by `fit`. |
91
+ | `objective` | `"squaredloss"` | Loss to minimize: `"squaredloss"` (regression) or `"logloss"` (classification). |
92
+ | `base_score` | `0.0` | Initial constant prediction before any tree is added. |
93
+ | `max_bins` | `255` | Maximum number of quantization bins per column. |
94
+ | `persist_path` | `None` | Directory for on-disk artifacts, if persistence is used. |
95
+ | `parallel` | `True` | Enable the parallel (Rayon) execution paths. |
96
+ | `seed` | `0` | Seed for reproducible random draws (subsampling). |
97
+ | `learning_rate` | `0.3` | Per-tree update step size. |
98
+ | `subsample` | `1.0` | Row subsampling fraction per tree. |
99
+ | `colsample_bytree` | `1.0` | Per-tree feature subsampling fraction. |
100
+ | `colsample_bylevel` | `1.0` | Per-level feature subsampling fraction. |
101
+ | `colsample_bynode` | `1.0` | Per-node feature subsampling fraction. |
102
+ | `max_depth` | `6` | Maximum tree depth (`0` = no limit). |
103
+ | `max_leaves` | `0` | Maximum number of leaves (`0` = no limit). |
104
+ | `grow_policy` | `"depthwise"` | Tree growth policy: `"depthwise"` or `"lossguide"`. |
105
+ | `reg_alpha` | `0.0` | L1 regularizer on leaf weights. |
106
+ | `reg_lambda` | `1.0` | L2 regularizer on leaf weights. |
107
+ | `min_split_gain` | `0.0` | Minimum gain required to take a split. |
108
+ | `min_child_weight` | `1.0` | Minimum hessian sum allowed in a leaf. |
109
+ | `allow_missing_splits` | `False` | Whether missing values may drive their own split. |
110
+ | `onehot_columns` | `[]` | Indices of columns to treat as one-hot categoricals (exact-bin splits). |
111
+
112
+ ### Methods
113
+
114
+ - **`fit(x, y) -> None`** — Train the booster. `x` is a 2-D `float32` matrix
115
+ of shape `(rows, cols)`; `y` is a 1-D `float32` target of length `rows`.
116
+ Replaces any previously trained model.
117
+ - **`fit_next() -> bool`** — Grow one additional round on the trained model.
118
+ Returns `False` when the round produced no tree (no gain).
119
+ - **`predict(x) -> np.ndarray`** — Score `x` (shape `(rows, cols)`, same
120
+ column count as training) with the trained model; returns one `float32`
121
+ score per row.
122
+ - **`loss() -> float | None`** — Mean loss of the current predictions against
123
+ the training labels, or `None` before `fit`.
124
+ - **`get_params() -> dict`** — Return the constructor kwargs as a dict of
125
+ `name -> value`; `GradientBooster(**gb.get_params())` reconstructs an
126
+ equivalent instance.
127
+
128
+ ## Notes
129
+
130
+ - **Zero-copy inputs.** The Rust core works on column-major (Fortran-order)
131
+ arrays. Pass `x` as `np.asfortranarray(x)` to avoid a copy; row-major or
132
+ strided inputs are transposed into column-major internally.
133
+ - **Error types.** I/O failures (e.g. an invalid `persist_path`) raise
134
+ `IOError`; other problems (an untrained model, a `NaN` in the target, a
135
+ column-count mismatch in `predict`, an invalid `objective`/`grow_policy`)
136
+ raise `ValueError`.
137
+ - **`onehot_columns`.** Columns listed here are split on an exact bin (`==`)
138
+ rather than an ordered threshold (`<=`), so high-cardinality categoricals
139
+ are first-class. Other columns are treated as linear (ordered) features.
140
+ - **Determinism.** With a fixed `seed` and `subsample`, a run is reproducible.
141
+
142
+ ## Tests
143
+
144
+ The test suite exercises the binding end-to-end (regression, classification,
145
+ `fit_next`, and error paths):
146
+
147
+ ```bash
148
+ pip install pytest
149
+ pytest tests/
150
+ ```
151
+
@@ -0,0 +1,224 @@
1
+ # gboost
2
+
3
+ A gradient-boosting library in Rust.
4
+
5
+ The distinguishing feature of `gboost` is out-of-core learning: the dataset is
6
+ staged to disk and each boosting round streams it through the gradient/hessian
7
+ computation, keeping only a subsample of rows in RAM — not the whole table.
8
+ You can train on tens of billions of rows on a machine that could never hold
9
+ the full columnar data at once.
10
+
11
+ It also works in-memory (fully columnar, histogram-binned, optionally parallel
12
+ via `rayon`) for datasets that do fit in RAM, behind the same object-safe
13
+ [`Booster`] trait.
14
+
15
+ `gboost` follows the XGBoost model: the same histogram-based boosting
16
+ algorithm, the same gradient/hessian leaf-value math, and training parameters
17
+ that mirror XGBoost's names (`learning_rate`, `subsample`, `colsample_bytree`,
18
+ `max_depth`, `min_split_gain`, `reg_alpha` / `reg_lambda`, …). If you know
19
+ XGBoost, the options will feel familiar — the main differences are the Rust
20
+ columnar API, a couple of gboost-specific options, and the out-of-core path.
21
+
22
+ ## Features
23
+
24
+ - **Out-of-core training** — stage columns to disk, then grow trees by
25
+ streaming the dataset with only a subsample of rows held in RAM, so memory
26
+ stays bounded regardless of dataset size. See [`ModelInit`] and
27
+ `BoostModel::init_large`.
28
+ - **One-hot (categorical) features** — a column can split on an exact bin
29
+ (`==`) rather than an ordered threshold (`<=`), so high-cardinality
30
+ categoricals are a first-class feature kind, not just `Linear` splits.
31
+ - **Half-precision floats** — the feature type `F` is generic over any
32
+ `QuantFloat`, so `f16` and `bf16` (from the `half` crate) are accepted in
33
+ addition to `f32` input type.
34
+ - **Histogram binning** — values are quantized into a bounded number of bins
35
+ (`max_bins` up to 65535), so split search scales with the bin count, not the
36
+ raw data.
37
+ - **Parallel training** — split search, histogram building and the per-batch
38
+ update pass run across threads via `rayon` (toggle with `set_parallel`).
39
+ - **Deterministic** — a single `seed` drives all random draws (subsampling),
40
+ so runs are reproducible.
41
+
42
+ ## Installation
43
+
44
+ ```toml
45
+ [dependencies]
46
+ gboost = "0.1"
47
+ ```
48
+
49
+ MIT-licensed. Edition 2024 requires Rust 1.85+.
50
+
51
+ ### Crate features
52
+
53
+ | Feature | Default | Description |
54
+ | ------------- | :-----: | ------------------------------------------------------------------ |
55
+ | `large-index` | no | Allow training targets with more than `u32::MAX` rows (`u64` row indices). |
56
+
57
+ ## Training on data that doesn't fit in RAM
58
+
59
+ This is the primary use case. The flow is:
60
+
61
+ 1. **Stage** the dataset to a persistence directory: for each feature column,
62
+ fit its quantizer and write the bin indices to `feature.<name>`; write the
63
+ target to `target`; the feature headers are appended to `model.txt`.
64
+ 2. **Build** a booster over that on-disk dataset with `BoostModel::init_large`.
65
+ Rows are *not* loaded wholesale — only a subsample of rows is kept in RAM,
66
+ and the first pass streams the full dataset to seed the per-row
67
+ gradient/hessian index.
68
+ 3. **Fit** one or more rounds. Each `fit_next` round takes the streaming
69
+ `update_large` path: it walks the on-disk batches, folds the new tree's
70
+ leaf values into the running predictions, and recomputes the derivatives.
71
+
72
+ The persistence path is mandatory on this route — set it with
73
+ `Options::set_persist_path`.
74
+
75
+ ```rust
76
+ use gboost::boost_model::{BoostModel, ModelInit};
77
+ use gboost::dataset::{SquaredLoss, FeatureKind};
78
+ use gboost::options::Options;
79
+ use std::path::PathBuf;
80
+
81
+ let persist_path = PathBuf::from("/tmp/gboost");
82
+ let options = Options::default()
83
+ .set_persist_path(Some(persist_path.clone()))
84
+ .set_max_bins(255)
85
+ .set_subsample(0.5)
86
+ .set_seed(42)
87
+ .set_parallel(true);
88
+
89
+ // 1. Stage columns to disk. Each column is fit, quantized, and written.
90
+ let mut init = ModelInit::new(persist_path.clone(), 255)?;
91
+ for (name, column) in [
92
+ ("age", &[/* one f32 per row */]),
93
+ ("income", &[/* ... */]),
94
+ ] {
95
+ init.write_feature::<u8, _>(name, FeatureKind::Linear, column)?;
96
+ }
97
+ init.write_target(&target)?; // one f32 per row
98
+ let _features = init.finish(); // get bin thresholds data
99
+
100
+ // 2. Build the booster over the on-disk dataset.
101
+ let mut model = BoostModel::<u8, u32, SquaredLoss>::init_large(options)?;
102
+
103
+ // 3. Grow rounds; each streams batches from disk.
104
+ model.fit_next()?;
105
+ model.fit_next()?;
106
+ ```
107
+
108
+ > **Very wide datasets.** If the row count exceeds `u32::MAX`, enable the
109
+ > `large-index` cargo feature and instantiate with a `u64` index
110
+ > (`BoostModel::<u8, u64, SquaredLoss>`).
111
+ >
112
+ > **Many columns on multi-core machines.** In the parallel update path, each
113
+ > rayon worker holds one open file handle per feature column (plus the target
114
+ > and prediction files). With `T` threads and `C` columns this is roughly
115
+ > `T × (C + 2)` open descriptors at once, so a wide dataset on a many-core
116
+ > system can exhaust the process or system file-descriptor limit
117
+ > (`EMFILE`/`ENFILE`). Raise `ulimit -n`, or cap the worker count
118
+ > (e.g. via `RAYON_NUM_THREADS`) if you hit this.
119
+
120
+ ## Training in-memory
121
+
122
+ For data that fits in RAM, use the `Booster` trait: build a boxed, object-safe
123
+ booster over in-memory feature columns, grow it, and predict. `inputs` holds
124
+ one column per feature, in feature order; `target` has one value per row.
125
+
126
+ ```rust
127
+ use gboost::boost_api::{new_booster, Booster};
128
+ use gboost::options::{ObjectiveKind, Options};
129
+
130
+ let inputs: Vec<Vec<f32>> = vec![/* one column: one f32 per row */; /* n_features */];
131
+ let cols = inputs.iter().map(|c| c.as_slice()).collect::<Vec<_>>();
132
+ let target: Vec<f32> = vec![/* per-row */];
133
+
134
+ let options = Options::default()
135
+ .set_objective(ObjectiveKind::SquaredLoss)
136
+ .set_learning_rate(0.1);
137
+
138
+ let mut booster = new_booster::<f32>(options, &cols, &target)?;
139
+
140
+ // Grow up to 50 rounds; stops early if a round adds no tree.
141
+ booster.fit(50)?;
142
+
143
+ let predictions = booster.predict(&cols)?;
144
+ let per_row_loss = booster.loss();
145
+ ```
146
+
147
+ ### Growing round by round
148
+
149
+ `fit` is a convenience wrapper over `fit_next`. Each call to `fit_next`
150
+ grows one tree and folds it into the ensemble. To control the stopping
151
+ condition yourself, loop until it reports no gain:
152
+
153
+ ```rust
154
+ use gboost::boost_api::Booster;
155
+
156
+ while booster.fit_next()? {
157
+ // keep boosting
158
+ }
159
+ ```
160
+
161
+ `fit_next` returns `Ok(false)` when a round produced no tree (no gain), which
162
+ is the natural early-stopping signal.
163
+
164
+ ### Explicit feature descriptors
165
+
166
+ `new_booster` synthesizes `linear` features named by position. To supply your
167
+ own names and kinds, use `new_with_features`; bin boundaries are still refit
168
+ from the inputs. Each column is either `linear` (ordered threshold splits) or
169
+ `onehot` (exact-bin splits for categoricals).
170
+
171
+ ```rust
172
+ use gboost::boost_api::{new_with_features, Booster};
173
+ use gboost::dataset::Feature;
174
+ use gboost::options::Options;
175
+
176
+ let inputs: Vec<Vec<f32>> = vec![/* one column per feature */];
177
+ let cols = inputs.iter().map(|c| c.as_slice()).collect::<Vec<_>>();
178
+ let target: Vec<f32> = vec![/* per-row */];
179
+
180
+ let options = Options::default().set_learning_rate(0.1);
181
+
182
+ let features = vec![
183
+ Feature::linear("age", None), // ordered threshold
184
+ Feature::onehot("country", None), // exact-bin categorical
185
+ ];
186
+ let mut booster = new_with_features::<f32>(options, &cols, &target, features)?;
187
+ booster.fit(50)?;
188
+ ```
189
+
190
+ ## Options
191
+
192
+ `Options` aggregates booster-, tree-, and node-level settings. Parameter names
193
+ and defaults mirror XGBoost, plus a couple of gboost-specific options. Every
194
+ field is reachable through self-consuming `set_*` methods, so a fully
195
+ configured value is built in one chain from `Options::default()`:
196
+
197
+ ```rust
198
+ let options = Options::default()
199
+ .set_objective(ObjectiveKind::LogLoss) // classification
200
+ .set_base_score(0.5)
201
+ .set_learning_rate(0.1)
202
+ .set_max_depth(10) // 0 = no limit
203
+ .set_max_leaves(100) // 0 = no limit
204
+ .set_allow_missing_splits(true);
205
+ ```
206
+
207
+ Options round-trip to a portable JSON config:
208
+
209
+ ```rust
210
+ let json = options.to_json(); // defaults and `persist_path` are pruned
211
+ let restored = Options::from_json(json)?;
212
+ ```
213
+
214
+ `persist_path` is machine-local and is never serialized; re-supply it via
215
+ `set_persist_path` after `from_json`.
216
+
217
+ ## Python bindings
218
+
219
+ A Python package is provided under [`py-gboost/`](py-gboost/). See that
220
+ directory for its own setup and tests.
221
+
222
+ ## License
223
+
224
+ MIT — see [LICENSE](LICENSE).