Skip to content

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

available_manifest_paths(
    store: DatasetStoreReader,
) -> tuple[str, ...]

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_device is 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:

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

MlDatasetIndex(frame: DataFrame)

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 train or validation.

Returns:

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:

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:

Returns:

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:

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:

windows = {
    DatasetProtocolId.cycling: WindowConfig(batch_size=8, seq_len=1024),
    DatasetProtocolId.eis: WindowConfig(batch_size=16, seq_len=48),
}

step property

step: int

Source-row distance between consecutive windows.

window_rows property

window_rows: int

Source rows needed for inputs and one-row-ahead targets.

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 CPU float32 tensors. RAM scales approximately as rows * 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 for windowed and 30M+ tokens/s for full_in_mem after 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 -1 for 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_window when 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 log1p followed 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

is_protocol(protocol: object) -> bool

Return whether any batch lane uses the requested protocol.

Parameters:

  • protocol (object) –

    Protocol enum, name, or serialized value.

pin_memory

pin_memory() -> Self

Return a batch with tensor fields copied to pinned CPU memory.

to

to(
    device: str | device | int | None,
    *,
    non_blocking: bool = False,
) -> Self

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 rules one-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:

Examples:

Scale voltage and current to [-1, 1]:

rules = minmax_scaling({"voltage": (2.5, 4.2), "current": (-5.0, 5.0)})