ML Data Loading¶
The loader consumes normalized manifests, preserves stream provenance, and materializes one-row-ahead targets. Build an index separately when several loaders or tools need to share the same validated split assignments.
Null input values are materialized as the -2.0 sentinel. Null targets remain
NaN and are excluded by finite-target loss masking, while the batch mask records
source-row availability.
Sequential loading traverses requested protocols in order. Shuffled protocol
groups support cycling, HPPC, and RPT, but not EIS. Finite stateful groups discard
a final segment shorter than stateful_n_windows; whole-stream batches also
truncate longer lanes to the shortest lane.
available_manifest_paths
¶
List canonical normalized manifests available in a store.
Parameters:
-
store(DatasetStoreReader) –Reader whose files should be searched.
Returns:
-
str–Sorted canonical paths matching
-
...–type=*/dataset=*/source=normalized/manifest.parquet.
create_dataloader
¶
create_dataloader(
store: DatasetStoreReader,
manifest_paths: ManifestPaths,
input_columns: tuple[str | MappingSpec, ...],
target_columns: tuple[str | MappingSpec, ...],
protocols: tuple[object, ...] | None = None,
protocol_mode: ProtocolMode = "strict",
validation: ValidationConfig | None = None,
scaling: tuple[ScalingRule, ...] = (),
config: LoaderConfig | None = None,
) -> DataLoader[Batch] | DevicePrefetchDataLoader
Build an ML index and create a protocol-aware batch loader.
This is the normal programmatic data entry point. It validates normalized manifests and revisions, assigns group-aware splits, plans windows, applies scaling, and materializes one-row-ahead targets.
Parameters:
-
store(DatasetStoreReader) –Reader for normalized manifests and parquet segments.
-
manifest_paths(ManifestPaths) –Manifest paths mapped to expected Git commits or prefixes.
-
input_columns(tuple[str | MappingSpec, ...]) –Ordered model input columns.
-
target_columns(tuple[str | MappingSpec, ...]) –Ordered model target columns.
-
protocols(tuple[object, ...] | None, default:None) –Optional protocols to retain.
-
protocol_mode(ProtocolMode, default:'strict') –Missing-protocol behavior across selected datasets.
-
validation(ValidationConfig | None, default:None) –Group-aware split policy.
-
scaling(tuple[ScalingRule, ...], default:()) –Runtime scaling rules. Tensor channel order follows selected columns, not rule declaration order.
-
config(LoaderConfig | None, default:None) –Windowing, planning, IO, multiprocessing, and device options.
Returns:
-
DataLoader[Batch] | DevicePrefetchDataLoader–A PyTorch loader yielding
Batch, or a CUDA-prefetch wrapper when -
DataLoader[Batch] | DevicePrefetchDataLoader–prefetch_to_deviceis enabled.
Raises:
-
ValueError–If manifests, columns, scaling, loader strategy, or device settings are incompatible.
Examples:
Create a deterministic CPU loader:
from batgrad.ml.data.config import LoaderConfig, WindowConfig
from batgrad.ml.data.loader import create_dataloader
loader = create_dataloader(
store,
{"type=public/dataset=example/source=normalized/manifest.parquet": "abc1234"},
input_columns=("voltage", "current"),
target_columns=("voltage",),
config=LoaderConfig(
strategy="sequential",
default_window=WindowConfig(batch_size=4, seq_len=128),
),
)
batch = next(iter(loader))
Note
windowed access minimizes memory and performs parquet reads per batch.
full_in_mem can be substantially faster but caches selected streams as
CPU float32 tensors and requires num_workers=0.
create_index
¶
create_index(
store: DatasetStoreReader,
manifest_paths: ManifestPaths,
protocols: tuple[object, ...] | None = None,
protocol_mode: ProtocolMode = "strict",
validation: ValidationConfig | None = None,
) -> MlDatasetIndex
Build the reusable index consumed by create_dataloader_from_index.
Parameters:
-
store(DatasetStoreReader) –Reader for normalized manifests.
-
manifest_paths(ManifestPaths) –Manifest paths mapped to expected Git revisions.
-
protocols(tuple[object, ...] | None, default:None) –Optional protocols to retain.
-
protocol_mode(ProtocolMode, default:'strict') –Strict or best-available protocol selection.
-
validation(ValidationConfig | None, default:None) –Group-aware split policy.
Returns:
-
MlDatasetIndex–A validated ML dataset index.
create_dataloader_from_index
¶
create_dataloader_from_index(
store: DatasetStoreReader,
index: MlDatasetIndex,
input_columns: tuple[str | MappingSpec, ...],
target_columns: tuple[str | MappingSpec, ...],
protocols: tuple[object, ...] | None = None,
scaling: tuple[ScalingRule, ...] = (),
config: LoaderConfig | None = None,
) -> DataLoader[Batch] | DevicePrefetchDataLoader
Create a loader from an already validated ML index.
Use this entry point to reuse one index across training, validation, previews,
or experiments with different window settings. Arguments and return behavior
otherwise match create_dataloader.
MlDatasetIndex
dataclass
¶
Canonical table of normalized streams available to ML loaders.
Attributes:
-
frame(DataFrame) –Deterministically sorted Polars frame containing manifest identity, protocol, row counts, normalized segment paths, split assignments, and stable manifest/ML row identifiers.
Methods:
-
filter_split–Return an index containing only one split.
filter_split
¶
filter_split(split: str) -> MlDatasetIndex
Return an index containing only one split.
Parameters:
-
split(str) –Split value to retain, normally
trainorvalidation.
Returns:
-
MlDatasetIndex–A new index backed by the filtered frame.
ValidationConfig
dataclass
¶
ValidationConfig(
strategy: Literal[
"sample", "provide", "merge"
] = "sample",
fraction: float = 0.2,
seed: int = 69,
group_by: tuple[str | MappingSpec, ...] = (
set_id,
cell_id,
cidx,
),
provided: tuple[
dict[str | MappingSpec, object], ...
] = tuple(),
)
Group-aware split policy used while constructing an ML index.
Attributes:
-
strategy(Literal['sample', 'provide', 'merge']) –"sample"hashes groups deterministically,"provide"selects explicit groups, and"merge"combines both. -
fraction(float) –Fraction of groups assigned to validation by sampling.
-
seed(int) –Hash seed used by sampling.
-
group_by(tuple[str | MappingSpec, ...]) –Manifest columns that define an indivisible group.
-
provided(tuple[dict[str | MappingSpec, object], ...]) –Explicit partial or complete group selectors.
Examples:
Hold out one cell explicitly:
from batgrad.contracts.mapping import BaseColumns
from batgrad.ml.data.config import ValidationConfig
validation = ValidationConfig.provide(
({BaseColumns.set_id: "pozzato-2022", BaseColumns.cell_id: "Cell1"},)
)
Methods:
-
sample–Create a deterministic sampled-group split.
-
provide–Create a split containing only explicitly selected groups.
-
merge–Create a split combining explicit and sampled groups.
sample
classmethod
¶
sample(
fraction: float = 0.2,
seed: int = 69,
group_by: tuple[str | MappingSpec, ...] = (
set_id,
cell_id,
cidx,
),
) -> ValidationConfig
Create a deterministic sampled-group split.
Parameters:
-
fraction(float, default:0.2) –Fraction of groups assigned to validation.
-
seed(int, default:69) –Hash seed.
-
group_by(tuple[str | MappingSpec, ...], default:(set_id, cell_id, cidx)) –Columns defining one group.
Returns:
-
ValidationConfig–A sampled-group validation policy.
provide
classmethod
¶
provide(
provided: tuple[dict[str | MappingSpec, object], ...],
group_by: tuple[str | MappingSpec, ...] = (
set_id,
cell_id,
cidx,
),
) -> ValidationConfig
Create a split containing only explicitly selected groups.
Parameters:
-
provided(tuple[dict[str | MappingSpec, object], ...]) –Partial or complete group selectors.
-
group_by(tuple[str | MappingSpec, ...], default:(set_id, cell_id, cidx)) –Columns defining one group.
Returns:
-
ValidationConfig–An explicit-group validation policy.
merge
classmethod
¶
merge(
provided: tuple[dict[str | MappingSpec, object], ...],
fraction: float = 0.2,
seed: int = 69,
group_by: tuple[str | MappingSpec, ...] = (
set_id,
cell_id,
cidx,
),
) -> ValidationConfig
Create a split combining explicit and sampled groups.
Parameters:
-
provided(tuple[dict[str | MappingSpec, object], ...]) –Partial or complete group selectors.
-
fraction(float, default:0.2) –Additional fraction selected by deterministic sampling.
-
seed(int, default:69) –Hash seed.
-
group_by(tuple[str | MappingSpec, ...], default:(set_id, cell_id, cidx)) –Columns defining one group.
Returns:
-
ValidationConfig–A merged explicit-and-sampled validation policy.
WindowConfig
dataclass
¶
WindowConfig(
batch_size: int = 32,
seq_len: int = 128,
drop_incomplete: bool = True,
step_rows: int | None = None,
)
Windowing settings for one protocol stream.
WindowConfig controls how a normalized manifest row is split into model
windows before tensor materialization. It is protocol-specific because time
series protocols such as cycling/HPPC and frequency-domain protocols such as
EIS usually need different sequence lengths.
Attributes:
-
batch_size(int) –Number of contiguous sequence lanes carved from each window.
-
seq_len(int) –Number of input and shifted-target positions per lane.
-
drop_incomplete(bool) –Drop a final source window that cannot fill every lane.
-
step_rows(int | None) –Source-row distance between windows. The default advances by
batch_size * seq_len.
Examples:
Configure cycling as long ordered time windows and EIS as one compact frequency window:
LoaderConfig
dataclass
¶
LoaderConfig(
split: str = train,
default_window: WindowConfig = WindowConfig(),
window_by_protocol: dict[
DatasetProtocolId, WindowConfig
] = dict(),
seed: int = 69,
strategy: BatchStrategy = "shuffled_protocol_groups",
protocol_order: tuple[DatasetProtocolId, ...] = (),
stateful_n_windows: int = 1,
cross_protocol_state_carry: CrossProtocolStateCarry
| None = None,
drop_incomplete_batches: bool = True,
drop_incomplete_distributed: bool = True,
data_access: DataAccessMode = "windowed",
group_key: tuple[
str | MappingSpec, ...
] = GROUP_KEY_CELL_CYCLE_PROTOCOL,
alignment_key: tuple[
str | MappingSpec, ...
] = ALIGNMENT_KEY_CELL_CYCLE,
num_workers: int = 0,
prefetch_factor: int = 2,
persistent_workers: bool = False,
pin_memory: bool = False,
multiprocessing_context: MultiprocessingContext
| None = "spawn",
device: str = "cpu",
prefetch_to_device: bool = False,
non_blocking: bool = True,
)
Runtime options for protocol-aware ML loading.
The canonical ML index stores facts about available manifest rows. Loader
configuration describes how those rows are planned into windows and yielded.
group_key identifies one protocol-specific stream/window source, while
alignment_key identifies the shared physical context used by future
multi-protocol schedules.
Usually group_key includes protocol and alignment_key does not:
group_key = (dataset id, cell id, cycle index, protocol)
alignment_key = (dataset id, cell id, cycle index)
With this setup cycling/HPPC/EIS remain separate streams but can later be bundled together for the same cell/cycle.
data_access controls the speed/RAM trade-off:
"windowed"reads only the windows required for each batch from parquet. This has the lowest RAM footprint and is easiest to reason about, but it is IO-bound and can be orders of magnitude slower than memory-backed loading."full_in_mem"preloads the full selected split, active protocol, and requested columns into CPUfloat32tensors. RAM scales approximately asrows * selected_columns * 4 bytes, plus runtime and temporary conversion overhead. A synthetic normalized smoke with batch=64, seq=1024, three inputs and one target measured roughly 0.1-0.2M tokens/s forwindowedand 30M+ tokens/s forfull_in_memafter the cache is built.
Attributes:
-
split(str) –Index split yielded by the loader.
-
default_window(WindowConfig) –Window shape used unless a protocol override exists.
-
window_by_protocol(dict[DatasetProtocolId, WindowConfig]) –Protocol-specific window overrides.
-
seed(int) –Reproducible planning and epoch-phase seed.
-
strategy(BatchStrategy) –Sequential windows or shuffled protocol-group streams.
-
protocol_order(tuple[DatasetProtocolId, ...]) –Explicit protocol traversal order.
-
stateful_n_windows(int) –Consecutive windows per stateful group, or
-1for whole-stream mode. -
cross_protocol_state_carry(CrossProtocolStateCarry | None) –"chain"groups aligned protocol streams for recurrent state carry. -
drop_incomplete_batches(bool) –Drop plans with fewer than the requested lanes.
-
drop_incomplete_distributed(bool) –Equalize plan counts across distributed ranks.
-
data_access(DataAccessMode) –Parquet-window or full-memory access.
-
group_key(tuple[str | MappingSpec, ...]) –Columns identifying a protocol-specific stream.
-
alignment_key(tuple[str | MappingSpec, ...]) –Columns identifying streams that may be aligned.
-
num_workers(int) –PyTorch worker process count.
-
prefetch_factor(int) –Batches prefetched by each worker.
-
persistent_workers(bool) –Retain workers between iterations.
-
pin_memory(bool) –Pin CPU tensors before device transfer.
-
multiprocessing_context(MultiprocessingContext | None) –Worker start method.
-
device(str) –Destination device used by optional prefetch.
-
prefetch_to_device(bool) –Asynchronously move batches to CUDA.
-
non_blocking(bool) –Use non-blocking tensor transfers where possible.
Note
Whole-stream shuffled batches truncate all lanes to the shortest lane.
Finite stateful groups discard a final group shorter than
stateful_n_windows. Shuffled protocol groups do not support EIS;
sequential mode traverses requested protocols in order. full_in_mem
requires num_workers=0; device prefetch requires CUDA.
Methods:
-
window_for–Return window settings for a protocol.
window_for
¶
window_for(protocol: DatasetProtocolId) -> WindowConfig
Return window settings for a protocol.
Parameters:
-
protocol(DatasetProtocolId) –Protocol whose override should be resolved.
Returns:
-
WindowConfig–The protocol override, or
default_windowwhen no override exists.
ScalingRule
dataclass
¶
ScalingRule(
column: str | MappingSpec,
input_min: float,
input_max: float,
output_min: float = -1.0,
output_max: float = 1.0,
clip: bool = False,
transform: ScalingTransform = "linear",
)
Runtime scaling rule for one tensor or frame column.
Attributes:
-
column(str | MappingSpec) –Canonical column name or mapping specification.
-
input_min(float) –Lower physical-unit bound.
-
input_max(float) –Upper physical-unit bound.
-
output_min(float) –Lower model-space bound.
-
output_max(float) –Upper model-space bound.
-
clip(bool) –Clip forward-scaled values to output bounds.
-
transform(ScalingTransform) –Linear scaling or
log1pfollowed by linear scaling.
Note
Tensor functions associate rules with channels by tuple order. Frame functions associate rules by column name.
Batch
dataclass
¶
Batch(
inputs: Tensor,
targets: Tensor,
mask: Tensor,
all_valid: bool,
state: BatchState,
)
Protocol-agnostic time-series batch yielded by ML data loaders.
Targets are shifted one source row ahead of inputs. Incomplete source rows and
null input values are represented by -2.0; null targets remain NaN so loss
calculation can exclude them. mask identifies target positions backed by
real source rows, while target finiteness provides feature-level validity.
Attributes:
-
inputs(Tensor) –Model-space tensor shaped
(B, T, C_in). -
targets(Tensor) –Model-space tensor shaped
(B, T, C_out)for the following source rows. -
mask(Tensor) –Valid-target mask shaped
(B, T)or(B, T, C_out). -
all_valid(bool) –Whether every target row is valid, allowing mask optimizations.
-
state(BatchState) –Source and stateful-sequence metadata; remains on the CPU.
Examples:
Inspect a batch without assuming a specific protocol:
batch = next(iter(loader))
assert batch.inputs.shape[:2] == batch.targets.shape[:2]
print(batch.state.group_keys)
Methods:
-
is_protocol–Return whether any batch lane uses the requested protocol.
-
pin_memory–Return a batch with tensor fields copied to pinned CPU memory.
-
to–Return a batch whose tensor fields are moved to
device.
is_protocol
¶
Return whether any batch lane uses the requested protocol.
Parameters:
-
protocol(object) –Protocol enum, name, or serialized value.
to
¶
Return a batch whose tensor fields are moved to device.
Traceability metadata is reused without modification.
Parameters:
-
device(str | device | int | None) –PyTorch tensor destination.
-
non_blocking(bool, default:False) –Attempt asynchronous transfer when supported.
Returns:
-
Self–A new batch with transferred tensor fields.
BatchState
dataclass
¶
BatchState(
split: str,
batch_idx: int,
protocols: tuple[DatasetProtocolId, ...],
manifest_paths: tuple[str, ...],
manifest_row_ids: tuple[int, ...],
group_keys: tuple[tuple[object, ...], ...],
alignment_keys: tuple[tuple[object, ...], ...],
segments: tuple[BatchSegmentRef, ...],
window_offsets: tuple[int, ...],
stateful_group_idx: int | None = None,
stateful_step_idx: int | None = None,
stateful_steps: int | None = None,
)
Traceability and recurrent-sequence metadata for a tensor batch.
Tuple-valued stream fields are lane-aligned. Stateful identifiers are set only when consecutive batches deliberately form one recurrent sequence.
Attributes:
-
split(str) –Source index split.
-
batch_idx(int) –Batch number within the loader iteration.
-
protocols(tuple[DatasetProtocolId, ...]) –Protocol associated with each lane.
-
manifest_paths(tuple[str, ...]) –Source manifest for each lane.
-
manifest_row_ids(tuple[int, ...]) –Source manifest row identifier for each lane.
-
group_keys(tuple[tuple[object, ...], ...]) –Protocol-specific stream keys.
-
alignment_keys(tuple[tuple[object, ...], ...]) –Physical-context keys used for cross-protocol alignment.
-
segments(tuple[BatchSegmentRef, ...]) –Normalized parquet ranges contributing to the batch.
-
window_offsets(tuple[int, ...]) –Source-row start offset for each lane.
-
stateful_group_idx(int | None) –Optional recurrent group identifier.
-
stateful_step_idx(int | None) –Zero-based position in the recurrent group.
-
stateful_steps(int | None) –Total number of steps in the recurrent group.
BatchSegmentRef
dataclass
¶
BatchSegmentRef(
path: str,
row_start: int,
row_count: int,
window_row_start: int,
window_row_count: int,
)
Traceable source range inside one normalized shard segment.
Attributes:
-
path(str) –Normalized parquet segment path.
-
row_start(int) –First source row read from the segment.
-
row_count(int) –Number of source rows read.
-
window_row_start(int) –First row within the logical materialized window.
-
window_row_count(int) –Number of window rows supplied by this segment.
Scaling¶
scale_data
¶
scale_data(
data: DataFrame, rules: tuple[ScalingRule, ...]
) -> DataFrame
scale_data(
data: LazyFrame, rules: tuple[ScalingRule, ...]
) -> LazyFrame
scale_data(
data: Tensor, rules: tuple[ScalingRule, ...]
) -> Tensor
scale_data(
data: DataFrame | LazyFrame | Tensor,
rules: tuple[ScalingRule, ...],
) -> DataFrame | LazyFrame | Tensor
Scale selected frame columns or all tensor channels into model space.
Parameters:
-
data(DataFrame | LazyFrame | Tensor) –Polars frame or tensor to scale. Tensor channels occupy the last dimension and must match
rulesone-to-one in tuple order. -
rules(tuple[ScalingRule, ...]) –Column scaling rules.
Returns:
-
DataFrame | LazyFrame | Tensor–A new object of the same concrete data type. Frame columns without a rule
-
DataFrame | LazyFrame | Tensor–are unchanged.
Raises:
-
ValueError–If a tensor's channel count differs from the rule count.
inverse_scale_data
¶
inverse_scale_data(
data: DataFrame | Tensor, rules: tuple[ScalingRule, ...]
) -> DataFrame | Tensor
Reverse scaling for selected frame columns or ordered tensor channels.
Clipping is intentionally not applied during inverse scaling.
Parameters:
-
data(DataFrame | Tensor) –Frame matched by rule name, or tensor matched by rule order.
-
rules(tuple[ScalingRule, ...]) –Scaling rules to reverse.
Returns:
-
DataFrame | Tensor–A new frame or tensor in physical units.
Raises:
-
ValueError–If a tensor's channel count differs from the rule count.
inverse_scale_tensor
¶
inverse_scale_tensor(
data: Tensor,
columns: tuple[str | MappingSpec, ...],
scaling: tuple[ScalingRule, ...],
) -> Tensor
Inverse-scale named tensor channels while leaving other channels unchanged.
Parameters:
-
data(Tensor) –Tensor whose last dimension corresponds to
columns. -
columns(tuple[str | MappingSpec, ...]) –Ordered tensor channel names.
-
scaling(tuple[ScalingRule, ...]) –Available rules, matched to channels by name.
Returns:
-
Tensor–A cloned tensor in which channels with matching rules are in physical
-
Tensor–units.
minmax_scaling
¶
minmax_scaling(
bounds: BoundsByColumn,
*,
output_min: float = -1.0,
output_max: float = 1.0,
clip: bool = False,
) -> tuple[ScalingRule, ...]
Construct linear scaling rules from ordered column bounds.
Parameters:
-
bounds(BoundsByColumn) –Mapping from columns to
(input_min, input_max)physical bounds. -
output_min(float, default:-1.0) –Shared lower model-space bound.
-
output_max(float, default:1.0) –Shared upper model-space bound.
-
clip(bool, default:False) –Whether forward scaling clips to output bounds.
Returns:
-
tuple[ScalingRule, ...]–Rules preserving the mapping's iteration order.
Examples:
Scale voltage and current to [-1, 1]: