Can each item choose which value to hide?

Dynamic masking
Use an item’s selected flag to hide its value and predict it from x.

Example

items:
  - {x: 0.4, value: 1.1, selected: true}
  - {x: -0.2, value: -0.1, selected: false}

value is hidden and scored only where selected is true; the other item remains visible.

Model tree with record, items, x, value. Selector notes identify hidden values. selected chooses which item values are hidden.

Model tree with record, items, x, value. Selector notes identify hidden values. selected chooses which item values are hidden.

Figure 1: Selector notes identify hidden values. selected chooses which item values are hidden.

Comparison

Score only selected real items. Change their hidden values to check they cannot leak into predictions, and shuffle visible x to remove its signal.

Result

P052 · Latest full run · Passing

Seed 5201 on cpu: 6 of 6 test checks met. History: 8 of 8 full runs met every check, across 3 distinct seeds. History can include earlier source versions. The experiment code has changed since this run; these measurements describe its earlier version.

Comparison Latest measurement
Correct visible x 1.0% of baseline error
Shuffled visible x 139.4% of baseline error

Baseline error compares prediction error with a constant guess. 100% matches that guess; lower is better. Details specify the baseline and root-mean-square error calculation.

Per-item selection works, including padded positions. This tests aligned local reconstruction, rather than retrieval across unrelated lists.

Experiment details and code

Latest full run

Seed 5201, cpu, recorded 2026-09-21T18:47:06.795550+00:00. Outcome: met.

Source fingerprint: d0ab50127f161f842ebf2c2b71401edb54460abff41dfc7a8c5f072336facd62. Python 3.12.6; Torch 2.12.0.

Measurement Value
steps 400
selected_coordinates 1792
rmse 0.0112693
baseline_rmse 1.16227
nrmse 0.00969595
corrupted_context_nrmse 1.39368
hidden_value_drift 0
Behavioral checks
Behavioral check Outcome
Selected held-out nRMSE below 0.25 Met
Corrupted context nRMSE above 0.85 Met
Hidden values cannot affect predictions Met
Prediction flags match selectors including padding Met
Training objectives match selectors including padding Met
Selected coordinates are absent from encoder input Met

Full run history

Runs may include earlier source versions. Repeating a seed is not an independent seed check.

Seed Outcome Recorded Code fingerprint
5201 met 2026-09-16T21:59:02.496106+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
5201 met 2026-09-16T22:00:03.129806+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
6101 met 2026-09-16T22:00:46.669862+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
6102 met 2026-09-16T22:01:30.372143+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
5201 met 2026-09-17T00:02:10.044012+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
5201 met 2026-09-17T01:00:41.350552+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
5201 met 2026-09-18T16:39:50.183029+00:00 933f774bac56ce0b9f399ace5b3ae9dae5a85656a00d0cb7aa4712e0ffddefc8
5201 met 2026-09-21T18:47:06.795550+00:00 1f7905e61cb3360a9d6686a246c5d84229e9caa1c052d13f4891b70efe14bba1

Recorded results.

Run this experiment

Proof execution is optional and separate from building this site. Use a RelFlow build matching the recorded provenance; replace the wheel path below with that artifact. Run from this documentation repository’s root:

uv run --with /path/to/matching-relflow.whl python proofs/run.py P052

Or, from a prepared proof environment, run the self-contained script directly:

PYTHONPATH=proofs python proofs/masking/query_reconstruction.py

Add --accelerator gpu for CUDA or --seed 42 for another seeded experiment. --steps 2 checks execution with a short training budget; it is recorded as a smoke run.

Download the complete proof.

Data and model

Each independent bag contains one to four items. Item-local x is uniform on [-1, 1], and value = 2*x + 0.3. Independent Boolean selectors hide roughly half the values, with at least one selected per bag. Entire bags are split into 2,048 training, 256 validation, and 1,024 test observations.

Training and controls

The xs preset supplies the architecture defaults; the items branch keeps reduction=None to retain its coordinate slots.

Fit for 400 AdamW updates at learning rate 0.002. The primary gate is selected coordinate nRMSE below 0.25, relative to the selected training-label mean. Replacing visible x with independent samples must push nRMSE above 0.85. Replacing only hidden value inputs must change predictions by less than 1e-5. Encoded training masks and output inferred flags must exactly match the selectors, including unselected and padded positions.

Complete experiment code

Code
"""P052: query-selected reconstruction over variable-length repeated records."""

from collections.abc import Iterator
from functools import partial

import lightning.pytorch as lit
import numpy as np
import pyarrow as pa
from reporting import report

import relflow as rf

PROOF_ID = "P052"
LENGTH = 4
BUDGET = 400


