mergeron 2024.738953.1__py3-none-any.whl → 2024.738972.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/License.txt +1 -1
- mergeron/core/__init__.py +3 -3
- mergeron/core/excel_helper.py +3 -1
- mergeron/core/ftc_merger_investigations_data.py +13 -16
- mergeron/core/guidelines_boundaries.py +48 -45
- mergeron/core/guidelines_boundaries_specialized_functions.py +24 -21
- mergeron/gen/__init__.py +72 -80
- mergeron/gen/{_data_generation_functions_nonpublic.py → _data_generation_functions.py} +103 -47
- mergeron/gen/data_generation.py +42 -42
- mergeron/gen/investigations_stats.py +1 -1
- mergeron/gen/market_sample.py +79 -0
- mergeron/gen/upp_tests.py +143 -98
- {mergeron-2024.738953.1.dist-info → mergeron-2024.738972.0.dist-info}/METADATA +32 -17
- {mergeron-2024.738953.1.dist-info → mergeron-2024.738972.0.dist-info}/RECORD +15 -14
- {mergeron-2024.738953.1.dist-info → mergeron-2024.738972.0.dist-info}/WHEEL +0 -0
mergeron/gen/__init__.py
CHANGED
|
@@ -5,23 +5,21 @@ Defines constants and containers for industry data generation and testing
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
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
8
|
import enum
|
|
16
9
|
from dataclasses import dataclass
|
|
10
|
+
from importlib.metadata import version
|
|
17
11
|
from typing import ClassVar, Protocol, TypeVar
|
|
18
12
|
|
|
19
13
|
import numpy as np
|
|
20
|
-
from attrs import Attribute, define, field, validators
|
|
14
|
+
from attrs import Attribute, define, field, frozen, validators
|
|
21
15
|
from numpy.typing import NBitBase, NDArray
|
|
22
16
|
|
|
17
|
+
from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
|
|
23
18
|
from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
|
|
24
19
|
|
|
20
|
+
__version__ = version(_PKG_NAME)
|
|
21
|
+
|
|
22
|
+
|
|
25
23
|
EMPTY_ARRAY_DEFAULT = np.zeros(2)
|
|
26
24
|
FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
|
|
27
25
|
|
|
@@ -74,7 +72,7 @@ class SHRConstants(enum.StrEnum):
|
|
|
74
72
|
"""
|
|
75
73
|
|
|
76
74
|
|
|
77
|
-
@
|
|
75
|
+
@frozen
|
|
78
76
|
class ShareSpec:
|
|
79
77
|
"""Market share specification
|
|
80
78
|
|
|
@@ -88,9 +86,28 @@ class ShareSpec:
|
|
|
88
86
|
|
|
89
87
|
"""
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
recapture_form: RECConstants
|
|
92
90
|
"""see RECConstants"""
|
|
93
91
|
|
|
92
|
+
recapture_rate: float | None
|
|
93
|
+
"""A value between 0 and 1.
|
|
94
|
+
|
|
95
|
+
None if market share specification requires direct generation of
|
|
96
|
+
outside good choice probabilities (RECConstants.OUTIN).
|
|
97
|
+
|
|
98
|
+
The recapture rate is usually calibrated to the numbers-equivalent of the
|
|
99
|
+
HHI threshold for the presumtion of harm from unilateral compoetitive effects
|
|
100
|
+
in published merger guidelines. Accordingly, values for the recapture rate may be:
|
|
101
|
+
|
|
102
|
+
* 0.855, **6-to-5 merger from symmetry**; US Guidelines, 1992, 2023
|
|
103
|
+
* 0.855, 6-to-5 merger from symmetry; EU Guidelines for horizontal mergers, 2004
|
|
104
|
+
* 0.82, **6-to-5 merger to symmetry**; EU Guidelines for horizontal mergers, 2004
|
|
105
|
+
* 0.80, 5-to-4 merger from symmetry; US Guidelines, 2010
|
|
106
|
+
* 0.78, **5-to-4 merger to symmetry**; US Guidelines, 2010
|
|
107
|
+
|
|
108
|
+
Highlighting indicates hypothetical mergers close to the boundary of the presumption.
|
|
109
|
+
"""
|
|
110
|
+
|
|
94
111
|
dist_type: SHRConstants
|
|
95
112
|
"""see SHRConstants"""
|
|
96
113
|
|
|
@@ -133,7 +150,7 @@ class FM2Constants(enum.StrEnum):
|
|
|
133
150
|
SYM = "symmetric"
|
|
134
151
|
|
|
135
152
|
|
|
136
|
-
@
|
|
153
|
+
@frozen
|
|
137
154
|
class PCMSpec:
|
|
138
155
|
"""Price-cost margin (PCM) specification
|
|
139
156
|
|
|
@@ -149,12 +166,12 @@ class PCMSpec:
|
|
|
149
166
|
|
|
150
167
|
"""
|
|
151
168
|
|
|
152
|
-
dist_type: PCMConstants
|
|
153
|
-
"""See PCMConstants"""
|
|
154
|
-
|
|
155
169
|
firm2_pcm_constraint: FM2Constants
|
|
156
170
|
"""See FM2Constants"""
|
|
157
171
|
|
|
172
|
+
dist_type: PCMConstants
|
|
173
|
+
"""See PCMConstants"""
|
|
174
|
+
|
|
158
175
|
dist_parms: NDArray[np.float64] | None
|
|
159
176
|
"""Parameter specification for tailoring PCM distribution
|
|
160
177
|
|
|
@@ -205,40 +222,33 @@ class SSZConstants(float, enum.ReprEnum):
|
|
|
205
222
|
"""When initial set of draws is not restricted in any way."""
|
|
206
223
|
|
|
207
224
|
|
|
208
|
-
# Validators for selected attributes of
|
|
225
|
+
# Validators for selected attributes of MarketSpec
|
|
209
226
|
def _sample_size_validator(
|
|
210
|
-
_object:
|
|
227
|
+
_object: MarketSpec, _attribute: Attribute[int], _value: int, /
|
|
211
228
|
) -> None:
|
|
212
229
|
if _value < 10**6:
|
|
213
230
|
raise ValueError(
|
|
214
|
-
f"Sample size must be
|
|
231
|
+
f"Sample size must be no less than {10**6:,d}; got, {_value:,d}."
|
|
215
232
|
)
|
|
216
233
|
|
|
217
234
|
|
|
218
|
-
def
|
|
219
|
-
|
|
220
|
-
_attribute: Attribute[float | None],
|
|
221
|
-
_value: float | None,
|
|
222
|
-
/,
|
|
235
|
+
def _share_spec_validator(
|
|
236
|
+
_instance: MarketSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
|
|
223
237
|
) -> None:
|
|
224
|
-
|
|
238
|
+
_r_bar = _value.recapture_rate
|
|
239
|
+
if _r_bar and not (0 < _r_bar <= 1):
|
|
225
240
|
raise ValueError("Recapture rate must lie in the interval, [0, 1).")
|
|
226
241
|
|
|
227
|
-
|
|
242
|
+
elif _r_bar and _value.recapture_form == RECConstants.OUTIN:
|
|
228
243
|
raise ValueError(
|
|
229
244
|
"Market share specification requires estimation of recapture rate from "
|
|
230
245
|
"generated data. Either delete recapture rate specification or set it to None."
|
|
231
246
|
)
|
|
232
247
|
|
|
233
|
-
|
|
234
|
-
def _share_spec_validator(
|
|
235
|
-
_instance: MarketSampleSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
|
|
236
|
-
) -> None:
|
|
237
|
-
_r_bar = _instance.recapture_rate
|
|
238
248
|
if _value.dist_type == SHRConstants.UNI:
|
|
239
|
-
if _value.
|
|
249
|
+
if _value.recapture_form == RECConstants.OUTIN:
|
|
240
250
|
raise ValueError(
|
|
241
|
-
f"Invalid recapture specification, {_value.
|
|
251
|
+
f"Invalid recapture specification, {_value.recapture_form!r} "
|
|
242
252
|
"for market share specification with Uniform distribution. "
|
|
243
253
|
"Redefine the market-sample specification, modifying the ."
|
|
244
254
|
"market-share specification or the recapture specification."
|
|
@@ -246,32 +256,31 @@ def _share_spec_validator(
|
|
|
246
256
|
elif _value.firm_counts_weights is not None:
|
|
247
257
|
raise ValueError(
|
|
248
258
|
"Generated data for markets with specified firm-counts or "
|
|
249
|
-
"varying firm counts are not feasible
|
|
259
|
+
"varying firm counts are not feasible for market shares "
|
|
250
260
|
"with Uniform distribution. Consider revising the "
|
|
251
261
|
r"distribution type to {SHRConstants.DIR_FLAT}, which gives "
|
|
252
262
|
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
253
263
|
"for firm-count, :math:`n`."
|
|
254
264
|
)
|
|
255
|
-
|
|
256
|
-
elif _value.recapture_spec != RECConstants.OUTIN and (
|
|
265
|
+
elif _value.recapture_form != RECConstants.OUTIN and (
|
|
257
266
|
_r_bar is None or not isinstance(_r_bar, float)
|
|
258
267
|
):
|
|
259
268
|
raise ValueError(
|
|
260
|
-
f"Recapture specification, {_value.
|
|
269
|
+
f"Recapture specification, {_value.recapture_form!r} requires that "
|
|
261
270
|
"the market sample specification inclues a recapture rate."
|
|
262
271
|
)
|
|
263
272
|
|
|
264
273
|
|
|
265
274
|
def _pcm_spec_validator(
|
|
266
|
-
_instance:
|
|
275
|
+
_instance: MarketSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
|
|
267
276
|
) -> None:
|
|
268
277
|
if (
|
|
269
|
-
_instance.share_spec.
|
|
278
|
+
_instance.share_spec.recapture_form == RECConstants.FIXED
|
|
270
279
|
and _value.firm2_pcm_constraint == FM2Constants.MNL
|
|
271
280
|
):
|
|
272
281
|
raise ValueError(
|
|
273
282
|
"{} {} {}".format(
|
|
274
|
-
f'Specification of "
|
|
283
|
+
f'Specification of "recapture_form", "{_instance.share_spec.recapture_form}"',
|
|
275
284
|
"requires Firm 2 margin must have property, ",
|
|
276
285
|
f'"{FM2Constants.IID}" or "{FM2Constants.SYM}".',
|
|
277
286
|
)
|
|
@@ -297,49 +306,35 @@ def _pcm_spec_validator(
|
|
|
297
306
|
)
|
|
298
307
|
|
|
299
308
|
|
|
300
|
-
@define(slots=
|
|
301
|
-
class
|
|
309
|
+
@define(slots=False)
|
|
310
|
+
class MarketSpec:
|
|
302
311
|
"""Parameter specification for market data generation."""
|
|
303
312
|
|
|
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
313
|
share_spec: ShareSpec = field(
|
|
326
314
|
kw_only=True,
|
|
327
|
-
default=ShareSpec(RECConstants.INOUT, SHRConstants.UNI, None, None),
|
|
315
|
+
default=ShareSpec(RECConstants.INOUT, 0.855, SHRConstants.UNI, None, None),
|
|
328
316
|
validator=[validators.instance_of(ShareSpec), _share_spec_validator],
|
|
329
317
|
)
|
|
330
|
-
"""
|
|
318
|
+
"""Market-share specification, see definition of ShareSpec"""
|
|
331
319
|
|
|
332
320
|
pcm_spec: PCMSpec = field(
|
|
333
321
|
kw_only=True,
|
|
334
|
-
default=PCMSpec(
|
|
322
|
+
default=PCMSpec(FM2Constants.IID, PCMConstants.UNI, None),
|
|
335
323
|
validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
|
|
336
324
|
)
|
|
337
|
-
"""
|
|
325
|
+
"""Margin specification, see definition of PCMSpec"""
|
|
326
|
+
|
|
327
|
+
price_spec: PRIConstants = field(
|
|
328
|
+
kw_only=True,
|
|
329
|
+
default=PRIConstants.SYM,
|
|
330
|
+
validator=validators.instance_of(PRIConstants),
|
|
331
|
+
)
|
|
332
|
+
"""Price specification, see PRIConstants"""
|
|
338
333
|
|
|
339
|
-
hsr_filing_test_type: SSZConstants = field(
|
|
334
|
+
hsr_filing_test_type: SSZConstants = field(
|
|
340
335
|
kw_only=True,
|
|
341
336
|
default=SSZConstants.ONE,
|
|
342
|
-
validator=validators.instance_of(SSZConstants),
|
|
337
|
+
validator=validators.instance_of(SSZConstants),
|
|
343
338
|
)
|
|
344
339
|
"""Method for modeling HSR filing threholds, see SSZConstants"""
|
|
345
340
|
|
|
@@ -351,19 +346,16 @@ class INVResolution(enum.StrEnum):
|
|
|
351
346
|
BOTH = "both"
|
|
352
347
|
|
|
353
348
|
|
|
354
|
-
@
|
|
349
|
+
@frozen
|
|
355
350
|
class UPPTestRegime:
|
|
356
|
-
resolution: INVResolution = field(
|
|
357
|
-
default=INVResolution.ENFT,
|
|
358
|
-
validator=validators.instance_of(INVResolution), # type: ignore
|
|
351
|
+
resolution: INVResolution = field(
|
|
352
|
+
default=INVResolution.ENFT, validator=validators.instance_of(INVResolution)
|
|
359
353
|
)
|
|
360
|
-
guppi_aggregator: UPPAggrSelector = field(
|
|
361
|
-
default=UPPAggrSelector.
|
|
362
|
-
validator=validators.instance_of(UPPAggrSelector), # type: ignore
|
|
354
|
+
guppi_aggregator: UPPAggrSelector = field(
|
|
355
|
+
default=UPPAggrSelector.MIN, validator=validators.instance_of(UPPAggrSelector)
|
|
363
356
|
)
|
|
364
|
-
divr_aggregator: UPPAggrSelector | None = field(
|
|
365
|
-
default=
|
|
366
|
-
validator=validators.instance_of(UPPAggrSelector | None), # type: ignore
|
|
357
|
+
divr_aggregator: UPPAggrSelector | None = field(
|
|
358
|
+
default=None, validator=validators.instance_of((UPPAggrSelector, type(None)))
|
|
367
359
|
)
|
|
368
360
|
|
|
369
361
|
|
|
@@ -469,7 +461,7 @@ class MarginDataSample:
|
|
|
469
461
|
|
|
470
462
|
@dataclass(slots=True, frozen=True)
|
|
471
463
|
class UPPTestsRaw:
|
|
472
|
-
"""arrays marking test failures and successes
|
|
464
|
+
"""Container for arrays marking test failures and successes
|
|
473
465
|
|
|
474
466
|
A test success is a draw ("market") that meeets the
|
|
475
467
|
specified test criterion, and a test failure is
|
|
@@ -494,9 +486,9 @@ class UPPTestsRaw:
|
|
|
494
486
|
|
|
495
487
|
@dataclass(slots=True, frozen=True)
|
|
496
488
|
class UPPTestsCounts:
|
|
497
|
-
"""
|
|
489
|
+
"""Counts of markets resolved as specified
|
|
498
490
|
|
|
499
|
-
Resolution
|
|
491
|
+
Resolution may be either "enforcement" or "clearance".
|
|
500
492
|
"""
|
|
501
493
|
|
|
502
494
|
by_firm_count: NDArray[np.int64]
|
|
@@ -19,11 +19,12 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
|
19
19
|
prng,
|
|
20
20
|
)
|
|
21
21
|
from . import (
|
|
22
|
+
EMPTY_ARRAY_DEFAULT,
|
|
22
23
|
FCOUNT_WTS_DEFAULT,
|
|
23
24
|
TF,
|
|
24
25
|
FM2Constants,
|
|
25
26
|
MarginDataSample,
|
|
26
|
-
|
|
27
|
+
MarketSpec,
|
|
27
28
|
PCMConstants,
|
|
28
29
|
PriceDataSample,
|
|
29
30
|
PRIConstants,
|
|
@@ -36,7 +37,8 @@ __version__ = version(_PKG_NAME)
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _gen_share_data(
|
|
39
|
-
|
|
40
|
+
_sample_size: int,
|
|
41
|
+
_mkt_sample_spec: MarketSpec,
|
|
40
42
|
_fcount_rng_seed_seq: SeedSequence | None,
|
|
41
43
|
_mktshr_rng_seed_seq: SeedSequence,
|
|
42
44
|
_nthreads: int = 16,
|
|
@@ -61,14 +63,12 @@ def _gen_share_data(
|
|
|
61
63
|
|
|
62
64
|
"""
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
_recapture_form, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts_raw = (
|
|
65
67
|
getattr(_mkt_sample_spec.share_spec, _f)
|
|
66
|
-
for _f in ("
|
|
68
|
+
for _f in ("recapture_form", "dist_type", "dist_parms", "firm_counts_weights")
|
|
67
69
|
)
|
|
68
70
|
|
|
69
|
-
_ssz =
|
|
70
|
-
|
|
71
|
-
_r_bar = _mkt_sample_spec.recapture_rate or 0.80
|
|
71
|
+
_ssz = _sample_size
|
|
72
72
|
|
|
73
73
|
match _dist_type_mktshr:
|
|
74
74
|
case SHRConstants.UNI:
|
|
@@ -84,7 +84,7 @@ def _gen_share_data(
|
|
|
84
84
|
)
|
|
85
85
|
_mkt_share_sample = _gen_market_shares_dirichlet_multisample(
|
|
86
86
|
_ssz,
|
|
87
|
-
|
|
87
|
+
_recapture_form,
|
|
88
88
|
_dist_type_mktshr,
|
|
89
89
|
_dist_parms_mktshr,
|
|
90
90
|
_firm_count_prob_wts,
|
|
@@ -98,9 +98,10 @@ def _gen_share_data(
|
|
|
98
98
|
f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
|
|
99
99
|
)
|
|
100
100
|
|
|
101
|
-
# If
|
|
101
|
+
# If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
|
|
102
102
|
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
103
|
-
|
|
103
|
+
_r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.855
|
|
104
|
+
if _recapture_form == RECConstants.INOUT:
|
|
104
105
|
_mkt_share_sample = ShareDataSample(
|
|
105
106
|
_mkt_share_sample.mktshr_array,
|
|
106
107
|
_mkt_share_sample.fcounts,
|
|
@@ -176,10 +177,10 @@ def _gen_market_shares_uniform(
|
|
|
176
177
|
|
|
177
178
|
def _gen_market_shares_dirichlet_multisample(
|
|
178
179
|
_s_size: int = 10**6,
|
|
179
|
-
|
|
180
|
+
_recapture_form: RECConstants = RECConstants.INOUT,
|
|
180
181
|
_dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
|
|
181
182
|
_dist_parms_dir: NDArray[np.floating[TF]] | None = None,
|
|
182
|
-
_firm_count_wts: NDArray[np.floating[TF]] | None = None,
|
|
183
|
+
_firm_count_wts: NDArray[np.floating[TF]] | None = None,
|
|
183
184
|
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
184
185
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
185
186
|
_nthreads: int = 16,
|
|
@@ -202,7 +203,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
202
203
|
firm count weights array for sample to be drawn
|
|
203
204
|
_dist_type_dir
|
|
204
205
|
Whether Dirichlet is Flat or Asymmetric
|
|
205
|
-
|
|
206
|
+
_recapture_form
|
|
206
207
|
r_1 = r_2 if "proportional", otherwise MNL-consistent
|
|
207
208
|
_fcount_rng_seed_seq
|
|
208
209
|
seed firm count rng, for replicable results
|
|
@@ -226,7 +227,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
226
227
|
*(
|
|
227
228
|
_f
|
|
228
229
|
for _f in zip(
|
|
229
|
-
2 + np.arange(len(_firm_count_wts)),
|
|
230
|
+
2 + np.arange(len(_firm_count_wts)),
|
|
230
231
|
_firm_count_wts / _firm_count_wts.sum(),
|
|
231
232
|
strict=True,
|
|
232
233
|
)
|
|
@@ -277,7 +278,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
277
278
|
_mktshr_sample_f = _gen_market_shares_dirichlet(
|
|
278
279
|
_dir_alphas_test,
|
|
279
280
|
len(_fcounts_match_rows),
|
|
280
|
-
|
|
281
|
+
_recapture_form,
|
|
281
282
|
_f_sseq,
|
|
282
283
|
_nthreads,
|
|
283
284
|
)
|
|
@@ -315,7 +316,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
315
316
|
def _gen_market_shares_dirichlet(
|
|
316
317
|
_dir_alphas: NDArray[np.floating[TF]],
|
|
317
318
|
_s_size: int = 10**6,
|
|
318
|
-
|
|
319
|
+
_recapture_form: RECConstants = RECConstants.INOUT,
|
|
319
320
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
320
321
|
_nthreads: int = 16,
|
|
321
322
|
/,
|
|
@@ -330,7 +331,7 @@ def _gen_market_shares_dirichlet(
|
|
|
330
331
|
sample size to be drawn
|
|
331
332
|
_r_bar
|
|
332
333
|
market recapture rate
|
|
333
|
-
|
|
334
|
+
_recapture_form
|
|
334
335
|
r_1 = r_2 if RECConstants.FIXED, otherwise MNL-consistent. If
|
|
335
336
|
RECConstants.OUTIN; the number of columns in the output share array
|
|
336
337
|
is len(_dir_alphas) - 1.
|
|
@@ -348,7 +349,7 @@ def _gen_market_shares_dirichlet(
|
|
|
348
349
|
if not isinstance(_dir_alphas, np.ndarray):
|
|
349
350
|
_dir_alphas = np.array(_dir_alphas)
|
|
350
351
|
|
|
351
|
-
if
|
|
352
|
+
if _recapture_form == RECConstants.OUTIN:
|
|
352
353
|
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
353
354
|
|
|
354
355
|
_mktshr_seed_seq_ch = (
|
|
@@ -380,9 +381,9 @@ def _gen_market_shares_dirichlet(
|
|
|
380
381
|
)
|
|
381
382
|
)
|
|
382
383
|
|
|
383
|
-
# If
|
|
384
|
+
# If recapture_form == 'inside_out', further calculations downstream
|
|
384
385
|
_aggregate_purchase_prob = np.nan * np.empty((_s_size, 1))
|
|
385
|
-
if
|
|
386
|
+
if _recapture_form == RECConstants.OUTIN:
|
|
386
387
|
_aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
|
|
387
388
|
_mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
|
|
388
389
|
|
|
@@ -394,27 +395,25 @@ def _gen_market_shares_dirichlet(
|
|
|
394
395
|
)
|
|
395
396
|
|
|
396
397
|
|
|
397
|
-
def
|
|
398
|
-
_frmshr_array: NDArray[np.
|
|
399
|
-
_nth_firm_share: NDArray[np.
|
|
400
|
-
_mkt_sample_spec:
|
|
398
|
+
def _gen_price_data(
|
|
399
|
+
_frmshr_array: NDArray[np.float64],
|
|
400
|
+
_nth_firm_share: NDArray[np.float64],
|
|
401
|
+
_mkt_sample_spec: MarketSpec,
|
|
401
402
|
_seed_seq: SeedSequence | None = None,
|
|
402
403
|
/,
|
|
403
404
|
) -> PriceDataSample:
|
|
404
|
-
_ssz = len(_frmshr_array)
|
|
405
|
-
|
|
406
405
|
_hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
|
|
407
406
|
|
|
408
407
|
_price_array, _price_ratio_array, _hsr_filing_test = (
|
|
409
|
-
np.ones_like(_frmshr_array),
|
|
410
|
-
np.empty_like(_frmshr_array),
|
|
411
|
-
np.empty(
|
|
408
|
+
np.ones_like(_frmshr_array, np.float64),
|
|
409
|
+
np.empty_like(_frmshr_array, np.float64),
|
|
410
|
+
np.empty(len(_frmshr_array), bool),
|
|
412
411
|
)
|
|
413
412
|
|
|
414
413
|
_pr_max_ratio = 5.0
|
|
415
|
-
match _mkt_sample_spec.
|
|
414
|
+
match _mkt_sample_spec.price_spec:
|
|
416
415
|
case PRIConstants.SYM:
|
|
417
|
-
_nth_firm_price = np.ones((
|
|
416
|
+
_nth_firm_price = np.ones((len(_frmshr_array), 1))
|
|
418
417
|
case PRIConstants.POS:
|
|
419
418
|
_price_array, _nth_firm_price = (
|
|
420
419
|
np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
@@ -434,9 +433,8 @@ def _gen_pr_data(
|
|
|
434
433
|
case _:
|
|
435
434
|
raise ValueError(
|
|
436
435
|
f"Condition regarding price symmetry"
|
|
437
|
-
f' "{_mkt_sample_spec.
|
|
436
|
+
f' "{_mkt_sample_spec.price_spec.value}" is invalid.'
|
|
438
437
|
)
|
|
439
|
-
# del _pr_max_ratio
|
|
440
438
|
|
|
441
439
|
_price_array = _price_array.astype(np.float64)
|
|
442
440
|
_rev_array = _price_array * _frmshr_array
|
|
@@ -444,7 +442,7 @@ def _gen_pr_data(
|
|
|
444
442
|
|
|
445
443
|
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
446
444
|
# the ratio has not changed since inception of the HSR filing test,
|
|
447
|
-
# so we treat it as a constant of merger policy.
|
|
445
|
+
# so we treat it as a constant of merger enforcement policy.
|
|
448
446
|
_test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
|
|
449
447
|
|
|
450
448
|
match _hsr_filing_test_type:
|
|
@@ -479,21 +477,21 @@ def _gen_pr_data(
|
|
|
479
477
|
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
480
478
|
case _:
|
|
481
479
|
# Otherwise, all draws meet the filing test
|
|
482
|
-
_hsr_filing_test = np.ones(
|
|
480
|
+
_hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
|
|
483
481
|
|
|
484
482
|
return PriceDataSample(_price_array, _hsr_filing_test)
|
|
485
483
|
|
|
486
484
|
|
|
487
485
|
def _gen_pcm_data(
|
|
488
486
|
_frmshr_array: NDArray[np.floating[TF]],
|
|
489
|
-
_mkt_sample_spec:
|
|
487
|
+
_mkt_sample_spec: MarketSpec,
|
|
490
488
|
_price_array: NDArray[np.floating[TF]],
|
|
491
489
|
_aggregate_purchase_prob: NDArray[np.floating[TF]],
|
|
492
490
|
_pcm_rng_seed_seq: SeedSequence,
|
|
493
491
|
_nthreads: int = 16,
|
|
494
492
|
/,
|
|
495
493
|
) -> MarginDataSample:
|
|
496
|
-
|
|
494
|
+
_recapture_form = _mkt_sample_spec.share_spec.recapture_form
|
|
497
495
|
_dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
|
|
498
496
|
getattr(_mkt_sample_spec.pcm_spec, _f)
|
|
499
497
|
for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
|
|
@@ -506,27 +504,25 @@ def _gen_pcm_data(
|
|
|
506
504
|
_mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
|
|
507
505
|
|
|
508
506
|
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
509
|
-
_dist_parms = np.ones(2, np.float64)
|
|
510
507
|
if _dist_type_pcm == PCMConstants.EMPR:
|
|
511
508
|
_pcm_array = resample_mgn_data(
|
|
512
509
|
_pcm_array.shape, # type: ignore
|
|
513
510
|
seed_sequence=_pcm_rng_seed_seq,
|
|
514
511
|
)
|
|
515
512
|
else:
|
|
516
|
-
if _dist_type_pcm == PCMConstants.
|
|
517
|
-
_dist_parms = (
|
|
518
|
-
DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
|
|
519
|
-
)
|
|
520
|
-
elif _dist_type_pcm == PCMConstants.BETA:
|
|
521
|
-
# Error-checking (could move to validators in definition of MarketSampleSpec)
|
|
522
|
-
|
|
513
|
+
if _dist_type_pcm == PCMConstants.BETA:
|
|
523
514
|
if _dist_parms_pcm is None:
|
|
524
|
-
_dist_parms_pcm =
|
|
515
|
+
_dist_parms_pcm = np.ones(2, np.float64)
|
|
525
516
|
|
|
526
517
|
elif _dist_type_pcm == PCMConstants.BETA_BND: # Bounded beta
|
|
527
518
|
if _dist_parms_pcm is None:
|
|
528
519
|
_dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
|
|
529
520
|
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
521
|
+
else:
|
|
522
|
+
# _dist_type_pcm == PCMConstants.UNI
|
|
523
|
+
_dist_parms = (
|
|
524
|
+
DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
|
|
525
|
+
)
|
|
530
526
|
|
|
531
527
|
_pcm_rng = MultithreadedRNG(
|
|
532
528
|
_pcm_array,
|
|
@@ -539,7 +535,7 @@ def _gen_pcm_data(
|
|
|
539
535
|
del _pcm_rng
|
|
540
536
|
|
|
541
537
|
if _dist_type_pcm == PCMConstants.BETA_BND:
|
|
542
|
-
_beta_min, _beta_max = _dist_parms_pcm[2:]
|
|
538
|
+
_beta_min, _beta_max = _dist_parms_pcm[2:]
|
|
543
539
|
_pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
|
|
544
540
|
del _beta_min, _beta_max
|
|
545
541
|
|
|
@@ -566,6 +562,66 @@ def _gen_pcm_data(
|
|
|
566
562
|
return MarginDataSample(_pcm_array, _mnl_test_array)
|
|
567
563
|
|
|
568
564
|
|
|
565
|
+
def _gen_divr_array(
|
|
566
|
+
_recapture_form: RECConstants,
|
|
567
|
+
_recapture_rate: float | None,
|
|
568
|
+
_frmshr_array: NDArray[np.float64],
|
|
569
|
+
_aggregate_purchase_prob: NDArray[np.float64] = EMPTY_ARRAY_DEFAULT,
|
|
570
|
+
/,
|
|
571
|
+
) -> NDArray[np.float64]:
|
|
572
|
+
"""
|
|
573
|
+
Given merging-firm shares and related parameters, return diverion ratios.
|
|
574
|
+
|
|
575
|
+
If recapture is specified as "Outside-in" (RECConstants.OUTIN), then the
|
|
576
|
+
choice-probability for the outside good must be supplied.
|
|
577
|
+
|
|
578
|
+
Parameters
|
|
579
|
+
----------
|
|
580
|
+
_recapture_form
|
|
581
|
+
Enum specifying Fixed (proportional), Inside-out, or Outside-in
|
|
582
|
+
|
|
583
|
+
_recapture_rate
|
|
584
|
+
If recapture is proportional or inside-out, the recapture rate
|
|
585
|
+
for the firm with the smaller share.
|
|
586
|
+
|
|
587
|
+
_frmshr_array
|
|
588
|
+
Merging-firm shares.
|
|
589
|
+
|
|
590
|
+
_aggregate_purchase_prob
|
|
591
|
+
1 minus probability that the outside good is chosen; converts
|
|
592
|
+
market shares to choice probabilities by multiplication.
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
Merging-firm diversion ratios for mergers in the sample.
|
|
597
|
+
|
|
598
|
+
"""
|
|
599
|
+
|
|
600
|
+
_divr_array: NDArray[np.float64]
|
|
601
|
+
if _recapture_form == RECConstants.FIXED:
|
|
602
|
+
_divr_array = _recapture_rate * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
|
|
603
|
+
|
|
604
|
+
else:
|
|
605
|
+
_purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
606
|
+
_divr_array = _purchprob_array[:, ::-1] / (1 - _purchprob_array)
|
|
607
|
+
|
|
608
|
+
_divr_assert_test = (
|
|
609
|
+
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
610
|
+
| (np.argmin(_frmshr_array, axis=1) == np.argmax(_divr_array, axis=1))
|
|
611
|
+
)[:, None]
|
|
612
|
+
if not all(_divr_assert_test):
|
|
613
|
+
raise ValueError(
|
|
614
|
+
"{} {} {} {}".format(
|
|
615
|
+
"Data construction fails tests:",
|
|
616
|
+
"the index of min(s_1, s_2) must equal",
|
|
617
|
+
"the index of max(d_12, d_21), for all draws.",
|
|
618
|
+
"unless frmshr_array sums to 1.00.",
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return _divr_array
|
|
623
|
+
|
|
624
|
+
|
|
569
625
|
def _beta_located(
|
|
570
626
|
_mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
|
|
571
627
|
) -> NDArray[np.float64]:
|