mergeron 2024.738963.0__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 +142 -93
- mergeron/core/guidelines_boundaries.py +289 -1077
- mergeron/core/guidelines_boundary_functions.py +1128 -0
- mergeron/core/{guidelines_boundaries_specialized_functions.py → guidelines_boundary_functions_extra.py} +76 -42
- 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 +257 -245
- mergeron/gen/data_generation.py +473 -221
- mergeron/gen/data_generation_functions.py +876 -0
- mergeron/gen/enforcement_stats.py +355 -0
- mergeron/gen/upp_tests.py +159 -259
- mergeron-2025.739265.0.dist-info/METADATA +115 -0
- mergeron-2025.739265.0.dist-info/RECORD +23 -0
- {mergeron-2024.738963.0.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 -259
- 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 -621
- 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.738963.0.dist-info/METADATA +0 -108
- mergeron-2024.738963.0.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
|
|
22
16
|
|
|
23
|
-
from ..
|
|
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
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
|
|
30
|
+
__version__ = VERSION
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
|
|
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
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
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,77 +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
|
-
"""
|
|
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
|
+
)
|
|
146
|
+
"""Parameters for tailoring market-share distribution
|
|
93
147
|
|
|
94
|
-
|
|
95
|
-
|
|
148
|
+
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
149
|
+
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
150
|
+
no less than the length of firm-count weights below; defaults depend on
|
|
151
|
+
type of Dirichlet-distribution specified.
|
|
96
152
|
|
|
97
|
-
|
|
98
|
-
|
|
153
|
+
"""
|
|
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
|
+
)
|
|
99
171
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
103
176
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
* 0.80, 5-to-4 merger from symmetry; US Guidelines, 2010
|
|
108
|
-
* 0.78, **5-to-4 merger to symmetry**; US Guidelines, 2010
|
|
177
|
+
Given frequencies are exogenous to generated market data sample;
|
|
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.
|
|
109
180
|
|
|
110
|
-
Highlighting indicates hypothetical mergers close to the boundary of the presumption.
|
|
111
181
|
"""
|
|
112
182
|
|
|
113
|
-
|
|
114
|
-
"""
|
|
183
|
+
recapture_form: RECForm = field(default=RECForm.INOUT)
|
|
184
|
+
"""See :class:`mergeron.RECForm`"""
|
|
115
185
|
|
|
116
|
-
|
|
117
|
-
"""
|
|
186
|
+
recapture_ratio: float | None = field(default=DEFAULT_REC_RATIO)
|
|
187
|
+
"""A value between 0 and 1.
|
|
118
188
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
122
|
-
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
123
|
-
no less than the length of firm-count weights below; defaults depend on
|
|
124
|
-
type of Dirichlet-distribution specified.
|
|
189
|
+
:code:`None` if market share specification requires direct generation of
|
|
190
|
+
outside good choice probabilities (:attr:`mergeron.RECForm.OUTIN`).
|
|
125
191
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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:
|
|
129
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
|
|
130
200
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
)
|
|
134
218
|
|
|
135
219
|
|
|
136
220
|
@enum.unique
|
|
137
|
-
class
|
|
221
|
+
class PCMDistribution(enum.StrEnum):
|
|
138
222
|
"""Margin distributions."""
|
|
139
223
|
|
|
140
224
|
UNI = "Uniform"
|
|
141
225
|
BETA = "Beta"
|
|
142
226
|
BETA_BND = "Bounded Beta"
|
|
143
|
-
EMPR = "Damodaran margin data"
|
|
227
|
+
EMPR = "Damodaran margin data, resampled"
|
|
144
228
|
|
|
145
229
|
|
|
146
230
|
@enum.unique
|
|
147
|
-
class
|
|
231
|
+
class FM2Constraint(enum.StrEnum):
|
|
148
232
|
"""Firm 2 margins - derivation methods."""
|
|
149
233
|
|
|
150
234
|
IID = "i.i.d"
|
|
@@ -152,7 +236,7 @@ class FM2Constants(enum.StrEnum):
|
|
|
152
236
|
SYM = "symmetric"
|
|
153
237
|
|
|
154
238
|
|
|
155
|
-
@
|
|
239
|
+
@frozen
|
|
156
240
|
class PCMSpec:
|
|
157
241
|
"""Price-cost margin (PCM) specification
|
|
158
242
|
|
|
@@ -168,24 +252,56 @@ class PCMSpec:
|
|
|
168
252
|
|
|
169
253
|
"""
|
|
170
254
|
|
|
171
|
-
|
|
172
|
-
"""See
|
|
173
|
-
|
|
174
|
-
dist_type: PCMConstants
|
|
175
|
-
"""See PCMConstants"""
|
|
255
|
+
dist_type: PCMDistribution = field(kw_only=False, default=PCMDistribution.UNI)
|
|
256
|
+
"""See :class:`PCMDistribution`"""
|
|
176
257
|
|
|
177
|
-
dist_parms:
|
|
258
|
+
dist_parms: ArrayDouble | None = field(kw_only=False, default=None)
|
|
178
259
|
"""Parameter specification for tailoring PCM distribution
|
|
179
260
|
|
|
180
261
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
181
262
|
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
182
263
|
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
183
264
|
for empirical distribution based on Damodaran margin data, optional, ignored
|
|
265
|
+
|
|
184
266
|
"""
|
|
185
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
|
+
|
|
186
302
|
|
|
187
303
|
@enum.unique
|
|
188
|
-
class
|
|
304
|
+
class SSZConstant(float, enum.ReprEnum):
|
|
189
305
|
"""
|
|
190
306
|
Scale factors to offset sample size reduction.
|
|
191
307
|
|
|
@@ -224,193 +340,46 @@ class SSZConstants(float, enum.ReprEnum):
|
|
|
224
340
|
"""When initial set of draws is not restricted in any way."""
|
|
225
341
|
|
|
226
342
|
|
|
227
|
-
# Validators for selected attributes of
|
|
228
|
-
def _sample_size_validator(
|
|
229
|
-
_object: MarketSampleSpec, _attribute: Attribute[int], _value: int, /
|
|
230
|
-
) -> None:
|
|
231
|
-
if _value < 10**6:
|
|
232
|
-
raise ValueError(
|
|
233
|
-
f"Sample size must be no less than {10**6:,d}; got, {_value:,d}."
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def _share_spec_validator(
|
|
238
|
-
_instance: MarketSampleSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
|
|
239
|
-
) -> None:
|
|
240
|
-
_r_bar = _value.recapture_rate
|
|
241
|
-
if _r_bar and not (0 < _r_bar <= 1):
|
|
242
|
-
raise ValueError("Recapture rate must lie in the interval, [0, 1).")
|
|
243
|
-
|
|
244
|
-
elif _r_bar and _value.recapture_form == RECConstants.OUTIN:
|
|
245
|
-
raise ValueError(
|
|
246
|
-
"Market share specification requires estimation of recapture rate from "
|
|
247
|
-
"generated data. Either delete recapture rate specification or set it to None."
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
if _value.dist_type == SHRConstants.UNI:
|
|
251
|
-
if _value.recapture_form == RECConstants.OUTIN:
|
|
252
|
-
raise ValueError(
|
|
253
|
-
f"Invalid recapture specification, {_value.recapture_form!r} "
|
|
254
|
-
"for market share specification with Uniform distribution. "
|
|
255
|
-
"Redefine the market-sample specification, modifying the ."
|
|
256
|
-
"market-share specification or the recapture specification."
|
|
257
|
-
)
|
|
258
|
-
elif _value.firm_counts_weights is not None:
|
|
259
|
-
raise ValueError(
|
|
260
|
-
"Generated data for markets with specified firm-counts or "
|
|
261
|
-
"varying firm counts are not feasible for market shares "
|
|
262
|
-
"with Uniform distribution. Consider revising the "
|
|
263
|
-
r"distribution type to {SHRConstants.DIR_FLAT}, which gives "
|
|
264
|
-
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
265
|
-
"for firm-count, :math:`n`."
|
|
266
|
-
)
|
|
267
|
-
elif _value.recapture_form != RECConstants.OUTIN and (
|
|
268
|
-
_r_bar is None or not isinstance(_r_bar, float)
|
|
269
|
-
):
|
|
270
|
-
raise ValueError(
|
|
271
|
-
f"Recapture specification, {_value.recapture_form!r} requires that "
|
|
272
|
-
"the market sample specification inclues a recapture rate."
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def _pcm_spec_validator(
|
|
277
|
-
_instance: MarketSampleSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
|
|
278
|
-
) -> None:
|
|
279
|
-
if (
|
|
280
|
-
_instance.share_spec.recapture_form == RECConstants.FIXED
|
|
281
|
-
and _value.firm2_pcm_constraint == FM2Constants.MNL
|
|
282
|
-
):
|
|
283
|
-
raise ValueError(
|
|
284
|
-
"{} {} {}".format(
|
|
285
|
-
f'Specification of "recapture_form", "{_instance.share_spec.recapture_form}"',
|
|
286
|
-
"requires Firm 2 margin must have property, ",
|
|
287
|
-
f'"{FM2Constants.IID}" or "{FM2Constants.SYM}".',
|
|
288
|
-
)
|
|
289
|
-
)
|
|
290
|
-
elif _value.dist_type.name.startswith("BETA"):
|
|
291
|
-
if _value.dist_parms is None:
|
|
292
|
-
pass
|
|
293
|
-
elif np.array_equal(_value.dist_parms, DIST_PARMS_DEFAULT):
|
|
294
|
-
raise ValueError(
|
|
295
|
-
f"The distribution parameters, {DIST_PARMS_DEFAULT!r} "
|
|
296
|
-
"are not valid with margin distribution, {_dist_type_pcm!r}"
|
|
297
|
-
)
|
|
298
|
-
elif (
|
|
299
|
-
_value.dist_type == PCMConstants.BETA
|
|
300
|
-
and len(_value.dist_parms) != len(("max", "min"))
|
|
301
|
-
) or (
|
|
302
|
-
_value.dist_type == PCMConstants.BETA_BND
|
|
303
|
-
and len(_value.dist_parms) != len(("mu", "sigma", "max", "min"))
|
|
304
|
-
):
|
|
305
|
-
raise ValueError(
|
|
306
|
-
f"Given number, {len(_value.dist_parms)} of parameters "
|
|
307
|
-
f'for PCM with distribution, "{_value.dist_type}" is incorrect.'
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
@define(slots=True, frozen=True)
|
|
312
|
-
class MarketSampleSpec:
|
|
313
|
-
"""Parameter specification for market data generation."""
|
|
314
|
-
|
|
315
|
-
sample_size: int = field(
|
|
316
|
-
default=10**6, validator=(validators.instance_of(int), _sample_size_validator)
|
|
317
|
-
)
|
|
318
|
-
"""sample size generated"""
|
|
319
|
-
|
|
320
|
-
share_spec: ShareSpec = field(
|
|
321
|
-
kw_only=True,
|
|
322
|
-
default=ShareSpec(RECConstants.INOUT, 0.855, SHRConstants.UNI, None, None),
|
|
323
|
-
validator=[validators.instance_of(ShareSpec), _share_spec_validator],
|
|
324
|
-
)
|
|
325
|
-
"""Market-share specification, see definition of ShareSpec"""
|
|
326
|
-
|
|
327
|
-
pcm_spec: PCMSpec = field(
|
|
328
|
-
kw_only=True,
|
|
329
|
-
default=PCMSpec(FM2Constants.IID, PCMConstants.UNI, None),
|
|
330
|
-
validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
|
|
331
|
-
)
|
|
332
|
-
"""Margin specification, see definition of PCMSpec"""
|
|
333
|
-
|
|
334
|
-
price_spec: PRIConstants = field(
|
|
335
|
-
kw_only=True,
|
|
336
|
-
default=PRIConstants.SYM,
|
|
337
|
-
validator=validators.instance_of(PRIConstants),
|
|
338
|
-
)
|
|
339
|
-
"""Price specification, see PRIConstants"""
|
|
340
|
-
|
|
341
|
-
hsr_filing_test_type: SSZConstants = field(
|
|
342
|
-
kw_only=True,
|
|
343
|
-
default=SSZConstants.ONE,
|
|
344
|
-
validator=validators.instance_of(SSZConstants),
|
|
345
|
-
)
|
|
346
|
-
"""Method for modeling HSR filing threholds, see SSZConstants"""
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
@enum.unique
|
|
350
|
-
class INVResolution(enum.StrEnum):
|
|
351
|
-
CLRN = "clearance"
|
|
352
|
-
ENFT = "enforcement"
|
|
353
|
-
BOTH = "both"
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
@define(slots=True, frozen=True)
|
|
357
|
-
class UPPTestRegime:
|
|
358
|
-
resolution: INVResolution = field(
|
|
359
|
-
default=INVResolution.ENFT, validator=validators.instance_of(INVResolution)
|
|
360
|
-
)
|
|
361
|
-
guppi_aggregator: UPPAggrSelector = field(
|
|
362
|
-
default=UPPAggrSelector.MAX, validator=validators.instance_of(UPPAggrSelector)
|
|
363
|
-
)
|
|
364
|
-
divr_aggregator: UPPAggrSelector | None = field(
|
|
365
|
-
default=guppi_aggregator,
|
|
366
|
-
validator=validators.instance_of((UPPAggrSelector, type(None))),
|
|
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
|