"""Functions for inference under relative magnitudes restrictions."""
from typing import NamedTuple
import numpy as np
import scipy.optimize as opt
from ...arp_no_nuisance import compute_arp_ci
from ...arp_nuisance import compute_arp_nuisance_ci, compute_least_favorable_cv
from ...numba import create_first_differences_matrix
from ...utils import basis_vector
class DeltaRMResult(NamedTuple):
"""Container for relative magnitudes identified set computation results.
Attributes
----------
id_lb : float
Lower bound of the identified set.
id_ub : float
Upper bound of the identified set.
"""
#: Lower bound of the identified set.
id_lb: float
#: Upper bound of the identified set.
id_ub: float
[docs]
def compute_conditional_cs_rm(
betahat,
sigma,
num_pre_periods,
num_post_periods,
l_vec=None,
m_bar=0,
alpha=0.05,
hybrid_flag="LF",
hybrid_kappa=None,
return_length=False,
post_period_moments_only=True,
grid_points=1000,
grid_lb=None,
grid_ub=None,
seed=None,
):
r"""Compute conditional confidence set for :math:`\Delta^{RM}(\bar{M})`.
Computes the confidence set by taking the union over all choices of
reference period :math:`s` and sign restrictions (+)/(-).
The relative magnitudes restriction :math:`\Delta^{RM}(\bar{M})` is defined in
Section 2.4.1 of [2]_ as
.. math::
\Delta^{RM}(\bar{M}) = \{\delta: \forall t \ge 0, |\delta_{t+1} - \delta_t| \le
\bar{M} \cdot \max_{s<0} |\delta_{s+1} - \delta_s|\}.
This restriction formalizes that post-treatment violations of parallel trends are not
excessively larger than pre-treatment violations. As shown in footnote 9 of [2]_,
:math:`\Delta^{RM}(\bar{M})` can be written as a finite union of polyhedra, which allows
for tractable computation.
The confidence set is constructed based on Lemma 2.2 in [2]_, which states that a
valid confidence set for a union of sets is the union of the confidence sets for
each component. Thus, we compute
.. math::
\mathcal{C}_n(\Delta^{RM}(\bar{M})) = \bigcup_{s<0, \text{sign} \in \{+,-\}}
\mathcal{C}_n(\Delta^{RM}_{s, \text{sign}}(\bar{M})),
where :math:`\Delta^{RM}_{s, \text{sign}}(\bar{M})` corresponds to the polyhedron where the
maximum pre-treatment violation occurs at period :math:`s` with a given sign.
Parameters
----------
betahat : ndarray
Estimated event study coefficients.
sigma : ndarray
Covariance matrix of betahat.
num_pre_periods : int
Number of pre-treatment periods.
num_post_periods : int
Number of post-treatment periods.
l_vec : ndarray, optional
Vector defining parameter of interest :math:`\theta = l'\tau_{post}`.
If None, defaults to first post-period, :math:`\tau_{post,1}`.
m_bar : float, default=0
Relative magnitude parameter :math:`\bar{M}`. Controls how much larger
post-treatment violations can be relative to pre-treatment violations.
alpha : float, default=0.05
Significance level.
hybrid_flag : {'LF', 'ARP'}, default='LF'
Type of hybrid test.
hybrid_kappa : float, optional
First-stage size for hybrid test. If None, defaults to alpha/10.
return_length : bool, default=False
If True, return only the length of the confidence interval.
post_period_moments_only : bool, default=True
If True, use only post-period moments for ARP test.
grid_points : int, default=1000
Number of grid points for confidence interval search.
grid_lb : float, optional
Lower bound for grid search. If None, calculated as
gridoff - gridhalf, where gridoff = betahat_post @ l_vec and
gridhalf = (m_bar * sum(1:T_post * |l_vec|) * maxpre) + (20 * sd_theta).
grid_ub : float, optional
Upper bound for grid search. If None, calculated as
gridoff + gridhalf, using the same formula as grid_lb.
seed : int, optional
Random seed for reproducibility.
Returns
-------
dict or float
If return_length is False, returns dict with 'grid' and 'accept' arrays.
If return_length is True, returns the length of the confidence interval.
Notes
-----
The confidence set is constructed using the moment inequality approach from Section 3
of [2]_. Testing :math:`H_0: \theta = \bar{\theta}` for :math:`\delta \in \Delta` is
equivalent to testing a system of moment inequalities with linear nuisance parameters,
as shown in (12) and (13) of [2]_. The conditional and hybrid tests from [1]_ are
used to handle the computational challenge of high-dimensional nuisance parameters by
exploiting the linear structure.
As detailed in footnote 9 of [2]_, :math:`\Delta^{RM}(\bar{M})` is decomposed into a
finite union of polyhedra
.. math::
\Delta^{RM}(\bar{M}) = \bigcup_{s<0} (\Delta_{s,+}^{RM}(\bar{M})
\cup \Delta_{s,-}^{RM}(\bar{M})),
where
.. math::
\Delta_{s,+}^{RM}(\bar{M}) = \{\delta: \forall t \ge 0,
|\delta_{t+1} - \delta_t| \le \bar{M}(\delta_{s+1} - \delta_s)\},
and
.. math::
\Delta_{s,-}^{RM}(\bar{M}) = \{\delta: \forall t \ge 0,
|\delta_{t+1} - \delta_t| \le -\bar{M}(\delta_{s+1} - \delta_s)\}.
References
----------
.. [1] Andrews, I., Roth, J., & Pakes, A. (2021). Inference for linear
conditional moment inequalities. Review of Economic Studies.
.. [2] Rambachan, A., & Roth, J. (2023). A more credible approach to
parallel trends. Review of Economic Studies, 90(5), 2555-2591.
"""
if num_pre_periods < 2:
raise ValueError("Need at least 2 pre-periods for relative magnitudes restriction")
if l_vec is None:
l_vec = basis_vector(1, num_post_periods)
if hybrid_kappa is None:
hybrid_kappa = alpha / 10
if grid_lb is None or grid_ub is None:
sel = slice(num_pre_periods, num_pre_periods + num_post_periods)
l_vec_flat = l_vec.flatten()
sigma_post = sigma[sel, sel]
sd_theta = float(np.sqrt(l_vec_flat @ sigma_post @ l_vec_flat))
if num_pre_periods > 1:
pre_betas_with_zero = np.concatenate([betahat[:num_pre_periods], [0]])
maxpre = float(np.max(np.abs(np.diff(pre_betas_with_zero))))
else:
maxpre = float(np.abs(betahat[0]))
gridoff = float(betahat[sel] @ l_vec_flat)
post_period_indices = np.arange(1, num_post_periods + 1)
gridhalf = float(m_bar * post_period_indices @ np.abs(l_vec_flat) * maxpre) + (20 * sd_theta)
if grid_ub is None:
grid_ub = gridoff + gridhalf
if grid_lb is None:
grid_lb = gridoff - gridhalf
min_s = -(num_pre_periods - 1)
s_values = list(range(min_s, 0))
all_accepts = np.zeros((grid_points, len(s_values) * 2))
col_idx = 0
for s in s_values:
# (+) restriction
cs_plus = _compute_conditional_cs_rm_fixed_s(
s=s,
max_positive=True,
m_bar=m_bar,
betahat=betahat,
sigma=sigma,
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
l_vec=l_vec,
alpha=alpha,
hybrid_flag=hybrid_flag,
hybrid_kappa=hybrid_kappa,
post_period_moments_only=post_period_moments_only,
grid_points=grid_points,
grid_lb=grid_lb,
grid_ub=grid_ub,
seed=seed,
)
all_accepts[:, col_idx] = cs_plus["accept"]
col_idx += 1
# (-) restriction
cs_minus = _compute_conditional_cs_rm_fixed_s(
s=s,
max_positive=False,
m_bar=m_bar,
betahat=betahat,
sigma=sigma,
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
l_vec=l_vec,
alpha=alpha,
hybrid_flag=hybrid_flag,
hybrid_kappa=hybrid_kappa,
post_period_moments_only=post_period_moments_only,
grid_points=grid_points,
grid_lb=grid_lb,
grid_ub=grid_ub,
seed=seed,
)
all_accepts[:, col_idx] = cs_minus["accept"]
col_idx += 1
# Take union: accept if any (s, sign) combination accepts
accept = np.any(all_accepts, axis=1).astype(float)
grid = np.linspace(grid_lb, grid_ub, grid_points)
if return_length:
grid_spacing = np.diff(grid)
grid_lengths = 0.5 * np.concatenate(
[[grid_spacing[0]], grid_spacing[:-1] + grid_spacing[1:], [grid_spacing[-1]]]
)
return np.sum(accept * grid_lengths)
return {"grid": grid, "accept": accept}
[docs]
def compute_identified_set_rm(m_bar, true_beta, l_vec, num_pre_periods, num_post_periods):
r"""Compute identified set for :math:`\Delta^{RM}(\bar{M})`.
Computes the identified set by taking the union over all choices of
reference period s and sign restrictions (+)/(-).
The identified set is computed based on the characterization in Lemma 2.1 of [2]_,
and the decomposition of :math:`\Delta^{RM}(\bar{M})` into a union of polyhedra
as described in Section 2.4.1 and footnote 9 of [2]_. The identified set for
:math:`\Delta^{RM}(\bar{M})` is the union of the identified sets for each component
polyhedron, as stated in (7) of [2]_
.. math::
\mathcal{S}(\beta, \Delta^{RM}(\bar{M})) = \bigcup_{s<0, \text{sign} \in \{+,-\}}
\mathcal{S}(\beta, \Delta^{RM}_{s, \text{sign}}(\bar{M})).
For each fixed :math:`(s, \text{sign})`, the bounds of the identified set
:math:`\mathcal{S}(\beta, \Delta^{RM}_{s, \text{sign}}(\bar{M}))` are obtained by solving
the linear programs from Lemma 2.1
.. math::
\theta^{ub}(\beta, \Delta) = l'\beta_{post} - \min_{\delta} \{l'\delta_{post}
\text{ s.t. } \delta \in \Delta, \delta_{pre} = \beta_{pre}\}
\theta^{lb}(\beta, \Delta) = l'\beta_{post} - \max_{\delta} \{l'\delta_{post}
\text{ s.t. } \delta \in \Delta, \delta_{pre} = \beta_{pre}\}
The final identified set is the interval from the minimum lower bound to the
maximum upper bound across all component polyhedra.
Parameters
----------
m_bar : float
Relative magnitude parameter :math:`\bar{M}`. Controls how much larger
post-treatment violations can be relative to pre-treatment violations.
true_beta : ndarray
True coefficient values :math:`\beta = (\beta_{pre}', \beta_{post}')'`.
l_vec : ndarray
Vector defining parameter of interest :math:`\theta = l'\tau_{post}`.
num_pre_periods : int
Number of pre-treatment periods :math:`\underline{T}`.
num_post_periods : int
Number of post-treatment periods :math:`\bar{T}`.
Returns
-------
DeltaRMResult
Lower and upper bounds of the identified set.
References
----------
.. [1] Andrews, I., Roth, J., & Pakes, A. (2021). Inference for linear
conditional moment inequalities. Review of Economic Studies.
.. [2] Rambachan, A., & Roth, J. (2023). A more credible approach to
parallel trends. Review of Economic Studies, 90(5), 2555-2591.
"""
if num_pre_periods < 2:
raise ValueError("Need at least 2 pre-periods for relative magnitudes restriction")
min_s = -(num_pre_periods - 1)
s_values = list(range(min_s, 0))
all_bounds = []
for s in s_values:
# Compute for (+) restriction
bounds_plus = _compute_identified_set_rm_fixed_s(
s=s,
m_bar=m_bar,
max_positive=True,
true_beta=true_beta,
l_vec=l_vec,
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
)
all_bounds.append(bounds_plus)
# Compute for (-) restriction
bounds_minus = _compute_identified_set_rm_fixed_s(
s=s,
m_bar=m_bar,
max_positive=False,
true_beta=true_beta,
l_vec=l_vec,
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
)
all_bounds.append(bounds_minus)
id_lb = min(b.id_lb for b in all_bounds)
id_ub = max(b.id_ub for b in all_bounds)
return DeltaRMResult(id_lb=id_lb, id_ub=id_ub)
def _create_relative_magnitudes_constraint_matrix(
num_pre_periods,
num_post_periods,
m_bar=1,
s=0,
max_positive=True,
drop_zero_period=True,
):
r"""Create constraint matrix for :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})`.
Creates the matrix :math:`A` for the polyhedral restriction :math:`A\delta \le d`
that defines :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})`.
This function implements the polyhedral decomposition of the relative magnitudes
restriction described in footnote 9 of [2]_. For a fixed reference period :math:`s<0`
and a sign, the constraint is on a specific polyhedron, e.g., for the positive sign
.. math::
\Delta_{s,+}^{RM}(\bar{M}) = \{\delta: \forall t \ge 0,
|\delta_{t+1} - \delta_t| \le \bar{M}(\delta_{s+1} - \delta_s)\}
This inequality is linearized by decomposing the absolute value into two linear
inequalities for each :math:`t \ge 0`
.. math::
\delta_{t+1} - \delta_t - \bar{M}(\delta_{s+1} - \delta_s) \le 0
-(\delta_{t+1} - \delta_t) - \bar{M}(\delta_{s+1} - \delta_s) \le 0
The matrix :math:`A` is constructed by stacking these linear constraints for all
relevant time periods. This formulation allows the problem to be solved using
standard linear programming techniques, as discussed in Section 2.4.5 of [2]_.
Parameters
----------
num_pre_periods : int
Number of pre-treatment periods :math:`\underline{T}`.
num_post_periods : int
Number of post-treatment periods :math:`\bar{T}`.
m_bar : float, default=1
Relative magnitude parameter :math:`\bar{M}`.
s : int, default=0
Reference period for relative magnitudes restriction.
Must be between :math:`-(\underline{T}-1)` and 0.
max_positive : bool, default=True
If True, assumes :math:`\delta_{s+1} - \delta_s \ge 0` (the '+' case);
if False, assumes :math:`\delta_{s+1} - \delta_s \le 0` (the '-' case).
drop_zero_period : bool, default=True
If True, drops period t=0 from the constraint matrix (standard normalization).
Returns
-------
ndarray
Constraint matrix A of shape (n_constraints, n_periods).
References
----------
.. [1] Andrews, I., Roth, J., & Pakes, A. (2021). Inference for linear
conditional moment inequalities. Review of Economic Studies.
.. [2] Rambachan, A., & Roth, J. (2023). A more credible approach to
parallel trends. Review of Economic Studies, 90(5), 2555-2591.
"""
if not -(num_pre_periods - 1) <= s <= 0:
raise ValueError(f"s must be between {-(num_pre_periods - 1)} and 0, got {s}")
total_periods = num_pre_periods + num_post_periods + 1
a_tilde = create_first_differences_matrix(num_pre_periods, num_post_periods)
v_max_diff = np.zeros(total_periods)
v_max_diff[(num_pre_periods + s) : (num_pre_periods + s + 2)] = [-1, 1]
if not max_positive:
v_max_diff = -v_max_diff
# Bounds: 1*v_max_diff for pre-periods, Mbar*v_max_diff for post-periods
a_ub = np.vstack(
[
np.tile(v_max_diff, (num_pre_periods, 1)),
np.tile(m_bar * v_max_diff, (num_post_periods, 1)),
]
)
# Construct A: |a_tilde * delta| <= a_ub * delta
# This becomes: a_tilde * delta <= a_ub * delta and -a_tilde * delta <= -(-a_ub) * delta
A = np.vstack([a_tilde - a_ub, -a_tilde - a_ub])
zero_rows = np.all(np.abs(A) <= 1e-10, axis=1)
A = A[~zero_rows]
if drop_zero_period:
A = np.delete(A, num_pre_periods, axis=1)
return A
def _compute_identified_set_rm_fixed_s(
s,
m_bar,
max_positive,
true_beta,
l_vec,
num_pre_periods,
num_post_periods,
):
r"""Compute identified set for :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})` at fixed s.
Helper function that solves the linear programs defined in Lemma 2.1 of [2]_
for a specific component polyhedron :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})`.
The bounds are given by
.. math::
\theta^{ub} = l'\beta_{post} - \min_{\delta} \{l'\delta_{post} \text{ s.t. }
\delta \in \Delta^{RM}_{s,\text{sign}}(\bar{M}), \delta_{pre} = \beta_{pre}\}
\theta^{lb} = l'\beta_{post} - \max_{\delta} \{l'\delta_{post} \text{ s.t. }
\delta \in \Delta^{RM}_{s,\text{sign}}(\bar{M}), \delta_{pre} = \beta_{pre}\}
The constraint set is a polyhedron, so these are linear programs.
Parameters
----------
s : int
Reference period for relative magnitudes restriction.
m_bar : float
Relative magnitude parameter :math:`\bar{M}`.
max_positive : bool
If True, uses (+) restriction; if False, uses (-) restriction.
true_beta : ndarray
True coefficient values (pre and post periods).
l_vec : ndarray
Vector defining parameter of interest.
num_pre_periods : int
Number of pre-treatment periods.
num_post_periods : int
Number of post-treatment periods.
Returns
-------
DeltaRMResult
Lower and upper bounds of the identified set for the specific polyhedron.
References
----------
.. [1] Andrews, I., Roth, J., & Pakes, A. (2021). Inference for linear
conditional moment inequalities. Review of Economic Studies.
.. [2] Rambachan, A., & Roth, J. (2023). A more credible approach to
parallel trends. Review of Economic Studies, 90(5), 2555-2591.
"""
if l_vec.ndim == 2:
l_vec = l_vec.flatten()
# Objective: min/max l'*delta_post
f_delta = np.concatenate([np.zeros(num_pre_periods), l_vec])
A_rm = _create_relative_magnitudes_constraint_matrix(
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
m_bar=m_bar,
s=s,
max_positive=max_positive,
)
d_rm = _create_relative_magnitudes_constraint_vector(A_rm)
pre_period_equality = np.hstack([np.eye(num_pre_periods), np.zeros((num_pre_periods, num_post_periods))])
A_ineq = A_rm
b_ineq = d_rm
A_eq = pre_period_equality
b_eq = true_beta[:num_pre_periods]
bounds = [(None, None) for _ in range(num_pre_periods + num_post_periods)]
if b_ineq.ndim != 1:
b_ineq = b_ineq.flatten()
if b_eq.ndim != 1:
b_eq = b_eq.flatten()
# Solve linear program: max l'*delta_post subject to delta in Delta_RM and delta_pre = beta_pre
# This gives the lower bound of the identified set: theta_lb = l'*beta_post - max(l'*delta_post)
result_max = opt.linprog(
c=-f_delta,
A_ub=A_ineq,
b_ub=b_ineq,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds,
method="highs",
)
# Solve linear program: min l'*delta_post subject to delta in Delta_RM and delta_pre = beta_pre
# This gives the upper bound of the identified set: theta_ub = l'*beta_post - min(l'*delta_post)
result_min = opt.linprog(
c=f_delta,
A_ub=A_ineq,
b_ub=b_ineq,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds,
method="highs",
)
if not result_max.success or not result_min.success:
observed_val = l_vec.flatten() @ true_beta[num_pre_periods:]
return DeltaRMResult(id_lb=observed_val, id_ub=observed_val)
id_ub = l_vec.flatten() @ true_beta[num_pre_periods:] - result_min.fun
id_lb = l_vec.flatten() @ true_beta[num_pre_periods:] + result_max.fun
return DeltaRMResult(id_lb=id_lb, id_ub=id_ub)
def _compute_conditional_cs_rm_fixed_s(
s,
max_positive,
m_bar,
betahat,
sigma,
num_pre_periods,
num_post_periods,
l_vec,
alpha=0.05,
hybrid_flag="LF",
hybrid_kappa=None,
post_period_moments_only=True,
grid_points=1000,
grid_lb=None,
grid_ub=None,
seed=None,
):
r"""Compute conditional confidence set for :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})` at fixed s.
Implements the moment inequality approach from Section 3 of [2]_ for constructing
a confidence set for a single polyhedron :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})`.
It uses the conditional/hybrid tests from [1]_ to handle nuisance parameters.
Testing :math:`H_0: \theta = \bar{\theta}` for :math:`\delta \in \Delta` is equivalent
to testing a system of moment inequalities with linear nuisance parameters, as
shown in (12) of [2]_
.. math::
H_0: \exists \tau_{\text {post }} \in \mathbb{R}^{\bar{T}} \text { s.t. }
l^{\prime} \tau_{\text {post }}=\bar{\theta} \text { and }
\mathbb{E}_{\hat{\beta}_{n} \sim \mathcal{N}\left(\delta+\tau, \Sigma_{n}\right)}
\left[Y_{n}-A L_{\text {post }} \tau_{\text {post }}\right] \leqslant 0
where :math:`Y_n = A\hat{\beta}_n - d`. This problem is then transformed into the
form of (13) in [2]_ for testing.
Parameters
----------
s : int
Reference period for relative magnitudes restriction.
max_positive : bool
If True, uses (+) restriction; if False, uses (-) restriction.
m_bar : float
Relative magnitude parameter :math:`\bar{M}`.
betahat : ndarray
Estimated event study coefficients.
sigma : ndarray
Covariance matrix of betahat.
num_pre_periods : int
Number of pre-treatment periods.
num_post_periods : int
Number of post-treatment periods.
l_vec : ndarray
Vector defining parameter of interest.
alpha : float, default=0.05
Significance level.
hybrid_flag : {'LF', 'ARP'}, default='LF'
Type of hybrid test.
hybrid_kappa : float, optional
First-stage size for hybrid test. If None, defaults to alpha/10.
post_period_moments_only : bool, default=True
If True, use only post-period moments for ARP test.
grid_points : int, default=1000
Number of grid points for confidence interval search.
grid_lb : float, optional
Lower bound for grid search.
grid_ub : float, optional
Upper bound for grid search.
seed : int, optional
Random seed for reproducibility.
Returns
-------
dict
Dictionary with 'grid' and 'accept' arrays.
Notes
-----
The conditional test from [1]_ addresses the computational challenge of a
:math:`\bar{T}-1` dimensional nuisance parameter by exploiting the linear
structure of the problem. It uses the dual of a linear program to identify
binding moments and conditions on sufficient statistics to eliminate nuisance
parameter dependence. The hybrid test combines this with a least-favorable critical
value to improve power when multiple moments are close to binding.
Theoretical guarantees for this method, including uniform asymptotic size control and
consistency, are provided in Section 3.3 and 3.4 of [2]_. Under the LICQ condition
(Definition 2 in [2]_), the conditional test is shown to have optimal local
asymptotic power (Proposition 3.3 in [2]_).
References
----------
.. [1] Andrews, I., Roth, J., & Pakes, A. (2021). Inference for linear
conditional moment inequalities. Review of Economic Studies.
.. [2] Rambachan, A., & Roth, J. (2023). A more credible approach to
parallel trends. Review of Economic Studies, 90(5), 2555-2591.
"""
if hybrid_kappa is None:
hybrid_kappa = alpha / 10
if hybrid_flag not in ["LF", "ARP"]:
raise ValueError("hybrid_flag must be 'LF' or 'ARP'")
hybrid_list = {"hybrid_kappa": hybrid_kappa}
A_rm = _create_relative_magnitudes_constraint_matrix(
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
m_bar=m_bar,
s=s,
max_positive=max_positive,
)
d_rm = _create_relative_magnitudes_constraint_vector(A_rm)
if post_period_moments_only and num_post_periods > 1:
post_period_indices = list(range(num_pre_periods, num_pre_periods + num_post_periods))
rows_for_arp = []
for i in range(A_rm.shape[0]):
if np.any(A_rm[i, post_period_indices] != 0):
rows_for_arp.append(i)
rows_for_arp = np.array(rows_for_arp) if rows_for_arp else None
else:
rows_for_arp = None
if grid_lb is None or grid_ub is None:
raise ValueError("grid_lb and grid_ub must be provided.")
if num_post_periods == 1:
if hybrid_flag == "LF":
lf_cv = compute_least_favorable_cv(
x_t=None,
sigma=A_rm @ sigma @ A_rm.T,
hybrid_kappa=hybrid_kappa,
seed=seed,
)
hybrid_list["lf_cv"] = lf_cv
# Use no-nuisance CI function
result = compute_arp_ci(
beta_hat=betahat,
sigma=sigma,
A=A_rm,
d=d_rm,
n_pre_periods=num_pre_periods,
n_post_periods=num_post_periods,
alpha=alpha,
hybrid_flag=hybrid_flag,
hybrid_kappa=hybrid_kappa,
lf_cv=hybrid_list.get("lf_cv"),
grid_lb=grid_lb,
grid_ub=grid_ub,
grid_points=grid_points,
)
return {"grid": result.accept_grid[:, 0], "accept": result.accept_grid[:, 1]}
# Multiple post-periods case
result = compute_arp_nuisance_ci(
betahat=betahat,
sigma=sigma,
l_vec=l_vec,
a_matrix=A_rm,
d_vec=d_rm,
num_pre_periods=num_pre_periods,
num_post_periods=num_post_periods,
alpha=alpha,
hybrid_flag=hybrid_flag,
hybrid_list=hybrid_list,
grid_lb=grid_lb,
grid_ub=grid_ub,
grid_points=grid_points,
rows_for_arp=rows_for_arp,
)
return {"grid": result.accept_grid[:, 0], "accept": result.accept_grid[:, 1]}
def _create_relative_magnitudes_constraint_vector(A_rm):
r"""Create constraint vector for :math:`\Delta^{RM}_{s,\text{sign}}(\bar{M})`.
Creates vector :math:`d` such that the constraint
.. math::
\delta \in \Delta^{RM}_{s,\text{sign}}(\bar{M})
can be written as :math:`A \delta \leq d`.
Parameters
----------
A_rm : ndarray
The constraint matrix A.
Returns
-------
ndarray
Constraint vector d (all zeros).
Notes
-----
The constraint vector is a vector of zeros because the relative magnitudes
bound is homogeneous and is incorporated directly into the constraint matrix
:math:`A` in the function :func:`_create_relative_magnitudes_constraint_matrix`.
The resulting system of inequalities is of the form :math:`A\delta \le 0`.
"""
return np.zeros(A_rm.shape[0])