mergeron 2024.738953.1__py3-none-any.whl → 2025.739265.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mergeron might be problematic. Click here for more details.
- mergeron/__init__.py +26 -6
- mergeron/core/__init__.py +5 -65
- mergeron/core/{damodaran_margin_data.py → empirical_margin_distribution.py} +74 -58
- mergeron/core/ftc_merger_investigations_data.py +147 -101
- mergeron/core/guidelines_boundaries.py +290 -1078
- mergeron/core/guidelines_boundary_functions.py +1128 -0
- mergeron/core/{guidelines_boundaries_specialized_functions.py → guidelines_boundary_functions_extra.py} +87 -55
- mergeron/core/pseudorandom_numbers.py +16 -22
- mergeron/data/__init__.py +3 -0
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron/demo/__init__.py +3 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +86 -0
- mergeron/gen/__init__.py +258 -246
- mergeron/gen/data_generation.py +473 -224
- mergeron/gen/data_generation_functions.py +876 -0
- mergeron/gen/enforcement_stats.py +355 -0
- mergeron/gen/upp_tests.py +171 -259
- mergeron-2025.739265.0.dist-info/METADATA +115 -0
- mergeron-2025.739265.0.dist-info/RECORD +23 -0
- {mergeron-2024.738953.1.dist-info → mergeron-2025.739265.0.dist-info}/WHEEL +1 -1
- mergeron/License.txt +0 -16
- mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
- mergeron/core/excel_helper.py +0 -257
- mergeron/core/proportions_tests.py +0 -520
- mergeron/ext/__init__.py +0 -5
- mergeron/ext/tol_colors.py +0 -851
- mergeron/gen/_data_generation_functions_nonpublic.py +0 -623
- mergeron/gen/investigations_stats.py +0 -709
- mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -121
- mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -82
- mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -57
- mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -104
- mergeron/jinja_LaTex_templates/mergeron.cls +0 -161
- mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -90
- mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -84
- mergeron-2024.738953.1.dist-info/METADATA +0 -93
- mergeron-2024.738953.1.dist-info/RECORD +0 -30
- /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
mergeron/gen/__init__.py
CHANGED
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Defines constants
|
|
2
|
+
Defines constants, specifications (classes with attributes defining varous parameters) and
|
|
3
|
+
containers for industry data generation and testing.
|
|
3
4
|
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
|
-
from importlib.metadata import version
|
|
9
|
-
|
|
10
|
-
from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
|
|
11
|
-
|
|
12
|
-
__version__ = version(_PKG_NAME)
|
|
13
|
-
|
|
14
|
-
|
|
15
9
|
import enum
|
|
16
10
|
from dataclasses import dataclass
|
|
17
|
-
from typing import ClassVar,
|
|
11
|
+
from typing import ClassVar, NamedTuple, Protocol
|
|
18
12
|
|
|
19
13
|
import numpy as np
|
|
20
|
-
from attrs import Attribute,
|
|
21
|
-
from numpy.
|
|
14
|
+
from attrs import Attribute, cmp_using, field, frozen, validators
|
|
15
|
+
from numpy.random import SeedSequence
|
|
16
|
+
|
|
17
|
+
from .. import ( # noqa: TID252
|
|
18
|
+
DEFAULT_REC_RATIO,
|
|
19
|
+
VERSION,
|
|
20
|
+
ArrayBIGINT,
|
|
21
|
+
ArrayBoolean,
|
|
22
|
+
ArrayDouble,
|
|
23
|
+
ArrayFloat,
|
|
24
|
+
ArrayINT,
|
|
25
|
+
RECForm,
|
|
26
|
+
UPPAggrSelector,
|
|
27
|
+
)
|
|
28
|
+
from ..core.pseudorandom_numbers import DEFAULT_DIST_PARMS # noqa: TID252
|
|
29
|
+
|
|
30
|
+
__version__ = VERSION
|
|
31
|
+
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
DEFAULT_EMPTY_ARRAY = np.zeros(2)
|
|
34
|
+
DEFAULT_FCOUNT_WTS = np.divide(
|
|
35
|
+
(_nr := np.arange(1, 7)[::-1]), _nr.sum(), dtype=np.float64
|
|
36
|
+
)
|
|
24
37
|
|
|
25
|
-
EMPTY_ARRAY_DEFAULT = np.zeros(2)
|
|
26
|
-
FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
class SeedSequenceData(NamedTuple):
|
|
40
|
+
mktshr_rng_seed_seq: SeedSequence
|
|
41
|
+
pcm_rng_seed_seq: SeedSequence
|
|
42
|
+
fcount_rng_seed_seq: SeedSequence | None
|
|
43
|
+
pr_rng_seed_seq: SeedSequence | None
|
|
30
44
|
|
|
31
45
|
|
|
32
46
|
@enum.unique
|
|
33
|
-
class
|
|
47
|
+
class PriceSpec(tuple[bool, str | None], enum.ReprEnum):
|
|
34
48
|
"""Price specification.
|
|
35
49
|
|
|
36
50
|
Whether prices are symmetric and, if not, the direction of correlation, if any.
|
|
@@ -44,11 +58,11 @@ class PRIConstants(tuple[bool, str | None], enum.ReprEnum):
|
|
|
44
58
|
|
|
45
59
|
|
|
46
60
|
@enum.unique
|
|
47
|
-
class
|
|
61
|
+
class SHRDistribution(enum.StrEnum):
|
|
48
62
|
"""Market share distributions."""
|
|
49
63
|
|
|
50
64
|
UNI = "Uniform"
|
|
51
|
-
"""Uniform distribution over
|
|
65
|
+
R"""Uniform distribution over :math:`s_1 + s_2 \leqslant 1`"""
|
|
52
66
|
|
|
53
67
|
DIR_FLAT = "Flat Dirichlet"
|
|
54
68
|
"""Shape parameter for all merging-firm-shares is unity (1)"""
|
|
@@ -63,7 +77,11 @@ class SHRConstants(enum.StrEnum):
|
|
|
63
77
|
DIR_ASYM = "Asymmetric Dirichlet"
|
|
64
78
|
"""Share distribution for merging-firm shares has a higher peak share
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
By default, shape parameter for merging-firm-share is 2.5, and
|
|
81
|
+
1.0 for all others. Defining, :attr:`mergeron.ShareSpec.dist_parms`
|
|
82
|
+
as a vector of shape parameters with length matching
|
|
83
|
+
that of :attr:`mergeron.ShareSpec.dist_parms` allows flexible specification
|
|
84
|
+
of Dirichlet-distributed share-data generation.
|
|
67
85
|
"""
|
|
68
86
|
|
|
69
87
|
DIR_COND = "Conditional Dirichlet"
|
|
@@ -74,58 +92,143 @@ class SHRConstants(enum.StrEnum):
|
|
|
74
92
|
"""
|
|
75
93
|
|
|
76
94
|
|
|
77
|
-
@
|
|
95
|
+
@frozen(kw_only=False)
|
|
78
96
|
class ShareSpec:
|
|
79
97
|
"""Market share specification
|
|
80
98
|
|
|
99
|
+
A key feature of market-share specification in this package is that
|
|
100
|
+
the draws represent markets with multiple different firm-counts.
|
|
101
|
+
Firm-counts are unspecified if the share distribution is
|
|
102
|
+
:attr:`mergeron.SHRDistribution.UNI`, for Dirichlet-distributed market-shares,
|
|
103
|
+
the default specification is that firm-counts vary between
|
|
104
|
+
2 and 7 firms with each value equally likely.
|
|
105
|
+
|
|
81
106
|
Notes
|
|
82
107
|
-----
|
|
83
|
-
If
|
|
84
|
-
|
|
108
|
+
If :attr:`mergeron.gen.ShareSpec.dist_type` == :attr:`mergeron.gen.SHRDistribution.UNI`,
|
|
109
|
+
then it is infeasible that
|
|
110
|
+
:attr:`mergeron.gen.ShareSpec.recapture_form` == :attr:`mergeron.RECForm.OUTIN`.
|
|
111
|
+
In other words, if the distribution of markets over firm-counts is unspecified,
|
|
112
|
+
recapture ratios cannot be estimated using outside-good choice probabilities.
|
|
85
113
|
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
For a sample with explicit firm counts, market shares must be specified as
|
|
115
|
+
having a supported Dirichlet distribution (see :class:`mergeron.gen.SHRDistribution`).
|
|
88
116
|
|
|
89
117
|
"""
|
|
90
118
|
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
dist_type:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
dist_type: SHRDistribution = field(default=SHRDistribution.DIR_FLAT)
|
|
120
|
+
"""See :class:`SHRDistribution`"""
|
|
121
|
+
|
|
122
|
+
@dist_type.validator # pyright: ignore
|
|
123
|
+
def __dtv(
|
|
124
|
+
_i: ShareSpec, _a: Attribute[SHRDistribution], _v: SHRDistribution
|
|
125
|
+
) -> None:
|
|
126
|
+
if _v == SHRDistribution.UNI:
|
|
127
|
+
if _i.firm_counts_weights is not None:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"The specified value is incompatible with "
|
|
130
|
+
" :code:`distypte`=:attr`:`SHRDistribution.UNI`. "
|
|
131
|
+
"Set value to None or Consider revising the "
|
|
132
|
+
r"distribution type to :attr:`SHRDistribution.DIR_FLAT`, which gives "
|
|
133
|
+
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
134
|
+
"for firm-count, :math:`n`. "
|
|
135
|
+
""
|
|
136
|
+
)
|
|
137
|
+
elif _i.recapture_form == RECForm.OUTIN:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"Market share specification requires estimation of recapture ratio from "
|
|
140
|
+
"generated data. Either delete recapture ratio specification or set it to None."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
dist_parms: ArrayFloat | ArrayINT | None = field(
|
|
144
|
+
default=None, eq=cmp_using(eq=np.array_equal)
|
|
145
|
+
)
|
|
98
146
|
"""Parameters for tailoring market-share distribution
|
|
99
147
|
|
|
100
148
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
101
|
-
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
102
|
-
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
103
149
|
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
104
150
|
no less than the length of firm-count weights below; defaults depend on
|
|
105
151
|
type of Dirichlet-distribution specified.
|
|
106
152
|
|
|
107
153
|
"""
|
|
108
|
-
firm_counts_weights: NDArray[np.float64 | np.int64] | None
|
|
109
|
-
"""relative or absolute frequencies of firm counts
|
|
110
154
|
|
|
155
|
+
@dist_parms.validator # pyright: ignore
|
|
156
|
+
def __dpv(
|
|
157
|
+
_i: ShareSpec,
|
|
158
|
+
_a: Attribute[ArrayFloat | ArrayINT | None],
|
|
159
|
+
_v: ArrayFloat | ArrayINT | None,
|
|
160
|
+
) -> None:
|
|
161
|
+
if (
|
|
162
|
+
_i.firm_counts_weights is not None
|
|
163
|
+
and _v is not None
|
|
164
|
+
and len(_v) < 1 + len(_i.firm_counts_weights)
|
|
165
|
+
):
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"If specified, the number of distribution parameters must be at least "
|
|
168
|
+
"the maximum firm-count premerger, which is 1 plus the length of the "
|
|
169
|
+
"vector specifying firm-count weights."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
firm_counts_weights: ArrayFloat | ArrayINT | None = field(
|
|
173
|
+
default=DEFAULT_FCOUNT_WTS, eq=cmp_using(eq=np.array_equal)
|
|
174
|
+
)
|
|
175
|
+
"""Relative or absolute frequencies of firm counts
|
|
111
176
|
|
|
112
177
|
Given frequencies are exogenous to generated market data sample;
|
|
113
|
-
defaults to
|
|
114
|
-
with weights in descending order from 5 to 1.
|
|
178
|
+
for Dirichlet-type distributions, defaults to DEFAULT_FCOUNT_WTS, which specifies
|
|
179
|
+
firm-counts of 2 to 6 with weights in descending order from 5 to 1.
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
recapture_form: RECForm = field(default=RECForm.INOUT)
|
|
184
|
+
"""See :class:`mergeron.RECForm`"""
|
|
185
|
+
|
|
186
|
+
recapture_ratio: float | None = field(default=DEFAULT_REC_RATIO)
|
|
187
|
+
"""A value between 0 and 1.
|
|
188
|
+
|
|
189
|
+
:code:`None` if market share specification requires direct generation of
|
|
190
|
+
outside good choice probabilities (:attr:`mergeron.RECForm.OUTIN`).
|
|
191
|
+
|
|
192
|
+
The recapture ratio is usually calibrated to the numbers-equivalent of the
|
|
193
|
+
HHI threshold for the presumtion of harm from unilateral competitive effects
|
|
194
|
+
in published merger guidelines. Accordingly, the recapture ratio rounded to
|
|
195
|
+
the nearest 5% is:
|
|
196
|
+
|
|
197
|
+
* 0.85, **7-to-6 merger from symmetry**; US Guidelines, 1982, 1984, 1992, 2023
|
|
198
|
+
* 0.80, 5-to-4 merger from symmetry
|
|
199
|
+
* 0.80, **5-to-4 merger to symmetry**; US Guidelines, 2010
|
|
200
|
+
|
|
201
|
+
Highlighting indicates hypothetical mergers in the neighborhood of (the boundary of)
|
|
202
|
+
the Guidelines presumption of harm. (In the EU Guidelines, concentration measures serve as
|
|
203
|
+
screens for further investigation, rather than as the basis for presumptions of harm or
|
|
204
|
+
presumptions no harm.)
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
@recapture_ratio.validator # pyright: ignore
|
|
209
|
+
def __rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
|
|
210
|
+
if _v and not (0 < _v <= 1):
|
|
211
|
+
raise ValueError("Recapture ratio must lie in the interval, [0, 1).")
|
|
212
|
+
elif _v is None and _i.recapture_form != RECForm.OUTIN:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Recapture specification, {_i.recapture_form!r} requires that "
|
|
215
|
+
"the market sample specification inclues a recapture ratio in the "
|
|
216
|
+
"interval [0, 1)."
|
|
217
|
+
)
|
|
115
218
|
|
|
116
219
|
|
|
117
220
|
@enum.unique
|
|
118
|
-
class
|
|
221
|
+
class PCMDistribution(enum.StrEnum):
|
|
119
222
|
"""Margin distributions."""
|
|
120
223
|
|
|
121
224
|
UNI = "Uniform"
|
|
122
225
|
BETA = "Beta"
|
|
123
226
|
BETA_BND = "Bounded Beta"
|
|
124
|
-
EMPR = "Damodaran margin data"
|
|
227
|
+
EMPR = "Damodaran margin data, resampled"
|
|
125
228
|
|
|
126
229
|
|
|
127
230
|
@enum.unique
|
|
128
|
-
class
|
|
231
|
+
class FM2Constraint(enum.StrEnum):
|
|
129
232
|
"""Firm 2 margins - derivation methods."""
|
|
130
233
|
|
|
131
234
|
IID = "i.i.d"
|
|
@@ -133,7 +236,7 @@ class FM2Constants(enum.StrEnum):
|
|
|
133
236
|
SYM = "symmetric"
|
|
134
237
|
|
|
135
238
|
|
|
136
|
-
@
|
|
239
|
+
@frozen
|
|
137
240
|
class PCMSpec:
|
|
138
241
|
"""Price-cost margin (PCM) specification
|
|
139
242
|
|
|
@@ -149,24 +252,56 @@ class PCMSpec:
|
|
|
149
252
|
|
|
150
253
|
"""
|
|
151
254
|
|
|
152
|
-
dist_type:
|
|
153
|
-
"""See
|
|
154
|
-
|
|
155
|
-
firm2_pcm_constraint: FM2Constants
|
|
156
|
-
"""See FM2Constants"""
|
|
255
|
+
dist_type: PCMDistribution = field(kw_only=False, default=PCMDistribution.UNI)
|
|
256
|
+
"""See :class:`PCMDistribution`"""
|
|
157
257
|
|
|
158
|
-
dist_parms:
|
|
258
|
+
dist_parms: ArrayDouble | None = field(kw_only=False, default=None)
|
|
159
259
|
"""Parameter specification for tailoring PCM distribution
|
|
160
260
|
|
|
161
261
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
162
262
|
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
163
263
|
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
164
264
|
for empirical distribution based on Damodaran margin data, optional, ignored
|
|
265
|
+
|
|
165
266
|
"""
|
|
166
267
|
|
|
268
|
+
@dist_parms.validator # pyright: ignore
|
|
269
|
+
def __dpv(
|
|
270
|
+
_i: PCMSpec, _a: Attribute[ArrayDouble | None], _v: ArrayDouble | None
|
|
271
|
+
) -> None:
|
|
272
|
+
if _i.dist_type.name.startswith("BETA"):
|
|
273
|
+
if _v is None:
|
|
274
|
+
pass
|
|
275
|
+
elif np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
276
|
+
raise ValueError(
|
|
277
|
+
f"The distribution parameters, {DEFAULT_DIST_PARMS!r} "
|
|
278
|
+
"are not valid with margin distribution, {_dist_type_pcm!r}"
|
|
279
|
+
)
|
|
280
|
+
elif (
|
|
281
|
+
_i.dist_type == PCMDistribution.BETA and len(_v) != len(("a", "b"))
|
|
282
|
+
) or (
|
|
283
|
+
_i.dist_type == PCMDistribution.BETA_BND
|
|
284
|
+
and len(_v) != len(("mu", "sigma", "max", "min"))
|
|
285
|
+
):
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"Given number, {len(_v)} of parameters "
|
|
288
|
+
f'for PCM with distribution, "{_i.dist_type}" is incorrect.'
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
elif _i.dist_type == PCMDistribution.EMPR and _v is not None:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
f"Empirical distribution does not require additional parameters; "
|
|
294
|
+
f'"given value, {_v!r} is ignored."'
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
firm2_pcm_constraint: FM2Constraint = field(
|
|
298
|
+
kw_only=False, default=FM2Constraint.IID
|
|
299
|
+
)
|
|
300
|
+
"""See :class:`FM2Constraint`"""
|
|
301
|
+
|
|
167
302
|
|
|
168
303
|
@enum.unique
|
|
169
|
-
class
|
|
304
|
+
class SSZConstant(float, enum.ReprEnum):
|
|
170
305
|
"""
|
|
171
306
|
Scale factors to offset sample size reduction.
|
|
172
307
|
|
|
@@ -205,212 +340,46 @@ class SSZConstants(float, enum.ReprEnum):
|
|
|
205
340
|
"""When initial set of draws is not restricted in any way."""
|
|
206
341
|
|
|
207
342
|
|
|
208
|
-
# Validators for selected attributes of
|
|
209
|
-
def _sample_size_validator(
|
|
210
|
-
_object: MarketSampleSpec, _attribute: Attribute[int], _value: int, /
|
|
211
|
-
) -> None:
|
|
212
|
-
if _value < 10**6:
|
|
213
|
-
raise ValueError(
|
|
214
|
-
f"Sample size must be not less than {10**6:,d}. Got, {_value:,d}."
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def _recapture_rate_validator(
|
|
219
|
-
_object: MarketSampleSpec,
|
|
220
|
-
_attribute: Attribute[float | None],
|
|
221
|
-
_value: float | None,
|
|
222
|
-
/,
|
|
223
|
-
) -> None:
|
|
224
|
-
if _value and not (0 < _value <= 1):
|
|
225
|
-
raise ValueError("Recapture rate must lie in the interval, [0, 1).")
|
|
226
|
-
|
|
227
|
-
if _value and _object.share_spec.recapture_spec == RECConstants.OUTIN:
|
|
228
|
-
raise ValueError(
|
|
229
|
-
"Market share specification requires estimation of recapture rate from "
|
|
230
|
-
"generated data. Either delete recapture rate specification or set it to None."
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def _share_spec_validator(
|
|
235
|
-
_instance: MarketSampleSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
|
|
236
|
-
) -> None:
|
|
237
|
-
_r_bar = _instance.recapture_rate
|
|
238
|
-
if _value.dist_type == SHRConstants.UNI:
|
|
239
|
-
if _value.recapture_spec == RECConstants.OUTIN:
|
|
240
|
-
raise ValueError(
|
|
241
|
-
f"Invalid recapture specification, {_value.recapture_spec!r} "
|
|
242
|
-
"for market share specification with Uniform distribution. "
|
|
243
|
-
"Redefine the market-sample specification, modifying the ."
|
|
244
|
-
"market-share specification or the recapture specification."
|
|
245
|
-
)
|
|
246
|
-
elif _value.firm_counts_weights is not None:
|
|
247
|
-
raise ValueError(
|
|
248
|
-
"Generated data for markets with specified firm-counts or "
|
|
249
|
-
"varying firm counts are not feasible with market shares "
|
|
250
|
-
"with Uniform distribution. Consider revising the "
|
|
251
|
-
r"distribution type to {SHRConstants.DIR_FLAT}, which gives "
|
|
252
|
-
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
253
|
-
"for firm-count, :math:`n`."
|
|
254
|
-
)
|
|
255
|
-
# Outside-in calibration only valid for Dir-distributed shares
|
|
256
|
-
elif _value.recapture_spec != RECConstants.OUTIN and (
|
|
257
|
-
_r_bar is None or not isinstance(_r_bar, float)
|
|
258
|
-
):
|
|
259
|
-
raise ValueError(
|
|
260
|
-
f"Recapture specification, {_value.recapture_spec!r} requires that "
|
|
261
|
-
"the market sample specification inclues a recapture rate."
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def _pcm_spec_validator(
|
|
266
|
-
_instance: MarketSampleSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
|
|
267
|
-
) -> None:
|
|
268
|
-
if (
|
|
269
|
-
_instance.share_spec.recapture_spec == RECConstants.FIXED
|
|
270
|
-
and _value.firm2_pcm_constraint == FM2Constants.MNL
|
|
271
|
-
):
|
|
272
|
-
raise ValueError(
|
|
273
|
-
"{} {} {}".format(
|
|
274
|
-
f'Specification of "recapture_spec", "{_instance.share_spec.recapture_spec}"',
|
|
275
|
-
"requires Firm 2 margin must have property, ",
|
|
276
|
-
f'"{FM2Constants.IID}" or "{FM2Constants.SYM}".',
|
|
277
|
-
)
|
|
278
|
-
)
|
|
279
|
-
elif _value.dist_type.name.startswith("BETA"):
|
|
280
|
-
if _value.dist_parms is None:
|
|
281
|
-
pass
|
|
282
|
-
elif np.array_equal(_value.dist_parms, DIST_PARMS_DEFAULT):
|
|
283
|
-
raise ValueError(
|
|
284
|
-
f"The distribution parameters, {DIST_PARMS_DEFAULT!r} "
|
|
285
|
-
"are not valid with margin distribution, {_dist_type_pcm!r}"
|
|
286
|
-
)
|
|
287
|
-
elif (
|
|
288
|
-
_value.dist_type == PCMConstants.BETA
|
|
289
|
-
and len(_value.dist_parms) != len(("max", "min"))
|
|
290
|
-
) or (
|
|
291
|
-
_value.dist_type == PCMConstants.BETA_BND
|
|
292
|
-
and len(_value.dist_parms) != len(("mu", "sigma", "max", "min"))
|
|
293
|
-
):
|
|
294
|
-
raise ValueError(
|
|
295
|
-
f"Given number, {len(_value.dist_parms)} of parameters "
|
|
296
|
-
f'for PCM with distribution, "{_value.dist_type}" is incorrect.'
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
@define(slots=True, frozen=True)
|
|
301
|
-
class MarketSampleSpec:
|
|
302
|
-
"""Parameter specification for market data generation."""
|
|
303
|
-
|
|
304
|
-
sample_size: int = field(
|
|
305
|
-
default=10**6, validator=(validators.instance_of(int), _sample_size_validator)
|
|
306
|
-
)
|
|
307
|
-
"""sample size generated"""
|
|
308
|
-
|
|
309
|
-
recapture_rate: float | None = field(
|
|
310
|
-
default=None, validator=_recapture_rate_validator
|
|
311
|
-
)
|
|
312
|
-
"""market recapture rate
|
|
313
|
-
|
|
314
|
-
Is None if market share specification requires generation of
|
|
315
|
-
outside good choice probabilities (RECConstants.OUTIN).
|
|
316
|
-
"""
|
|
317
|
-
|
|
318
|
-
pr_sym_spec: PRIConstants = field( # type: ignore
|
|
319
|
-
kw_only=True,
|
|
320
|
-
default=PRIConstants.SYM,
|
|
321
|
-
validator=validators.instance_of(PRIConstants), # type: ignore
|
|
322
|
-
)
|
|
323
|
-
"""Price specification, see PRIConstants"""
|
|
324
|
-
|
|
325
|
-
share_spec: ShareSpec = field(
|
|
326
|
-
kw_only=True,
|
|
327
|
-
default=ShareSpec(RECConstants.INOUT, SHRConstants.UNI, None, None),
|
|
328
|
-
validator=[validators.instance_of(ShareSpec), _share_spec_validator],
|
|
329
|
-
)
|
|
330
|
-
"""See definition of ShareSpec"""
|
|
331
|
-
|
|
332
|
-
pcm_spec: PCMSpec = field(
|
|
333
|
-
kw_only=True,
|
|
334
|
-
default=PCMSpec(PCMConstants.UNI, FM2Constants.IID, None),
|
|
335
|
-
validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
|
|
336
|
-
)
|
|
337
|
-
"""See definition of PCMSpec"""
|
|
338
|
-
|
|
339
|
-
hsr_filing_test_type: SSZConstants = field( # type: ignore
|
|
340
|
-
kw_only=True,
|
|
341
|
-
default=SSZConstants.ONE,
|
|
342
|
-
validator=validators.instance_of(SSZConstants), # type: ignore
|
|
343
|
-
)
|
|
344
|
-
"""Method for modeling HSR filing threholds, see SSZConstants"""
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@enum.unique
|
|
348
|
-
class INVResolution(enum.StrEnum):
|
|
349
|
-
CLRN = "clearance"
|
|
350
|
-
ENFT = "enforcement"
|
|
351
|
-
BOTH = "both"
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
@define(slots=True, frozen=True)
|
|
355
|
-
class UPPTestRegime:
|
|
356
|
-
resolution: INVResolution = field( # type: ignore
|
|
357
|
-
default=INVResolution.ENFT,
|
|
358
|
-
validator=validators.instance_of(INVResolution), # type: ignore
|
|
359
|
-
)
|
|
360
|
-
guppi_aggregator: UPPAggrSelector = field( # type: ignore
|
|
361
|
-
default=UPPAggrSelector.MAX,
|
|
362
|
-
validator=validators.instance_of(UPPAggrSelector), # type: ignore
|
|
363
|
-
)
|
|
364
|
-
divr_aggregator: UPPAggrSelector | None = field( # type: ignore
|
|
365
|
-
default=guppi_aggregator,
|
|
366
|
-
validator=validators.instance_of(UPPAggrSelector | None), # type: ignore
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
# https://stackoverflow.com/questions/54668000
|
|
371
|
-
class DataclassInstance(Protocol):
|
|
372
|
-
"""Generic dataclass-instance"""
|
|
373
|
-
|
|
374
|
-
__dataclass_fields__: ClassVar
|
|
343
|
+
# Validators for selected attributes of MarketSpec
|
|
375
344
|
|
|
376
345
|
|
|
377
346
|
@dataclass(slots=True, frozen=True)
|
|
378
347
|
class MarketDataSample:
|
|
379
348
|
"""Container for generated markets data sample."""
|
|
380
349
|
|
|
381
|
-
frmshr_array:
|
|
350
|
+
frmshr_array: ArrayDouble
|
|
382
351
|
"""Merging-firm shares (with two merging firms)"""
|
|
383
352
|
|
|
384
|
-
pcm_array:
|
|
353
|
+
pcm_array: ArrayDouble
|
|
385
354
|
"""Merging-firms' prices (normalized to 1, in default specification)"""
|
|
386
355
|
|
|
387
|
-
price_array:
|
|
356
|
+
price_array: ArrayDouble
|
|
388
357
|
"""Merging-firms' price-cost margins (PCM)"""
|
|
389
358
|
|
|
390
|
-
fcounts:
|
|
359
|
+
fcounts: ArrayBIGINT
|
|
391
360
|
"""Number of firms in market"""
|
|
392
361
|
|
|
393
|
-
aggregate_purchase_prob:
|
|
362
|
+
aggregate_purchase_prob: ArrayDouble
|
|
394
363
|
"""
|
|
395
364
|
One (1) minus probability that the outside good is chosen
|
|
396
365
|
|
|
397
366
|
Converts market shares to choice probabilities by multiplication.
|
|
398
367
|
"""
|
|
399
368
|
|
|
400
|
-
nth_firm_share:
|
|
369
|
+
nth_firm_share: ArrayDouble
|
|
401
370
|
"""Market-share of n-th firm
|
|
402
371
|
|
|
403
372
|
Relevant for testing for draws the do or
|
|
404
373
|
do not meet HSR filing thresholds.
|
|
405
374
|
"""
|
|
406
375
|
|
|
407
|
-
divr_array:
|
|
376
|
+
divr_array: ArrayDouble
|
|
408
377
|
"""Diversion ratio between the merging firms"""
|
|
409
378
|
|
|
410
|
-
hhi_post:
|
|
379
|
+
hhi_post: ArrayDouble
|
|
411
380
|
"""Post-merger change in Herfindahl-Hirschmann Index (HHI)"""
|
|
412
381
|
|
|
413
|
-
hhi_delta:
|
|
382
|
+
hhi_delta: ArrayDouble
|
|
414
383
|
"""Change in HHI from combination of merging firms"""
|
|
415
384
|
|
|
416
385
|
|
|
@@ -422,16 +391,16 @@ class ShareDataSample:
|
|
|
422
391
|
and aggregate purchase probability.
|
|
423
392
|
"""
|
|
424
393
|
|
|
425
|
-
mktshr_array:
|
|
394
|
+
mktshr_array: ArrayDouble
|
|
426
395
|
"""All-firm shares (with two merging firms)"""
|
|
427
396
|
|
|
428
|
-
fcounts:
|
|
397
|
+
fcounts: ArrayBIGINT
|
|
429
398
|
"""All-firm-count for each draw"""
|
|
430
399
|
|
|
431
|
-
nth_firm_share:
|
|
400
|
+
nth_firm_share: ArrayDouble
|
|
432
401
|
"""Market-share of n-th firm"""
|
|
433
402
|
|
|
434
|
-
aggregate_purchase_prob:
|
|
403
|
+
aggregate_purchase_prob: ArrayDouble
|
|
435
404
|
"""Converts market shares to choice probabilities by multiplication."""
|
|
436
405
|
|
|
437
406
|
|
|
@@ -439,10 +408,10 @@ class ShareDataSample:
|
|
|
439
408
|
class PriceDataSample:
|
|
440
409
|
"""Container for generated price array, and related."""
|
|
441
410
|
|
|
442
|
-
price_array:
|
|
411
|
+
price_array: ArrayDouble
|
|
443
412
|
"""Merging-firms' prices"""
|
|
444
413
|
|
|
445
|
-
hsr_filing_test:
|
|
414
|
+
hsr_filing_test: ArrayBoolean
|
|
446
415
|
"""Flags draws as meeting HSR filing thresholds or not"""
|
|
447
416
|
|
|
448
417
|
|
|
@@ -450,10 +419,10 @@ class PriceDataSample:
|
|
|
450
419
|
class MarginDataSample:
|
|
451
420
|
"""Container for generated margin array and related MNL test array."""
|
|
452
421
|
|
|
453
|
-
pcm_array:
|
|
422
|
+
pcm_array: ArrayDouble
|
|
454
423
|
"""Merging-firms' PCMs"""
|
|
455
424
|
|
|
456
|
-
mnl_test_array:
|
|
425
|
+
mnl_test_array: ArrayBoolean
|
|
457
426
|
"""Flags infeasible observations as False and rest as True
|
|
458
427
|
|
|
459
428
|
Applying restrictions from Bertrand-Nash oligopoly
|
|
@@ -467,40 +436,83 @@ class MarginDataSample:
|
|
|
467
436
|
"""
|
|
468
437
|
|
|
469
438
|
|
|
439
|
+
@enum.unique
|
|
440
|
+
class INVResolution(enum.StrEnum):
|
|
441
|
+
CLRN = "clearance"
|
|
442
|
+
ENFT = "enforcement"
|
|
443
|
+
BOTH = "both"
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@frozen
|
|
447
|
+
class UPPTestRegime:
|
|
448
|
+
"""Configuration for UPP tests."""
|
|
449
|
+
|
|
450
|
+
resolution: INVResolution = field(
|
|
451
|
+
kw_only=False,
|
|
452
|
+
default=INVResolution.ENFT,
|
|
453
|
+
validator=validators.in_([INVResolution.CLRN, INVResolution.ENFT]),
|
|
454
|
+
)
|
|
455
|
+
"""Whether to test clearance, enforcement, or both."""
|
|
456
|
+
|
|
457
|
+
guppi_aggregator: UPPAggrSelector = field(
|
|
458
|
+
kw_only=False, default=UPPAggrSelector.MIN
|
|
459
|
+
)
|
|
460
|
+
"""Aggregator for GUPPI test."""
|
|
461
|
+
|
|
462
|
+
divr_aggregator: UPPAggrSelector = field(kw_only=False, default=UPPAggrSelector.MIN)
|
|
463
|
+
"""Aggregator for diversion ratio test."""
|
|
464
|
+
|
|
465
|
+
|
|
470
466
|
@dataclass(slots=True, frozen=True)
|
|
471
467
|
class UPPTestsRaw:
|
|
472
|
-
"""arrays marking test failures and successes
|
|
468
|
+
"""Container for arrays marking test failures and successes
|
|
473
469
|
|
|
474
470
|
A test success is a draw ("market") that meeets the
|
|
475
471
|
specified test criterion, and a test failure is
|
|
476
|
-
one that does not; test criteria are
|
|
477
|
-
|
|
472
|
+
one that does not; test criteria are evaluated in
|
|
473
|
+
:func:`enforcement_stats.gen_upp_arrays`.
|
|
478
474
|
"""
|
|
479
475
|
|
|
480
|
-
guppi_test_simple:
|
|
476
|
+
guppi_test_simple: ArrayBoolean
|
|
481
477
|
"""True if GUPPI estimate meets criterion"""
|
|
482
478
|
|
|
483
|
-
guppi_test_compound:
|
|
479
|
+
guppi_test_compound: ArrayBoolean
|
|
484
480
|
"""True if both GUPPI estimate and diversion ratio estimate
|
|
485
481
|
meet criterion
|
|
486
482
|
"""
|
|
487
483
|
|
|
488
|
-
cmcr_test:
|
|
484
|
+
cmcr_test: ArrayBoolean
|
|
489
485
|
"""True if CMCR estimate meets criterion"""
|
|
490
486
|
|
|
491
|
-
ipr_test:
|
|
487
|
+
ipr_test: ArrayBoolean
|
|
492
488
|
"""True if IPR (partial price-simulation) estimate meets criterion"""
|
|
493
489
|
|
|
494
490
|
|
|
495
491
|
@dataclass(slots=True, frozen=True)
|
|
496
492
|
class UPPTestsCounts:
|
|
497
|
-
"""
|
|
493
|
+
"""Counts of markets resolved as specified
|
|
494
|
+
|
|
495
|
+
Resolution may be either :attr:`INVResolution.ENFT`,
|
|
496
|
+
:attr:`INVResolution.CLRN`, or :attr:`INVResolution.BOTH`.
|
|
497
|
+
In the case of :attr:`INVResolution.BOTH`, two colums of counts
|
|
498
|
+
are returned: one for each resolution.
|
|
498
499
|
|
|
499
|
-
Resolution is specified in a UPPTestRegime object.
|
|
500
500
|
"""
|
|
501
501
|
|
|
502
|
-
by_firm_count:
|
|
503
|
-
by_delta:
|
|
504
|
-
by_conczone:
|
|
505
|
-
"""Zones are "unoncentrated", "moderately concentrated", and "highly concentrated"
|
|
502
|
+
by_firm_count: ArrayBIGINT
|
|
503
|
+
by_delta: ArrayBIGINT
|
|
504
|
+
by_conczone: ArrayBIGINT
|
|
505
|
+
"""Zones are "unoncentrated", "moderately concentrated", and "highly concentrated",
|
|
506
|
+
with futher detail by HHI and ΔHHI for mergers in the "unconcentrated" and
|
|
507
|
+
"moderately concentrated" zones. See
|
|
508
|
+
:attr:`mergeron.gen.enforcement_stats.HMG_PRESUMPTION_ZONE_MAP` and
|
|
509
|
+
:attr:`mergeron.gen.enforcement_stats.ZONE_VALS` for more detail.
|
|
510
|
+
|
|
506
511
|
"""
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# https://stackoverflow.com/questions/54668000
|
|
515
|
+
class DataclassInstance(Protocol):
|
|
516
|
+
"""Generic dataclass-instance"""
|
|
517
|
+
|
|
518
|
+
__dataclass_fields__: ClassVar
|