Spaces:
Running on Zero
Running on Zero
Download experiments/missingness_protocol.py from config-h/WLCR-SEA_Predictor: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/spaces/config-h/WLCR-SEA_Predictor/resolve/main/experiments/missingness_protocol.py
- Command line
-
hf download hf://spaces/config-h/WLCR-SEA_Predictor/experiments/missingness_protocol.py
-
curl -L -o missingness_protocol.py https://huggingface.co/spaces/config-h/WLCR-SEA_Predictor/resolve/main/experiments/missingness_protocol.py
10.2 kB
| from __future__ import annotations | |
| """Deterministic missing-telemetry protocols shared by paper experiments. | |
| Corruption is generated on each cell's unique absolute time axis and then | |
| projected into overlapping 336-hour requests. Consequently, a real | |
| cell--timestamp--indicator has one corruption state in every request that | |
| contains it. Rates on the unique time axis and rates on repeated window | |
| exposures are deliberately reported as different estimands. | |
| """ | |
| import hashlib | |
| from typing import Mapping | |
| import numpy as np | |
| INPUT_HOURS = 336 | |
| TARGET_COUNT = 4 | |
| SUPPORTED_MECHANISMS = ( | |
| "none", | |
| "mcar", | |
| "block", | |
| "recent_tail", | |
| "asynchronous", | |
| "mixed", | |
| ) | |
| def stable_uniform(*tokens: object) -> float: | |
| """Return a deterministic pseudo-uniform value in ``[0, 1)``.""" | |
| payload = "|".join(str(token) for token in tokens).encode("utf-8") | |
| integer = int.from_bytes(hashlib.sha256(payload).digest()[:8], "big") | |
| return integer / float(2**64) | |
| def global_corruption_mask( | |
| cells: np.ndarray, | |
| history_end_hours: np.ndarray, | |
| *, | |
| mechanism: str, | |
| requested_rate: float, | |
| seed: int, | |
| ) -> np.ndarray: | |
| """Generate an absolute-cell-timeline mask before window extraction. | |
| ``recent_tail`` means the tail of the complete unique timeline available | |
| for one cell in the evaluated split. It is *not* a request-relative tail; | |
| that choice preserves one state for a timestamp shared by overlapping | |
| requests. | |
| """ | |
| cells = np.asarray(cells).astype(str) | |
| end_hours = np.asarray(history_end_hours, dtype=np.int64) | |
| if cells.ndim != 1 or end_hours.shape != cells.shape: | |
| raise ValueError("cells and history_end_hours must be aligned vectors") | |
| if mechanism not in SUPPORTED_MECHANISMS: | |
| raise ValueError(f"unknown missingness mechanism: {mechanism}") | |
| if not 0.0 <= requested_rate <= 1.0: | |
| raise ValueError("requested_rate must be in [0,1]") | |
| output = np.zeros((len(cells), INPUT_HOURS, TARGET_COUNT), dtype=bool) | |
| if mechanism == "none" or requested_rate == 0.0: | |
| return output | |
| offsets = np.arange(INPUT_HOURS, dtype=np.int64) - (INPUT_HOURS - 1) | |
| for cell in sorted(set(cells.tolist())): | |
| window_indices = np.flatnonzero(cells == cell) | |
| hour_matrix = end_hours[window_indices, None] + offsets[None, :] | |
| unique_hours = np.unique(hour_matrix) | |
| local_mechanism = mechanism | |
| if mechanism == "mixed": | |
| choices = ("mcar", "block", "recent_tail", "asynchronous") | |
| choice = min( | |
| int(stable_uniform(seed, cell, "mixed") * len(choices)), | |
| len(choices) - 1, | |
| ) | |
| local_mechanism = choices[choice] | |
| removed = np.zeros((len(unique_hours), TARGET_COUNT), dtype=bool) | |
| if local_mechanism == "mcar": | |
| for position, hour in enumerate(unique_hours): | |
| if stable_uniform(seed, cell, int(hour), "mcar") < requested_rate: | |
| removed[position, :] = True | |
| elif local_mechanism == "asynchronous": | |
| for position, hour in enumerate(unique_hours): | |
| for metric in range(TARGET_COUNT): | |
| removed[position, metric] = ( | |
| stable_uniform(seed, cell, int(hour), metric, "async") | |
| < requested_rate | |
| ) | |
| elif local_mechanism in {"block", "recent_tail"}: | |
| length = int(round(requested_rate * len(unique_hours))) | |
| if requested_rate > 0.0: | |
| length = max(1, length) | |
| length = min(length, len(unique_hours)) | |
| if local_mechanism == "recent_tail": | |
| start = len(unique_hours) - length | |
| else: | |
| maximum = len(unique_hours) - length | |
| start = int(stable_uniform(seed, cell, "block") * (maximum + 1)) | |
| start = min(start, maximum) | |
| removed[start : start + length, :] = True | |
| positions = np.searchsorted(unique_hours, hour_matrix) | |
| output[window_indices] = removed[positions] | |
| return output | |
| def _rate_statistics(original: np.ndarray, extra: np.ndarray) -> dict[str, float | int]: | |
| original = np.asarray(original, dtype=bool) | |
| extra = np.asarray(extra, dtype=bool) | |
| if original.shape != extra.shape: | |
| raise ValueError("corruption statistics require aligned masks") | |
| newly_removed = original & extra | |
| final_missing = ~original | extra | |
| observed = int(np.sum(original)) | |
| return { | |
| "positions": int(original.size), | |
| "observed_positions": observed, | |
| "original_missing_rate": float(np.mean(~original)), | |
| "selected_for_corruption_rate": float(np.mean(extra)), | |
| "newly_removed_rate": float(np.mean(newly_removed)), | |
| "newly_removed_fraction_of_observed": ( | |
| float(np.sum(newly_removed) / observed) if observed else 0.0 | |
| ), | |
| "final_total_missing_rate": float(np.mean(final_missing)), | |
| } | |
| def window_exposure_statistics( | |
| original_masks: np.ndarray, additional_missing: np.ndarray | |
| ) -> dict[str, float | int]: | |
| """Return rates over repeated request-window exposures.""" | |
| return _rate_statistics(original_masks, additional_missing) | |
| def unique_cell_time_statistics( | |
| original_masks: np.ndarray, | |
| additional_missing: np.ndarray, | |
| cells: np.ndarray, | |
| history_end_hours: np.ndarray, | |
| ) -> dict[str, float | int]: | |
| """Return rates over unique cell--hour--indicator positions. | |
| Repeated exposures must agree on both the original observation state and | |
| the generated corruption state. A disagreement is a protocol violation. | |
| """ | |
| original = np.asarray(original_masks, dtype=bool) | |
| extra = np.asarray(additional_missing, dtype=bool) | |
| cells = np.asarray(cells).astype(str) | |
| end_hours = np.asarray(history_end_hours, dtype=np.int64) | |
| if original.shape != extra.shape: | |
| raise ValueError("corruption statistics require aligned masks") | |
| if original.shape != (len(cells), INPUT_HOURS, TARGET_COUNT): | |
| raise ValueError("history masks do not match the supplied window identities") | |
| if end_hours.shape != cells.shape: | |
| raise ValueError("history_end_hours must align with cells") | |
| offsets = np.arange(INPUT_HOURS, dtype=np.int64) - (INPUT_HOURS - 1) | |
| unique_original: list[np.ndarray] = [] | |
| unique_extra: list[np.ndarray] = [] | |
| for cell in sorted(set(cells.tolist())): | |
| selected = np.flatnonzero(cells == cell) | |
| hours = end_hours[selected, None] + offsets[None, :] | |
| unique_hours = np.unique(hours) | |
| cell_original = np.zeros((len(unique_hours), TARGET_COUNT), dtype=bool) | |
| cell_extra = np.zeros_like(cell_original) | |
| initialized = np.zeros(len(unique_hours), dtype=bool) | |
| positions = np.searchsorted(unique_hours, hours) | |
| for local_window, global_window in enumerate(selected): | |
| for offset_index, position in enumerate(positions[local_window]): | |
| observed_value = original[global_window, offset_index] | |
| extra_value = extra[global_window, offset_index] | |
| if initialized[position]: | |
| if not np.array_equal(cell_original[position], observed_value): | |
| raise ValueError( | |
| "original observation mask is inconsistent across overlapping requests" | |
| ) | |
| if not np.array_equal(cell_extra[position], extra_value): | |
| raise ValueError( | |
| "corruption mask is inconsistent across overlapping requests" | |
| ) | |
| else: | |
| cell_original[position] = observed_value | |
| cell_extra[position] = extra_value | |
| initialized[position] = True | |
| unique_original.append(cell_original) | |
| unique_extra.append(cell_extra) | |
| return _rate_statistics( | |
| np.concatenate(unique_original, axis=0), | |
| np.concatenate(unique_extra, axis=0), | |
| ) | |
| def corruption_statistics( | |
| original_masks: np.ndarray, | |
| additional_missing: np.ndarray, | |
| *, | |
| cells: np.ndarray | None = None, | |
| history_end_hours: np.ndarray | None = None, | |
| mechanism: str | None = None, | |
| requested_rate: float | None = None, | |
| seed: int | None = None, | |
| ) -> dict[str, object]: | |
| """Report both unique-axis and repeated-exposure corruption rates. | |
| For backward compatibility, the three historical exposure keys are also | |
| repeated at the top level. | |
| """ | |
| exposure = window_exposure_statistics(original_masks, additional_missing) | |
| payload: dict[str, object] = { | |
| "scope": "absolute_cell_timeline", | |
| "recent_tail_definition": "tail of each cell's unique evaluated timeline", | |
| "mechanism": mechanism, | |
| "requested_rate": requested_rate, | |
| "seed": seed, | |
| "window_exposure": exposure, | |
| "original_missing_rate": exposure["original_missing_rate"], | |
| "newly_removed_rate": exposure["newly_removed_rate"], | |
| "final_total_missing_rate": exposure["final_total_missing_rate"], | |
| } | |
| if (cells is None) != (history_end_hours is None): | |
| raise ValueError("cells and history_end_hours must be supplied together") | |
| if cells is not None and history_end_hours is not None: | |
| payload["unique_cell_time"] = unique_cell_time_statistics( | |
| original_masks, | |
| additional_missing, | |
| cells, | |
| history_end_hours, | |
| ) | |
| return payload | |
| def flatten_statistics(report: Mapping[str, object]) -> dict[str, object]: | |
| """Flatten a protocol report into stable CSV-friendly columns.""" | |
| row: dict[str, object] = { | |
| "corruption_scope": report.get("scope", "absolute_cell_timeline"), | |
| "recent_tail_definition": report.get("recent_tail_definition", ""), | |
| } | |
| for prefix, key in ( | |
| ("unique", "unique_cell_time"), | |
| ("exposure", "window_exposure"), | |
| ): | |
| values = report.get(key) | |
| if not isinstance(values, Mapping): | |
| continue | |
| for name, value in values.items(): | |
| row[f"{prefix}_{name}"] = value | |
| return row | |