Relaxed / Penalized Synthetic Control (RESCM)#
When to Use This Estimator#
The classic synthetic control method (SCM) of Abadie, Diamond and Hainmueller [ABADIE2010] builds a counterfactual as a convex combination of donor units – weights on the simplex, no intercept. That convex hull restriction is what makes SCM interpretable and robust, but it is also brittle when the donor pool is large relative to the pre-period: with \(N\) donors and only \(T_1\) pre-periods, the least-squares fit underlying SCM is under-determined once \(N \gtrsim T_1\), the weights become unstable, and many “equally good” solutions exist.
RESCM is a single convex program that nests a whole family of SCM
estimators as corner cases, so you can dial the donor-pool regularization
continuously from classic SCM all the way to difference-in-differences (equal
weights), and pick the corner that suits your data. Two branches are exposed,
each with the estimation/inference theory of its own paper:
Penalized branch – \(\min \tfrac{1}{2}\|y_0 - \mu - Y\omega\|_2^2 + P(\omega)\) with \(P\) an \(\ell_1\) / \(\ell_2\) / \(\ell_\infty\) (or mixed) penalty. The \(\ell_\infty\) member is the L-infinity-norm SCM of Wang, Xing and Ye [LinfSC], which spreads weight across donors (capping the largest weight) rather than concentrating it; classic Abadie SCM is the no-penalty (\(\lambda = 0\)) simplex corner, and equal-weights/DiD is the heavy-\(\ell_\infty\) limit [DoudchenkoImbens2017].
Relaxation branch – the SCM-relaxation of Liao, Shi and Zheng [RelaxSC]: keep the simplex \(\omega \in \Delta_J\), but relax the exact balance first-order condition to an \(\ell_\infty\) tolerance \(\eta\), then among all weights satisfying the relaxed condition pick the one minimizing an information-theoretic divergence \(D(\omega)\) (squared \(\ell_2\), entropy, or empirical likelihood). The divergence picks a unique, stable weight (e.g. closest-to-uniform), and under a latent group structure recovers equal-within-group weights.
Use RESCM when you have a single treated unit and a large donor pool
and want either (i) a dense, stable counterfactual that does not hinge on a
handful of donors (LINF / RELAX_*), or (ii) a one-stop interface to
compare classic SCM against its penalized and relaxed cousins on the same
panel. Pick estimators by name through methods.
Notation#
We use the synthetic-control canon. Unit \(j=0\) is treated and \(\mathcal{N} = \{1, \ldots, N\}\) indexes the donors; \(\mathbf{y}_0\) is the treated outcome and \(\mathbf{Y} = (y_{jt})\) the \(T \times N\) donor matrix. The intervention occurs after the pre-period \(\mathcal{T}_1 = \{1, \ldots, T_1\}\); the post-period is \(\mathcal{T}_2 = \{T_1+1, \ldots, T\}\) with \(T_2 = |\mathcal{T}_2|\). Donor weights \(\boldsymbol{\omega} = (\omega_1, \ldots, \omega_N)'\) live on the simplex \(\Delta_N = \{\boldsymbol{\omega} : \omega_j \ge 0,\, \sum_j \omega_j = 1\}\) (relaxation branch) or are penalized (penalized branch); \(\mu\) is an optional intercept. The counterfactual is \(\hat{y}_{0t}^0 = \mu + \mathbf{Y}_{t\cdot}\,\boldsymbol{\omega}\), the effect \(\hat{\Delta}_t = y_{0t} - \hat{y}_{0t}^0\), and the ATE \(\bar{\Delta} = T_2^{-1}\sum_{t\in\mathcal{T}_2}\hat{\Delta}_t\). \(\|\mathbf{A}\|_\infty = \max_{ij}|a_{ij}|\). With pre-period donor Gram matrix \(\hat{\boldsymbol{\Sigma}} = T_1^{-1}\sum_{t\in\mathcal{T}_1} \mathbf{Y}_{t\cdot}'\mathbf{Y}_{t\cdot}\) and cross-moment \(\hat{\boldsymbol{\Upsilon}} = T_1^{-1}\sum_{t\in\mathcal{T}_1} \mathbf{Y}_{t\cdot}' y_{0t}\), the SCM least-squares first-order condition is \(\hat{\boldsymbol{\Sigma}}\boldsymbol{\omega} = \hat{\boldsymbol{\Upsilon}}\).
The unified convex program#
Every RESCM corner case is a special case of one program. The
penalized form fits the in-sample loss with a regularizer,
with constraint set \(\mathcal{C}\) (simplex by default), mixing \(\alpha\in[0,1]\), and strength \(\lambda\) chosen by K-fold cross-validation. The relaxation form instead minimizes a divergence subject to a relaxed balance condition,
where the three divergences are squared \(\ell_2\), entropy, and empirical likelihood respectively, and \(\eta\) is selected by validation. Classic SCM is recovered at \(\lambda=0\) (penalized, simplex) or \(\eta = 0\) (relaxation); the equal-weights/DiD estimator is the \(\lambda\to\infty\) \(\ell_\infty\) limit or large-\(\eta\) limit.
Penalized branch: L-infinity SCM (Wang, Xing & Ye)#
Idea. Classic SCM tends to load heavily on one or two donors. When the
true data-generating process spreads signal across many donors, that
concentration is fragile – a single idiosyncratic donor shock contaminates the
counterfactual. The \(\ell_\infty\) penalty
\(\|\boldsymbol{\omega}\|_\infty = \max_j |\omega_j|\) directly caps the
largest weight, so minimizing it (under the simplex) pushes the solution
toward spreading weight across donors – in the limit, equal weights. The
mixed \(\ell_1 + \ell_\infty\) penalty (L1LINF) trades sparsity against
spreading. Both shrink toward a stable, dense counterfactual that tolerates
\(N > T_1\).
Assumptions (Wang, Xing & Ye [LinfSC]).
Assumption 1 (factor model). The outcomes follow a linear factor model \(y_{jt} = \boldsymbol{\lambda}_j'\mathbf{f}_t + u_{jt}\) with the treated unit’s loading in (or near) the convex hull of the donor loadings, so an \(\ell_\infty\)-regularized convex combination approximates the treated factor structure.
Assumption 2 (weak dependence). The idiosyncratic errors \(u_{jt}\) are mean-zero and weakly dependent over time (strong-mixing with summable autocovariances), permitting a HAC long-run variance and a sequential CLT for the post-period mean.
Assumption 3 (regularization rate). The penalty strength \(\lambda\) (and mixing \(\alpha\)) are chosen so the weight estimate is consistent for its population target; in practice both are selected by K-fold cross-validation on the pre-period.
Remark. The \(\ell_\infty\) cap is what buys stability in high dimensions: by refusing to let any single weight dominate, the estimator’s prediction variance stays controlled even when \(N \gg T_1\), where the unconstrained least-squares (and concentrated classic SCM) solutions degrade. This is the synthetic-control analogue of the L2-relaxation argument for dense coefficients.
Inference. Wang, Xing and Ye extend the synthetic-control ATE inference of Li [LiSCM2020] to the dense, weakly dependent setting. With fixed weights the post-period gap \(\hat{\Delta}_t\) has mean \(\bar{\Delta}\), and
with \(\hat{\rho}^2_{(1)}\) the HAC long-run variance of the pre-period
prediction residuals (first-stage weight-estimation uncertainty) and
\(\hat{\rho}^2_{(2)}\) that of the de-meaned post-period effects. This is
the two-term variance RESCM uses for all corner cases (see Inference
below).
When to use. Dense, factor-driven donor structure; high dimension (\(N>T_1\) permitted); when you want a counterfactual robust to any single donor’s idiosyncrasies rather than a sparse, concentrated fit.
Relaxation branch: SCM-relaxation (Liao, Shi & Zheng)#
Idea. Classic SCM solves a least-squares problem whose first-order condition is \(\hat{\boldsymbol{\Sigma}}\boldsymbol{\omega} = \hat{\boldsymbol{\Upsilon}}\) – exact balance of the donor moments. When \(N\) is large this condition is over-strict: it is satisfied (or nearly so) by a continuum of weights, and the chosen one is arbitrary and unstable. SCM-relaxation keeps the simplex but relaxes the balance condition to an \(\ell_\infty\) tolerance \(\eta\), then selects, among all admissible weights, the one minimizing a divergence \(D(\boldsymbol{\omega})\):
The divergence breaks the tie deterministically: \(\ell_2\)
(RELAX_L2) picks the minimum-norm weight, entropy (RELAX_ENTROPY)
the maximum-entropy / closest-to-uniform weight, and empirical likelihood
(RELAX_EL) the EL-optimal weight. \(\eta=0\) reduces to (a divergence-
selected) classic SCM; large \(\eta\) admits the whole simplex and the
divergence alone determines the weight (entropy/EL \(\to\) equal weights).
Assumptions (Liao, Shi & Zheng [RelaxSC]).
Assumption 1 (approximate factor model with group structure). Units load on common factors and fall into latent groups; the treated unit’s loading is spanned by the donor loadings up to an approximation error that the relaxation tolerance \(\eta\) absorbs.
Assumption 2 (identifiable divergence minimizer). The divergence \(D\) is strictly convex on the simplex, so the relaxed feasible set has a unique minimizer – this is what restores well-posedness when the exact balance condition does not.
Assumption 3 (weak dependence and moments). The errors are weakly dependent with bounded moments, so sample moments \(\hat{\boldsymbol{\Sigma}}, \hat{\boldsymbol{\Upsilon}}\) converge and a post-period CLT applies.
Remark. The key result (oracle prediction) is that the relaxed weight predicts the treated counterfactual as well as the infeasible oracle that knows the factor structure, and under the latent group structure the divergence- selected weight is equal within groups – a transparent, interpretable solution that the arbitrary classic-SCM tie-break does not deliver.
Inference. Liao, Shi and Zheng’s main theory concerns prediction
consistency (the oracle property). For an ATE confidence interval mlsynth
applies the same weak-dependence two-term HAC test as the penalized branch
(Li [LiSCM2020]), treating the relaxed weights as the fixed first stage. See
the caveat under Verification.
The named corner cases#
methods selects estimators by name; each resolves to one exact call of the
convex engine.
Name |
Branch |
Estimator |
|---|---|---|
|
penalized |
Classic Abadie simplex SCM (\(\lambda=0\)). |
|
penalized |
\(\ell_1\) penalty; sparse donor weights. |
|
penalized |
\(\ell_2\) penalty; dense shrunken weights. |
|
penalized |
Elastic net (\(\ell_1 + \ell_2\)); \(\alpha\) by CV. |
|
penalized |
L-infinity-norm SCM [LinfSC]; spreads weight, nests DiD. |
|
penalized |
Mixed \(\ell_1 + \ell_\infty\) penalty. |
|
relaxation |
SCM-relaxation, \(\ell_2\) divergence [RelaxSC]. |
|
relaxation |
SCM-relaxation, entropy divergence. |
|
relaxation |
SCM-relaxation, empirical-likelihood divergence. |
Inference#
Once the donor weights are fixed by any corner case, the post-period gap
\(\hat{\Delta}_t = y_{0t} - \hat{y}_{0t}^0\) is a scalar series whose mean
is the ATE. RESCM reports the Li [LiSCM2020] two-term long-run variance
used by the L-infinity paper,
where \(\hat{\rho}^2_{(1)}\) is the HAC long-run variance of the pre-period prediction residuals – carrying the first-stage weight-estimation uncertainty – and \(\hat{\rho}^2_{(2)}\) is the HAC long-run variance of the de-meaned post-period effects (Bartlett kernel, \(\lfloor T_2^{1/4}\rfloor\) lag). The pre-period term is essential for dense penalized/relaxed weights: unlike forward-selection PDA – whose sample splitting makes pre/post asymptotically independent and lets a post-only variance suffice – the dense estimators reuse the entire pre-window to fit the weights, so dropping \(\hat{\rho}^2_{(1)}\) understates the standard error.
Empirical Illustration: California’s Proposition 99#
The canonical synthetic-control application [ABADIE2010] studies the effect of
California’s 1988 Proposition 99 tobacco-control program on per-capita cigarette
sales, with 38 control states over 1970-2000. Running RESCM with the
classic SC corner alongside the two papers’ headline estimators reuses the
same panel and returns each method’s counterfactual, ATE, and a HAC confidence
interval.
import pandas as pd
from mlsynth import RESCM
url = "https://raw.githubusercontent.com/jgreathouse9/mlsynth/refs/heads/main/basedata/smoking_data.csv"
df = pd.read_csv(url)
df["Proposition 99"] = df["Proposition 99"].astype(int)
res = RESCM({"df": df, "outcome": "cigsale", "treat": "Proposition 99",
"unitid": "state", "time": "year",
"methods": ["SC", "LINF", "RELAX_L2"], "alpha": 0.05,
"display_graphs": True}).fit()
for name, fit in res.fits.items():
print(f"{name:9s} ATE {fit.att:8.3f} SE {fit.att_se:6.3f} "
f"95% CI ({fit.ci[0]:7.2f},{fit.ci[1]:7.2f}) "
f"p={fit.p_value:.3f} donors={len(fit.donor_weights)}")
This prints:
SC ATE -17.371 SE 2.304 95% CI ( -21.89, -12.86) p=0.000 donors=6
LINF ATE -17.359 SE 2.303 95% CI ( -21.87, -12.85) p=0.000 donors=6
RELAX_L2 ATE -22.190 SE 4.115 95% CI ( -30.26, -14.12) p=0.000 donors=7
The penalized corners (SC, LINF) agree on an outcome-only effect of
about \(-17\) cigarette packs per capita: when the donor pool fits the
pre-period well (pre-treatment \(R^2 \approx 0.98\), pre-RMSE
\(1.45\)), cross-validation picks near-zero \(\ell_\infty\) shrinkage
and LINF collapses onto classic SCM. The relaxation corner (RELAX_L2)
trades a little pre-fit (\(R^2 \approx 0.97\)) for a more constrained
weight and lands at a larger effect, \(-22.2\), whose wider confidence
interval reflects the larger pre-period residual variance the two-term standard
error correctly propagates.
Verification#
Note
Empirical (Path A, Proposition 99). SC/LINF/RELAX_L2 run on
the smoking panel (above); the penalized corners reproduce the classic
outcome-only SCM effect (\(\approx -17\)) and the relaxation corner the
denser, larger estimate, both significant and consistent with the
literature.
Simulation (Path B, high dimension). A size/power Monte Carlo in the
regime these methods target – \(N=90\) donors, \(T_1=36\)
pre-periods (\(N \gg T_1\)), \(T_2=36\), three-factor AR(1) DGP,
treated loading a convex mix of donors, idiosyncratic \(N(0,1)\) –
rejecting H0: ATE = 0 at 5% (50 replications; \(\delta=0\) is size,
\(\delta=1\) is power):
\(\delta\) |
|
|
|
|---|---|---|---|
0 (size) |
0.20 |
0.22 |
0.28 |
1 (power) |
0.98 |
0.98 |
0.94 |
Estimation is unbiased and powerful (mean ATT bias \(\approx 0\);
power \(0.94\)-\(0.98\)), but the analytic ATE test over-rejects
in this short, high-dimensional panel. A diagnostic isolates two mechanisms:
SC genuinely over-fits the 36-period pre-window (pre-RMSE \(0.77\) vs
the true noise sd \(1.0\)), so \(\hat{\rho}^2_{(1)}\) understates
estimation uncertainty – it self-corrects as \(T_1\) grows (pre-RMSE
\(\to 0.94\) at \(T_1=250\)); RELAX_L2 does not over-fit
(pre-RMSE \(\approx 1.0\)), so its over-rejection reflects the analytic
influence function not capturing how strongly-relaxed dense weights feed into
the ATE. Both papers’ asymptotics require \(T_1\) large relative to
\(N\); the normal approximation is unreliable at \(T_1 < N\), and
the two-term variance (which already cuts the post-only over-rejection by
\(\sim 35\%\)) does not fully close the gap here. For honest
finite-sample inference in this regime, prefer a placebo / conformal
procedure [CWZ2021]. (Only 50 replications – noisy; the relaxation
\(\eta\) is validated by CV, not fixed.)
Core API#
Relaxed/penalized synthetic control (RESCM).
A thin, NumPy-first orchestration over mlsynth.utils.laxscm_helpers.
RESCM is a single convex synthetic-control program that nests a family of
estimators as corner cases, selected by name:
penalized branch – classic
SC,LASSO,RIDGE,ENETand the L-infinity-norm SCM (LINF/L1LINF) of Wang, Xing & Ye (2025):min ||y0 - mu - Y omega||^2 + P(omega).relaxation branch –
RELAX_L2/RELAX_ENTROPY/RELAX_EL, the SCM-relaxation of Liao, Shi & Zheng (2026): keep the simplex and relax the exact balance first-order condition to an L-infinity tolerance, then minimise an information-theoretic divergence.
Pick estimators with methods; the first one drives the convenience aliases
on the returned RESCMResults.
- class mlsynth.estimators.laxscm.RESCM(config: RESCMConfig | dict)#
Bases:
objectRelaxed/penalized synthetic-control estimator.
- Parameters:
config (RESCMConfig or dict) – Validated configuration. Beyond the common fields, RESCM reads
methods(which named corner cases to fit),alpha(CI level),tau/n_splits/n_taus(relaxation-branch CV controls), andsolver.
References
Liao, C., Shi, Z., & Zheng, Y. (2026). A Relaxation Approach to Synthetic Control. arXiv:2508.01793.
Wang, L., Xing, X., & Ye, Y. (2025). An L-infinity Norm Synthetic Control Approach. arXiv:2510.26053.
Abadie, A., Diamond, A., & Hainmueller, J. (2010). Synthetic control methods for comparative case studies. JASA, 105(490), 493-505.
Doudchenko, N., & Imbens, G. W. (2017). Balancing, Regression, Difference-in-Differences and Synthetic Control Methods: A Synthesis. arXiv:1610.07748.
- fit() RESCMResults#
Fit the requested RESCM corner case(s) and return typed results.
- Returns:
RESCMResults – Container of per-method
RESCMMethodFitobjects (donor weights, counterfactual, gap, ATE, HAC standard error, CI, p-value), with convenience aliases (att,att_se,counterfactual,donor_weights) forwarding to the first requested method.
Configuration#
- class mlsynth.config_models.RESCMConfig(*, df: ~pandas.DataFrame, outcome: str, treat: str, unitid: str, time: str, display_graphs: bool = True, save: bool | str = False, counterfactual_color: ~typing.List[str] = <factory>, treated_color: str = 'black', methods: ~typing.List[str] = <factory>, tau: float | ~typing.Literal['heuristic'] | None = None, n_splits: ~typing.Annotated[int | None, ~annotated_types.Ge(ge=2)] = None, n_taus: ~typing.Annotated[int | None, ~annotated_types.Ge(ge=1)] = None, solver: ~typing.Any = 'CLARABEL', alpha: ~typing.Annotated[float, ~annotated_types.Gt(gt=0.0), ~annotated_types.Lt(lt=1.0)] = 0.05)#
Configuration for the Relaxed/Balanced SCM (RESCM) estimator.
Pick one or more named corner-case estimators of the RESCM convex program via
methods(e.g.["SC", "LINF", "RELAX_L2"]); the first listed drives the convenience aliases on the returnedRESCMResults. Valid names and aliases come from the registry inmlsynth.utils.laxscm_helpers.specs(METHOD_SPECS).- model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'forbid'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Result Containers#
RESCM.fit() returns a
RESCMResults, whose fits
maps each named corner case to a
RESCMMethodFit (donor weights,
intercept, counterfactual, gap, ATE, two-term HAC standard error, CI, p-value,
nonzero donor weights, fit diagnostics, and the realized hyperparameters).
Convenience aliases (att, att_se, counterfactual,
donor_weights) forward to the first requested method. The prepared,
NumPy-only panel is exposed as a
RESCMInputs, with units and
time addressed through an IndexSet.
Frozen, NumPy-first containers for the Relaxed/penalized SCM engine (RESCM).
RESCM is a single convex synthetic-control program that nests a family of estimators as corner cases. Two branches are exposed, each with its own source paper:
relaxed – SCM-relaxation (Liao, Shi & Zheng 2026): keep the simplex
omega in Delta_Jand relax the exact balance first-order condition to an L-infinity tolerance, then minimise an information-theoretic divergenceD(omega)(l2/entropy/el).elastic – penalized SCM:
min ||y0 - mu - Y omega||^2 + P(omega)withPan L1 / L2 / L-infinity (or mixed) penalty. The L-infinity branch is the L-infinity-norm SCM of Wang, Xing & Ye (2025); classic Abadie SC is thelambda = 0simplex corner.
Everything below is pure NumPy; units/time are addressed through
IndexSet. The only DataFrame touchpoint is setup.
- class mlsynth.utils.laxscm_helpers.structures.RESCMInputs(unit_index: ~mlsynth.utils.fast_scm_helpers.structure.IndexSet, time_index: ~mlsynth.utils.fast_scm_helpers.structure.IndexSet, y: ~numpy.ndarray, X: ~numpy.ndarray, T0: int, treated_label: ~typing.Any, metadata: ~typing.Dict[str, ~typing.Any] = <factory>)#
Bases:
objectPreprocessed, NumPy-only panel for the RESCM engine.
- Parameters:
unit_index (IndexSet) – All
Ndonor units (column order ofX).time_index (IndexSet) – All
Tperiods (row order ofyandX).y (np.ndarray) – Treated-unit outcome over all periods, shape
(T,).X (np.ndarray) – Donor outcomes, shape
(T, N).T0 (int) – Number of pre-treatment periods (
T1); post isT2 = T - T0.treated_label (Any) – Identifier of the treated unit.
metadata (dict) – Free-form provenance.
- X: ndarray#
- property donor_labels: ndarray#
- time_index: IndexSet#
- unit_index: IndexSet#
- y: ndarray#
- class mlsynth.utils.laxscm_helpers.structures.RESCMMethodFit(name: str, branch: str, display_name: str, weights: ~numpy.ndarray, intercept: float, counterfactual: ~numpy.ndarray, gap: ~numpy.ndarray, att: float, att_se: float, ci: ~typing.Tuple[float, float], p_value: float, donor_weights: ~typing.Dict[~typing.Any, float], fit_diagnostics: ~typing.Dict[str, ~typing.Any] = <factory>, hyperparameters: ~typing.Dict[str, ~typing.Any] = <factory>, metadata: ~typing.Dict[str, ~typing.Any] = <factory>)#
Bases:
objectA single RESCM corner-case fit (e.g.
SC/LINF/RELAX_L2).- counterfactual: ndarray#
- gap: ndarray#
- weights: ndarray#
- class mlsynth.utils.laxscm_helpers.structures.RESCMResults(inputs: ~mlsynth.utils.laxscm_helpers.structures.RESCMInputs, fits: ~typing.Dict[str, ~mlsynth.utils.laxscm_helpers.structures.RESCMMethodFit], selected_variant: str, metadata: ~typing.Dict[str, ~typing.Any] = <factory>)#
Bases:
objectTop-level container returned by
mlsynth.RESCM.fit().- property counterfactual: ndarray#
- fits: Dict[str, RESCMMethodFit]#
- property gap: ndarray#
- inputs: RESCMInputs#
Helper Modules#
The named-estimator registry: each entry maps a name to the exact convex-engine call (branch and keyword arguments).
Named corner-case estimators of the RESCM convex program.
The legacy API exposed a nested models_to_run dict where the caller had to
know which second_norm / relaxation / constraint_type / alpha
combination realised which estimator. This module replaces that with a flat
registry of named estimators: the user picks methods by name and each name
resolves to the exact engine call.
Every spec dispatches to one of two engine entry points
(mlsynth.utils.laxscm_helpers.crossval.fit_relaxed_scm() or fit_en_scm); kwargs
are forwarded verbatim.
- class mlsynth.utils.laxscm_helpers.specs.MethodSpec(name: str, branch: str, description: str, kwargs: ~typing.Dict[str, ~typing.Any] = <factory>)#
How a named RESCM estimator maps onto the convex engine.
- mlsynth.utils.laxscm_helpers.specs.normalize_method(name: str) str#
Map a user-supplied method name to a registry key (case-insensitive).
- mlsynth.utils.laxscm_helpers.specs.resolve_specs(methods) list[MethodSpec]#
Return the ordered list of
MethodSpecfor the requested names.
Data preparation – the only DataFrame touchpoint: pivots to NumPy, builds the unit/time ``IndexSet``es, and splits pre/post.
Long-DataFrame -> NumPy boundary for RESCM (the only pandas touchpoint).
- mlsynth.utils.laxscm_helpers.setup.derive_treatment(df: DataFrame, unitid: str, time: str, treat: str) Tuple[Any, Any]#
Read the single treated unit and its first treated period from
treat.
- mlsynth.utils.laxscm_helpers.setup.prepare_rescm_inputs(df: DataFrame, *, unitid: str, time: str, outcome: str, treat: str) RESCMInputs#
Pivot the panel to NumPy, build
IndexSets, split pre/post.- Returns:
RESCMInputs – Pure-NumPy container: treated vector
y(T,), donor matrixX(T, N),T0, and unit/timeIndexSets.
The weak-dependence two-term HAC ATE inference (Li 2020).
Weak-dependence ATE inference for the RESCM counterfactual.
Once donor weights are fixed, the post-treatment gap d_t = y_t - yhat_t has
mean equal to the ATE. Under weak dependence (Li 2020, extended to dense weights
by Wang, Xing & Ye 2025), the ATE is asymptotically normal,
Z = ATE / sqrt( rho1^2 / T1 + rho2^2 / T2 ) -> N(0, 1),
with rho1^2 the HAC long-run variance of the pre-period prediction
residuals (which carries the first-stage weight-estimation uncertainty) and
rho2^2 the HAC long-run variance of the de-meaned post-period effects.
The pre-period term is essential for dense penalized/relaxed weights: unlike
forward selection (whose sample splitting makes the pre/post periods
asymptotically independent and lets a post-only variance suffice), the dense
estimators reuse the whole pre-window to fit the weights, so ignoring rho1
severely understates the standard error when N is large relative to T1.
- mlsynth.utils.laxscm_helpers.inference.ate_inference(gap: ndarray, T0: int, alpha: float = 0.05) Tuple[float, float, Tuple[float, float], float]#
Return
(att, se, ci, p_value)for the post-period gap mean.Uses the Li (2020) two-term long-run variance: pre-period residual LRV (estimation uncertainty) plus de-meaned post-period effect LRV.
Run loop dispatching each corner case to the convex engine and assembling the typed per-method fits.
Run the requested RESCM corner case(s) and assemble typed per-method fits.
- mlsynth.utils.laxscm_helpers.estimation.run_rescm(inputs: RESCMInputs, methods: List[str], *, tau: float | None = None, n_splits: int | None = None, n_taus: int | None = None, solver: str = 'CLARABEL', alpha: float = 0.05) Dict[str, RESCMMethodFit]#
Fit each requested RESCM corner case and attach weak-dependence ATE inference.