def records(*, rows: int, seed: int) -> Iterator[dict]:
    rng = np.random.default_rng(seed)
    for _ in range(rows):
        length = int(rng.integers(1, LENGTH + 1))
        values = rng.uniform(-1.0, 1.0, length)
        selected = rng.random(length) < 0.5
        selected[int(rng.integers(length))] = True
        yield {
            "items": [
                {"x": float(value), "value": float(2 * value + 0.3), "selected": bool(chosen)}
                for value, chosen in zip(values, selected, strict=True)
            ]
        }


def build() -> rf.Model:
    return rf.Model.xs(
        batch_size=64,
        items=rf.Branch(
            length=LENGTH,
            reduction=None,
            x=rf.Number,
            value=rf.Number(objective="mse", mask=rf.Mask(query="selected", skip=True, reconstruct=True)),
        ),
    )


def prediction(model: rf.Model, rows: list[dict]) -> tuple[np.ndarray, np.ndarray]:
    output = model.predict(pa.Table.from_pylist(rows))["predictions"].to_pylist()
    # Evaluation compares real coordinates against fixed-width selector matrices.
    # Public output has no cap padding; absent coordinates receive no score.
    content = np.zeros((len(rows), LENGTH), dtype=np.float64)
    inferred = np.zeros((len(rows), LENGTH), dtype=bool)
    for index, row in enumerate(output):
        values = [item["value"] for item in row["items"]]
        content[index, : len(values)] = [value["content"] for value in values]
        inferred[index, : len(values)] = [value["inferred"] for value in values]
    return content, inferred


def run(seed: int, steps: int | None, accelerator: str) -> tuple[dict, dict]:
    lit.seed_everything(seed, workers=True)
    model = build()
    model.optimizer = rf.adamw(learning_rate=0.002)
    data = rf.SyntheticDataModule(
        model=model,
        train=partial(records, rows=2048, seed=seed + 1),
        validate=partial(records, rows=256, seed=seed + 2),
        seed=seed,
    )
    trainer = lit.Trainer(
        accelerator=accelerator,
        devices=1,
        max_epochs=-1,
        max_steps=BUDGET if steps is None else min(steps, BUDGET),
        deterministic=True,
        logger=False,
        enable_checkpointing=False,
        enable_progress_bar=False,
        enable_model_summary=False,
        num_sanity_val_steps=0,
    )
    trainer.fit(model, datamodule=data)
    model.eval()
    train = list(records(rows=2048, seed=seed + 1))
    test = list(records(rows=1024, seed=seed + 3))
    expected = np.asarray(
        [[item["selected"] for item in row["items"]] + [False] * (LENGTH - len(row["items"])) for row in test]
    )
    actual = np.asarray([item["value"] for row in test for item in row["items"] if item["selected"]])
    mean = float(np.mean([item["value"] for row in train for item in row["items"] if item["selected"]]))
    baseline = float(np.sqrt(np.mean((actual - mean) ** 2)))
    content, inferred = prediction(model, test)
    rng = np.random.default_rng(seed + 4)
    corrupted = [{"items": [{**item, "x": float(rng.uniform(-1, 1))} for item in row["items"]]} for row in test]
    poisoned = [
        {"items": [{**item, "value": 1000.0 if item["selected"] else item["value"]} for item in row["items"]]}
        for row in test
    ]
    broken, _ = prediction(model, corrupted)
    replaced, _ = prediction(model, poisoned)
    # Audit labels on an independent model, leaving fitted normalizers frozen.
    field = build().encode(pa.Table.from_pylist(test), strata="train")["/items/value"]
    measured = float(np.sqrt(np.mean((content[expected] - actual) ** 2)))
    metrics = {
        "steps": trainer.global_step,
        "selected_coordinates": int(expected.sum()),
        "rmse": measured,
        "baseline_rmse": baseline,
        "nrmse": measured / baseline,
        "corrupted_context_nrmse": float(np.sqrt(np.mean((broken[expected] - actual) ** 2))) / baseline,
        "hidden_value_drift": float(np.max(np.abs(content[expected] - replaced[expected]))),
    }
    return metrics, {
        "Selected held-out nRMSE below 0.25": metrics["nrmse"] < 0.25,
        "Corrupted context nRMSE above 0.85": metrics["corrupted_context_nrmse"] > 0.85,
        "Hidden values cannot affect predictions": metrics["hidden_value_drift"] < 1e-5,
        "Prediction flags match selectors including padding": bool(np.array_equal(inferred, expected)),
        "Training objectives match selectors including padding": bool(
            np.array_equal(field.trainable.cpu().numpy().reshape(expected.shape), expected)
        ),
        "Selected coordinates are absent from encoder input": not bool(
            field.present.cpu().numpy().reshape(expected.shape)[expected].any()
        ),
    }


if __name__ == "__main__":
    report(PROOF_ID, run, seed=5201)