"""Paper-scale CPU reproduction of the synthetic Table-2 comparison.""" from __future__ import annotations import json import math import multiprocessing from concurrent.futures import ProcessPoolExecutor from pathlib import Path import numpy as np import ot import torch from scipy.optimize import linear_sum_assignment from scipy.stats import t as student_t from threadpoolctl import threadpool_limits BASE_SEED = 260602047 PER_CLUSTER = 500 TRIALS = 100 ITERATIONS = 200 ALPHA = 0.5 WORKERS = 4 THREADS_PER_WORKER = 16 METHODS = ("CDOT", "FGW", "IsoRank") PAPER_VALUES = {"CDOT": 0.0016, "FGW": 0.0034, "IsoRank": 0.0033} _THREAD_LIMITER = None def stable(value: float) -> float: return round(float(value), 12) def initialize_worker() -> None: global _THREAD_LIMITER _THREAD_LIMITER = threadpool_limits(limits=THREADS_PER_WORKER) torch.set_num_threads(THREADS_PER_WORKER) torch.set_num_interop_threads(1) def generate_trial( per_cluster: int, seed: int ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: rng = np.random.default_rng(seed) offsets = np.asarray( ((0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)), dtype=np.float64, ) xs: list[np.ndarray] = [] ys: list[np.ndarray] = [] labels: list[np.ndarray] = [] for label, offset in enumerate(offsets): xs.append(rng.random((per_cluster, 2)) + offset) ys.append(rng.random((per_cluster, 2)) + offset) labels.append(np.full(per_cluster, label, dtype=np.int64)) joined = np.concatenate(labels) return np.vstack(xs), np.vstack(ys), joined, joined.copy() def normalized_distance(points: np.ndarray) -> torch.Tensor: tensor = torch.from_numpy(points) distance = torch.cdist(tensor, tensor) maximum = torch.max(distance) if float(maximum) <= 0: raise ValueError("degenerate point cloud") return distance / maximum def feature_cost( source_labels: np.ndarray, target_labels: np.ndarray ) -> torch.Tensor: return torch.from_numpy( (source_labels[:, None] != target_labels[None, :]).astype(np.float64) ) def barycentric_mse( source: np.ndarray, transported: torch.Tensor ) -> float: source_tensor = torch.from_numpy(source) return float( torch.mean(torch.sum((source_tensor - transported) ** 2, dim=1)) ) def initial_state( distance_x: torch.Tensor, distance_y: torch.Tensor, cost: torch.Tensor, alpha: float, ) -> tuple[torch.Tensor, torch.Tensor, float, float]: n = cost.shape[0] ax = distance_x.sum(dim=1) by = distance_y.sum(dim=0) residual = (ax[:, None] - by[None, :]) / (n * n) a_residual = ( (distance_x @ ax)[:, None] - ax[:, None] * by[None, :] ) / (n * n) residual_b = ( ax[:, None] * by[None, :] - (by @ distance_y)[None, :] ) / (n * n) gradient = (1.0 - alpha) * cost + alpha * n * n * ( a_residual - residual_b ) feature_value = float(cost.mean()) objective = float( (1.0 - alpha) * feature_value + 0.5 * alpha * n * n * torch.sum(residual * residual) ) return residual, gradient, feature_value, objective def cdot_lazy( source: np.ndarray, target: np.ndarray, distance_x_raw: torch.Tensor, distance_y_raw: torch.Tensor, cost: torch.Tensor, iterations: int, alpha: float, ) -> tuple[float, dict[str, object]]: n = cost.shape[0] distance_x = distance_x_raw / n distance_y = distance_y_raw / n residual, gradient, feature_value, previous = initial_state( distance_x, distance_y, cost, alpha ) distance_x_squared = distance_x @ distance_x distance_y_squared = distance_y @ distance_y target_tensor = torch.from_numpy(target) transported = target_tensor.mean(dim=0).expand(n, -1).clone() nonmonotone = boundary = 0 mass_coefficient = 1.0 final_gap = math.inf for iteration in range(iterations): rows, permutation_np = linear_sum_assignment(gradient.numpy()) if not np.array_equal(rows, np.arange(n)): raise RuntimeError("unexpected LAP row ordering") permutation = torch.from_numpy(permutation_np.astype(np.int64)) inverse = torch.empty_like(permutation) inverse[permutation] = torch.arange(n) atom_residual = ( distance_x[:, inverse] - distance_y[permutation, :] ) / n residual_direction = atom_residual - residual atom_feature = float( cost[torch.arange(n), permutation].sum() / n ) linear = float( (1.0 - alpha) * (atom_feature - feature_value) + alpha * n * n * torch.sum(residual * residual_direction) ) quadratic = float( 0.5 * alpha * n * n * torch.sum(residual_direction * residual_direction) ) final_gap = max(0.0, -linear) if quadratic > 1e-24: step = float(np.clip(-linear / (2.0 * quadratic), 0.0, 1.0)) else: step = float(linear < 0) boundary += int(step <= 1e-14 or step >= 1.0 - 1e-14) residual = residual + step * residual_direction feature_value = (1.0 - step) * feature_value + step * atom_feature transported = ( (1.0 - step) * transported + step * target_tensor[permutation] ) current = float( (1.0 - alpha) * feature_value + 0.5 * alpha * n * n * torch.sum(residual * residual) ) nonmonotone += int(current > previous + 2e-9) previous = current mass_coefficient = (1.0 - step) * mass_coefficient + step if iteration < iterations - 1: cross = distance_x[:, inverse] @ distance_y atom_gradient = (1.0 - alpha) * cost + alpha * n * n * ( distance_x_squared[:, inverse] / n - 2.0 * cross / n + distance_y_squared[permutation, :] / n ) gradient = (1.0 - step) * gradient + step * atom_gradient return barycentric_mse(source, transported), { "iterations": iterations, "final_objective": stable(previous), "final_fw_gap": stable(final_gap), "nonmonotone_steps": nonmonotone, "boundary_steps": boundary, "marginal_error_certificate": stable( abs(mass_coefficient - 1.0) / n ), "solver": "Algorithm-2 affine lazy gradient, exact LAP and quadratic line search", } def cdot_standard_small( source: np.ndarray, target: np.ndarray, distance_x_raw: torch.Tensor, distance_y_raw: torch.Tensor, cost: torch.Tensor, iterations: int, alpha: float, ) -> tuple[float, float]: n = cost.shape[0] distance_x = distance_x_raw / n distance_y = distance_y_raw / n coupling = torch.full_like(cost, 1.0 / (n * n)) previous = math.inf for _ in range(iterations): residual = distance_x @ coupling - coupling @ distance_y gradient = (1.0 - alpha) * cost + alpha * n * n * ( distance_x @ residual - residual @ distance_y ) rows, permutation_np = linear_sum_assignment(gradient.numpy()) permutation = torch.from_numpy(permutation_np.astype(np.int64)) atom = torch.zeros_like(coupling) atom[torch.from_numpy(rows), permutation] = 1.0 / n direction = atom - coupling residual_direction = ( distance_x @ direction - direction @ distance_y ) linear = float( (1.0 - alpha) * torch.sum(cost * direction) + alpha * n * n * torch.sum(residual * residual_direction) ) quadratic = float( 0.5 * alpha * n * n * torch.sum(residual_direction * residual_direction) ) step = ( float(np.clip(-linear / (2.0 * quadratic), 0.0, 1.0)) if quadratic > 1e-24 else float(linear < 0) ) coupling += step * direction residual += step * residual_direction previous = float( (1.0 - alpha) * torch.sum(cost * coupling) + 0.5 * alpha * n * n * torch.sum(residual * residual) ) transported = n * (coupling @ torch.from_numpy(target)) return barycentric_mse(source, transported), previous def fgw( source: np.ndarray, target: np.ndarray, distance_x: torch.Tensor, distance_y: torch.Tensor, cost: torch.Tensor, iterations: int, alpha: float, ) -> tuple[float, dict[str, object]]: n = cost.shape[0] weights = np.full(n, 1.0 / n) coupling, log = ot.gromov.fused_gromov_wasserstein( cost.numpy(), distance_x.numpy(), distance_y.numpy(), weights, weights, loss_fun="square_loss", alpha=alpha, armijo=False, G0=np.outer(weights, weights), max_iter=iterations, tol_rel=1e-9, tol_abs=1e-9, log=True, ) losses = np.asarray(log.get("loss", []), dtype=np.float64) transported = n * coupling @ target marginal = max( float(np.max(np.abs(coupling.sum(axis=0) - weights))), float(np.max(np.abs(coupling.sum(axis=1) - weights))), ) return float(np.mean(np.sum((source - transported) ** 2, axis=1))), { "iterations": max(0, len(losses) - 1), "final_objective": stable(losses[-1]), "nonmonotone_steps": int( np.sum(losses[1:] > losses[:-1] + 2e-9) ), "marginal_error": stable(marginal), "solver": "POT fused_gromov_wasserstein default conditional gradient", } def isorank( source: np.ndarray, target: np.ndarray, distance_x: torch.Tensor, distance_y: torch.Tensor, source_labels: np.ndarray, target_labels: np.ndarray, iterations: int, damping: float, ) -> tuple[float, dict[str, object]]: affinity_x = torch.exp(-0.5 * distance_x * distance_x) affinity_y = torch.exp(-0.5 * distance_y * distance_y) affinity_x /= affinity_x.sum(dim=1, keepdim=True) affinity_y /= affinity_y.sum(dim=1, keepdim=True) categories = sorted(set(source_labels) | set(target_labels)) source_onehot = torch.from_numpy( np.column_stack([source_labels == label for label in categories]) .astype(np.float64) ) target_onehot = torch.from_numpy( np.column_stack([target_labels == label for label in categories]) .astype(np.float64) ) target_counts = target_onehot.sum(dim=0) if torch.any(target_counts == 0): raise ValueError("IsoRank prior has an empty target feature class") left = source_onehot right = target_onehot / target_counts left_blocks: list[torch.Tensor] = [] right_blocks: list[torch.Tensor] = [] coefficient = 1.0 for _ in range(iterations): weight = (1.0 - damping) * coefficient root = math.sqrt(weight) left_blocks.append(root * left) right_blocks.append(root * right) left = affinity_x @ left right = affinity_y @ right coefficient *= damping root = math.sqrt(coefficient) left_blocks.append(root * left) right_blocks.append(root * right) similarity = torch.cat(left_blocks, dim=1) @ torch.cat( right_blocks, dim=1 ).T rows, permutation = linear_sum_assignment(-similarity.numpy()) if not np.array_equal(rows, np.arange(len(source))): raise RuntimeError("unexpected IsoRank LAP row ordering") transported = target[permutation] return float(np.mean(np.sum((source - transported) ** 2, axis=1))), { "iterations": iterations, "fixed_point_residual_upper_bound": stable( 2.0 * damping**iterations ), "marginal_error": 0.0, "solver": "exact low-rank evaluation of the stated IsoRank recurrence plus LAP", } def run_trial(trial: int) -> list[dict[str, object]]: seed = BASE_SEED + trial source, target, source_labels, target_labels = generate_trial( PER_CLUSTER, seed ) distance_x = normalized_distance(source) distance_y = normalized_distance(target) cost = feature_cost(source_labels, target_labels) results: list[dict[str, object]] = [] cdot_mse, cdot_diagnostics = cdot_lazy( source, target, distance_x, distance_y, cost, ITERATIONS, ALPHA, ) results.append( { "trial": trial, "seed": seed, "method": "CDOT", "mse": stable(cdot_mse), **cdot_diagnostics, } ) fgw_mse, fgw_diagnostics = fgw( source, target, distance_x, distance_y, cost, ITERATIONS, ALPHA, ) results.append( { "trial": trial, "seed": seed, "method": "FGW", "mse": stable(fgw_mse), **fgw_diagnostics, } ) isorank_mse, isorank_diagnostics = isorank( source, target, distance_x, distance_y, source_labels, target_labels, ITERATIONS, ALPHA, ) results.append( { "trial": trial, "seed": seed, "method": "IsoRank", "mse": stable(isorank_mse), **isorank_diagnostics, } ) return results def summarize(values: np.ndarray) -> dict[str, float | int]: standard_deviation = float(values.std(ddof=1)) half_width = float( student_t.ppf(0.975, len(values) - 1) * standard_deviation / math.sqrt(len(values)) ) return { "trials": len(values), "mean_mse": stable(values.mean()), "sample_std_mse": stable(standard_deviation), "ci95_low": stable(values.mean() - half_width), "ci95_high": stable(values.mean() + half_width), } def paired_summary( cdot_values: np.ndarray, baseline_values: np.ndarray ) -> dict[str, float]: differences = cdot_values - baseline_values standard_deviation = float(differences.std(ddof=1)) half_width = float( student_t.ppf(0.975, len(differences) - 1) * standard_deviation / math.sqrt(len(differences)) ) return { "mean_cdot_minus_baseline": stable(differences.mean()), "ci95_low": stable(differences.mean() - half_width), "ci95_high": stable(differences.mean() + half_width), } def parity_and_controls() -> dict[str, object]: source, target, labels_x, labels_y = generate_trial(10, BASE_SEED - 1) distance_x = normalized_distance(source) distance_y = normalized_distance(target) cost = feature_cost(labels_x, labels_y) lazy_mse, lazy_diagnostics = cdot_lazy( source, target, distance_x, distance_y, cost, 20, ALPHA ) standard_mse, standard_objective = cdot_standard_small( source, target, distance_x, distance_y, cost, 20, ALPHA ) dense_affinity_x = torch.exp(-0.5 * distance_x * distance_x) dense_affinity_y = torch.exp(-0.5 * distance_y * distance_y) dense_affinity_x /= dense_affinity_x.sum(dim=1, keepdim=True) dense_affinity_y /= dense_affinity_y.sum(dim=1, keepdim=True) dense_prior = 1.0 - feature_cost(labels_x, labels_y) dense_prior /= dense_prior.sum(dim=1, keepdim=True) dense_similarity = dense_prior.clone() for _ in range(20): dense_similarity = ( ALPHA * dense_affinity_x @ dense_similarity @ dense_affinity_y.T + (1.0 - ALPHA) * dense_prior ) _, low_rank_diag = isorank( source, target, distance_x, distance_y, labels_x, labels_y, 20, ALPHA, ) categories = sorted(set(labels_x)) left = torch.from_numpy( np.column_stack([labels_x == label for label in categories]).astype( np.float64 ) ) right = torch.from_numpy( np.column_stack([labels_y == label for label in categories]).astype( np.float64 ) ) right /= right.sum(dim=0) blocks_x: list[torch.Tensor] = [] blocks_y: list[torch.Tensor] = [] coefficient = 1.0 for _ in range(20): root = math.sqrt((1.0 - ALPHA) * coefficient) blocks_x.append(root * left) blocks_y.append(root * right) left = dense_affinity_x @ left right = dense_affinity_y @ right coefficient *= ALPHA blocks_x.append(math.sqrt(coefficient) * left) blocks_y.append(math.sqrt(coefficient) * right) low_rank_similarity = torch.cat(blocks_x, dim=1) @ torch.cat( blocks_y, dim=1 ).T permuted_labels = np.roll(labels_y, 10) permuted_cost = feature_cost(labels_x, permuted_labels) normal_mse, _ = cdot_lazy( source, target, distance_x, distance_y, cost, 50, ALPHA ) permuted_mse, _ = cdot_lazy( source, target, distance_x, distance_y, permuted_cost, 50, ALPHA, ) gates = { "lazy_standard_objective_match": abs( float(lazy_diagnostics["final_objective"]) - standard_objective ) < 1e-9, "lazy_standard_mse_match": abs(lazy_mse - standard_mse) < 1e-9, "low_rank_dense_isorank_match": float( torch.max(torch.abs(low_rank_similarity - dense_similarity)) ) < 1e-10, "wrong_feature_control_degrades_mse": permuted_mse > normal_mse + 0.1, "low_rank_residual_bound_finite": math.isfinite( float(low_rank_diag["fixed_point_residual_upper_bound"]) ), } return { "lazy_standard_objective_abs_error": stable( abs( float(lazy_diagnostics["final_objective"]) - standard_objective ) ), "lazy_standard_mse_abs_error": stable(abs(lazy_mse - standard_mse)), "low_rank_dense_isorank_max_abs_error": stable( torch.max(torch.abs(low_rank_similarity - dense_similarity)) ), "normal_feature_mse": stable(normal_mse), "wrong_feature_mse": stable(permuted_mse), "gates": gates, "all_gates_pass": all(gates.values()), } def run(output: Path) -> dict[str, object]: output.mkdir(parents=True, exist_ok=True) controls = parity_and_controls() rows: list[dict[str, object]] = [] context = multiprocessing.get_context("spawn") with ProcessPoolExecutor( max_workers=WORKERS, mp_context=context, initializer=initialize_worker, ) as executor: for completed, trial_rows in enumerate( executor.map(run_trial, range(TRIALS)), 1 ): rows.extend(trial_rows) print( json.dumps( {"claim_3_completed_trials": completed, "trials": TRIALS} ), flush=True, ) by_method = { method: np.asarray( [row["mse"] for row in rows if row["method"] == method], dtype=np.float64, ) for method in METHODS } summaries = { method: { **summarize(values), "paper_mean_mse": PAPER_VALUES[method], "absolute_mean_difference_from_paper": stable( abs(values.mean() - PAPER_VALUES[method]) ), } for method, values in by_method.items() } paired = { baseline: paired_summary(by_method["CDOT"], by_method[baseline]) for baseline in ("FGW", "IsoRank") } ordering_supported = all( comparison["ci95_high"] < 0 for comparison in paired.values() ) integrity_gates = { "paper_scale_n500_per_region": PER_CLUSTER == 500, "paper_total_N2000": PER_CLUSTER * 4 == 2000, "paper_100_trials": TRIALS == 100, "paper_alpha_half": ALPHA == 0.5, "paper_T200": ITERATIONS == 200, "all_300_method_rows_present": len(rows) == 300, "every_trial_method_cell_unique": len( {(row["trial"], row["method"]) for row in rows} ) == 300, "all_mse_finite_nonnegative": all( math.isfinite(float(row["mse"])) and float(row["mse"]) >= 0 for row in rows ), "all_cdot_marginals_certified": max( float(row["marginal_error_certificate"]) for row in rows if row["method"] == "CDOT" ) < 1e-10, "all_fgw_marginals_preserved": max( float(row["marginal_error"]) for row in rows if row["method"] == "FGW" ) < 1e-8, "cdot_optimization_monotone": all( int(row["nonmonotone_steps"]) == 0 for row in rows if row["method"] == "CDOT" ), "fgw_optimization_monotone": all( int(row["nonmonotone_steps"]) == 0 for row in rows if row["method"] == "FGW" ), "parity_and_negative_controls_pass": controls["all_gates_pass"], } status = ( "VERIFIED" if all(integrity_gates.values()) and ordering_supported else "FALSIFIED" if all(integrity_gates.values()) and any(comparison["ci95_low"] > 0 for comparison in paired.values()) else "BLOCKED" ) result = { "claim": 3, "status": status, "claim_contract": { "primary_test": "At n=500 per region (N=2000), the 100-trial CDOT mean MSE is below both FGW and IsoRank at alpha=0.5 and T=200.", "ordering_acceptance": "upper endpoint of each paired two-sided 95% t interval for CDOT-minus-baseline is below zero", "exact_value_audit": "paper and rerun means are reported separately; ordering support is not represented as exact displayed-value agreement", }, "protocol": { "per_cluster_n": PER_CLUSTER, "total_N": 4 * PER_CLUSTER, "trials": TRIALS, "iterations": ITERATIONS, "alpha": ALPHA, "distance_normalization": "each Euclidean distance matrix divided by its maximum", "feature_cost": "0/1 region-label mismatch", "mse": "sample mean squared Euclidean error between X and N*pi@Y", "seed_policy": f"NumPy PCG64 seeds {BASE_SEED}..{BASE_SEED + TRIALS - 1}", "workers": WORKERS, "threads_per_worker": THREADS_PER_WORKER, }, "source_table_2": { "trials": 100, "CDOT": {"mean_mse": 0.0016, "displayed_std": 0.00}, "FGW": {"mean_mse": 0.0034, "displayed_std": 0.00}, "IsoRank": {"mean_mse": 0.0033, "displayed_std": 0.00}, }, "summaries": summaries, "paired_cdot_minus_baseline": paired, "paper_ordering_supported": ordering_supported, "controls": controls, "gates": integrity_gates, "all_gates_pass": all(integrity_gates.values()), "disclosed_deviations": [ "The paper publishes neither code nor random seeds; deterministic independent seeds are registered here.", "The paper leaves the CDOT empirical step-size policy unspecified; exact quadratic line search is used and checked against standard FW.", "The paper does not specify a CDOT initial coupling; the independent product coupling is used.", "POT's disclosed conditional-gradient stopping rule may converge before the maximum T=200 iterations.", "The low-rank IsoRank recurrence is algebraically equivalent to the stated dense recurrence and is parity-checked before use.", ], } (output / "claim_3_trials.json").write_text( json.dumps(rows, indent=2) + "\n", encoding="utf-8" ) (output / "claim_3_negative_controls.json").write_text( json.dumps(controls, indent=2) + "\n", encoding="utf-8" ) (output / "claim_3_result.json").write_text( json.dumps(result, indent=2) + "\n", encoding="utf-8" ) if not result["all_gates_pass"]: failed = [ name for name, passed in integrity_gates.items() if not passed ] raise RuntimeError("Claim 3 integrity gates failed: " + ", ".join(failed)) return result