vike-study
The study runner that can fit — the one host that hands a compiled study a real LightGBM learner and leaves a run behind.
vike-study runs one compiled user study over the hist store and leaves a run behind. It is
the study runner that can fit: until it existed, nothing in the workspace could fit a compiled
study at all.
vike-study --study NAME --recipe FILE --from WHEN --to WHEN --store DIR
[--lightgbm PATH] [--scratch DIR] [--runs-root DIR]
--study NAME the study's folder name under user_data/research/studies/rust/
--recipe FILE its .toml recipe; an optional [learner] table carries the GBDT base params
--from/--to WHEN YYYY-MM-DD, YYYY-MM-DDTHH, or bare unix SECONDS
--store DIR the hist store root the study reads
--lightgbm PATH the PINNED trainer. Omitted, the study runs with NO learner and a fitting
study refuses BY NAME rather than silently producing nothing
--scratch DIR per-fit scratch root (default: <runs-root>/../scratch/vike-study — the
project folder, never the system temp directory)
--runs-root DIR where the run is minted (default: user_data/runs)The bin (crates/vike-studio-core/src/bin/vike-study.rs) is a thin shim over
vike_studio_core::study_cli::run, which reads no environment and no std::env::args() — argv is
a parameter, which is what lets the vike multicall dispatcher own the process's single sweep and
link one copy of the DataFusion closure for every tool.
The gap it closes
The one caller of run_study_plan was the Studio, whose ML surface is the
inference half only (crates/vike-studio-core/src/ml.rs). It supplies no learner, so a study
needing a gradient-boosted classifier per fold answered StudyError::NoLearner on every
invocation — "the documented ceiling of a host with no LightGBM binary". That left the whole
compiled tier unrunnable in practice rather than in principle. This binary is the host that
supplies a learner.
--lightgbm names the pinned trainer. Omit it and the study runs with no learner, and a
fitting study refuses by name rather than silently producing nothing — the distinction between "you
did not give me a trainer" and "there was nothing to find" is the whole point of the refusal.
Why a feature-gated bin in this crate
The host must sit strictly above run_study_plan (layer 55) and be able to construct a
DataFusionHist. vike-cli (layer 65) clears the layer bound, but that
crate is deliberately light: it reaches history through vike-datahub-client, over the wire,
and names no DataFusion at all — the light-consumers CI lane compiles it as the
default-features-off consumer precisely to keep that true. Buying a verb with that property would
have charged every vike-cli user for it.
A bin beside the function it calls costs nothing instead. study-cli is off by default, so the
studio-standalone lane's structural check — the default normal-dep tree must carry no DataFusion
crate — is untouched, and required-features = ["study-cli"]
(crates/vike-studio-core/Cargo.toml) keeps this bin out of every build that has not asked for it.
It is not in a default build
vike-study is a required-features bin. A plain cargo build of the workspace does not produce
it, and the release ships only the configurations its workflow names. Build it with the
study-cli feature enabled, or reach it through the vike dispatcher built with that feature.
The [learner] table
A study's LightGBM base parameters are a property of the study — the non-searched keys its
published results were measured at — while vike_ml::GbdtLearner knows nothing about any study and
the host is what constructs it. In the cohort study those keys had no home a host could reach: they
lived in a #[cfg(test)] module whose own doc called itself "their only home", so any real run
would have used LightGBM's defaults and quietly fitted a different model than the one every
recorded number came from.
So the recipe carries them, under [learner], and this binary reads that table. The recipe is
already the study's own file and already travels with it; the study itself ignores the table (its
reader is lenient, as the strategy tier's is); and nothing study-specific is compiled into this
generic verb. An absent key keeps LightGBM's default, so a partial table is meaningful rather than
an error — a study that fixes two of five says exactly that (study_cli::learner_params). An
absent table means GbdtParams::default(), which is the right answer for a caller with no opinion
and the wrong one to assume in silence, so the run prints which of the two it used.
Time boundaries
--from and --to accept YYYY-MM-DD, YYYY-MM-DDTHH, or bare unix seconds, converted to
the epoch milliseconds TsRange speaks (study_cli::boundary_ms). Seconds are accepted because
every anchor in the cohort study's own record is written that way, and re-deriving one by hand is
how a window silently moves.
--scratch defaults to a directory under the runs root's parent — the project folder, never the
system temp directory — so a per-fit scratch tree lands beside everything else the program writes.
Related: research, running a study, Rust studies, training, the fit cache, runs on disk.