DROSC — Basque robustness sweep vs the authors’ R

DROSC — Basque robustness sweep vs the authors’ R#

Estimator:

Distributionally Robust Synthetic Control (DROSC)mlsynth.DROSC

Source:

Koo, T. & Guo, Z. (2026). “Distributionally Robust Synthetic Control: Ensuring Robustness Against Highly Correlated Controls and Weight Shifts.” arXiv:2511.02632. Reference code: taehyeonkoo/DRoSC (helpers.R).

Replication type:

cross-validation against the authors’ own R (limSolve::lsei) run live via Rscript, the deterministic worst-case point estimand.

Status:

verified – the estimand and the donor weights match value-for-value.

Benchmark:

benchmarks/cases/drosc_basque.py (source).

Why this case exists#

DROSC’s worst-case estimand is defined by an inequality-constrained optimisation, and the authors solve it with R’s limSolve::lsei. This case confirms that mlsynth’s cvxpy port targets the same optimisation and returns the same effect across the robustness-radius sweep, on the paper’s own empirical application – the Abadie-Gardeazabal Basque Country / ETA-terrorism study (\(T_0 = 15\) pre-periods, \(N = 16\) donor regions, \(T_1 = 28\) post-periods).

The robustness sweep#

As the radius \(\lambda\) grows, the compatible-weight set widens and the effect shrinks from the classical-synthetic-control neighbourhood toward zero. mlsynth reproduces the authors’ DRoSC estimand at every radius:

The classical synthetic-control ATT on the same outcome-only fit is −0.895; the effect is no longer distinguishable from zero once the robustness radius reaches about 0.06. The worst-case estimand matches R to \(\sim 10^{-7}\) at every radius (the residual is cvxpy-vs-lsei solver noise on the shared optimum).

The donor weights#

At \(\lambda = 0\) the moment band is tightest and the weights are pinned; mlsynth reproduces them by name to five decimals:

Donor

mlsynth

R DRoSC

Madrid

0.388

0.388

Baleares

0.274

0.274

Cataluna

0.203

0.203

Principado De Asturias

0.135

0.135

The perturbation union confidence interval (inference=True) is stochastic and seed-dependent – it agrees with the R interval within Monte-Carlo error but is not pinned value-for-value – so the benchmark cross-validates the deterministic estimand, which is exact.

Reproduce#

python benchmarks/run_benchmarks.py --case drosc_basque

The mlsynth side reads basedata/basque_jasa.csv. The reference is the authors’ own code, run live: benchmarks/reference/drosc_basque/reference.R clones github.com/taehyeonkoo/DRoSC (cached), sources its unmodified src/helpers.R, and solves with limSolve::lsei each time the case runs. The case BenchmarkSkippeds when Rscript / limSolve / the clone is unavailable, so a missing R toolchain never reds the suite. Provision the solver with benchmarks/R/install_drosc.sh (limSolve from the GitHub CRAN mirror, since CRAN is firewalled), then run the reference directly with

Rscript benchmarks/reference/drosc_basque/reference.R basedata/basque_jasa.csv