Identification-robust moment confidence sets
The structural module now offers point-null moment tests and continuous confidence sets for an explicitly supported scalar moment class. These methods do not invert a model Jacobian or presume that parameters are strongly identified. They complement local structural inference and simulation-aware SMM.
The point-null test
For empirical moments \(\hat m\), model moments \(m(\theta)\), and the full covariance \(V(\theta)\) of their difference, the statistic is
Compare it with a chi-square cutoff with q degrees of freedom, where q is the number of moments. Do not subtract the minimized objective or use q-p. The covariance is already on the estimator scale; no sample-size multiplier is applied. This implements a full point-null S-test in the spirit of Stock and Wright (2000), Theorem 2.
import numpy as np
from puremacro.structural import (
MomentTargets, QuadraticMomentCurve, moment_restriction_test,
invert_quadratic_moments, evaluate_moment_grid,
SimulationPlan, simulate_moments, simulated_moment_test,
)
targets = MomentTargets([1.0], [[0.01]], ("second",))
point = moment_restriction_test(targets, [1.0], calibration="gaussian_known")
assert point.valid and point.accepted and point.df == 1
print(point.to_frame())
calibration="gaussian_known" is the caller's assertion that the null moment
error is exactly Gaussian with known positive definite covariance. It is not
automatically verified. Default calibration="asymptotic" instead requires a
valid null CLT and consistent covariance estimation; uniform validity requires
corresponding uniform assumptions. Neither checks correct specification.
Supply covariance=... only for the total moment-error covariance at the
candidate, including any model/simulation uncertainty. An arbitrary fitting
weight or a covariance evaluated at a potentially inconsistent estimate is not
a substitute. This first interface does not concentrate nuisance parameters;
all parameters entering a point-null prediction must be specified.
valid means numerically evaluated. Nonfinite model moments and singular or
numerically singular covariance produce unresolved results with accepted=None.
No pseudoinverse or ridge silently changes the test. Malformed labels, shapes
or non-PSD matrices raise errors. Report exports retain the status.
Continuous inversion for a declared scalar curve
QuadraticMomentCurve supports
Here a and b are q-vectors, while the common index has scalar coefficients
linear and quadratic. This is a limited class, not an arbitrary vector of
quadratic functions. Covariance must be constant across the candidate parameter.
The interface solves for the accepted index interval and pulls it back through
the declared curve, preserving every numerically resolved component.
curve = QuadraticMomentCurve(
[0.0], [1.0], targets.labels, "gaussian-theta-squared-v1",
linear=0.0, quadratic=1.0,
)
confidence = invert_quadratic_moments(curve, targets, calibration="gaussian_known")
assert confidence.resolved and len(confidence.intervals) == 2
assert confidence.contains(-1.0) and confidence.contains(1.0)
assert not confidence.contains(0.0)
print(confidence.summary())
print(confidence.to_frame())
clipped = invert_quadratic_moments(
curve, targets, domain=(0.0, 1.0), calibration="gaussian_known",
)
assert clipped.truncated and clipped.boundary_contact == (False, True)
The example's set has two separate components near -1 and +1. It must not be replaced by the interval between its outermost endpoints. The solver also retains singleton tangencies, empty sets and the entire real line. An exactly zero loading does not imply a narrow interval: the maintained moment test may accept every parameter or reject all of them. Small nonzero loadings are never silently set to zero using an identification threshold.
global_intervals records the analytic inverse image; intervals intersects
it with the declared domain, a search/display window. global_geometry
distinguishes bounded, unbounded/all-real, empty and unresolved cases.
truncated indicates that some global set lies outside the window, even if
the displayed set is empty. boundary_contact describes accepted finite
window endpoints. Unboundedness follows from the declared curve, not merely
from reaching a grid boundary. Coverage inside a window requires the true
parameter to lie in that prespecified window.
resolved concerns floating-point geometry. Ambiguous near-tangencies,
unrepresentable roots and failed endpoint residual checks remain unresolved;
contains() then raises an error. It does not turn numerical uncertainty into
an empty set. summary() reports status separately from component rows.
Markdown, LaTeX and Typst exports include status even for empty/unresolved sets.
General models: sampled hypotheses remain sampled
evaluate_moment_grid accepts a callback returning MomentTestResult. It can
use a different covariance at every candidate and retain invalid evaluations.
It intentionally provides no continuous-completeness claim.
test_at = lambda theta: moment_restriction_test(
targets, curve(theta), calibration="gaussian_known",
)
sampled = evaluate_moment_grid(test_at, [-1.5, 0.0, 1.5])
assert not sampled.accepted.any()
assert not sampled.continuous_complete
assert len(sampled.unresolved_cells()) == 2
assert len(confidence.intervals) == 2
All three sampled points reject, although the continuous set has two accepted
islands. accepted_runs() therefore reports sampled node spans, not
confidence-interval endpoints. Every between-node cell remains unresolved,
including cells whose two endpoints reject. Invalid nodes carry accepted=None
in the table and separate accepted runs. Unexpected callback exceptions
propagate instead of becoming rejection penalties. All nodes must share alpha,
moment labels, q and calibration.
Simulation uncertainty at the hypothesized parameter
The simulation adapter uses the actual joint batch at the candidate, including its counts/statuses and full simulated-mean covariance. Assert independence from data or supply cross-covariance as in the SMM interface.
empirical = MomentTargets([0.3], [[0.02]], ("mean",))
plan = SimulationPlan(seed=618, replications=64, periods=20)
def simulator(theta, rng, budget):
return [theta[0] + (1 + theta[0]**2) * rng.normal(size=budget.periods).mean()]
def simulated_test_at(theta):
batch = simulate_moments(
simulator, [theta], plan, labels=empirical.labels,
definition_id="heteroskedastic-normal-mean-v1",
)
return simulated_moment_test(empirical, batch, assume_independent=True)
simulation_grid = evaluate_moment_grid(simulated_test_at, np.linspace(-0.5, 1.0, 31))
assert simulation_grid.valid.all()
assert simulation_grid.tests[0].calibration == "asymptotic"
print(simulation_grid.accepted_runs())
The total covariance is recomputed at each hypothesis; the example deliberately
changes simulation variance with the parameter. A fixed SMM pilot weight is
never reused as the test covariance. simulated_moment_test offers only
asymptotic calibration because covariance estimated from a finite number of
replications does not yield an exact chi-square pivot. Invalid batches remain
attached to unresolved results. Burn-in, finite-horizon-statistic and
discretization biases still require separate control.
Interpretation and validation limits
The null maintains both the parameter and the moment restrictions. Empty sets
can reflect incompatible moments; a small set alone does not establish precise
identification. There is no automatic weak-identification diagnostic, arbitrary
nuisance profiling, conditional QLR test, singularity-robust inference or
continuous inversion of arbitrary callbacks in this first slice. In
puremacro.inference.weak_iv, kleibergen_paap_f computes the reduced-rank
Kleibergen–Paap (2006) rk Wald \(F\) statistic for \(k \ge 1\) endogenous
regressors, and anderson_rubin_band returns (nan, nan) with a
RuntimeWarning when the Anderson–Rubin acceptance set is empty on the supplied
grid instead of returning the grid endpoints. In puremacro.narrative.validate,
weak_iv_summary partials out the intercept and any controls from endog,
outcome and Z before evaluating Cragg–Donald, Kleibergen–Paap and
Anderson–Rubin diagnostics.
Run python -m puremacro.examples.identification_robust_sets --output /tmp/weak-id
for component tables, a missed-island demonstration, a simulation grid, report
exports, a figure and JSON provenance. Independent tests check Gaussian
quadratic forms, analytic endpoints, small nonzero loadings, tangencies,
truncation and invalid evaluations. The local acceptance study uses five
known-covariance Gaussian designs with 2,000 trials each, including strong,
weak, absent and turning-point identification. These references do not certify
arbitrary economic models or estimated-covariance finite-sample coverage.