mergeron 2025.739290.3__py3-none-any.whl → 2025.739290.5__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 +103 -48
- mergeron/core/__init__.py +105 -4
- mergeron/core/empirical_margin_distribution.py +100 -78
- mergeron/core/ftc_merger_investigations_data.py +309 -316
- mergeron/core/guidelines_boundaries.py +67 -138
- mergeron/core/guidelines_boundary_functions.py +202 -379
- mergeron/core/guidelines_boundary_functions_extra.py +264 -106
- mergeron/core/pseudorandom_numbers.py +73 -64
- mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron/data/ftc_invdata.zip +0 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +9 -7
- mergeron/gen/__init__.py +138 -161
- mergeron/gen/data_generation.py +181 -149
- mergeron/gen/data_generation_functions.py +220 -237
- mergeron/gen/enforcement_stats.py +78 -109
- mergeron/gen/upp_tests.py +119 -194
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/METADATA +2 -3
- mergeron-2025.739290.5.dist-info/RECORD +24 -0
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron-2025.739290.3.dist-info/RECORD +0 -23
|
@@ -18,7 +18,7 @@ from .. import ( # noqa: TID252
|
|
|
18
18
|
ArrayFloat,
|
|
19
19
|
RECForm,
|
|
20
20
|
)
|
|
21
|
-
from ..core.empirical_margin_distribution import
|
|
21
|
+
from ..core.empirical_margin_distribution import margin_data_resampler # noqa: TID252
|
|
22
22
|
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
23
23
|
DEFAULT_BETA_DIST_PARMS,
|
|
24
24
|
DEFAULT_DIST_PARMS,
|
|
@@ -26,6 +26,7 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
|
26
26
|
prng,
|
|
27
27
|
)
|
|
28
28
|
from . import (
|
|
29
|
+
DEFAULT_BETA_BND_DIST_PARMS,
|
|
29
30
|
DEFAULT_FCOUNT_WTS,
|
|
30
31
|
FM2Constraint,
|
|
31
32
|
MarginDataSample,
|
|
@@ -69,36 +70,23 @@ def gen_share_data(
|
|
|
69
70
|
|
|
70
71
|
"""
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
dist_type_mktshr, firm_count_prob_wts, dist_parms_mktshr, recapture_form = (
|
|
73
74
|
getattr(_share_spec, _f)
|
|
74
75
|
for _f in ("dist_type", "firm_counts_weights", "dist_parms", "recapture_form")
|
|
75
76
|
)
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
DEFAULT_DIST_PARMS
|
|
81
|
-
if _dist_type_mktshr == "Uniform"
|
|
82
|
-
else np.ones(1 + len(_firm_count_prob_wts), float)
|
|
78
|
+
if dist_type_mktshr == SHRDistribution.UNI:
|
|
79
|
+
mkt_share_sample = gen_market_shares_uniform(
|
|
80
|
+
_sample_size, dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
|
|
83
81
|
)
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
None
|
|
93
|
-
if _firm_count_prob_wts is None
|
|
94
|
-
else np.array(_firm_count_prob_wts, float)
|
|
95
|
-
)
|
|
96
|
-
_mkt_share_sample = _gen_market_shares_dirichlet_multimarket(
|
|
97
|
-
_ssz,
|
|
98
|
-
_recapture_form,
|
|
99
|
-
_dist_type_mktshr,
|
|
100
|
-
_dist_parms_mktshr,
|
|
101
|
-
_firm_count_prob_wts,
|
|
83
|
+
elif dist_type_mktshr.name.startswith("DIR_"):
|
|
84
|
+
mkt_share_sample = _gen_market_shares_dirichlet_multimarket(
|
|
85
|
+
_sample_size,
|
|
86
|
+
recapture_form,
|
|
87
|
+
dist_type_mktshr,
|
|
88
|
+
dist_parms_mktshr,
|
|
89
|
+
firm_count_prob_wts,
|
|
102
90
|
_fcount_rng_seed_seq,
|
|
103
91
|
_mktshr_rng_seed_seq,
|
|
104
92
|
_nthreads,
|
|
@@ -106,21 +94,21 @@ def gen_share_data(
|
|
|
106
94
|
|
|
107
95
|
else:
|
|
108
96
|
raise ValueError(
|
|
109
|
-
f'Unexpected type, "{
|
|
97
|
+
f'Unexpected type, "{dist_type_mktshr}" for share distribution.'
|
|
110
98
|
)
|
|
111
99
|
|
|
112
100
|
# If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
frmshr_array = mkt_share_sample.mktshr_array[:, :2]
|
|
102
|
+
r_bar_ = _share_spec.recapture_ratio or DEFAULT_REC_RATIO
|
|
103
|
+
if recapture_form == RECForm.INOUT:
|
|
104
|
+
mkt_share_sample = ShareDataSample(
|
|
105
|
+
mkt_share_sample.mktshr_array,
|
|
106
|
+
mkt_share_sample.fcounts,
|
|
107
|
+
mkt_share_sample.nth_firm_share,
|
|
108
|
+
r_bar_ / (1 - (1 - r_bar_) * frmshr_array.min(axis=1, keepdims=True)),
|
|
121
109
|
)
|
|
122
110
|
|
|
123
|
-
return
|
|
111
|
+
return mkt_share_sample
|
|
124
112
|
|
|
125
113
|
|
|
126
114
|
def gen_market_shares_uniform(
|
|
@@ -149,46 +137,41 @@ def gen_market_shares_uniform(
|
|
|
149
137
|
|
|
150
138
|
"""
|
|
151
139
|
|
|
152
|
-
|
|
140
|
+
frmshr_array: ArrayDouble = np.empty((_s_size, 2), float)
|
|
153
141
|
|
|
154
|
-
|
|
155
|
-
|
|
142
|
+
MultithreadedRNG(
|
|
143
|
+
frmshr_array,
|
|
156
144
|
dist_type="Uniform",
|
|
157
145
|
dist_parms=_dist_parms_mktshr,
|
|
158
146
|
seed_sequence=_mktshr_rng_seed_seq,
|
|
159
147
|
nthreads=_nthreads,
|
|
160
|
-
)
|
|
161
|
-
_mrng.fill()
|
|
148
|
+
).fill()
|
|
162
149
|
|
|
163
150
|
# Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 <= 1
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
np.abs(np.diff(
|
|
151
|
+
frmshr_array = np.hstack((
|
|
152
|
+
frmshr_array.min(axis=1, keepdims=True),
|
|
153
|
+
np.abs(np.diff(frmshr_array, axis=1)),
|
|
167
154
|
))
|
|
168
155
|
|
|
169
156
|
# Keep only share combinations representing feasible mergers
|
|
170
157
|
# This is a no-op for 64-bit floats, but is necessary for smaller floats
|
|
171
|
-
|
|
158
|
+
frmshr_array = frmshr_array[frmshr_array.min(axis=1) > 0]
|
|
172
159
|
|
|
173
160
|
# Let a third column have values of "np.nan", so HHI calculations return "np.nan"
|
|
174
|
-
|
|
175
|
-
|
|
161
|
+
mktshr_array_ = np.pad(
|
|
162
|
+
frmshr_array, ((0, 0), (0, 1)), "constant", constant_values=np.nan
|
|
176
163
|
)
|
|
177
164
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
np.empty(_fcounts.shape, np.float64)
|
|
181
|
-
for _ in ("nth_firm_share", "aggregate_purchase_prob")
|
|
165
|
+
fcounts_, nth_firm_share_, aggregate_purchase_prob_ = (
|
|
166
|
+
np.empty((_s_size, 1), type_) for type_ in (np.uint8, np.float64, np.float64)
|
|
182
167
|
)
|
|
183
168
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
_nth_firm_share.fill(np.nan)
|
|
188
|
-
_aggregate_purchase_prob.fill(np.nan)
|
|
169
|
+
fcounts_.fill(0)
|
|
170
|
+
nth_firm_share_.fill(np.nan)
|
|
171
|
+
aggregate_purchase_prob_.fill(np.nan)
|
|
189
172
|
|
|
190
173
|
return ShareDataSample(
|
|
191
|
-
|
|
174
|
+
mktshr_array_, fcounts_, nth_firm_share_, aggregate_purchase_prob_
|
|
192
175
|
)
|
|
193
176
|
|
|
194
177
|
|
|
@@ -245,83 +228,79 @@ def _gen_market_shares_dirichlet_multimarket(
|
|
|
245
228
|
else _firm_count_wts
|
|
246
229
|
)
|
|
247
230
|
|
|
248
|
-
|
|
249
|
-
|
|
231
|
+
min_choice_wt = 0.03 if _dist_type_dir == SHRDistribution.DIR_FLAT_CONSTR else 0.00
|
|
232
|
+
fcount_keys, choice_wts = zip(
|
|
250
233
|
*(
|
|
251
|
-
|
|
252
|
-
for
|
|
253
|
-
2 + np.arange(len(_firm_count_wts)),
|
|
234
|
+
f_
|
|
235
|
+
for f_ in zip(
|
|
236
|
+
2 + np.arange(len(_firm_count_wts), dtype=np.uint8),
|
|
254
237
|
_firm_count_wts / _firm_count_wts.sum(),
|
|
255
238
|
strict=True,
|
|
256
239
|
)
|
|
257
|
-
if
|
|
240
|
+
if f_[1] > min_choice_wt
|
|
258
241
|
)
|
|
259
242
|
)
|
|
260
|
-
|
|
243
|
+
choice_wts /= sum(choice_wts)
|
|
261
244
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
_dist_parms_dir[:
|
|
245
|
+
fc_max = fcount_keys[-1]
|
|
246
|
+
dir_alphas_full = (
|
|
247
|
+
_dist_parms_dir[:fc_max] if len(_dist_parms_dir) else [1.0] * fc_max
|
|
265
248
|
)
|
|
266
249
|
if _dist_type_dir == SHRDistribution.DIR_ASYM:
|
|
267
|
-
|
|
250
|
+
dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, fc_max)
|
|
268
251
|
|
|
269
252
|
if _dist_type_dir == SHRDistribution.DIR_COND:
|
|
270
253
|
|
|
271
254
|
def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
|
|
272
|
-
|
|
273
|
-
if _fcv > len(
|
|
274
|
-
|
|
275
|
-
return np.array(
|
|
255
|
+
dat_ = [2.5] * 2
|
|
256
|
+
if _fcv > len(dat_):
|
|
257
|
+
dat_ += [1.0 / (_fcv - 2)] * (_fcv - 2)
|
|
258
|
+
return np.array(dat_, float)
|
|
276
259
|
|
|
277
260
|
else:
|
|
278
261
|
|
|
279
262
|
def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
|
|
280
|
-
return np.array(
|
|
263
|
+
return np.array(dir_alphas_full[:_fcv], float)
|
|
281
264
|
|
|
282
|
-
|
|
283
|
-
|
|
265
|
+
fcounts_ = prng(_fcount_rng_seed_seq).choice(
|
|
266
|
+
fcount_keys, size=(_s_size, 1), p=choice_wts
|
|
284
267
|
)
|
|
285
268
|
|
|
286
|
-
|
|
287
|
-
_mktshr_rng_seed_seq.spawn(len(_fcount_keys))
|
|
288
|
-
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
289
|
-
else SeedSequence(pool_size=8).spawn(len(_fcounts))
|
|
290
|
-
)
|
|
269
|
+
mktshr_seed_seq_ch = _mktshr_rng_seed_seq.spawn(len(fcount_keys))
|
|
291
270
|
|
|
292
|
-
|
|
271
|
+
aggregate_purchase_prob_, nth_firm_share_ = (
|
|
293
272
|
np.empty((_s_size, 1)) for _ in range(2)
|
|
294
273
|
)
|
|
295
|
-
|
|
296
|
-
for
|
|
297
|
-
|
|
298
|
-
|
|
274
|
+
mktshr_array_ = np.empty((_s_size, fc_max), float)
|
|
275
|
+
for f_val, f_sseq in zip(fcount_keys, mktshr_seed_seq_ch, strict=True):
|
|
276
|
+
fcounts_match_rows = np.where(fcounts_ == f_val)[0]
|
|
277
|
+
dir_alphas_test = _gen_dir_alphas(f_val)
|
|
299
278
|
|
|
300
279
|
try:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
len(
|
|
280
|
+
mktshr_sample_f = gen_market_shares_dirichlet(
|
|
281
|
+
dir_alphas_test,
|
|
282
|
+
len(fcounts_match_rows),
|
|
304
283
|
_recapture_form,
|
|
305
|
-
|
|
284
|
+
f_sseq,
|
|
306
285
|
_nthreads,
|
|
307
286
|
)
|
|
308
|
-
except ValueError as
|
|
309
|
-
print(
|
|
310
|
-
raise
|
|
287
|
+
except ValueError as err_:
|
|
288
|
+
print(f_val, len(fcounts_match_rows))
|
|
289
|
+
raise err_
|
|
311
290
|
|
|
312
291
|
# Push data for present sample to parent
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
((0, 0), (0,
|
|
292
|
+
mktshr_array_[fcounts_match_rows] = np.pad(
|
|
293
|
+
mktshr_sample_f.mktshr_array,
|
|
294
|
+
((0, 0), (0, fc_max - mktshr_sample_f.mktshr_array.shape[1])),
|
|
316
295
|
"constant",
|
|
317
296
|
)
|
|
318
|
-
|
|
319
|
-
|
|
297
|
+
aggregate_purchase_prob_[fcounts_match_rows] = (
|
|
298
|
+
mktshr_sample_f.aggregate_purchase_prob
|
|
320
299
|
)
|
|
321
|
-
|
|
300
|
+
nth_firm_share_[fcounts_match_rows] = mktshr_sample_f.nth_firm_share
|
|
322
301
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
302
|
+
if (iss_ := np.round(np.einsum("ij->", mktshr_array_))) != _s_size or iss_ != len(
|
|
303
|
+
mktshr_array_
|
|
325
304
|
):
|
|
326
305
|
raise ValueError(
|
|
327
306
|
"DATA GENERATION ERROR: {} {} {}".format(
|
|
@@ -332,7 +311,7 @@ def _gen_market_shares_dirichlet_multimarket(
|
|
|
332
311
|
)
|
|
333
312
|
|
|
334
313
|
return ShareDataSample(
|
|
335
|
-
|
|
314
|
+
mktshr_array_, fcounts_, nth_firm_share_, aggregate_purchase_prob_
|
|
336
315
|
)
|
|
337
316
|
|
|
338
317
|
|
|
@@ -377,27 +356,29 @@ def gen_market_shares_dirichlet(
|
|
|
377
356
|
if _recapture_form == RECForm.OUTIN:
|
|
378
357
|
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
379
358
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
else SeedSequence(pool_size=8)
|
|
384
|
-
)
|
|
385
|
-
|
|
386
|
-
_mktshr_array = np.empty((_s_size, len(_dir_alphas)), float)
|
|
387
|
-
_mrng = MultithreadedRNG(
|
|
388
|
-
_mktshr_array,
|
|
359
|
+
mktshr_array = np.empty((_s_size, len(_dir_alphas)), float)
|
|
360
|
+
MultithreadedRNG(
|
|
361
|
+
mktshr_array,
|
|
389
362
|
dist_type="Dirichlet",
|
|
390
363
|
dist_parms=_dir_alphas,
|
|
391
|
-
seed_sequence=
|
|
364
|
+
seed_sequence=_mktshr_rng_seed_seq,
|
|
392
365
|
nthreads=_nthreads,
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
366
|
+
).fill()
|
|
367
|
+
# mrng_ = MultithreadedRNG(
|
|
368
|
+
# mktshr_array,
|
|
369
|
+
# dist_type="Dirichlet",
|
|
370
|
+
# dist_parms=_dir_alphas,
|
|
371
|
+
# seed_sequence=_mktshr_rng_seed_seq,
|
|
372
|
+
# nthreads=_nthreads,
|
|
373
|
+
# )
|
|
374
|
+
# mrng_.fill()
|
|
375
|
+
# del mrng_
|
|
376
|
+
|
|
377
|
+
if (iss_ := np.round(np.einsum("ij->", mktshr_array))) != _s_size or iss_ != len(
|
|
378
|
+
mktshr_array
|
|
398
379
|
):
|
|
399
|
-
print(_dir_alphas,
|
|
400
|
-
print(repr(
|
|
380
|
+
print(_dir_alphas, iss_, repr(_s_size), len(mktshr_array))
|
|
381
|
+
print(repr(mktshr_array[-10:, :]))
|
|
401
382
|
raise ValueError(
|
|
402
383
|
"DATA GENERATION ERROR: {} {} {}".format(
|
|
403
384
|
"Generation of sample shares is inconsistent:",
|
|
@@ -407,17 +388,17 @@ def gen_market_shares_dirichlet(
|
|
|
407
388
|
)
|
|
408
389
|
|
|
409
390
|
# If recapture_form == 'inside_out', further calculations downstream
|
|
410
|
-
|
|
411
|
-
|
|
391
|
+
aggregate_purchase_prob_ = np.empty((_s_size, 1), float)
|
|
392
|
+
aggregate_purchase_prob_.fill(np.nan)
|
|
412
393
|
if _recapture_form == RECForm.OUTIN:
|
|
413
|
-
|
|
414
|
-
|
|
394
|
+
aggregate_purchase_prob_ = 1 - mktshr_array[:, [-1]] # type: ignore
|
|
395
|
+
mktshr_array = mktshr_array[:, :-1] / aggregate_purchase_prob_
|
|
415
396
|
|
|
416
397
|
return ShareDataSample(
|
|
417
|
-
|
|
418
|
-
(
|
|
419
|
-
|
|
420
|
-
|
|
398
|
+
mktshr_array,
|
|
399
|
+
(mktshr_array.shape[-1] * np.ones((_s_size, 1))).astype(np.int64),
|
|
400
|
+
mktshr_array[:, [-1]],
|
|
401
|
+
aggregate_purchase_prob_,
|
|
421
402
|
)
|
|
422
403
|
|
|
423
404
|
|
|
@@ -462,19 +443,19 @@ def gen_divr_array(
|
|
|
462
443
|
|
|
463
444
|
"""
|
|
464
445
|
|
|
465
|
-
|
|
446
|
+
divr_array: ArrayDouble
|
|
466
447
|
if _recapture_form == RECForm.FIXED:
|
|
467
|
-
|
|
448
|
+
divr_array = _recapture_ratio * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
|
|
468
449
|
|
|
469
450
|
else:
|
|
470
|
-
|
|
471
|
-
|
|
451
|
+
purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
452
|
+
divr_array = purchprob_array[:, ::-1] / (1 - purchprob_array)
|
|
472
453
|
|
|
473
|
-
|
|
454
|
+
divr_assert_test = (
|
|
474
455
|
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
475
|
-
| (np.argmin(_frmshr_array, axis=1) == np.argmax(
|
|
456
|
+
| (np.argmin(_frmshr_array, axis=1) == np.argmax(divr_array, axis=1))
|
|
476
457
|
)[:, None]
|
|
477
|
-
if not all(
|
|
458
|
+
if not all(divr_assert_test):
|
|
478
459
|
raise ValueError(
|
|
479
460
|
"{} {} {} {}".format(
|
|
480
461
|
"Data construction fails tests:",
|
|
@@ -484,10 +465,10 @@ def gen_divr_array(
|
|
|
484
465
|
)
|
|
485
466
|
)
|
|
486
467
|
|
|
487
|
-
return
|
|
468
|
+
return divr_array
|
|
488
469
|
|
|
489
470
|
|
|
490
|
-
def gen_margin_price_data(
|
|
471
|
+
def gen_margin_price_data( # noqa: PLR0914
|
|
491
472
|
_frmshr_array: ArrayDouble,
|
|
492
473
|
_nth_firm_share: ArrayDouble,
|
|
493
474
|
_aggregate_purchase_prob: ArrayDouble,
|
|
@@ -539,86 +520,86 @@ def gen_margin_price_data(
|
|
|
539
520
|
Simulated margin- and price-data arrays for mergers in the sample.
|
|
540
521
|
"""
|
|
541
522
|
|
|
542
|
-
|
|
523
|
+
margin_data = MarginDataSample(
|
|
543
524
|
np.empty_like(_frmshr_array), np.ones(len(_frmshr_array)) == 0
|
|
544
525
|
)
|
|
545
526
|
|
|
546
|
-
|
|
547
|
-
|
|
527
|
+
nth_firm_price: ArrayDouble
|
|
528
|
+
price_array: ArrayDouble = np.ones_like(_frmshr_array, np.float64)
|
|
548
529
|
|
|
549
|
-
|
|
530
|
+
pr_max_ratio = 5.0
|
|
550
531
|
match _price_spec:
|
|
551
532
|
case PriceSpec.SYM:
|
|
552
|
-
|
|
533
|
+
nth_firm_price = np.ones((len(_frmshr_array), 1), np.float64)
|
|
553
534
|
case PriceSpec.POS:
|
|
554
|
-
|
|
555
|
-
np.ceil(_p *
|
|
535
|
+
price_array, nth_firm_price = (
|
|
536
|
+
np.ceil(_p * pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
556
537
|
)
|
|
557
538
|
case PriceSpec.NEG:
|
|
558
|
-
|
|
559
|
-
np.ceil((1 - _p) *
|
|
539
|
+
price_array, nth_firm_price = (
|
|
540
|
+
np.ceil((1 - _p) * pr_max_ratio)
|
|
560
541
|
for _p in (_frmshr_array, _nth_firm_share)
|
|
561
542
|
)
|
|
562
|
-
case PriceSpec.
|
|
563
|
-
|
|
564
|
-
1 + np.arange(
|
|
543
|
+
case PriceSpec.RNG:
|
|
544
|
+
price_array_gen = prng(_pr_rng_seed_seq).choice(
|
|
545
|
+
1 + np.arange(pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
565
546
|
)
|
|
566
|
-
|
|
567
|
-
|
|
547
|
+
price_array = price_array_gen[:, :2]
|
|
548
|
+
nth_firm_price = price_array_gen[:, [2]]
|
|
568
549
|
# del _price_array_gen
|
|
569
550
|
case PriceSpec.CSY:
|
|
570
551
|
# TODO:
|
|
571
552
|
# evolve FM2Constraint (save running MNL test twice); evolve copy of _mkt_sample_spec=1q
|
|
572
553
|
# generate the margin data
|
|
573
554
|
# generate price and margin data
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
np.ones_like(
|
|
555
|
+
frmshr_array_plus = np.hstack((_frmshr_array, _nth_firm_share))
|
|
556
|
+
pcm_spec_here = evolve(_pcm_spec, firm2_pcm_constraint=FM2Constraint.IID)
|
|
557
|
+
margin_data = _gen_margin_data(
|
|
558
|
+
frmshr_array_plus,
|
|
559
|
+
np.ones_like(frmshr_array_plus, np.float64),
|
|
579
560
|
_aggregate_purchase_prob,
|
|
580
|
-
|
|
561
|
+
pcm_spec_here,
|
|
581
562
|
_pcm_rng_seed_seq,
|
|
582
563
|
_nthreads,
|
|
583
564
|
)
|
|
584
565
|
|
|
585
|
-
|
|
586
|
-
getattr(
|
|
566
|
+
pcm_array, mnl_test_array = (
|
|
567
|
+
getattr(margin_data, _f) for _f in ("pcm_array", "mnl_test_array")
|
|
587
568
|
)
|
|
588
569
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
570
|
+
price_array_here = 1 / (1 - pcm_array)
|
|
571
|
+
price_array = price_array_here[:, :2]
|
|
572
|
+
nth_firm_price = price_array_here[:, [-1]]
|
|
592
573
|
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.MNL:
|
|
593
574
|
# Generate i.i.d. PCMs then take PCM0 and construct PCM1
|
|
594
575
|
# Regenerate MNL test
|
|
595
|
-
|
|
596
|
-
|
|
576
|
+
purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
577
|
+
pcm_array[:, 1] = np.divide(
|
|
597
578
|
(
|
|
598
|
-
|
|
579
|
+
m1_nr := np.divide(
|
|
599
580
|
np.einsum(
|
|
600
581
|
"ij,ij,ij->ij",
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
1 -
|
|
582
|
+
price_array[:, [0]],
|
|
583
|
+
pcm_array[:, [0]],
|
|
584
|
+
1 - purchase_prob_array[:, [0]],
|
|
604
585
|
),
|
|
605
|
-
1 -
|
|
586
|
+
1 - purchase_prob_array[:, [1]],
|
|
606
587
|
)
|
|
607
588
|
),
|
|
608
|
-
1 +
|
|
589
|
+
1 + m1_nr,
|
|
609
590
|
)
|
|
610
|
-
|
|
591
|
+
mnl_test_array = (pcm_array[:, [1]] >= 0) & (pcm_array[:, [1]] <= 1)
|
|
611
592
|
|
|
612
|
-
|
|
613
|
-
del
|
|
593
|
+
margin_data = MarginDataSample(pcm_array[:, :2], mnl_test_array)
|
|
594
|
+
del price_array_here
|
|
614
595
|
case _:
|
|
615
596
|
raise ValueError(
|
|
616
597
|
f'Specification of price distribution, "{_price_spec.value}" is invalid.'
|
|
617
598
|
)
|
|
618
599
|
if _price_spec != PriceSpec.CSY:
|
|
619
|
-
|
|
600
|
+
margin_data = _gen_margin_data(
|
|
620
601
|
_frmshr_array,
|
|
621
|
-
|
|
602
|
+
price_array,
|
|
622
603
|
_aggregate_purchase_prob,
|
|
623
604
|
_pcm_spec,
|
|
624
605
|
_pcm_rng_seed_seq,
|
|
@@ -626,13 +607,13 @@ def gen_margin_price_data(
|
|
|
626
607
|
)
|
|
627
608
|
|
|
628
609
|
# _price_array = _price_array.astype(np.float64)
|
|
629
|
-
|
|
630
|
-
|
|
610
|
+
rev_array = price_array * _frmshr_array
|
|
611
|
+
nth_firm_rev = nth_firm_price * _nth_firm_share
|
|
631
612
|
|
|
632
613
|
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
633
614
|
# the ratio has not changed since inception of the HSR filing test,
|
|
634
615
|
# so we treat it as a constant of merger enforcement policy.
|
|
635
|
-
|
|
616
|
+
test_rev_ratio, test_rev_ratio_inv = 10, 1 / 10
|
|
636
617
|
|
|
637
618
|
match _hsr_filing_test_type:
|
|
638
619
|
case SSZConstant.HSR_TEN:
|
|
@@ -643,8 +624,8 @@ def gen_margin_price_data(
|
|
|
643
624
|
# Revenue ratio has been 10-to-1 since inception
|
|
644
625
|
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
645
626
|
# ratio restriction on the merging firms' revenues
|
|
646
|
-
|
|
647
|
-
|
|
627
|
+
rev_ratio = (rev_array.min(axis=1) / rev_array.max(axis=1)).round(4)
|
|
628
|
+
hsr_filing_test = rev_ratio >= test_rev_ratio_inv
|
|
648
629
|
# del _rev_array, _rev_ratio
|
|
649
630
|
case SSZConstant.HSR_NTH:
|
|
650
631
|
# To get around the 10-to-1 ratio restriction, specify that the nth firm test:
|
|
@@ -653,25 +634,26 @@ def gen_margin_price_data(
|
|
|
653
634
|
# the size test is considered met.
|
|
654
635
|
# Alternatively, if the smaller merging firm has 10% or greater share,
|
|
655
636
|
# the value of transaction test is considered met.
|
|
656
|
-
|
|
657
|
-
|
|
637
|
+
rev_ratio_to_nth = np.round(np.sort(rev_array, axis=1) / nth_firm_rev, 4)
|
|
638
|
+
hsr_filing_test = (
|
|
658
639
|
np.einsum(
|
|
659
640
|
"ij->i",
|
|
660
|
-
1 * (
|
|
641
|
+
1 * (rev_ratio_to_nth > [1, test_rev_ratio]),
|
|
661
642
|
dtype=np.int64,
|
|
662
643
|
)
|
|
663
|
-
==
|
|
644
|
+
== rev_ratio_to_nth.shape[1]
|
|
664
645
|
)
|
|
665
646
|
|
|
666
647
|
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
667
648
|
case _:
|
|
668
649
|
# Otherwise, all draws meet the filing test
|
|
669
|
-
|
|
670
|
-
_hsr_filing_test = _hsr_filing_test | (
|
|
671
|
-
_frmshr_array.min(axis=1) >= _test_rev_ratio_inv
|
|
672
|
-
)
|
|
650
|
+
hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
|
|
673
651
|
|
|
674
|
-
|
|
652
|
+
# Assume that if minimum mergeing-firm share is 10%, merger filing required
|
|
653
|
+
# under value-of-transactions test or merger triggers post-consummation review
|
|
654
|
+
hsr_filing_test |= _frmshr_array.min(axis=1) >= test_rev_ratio_inv
|
|
655
|
+
|
|
656
|
+
return margin_data, PriceDataSample(price_array, hsr_filing_test)
|
|
675
657
|
|
|
676
658
|
|
|
677
659
|
def _gen_margin_data(
|
|
@@ -683,90 +665,91 @@ def _gen_margin_data(
|
|
|
683
665
|
_nthreads: int,
|
|
684
666
|
/,
|
|
685
667
|
) -> MarginDataSample:
|
|
686
|
-
|
|
668
|
+
dist_type_pcm, dist_parms_pcm, dist_firm2_pcm = (
|
|
687
669
|
getattr(_pcm_spec, _f)
|
|
688
|
-
for _f in ("dist_type", "
|
|
670
|
+
for _f in ("dist_type", "dist_parms", "firm2_pcm_constraint")
|
|
689
671
|
)
|
|
690
672
|
|
|
691
|
-
|
|
673
|
+
pcm_array = (
|
|
692
674
|
np.empty((len(_frmshr_array), 1), float)
|
|
693
675
|
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.SYM
|
|
694
676
|
else np.empty_like(_frmshr_array, float)
|
|
695
677
|
)
|
|
696
678
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if
|
|
700
|
-
|
|
701
|
-
|
|
679
|
+
dist_parms_: ArrayFloat
|
|
680
|
+
beta_min, beta_max = [0.0] * 2 # placeholder
|
|
681
|
+
if dist_type_pcm == PCMDistribution.EMPR:
|
|
682
|
+
pcm_array = margin_data_resampler(
|
|
683
|
+
pcm_array.shape, seed_sequence=_pcm_rng_seed_seq
|
|
702
684
|
)
|
|
703
685
|
else:
|
|
704
|
-
|
|
705
|
-
if
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
(
|
|
709
|
-
|
|
710
|
-
if
|
|
711
|
-
else
|
|
686
|
+
dist_type_: Literal["Beta", "Uniform"]
|
|
687
|
+
if dist_type_pcm in {PCMDistribution.BETA, PCMDistribution.BETA_BND}:
|
|
688
|
+
dist_type_ = "Beta"
|
|
689
|
+
if dist_type_pcm == PCMDistribution.BETA_BND:
|
|
690
|
+
dist_parms_pcm = (
|
|
691
|
+
DEFAULT_BETA_BND_DIST_PARMS
|
|
692
|
+
if dist_parms_pcm is None # Eliminated by converter
|
|
693
|
+
else dist_parms_pcm
|
|
694
|
+
)
|
|
695
|
+
dist_parms_ = beta_located_bound(dist_parms_pcm)
|
|
696
|
+
else:
|
|
697
|
+
dist_parms_ = (
|
|
698
|
+
DEFAULT_BETA_DIST_PARMS
|
|
699
|
+
if dist_parms_pcm is None
|
|
700
|
+
else dist_parms_pcm
|
|
712
701
|
)
|
|
713
|
-
if _dist_parms_pcm is None or not len(_dist_parms_pcm)
|
|
714
|
-
else _dist_parms_pcm
|
|
715
|
-
)
|
|
716
|
-
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
717
702
|
|
|
718
703
|
else:
|
|
719
|
-
|
|
720
|
-
|
|
704
|
+
dist_type_ = "Uniform"
|
|
705
|
+
dist_parms_ = (
|
|
721
706
|
DEFAULT_DIST_PARMS
|
|
722
|
-
if
|
|
723
|
-
else
|
|
707
|
+
if dist_parms_pcm is None or not len(dist_parms_pcm)
|
|
708
|
+
else dist_parms_pcm
|
|
724
709
|
)
|
|
725
710
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
dist_type=
|
|
729
|
-
dist_parms=
|
|
711
|
+
MultithreadedRNG(
|
|
712
|
+
pcm_array,
|
|
713
|
+
dist_type=dist_type_,
|
|
714
|
+
dist_parms=dist_parms_,
|
|
730
715
|
seed_sequence=_pcm_rng_seed_seq,
|
|
731
716
|
nthreads=_nthreads,
|
|
732
|
-
)
|
|
733
|
-
_pcm_rng.fill()
|
|
734
|
-
del _pcm_rng
|
|
717
|
+
).fill()
|
|
735
718
|
|
|
736
|
-
if
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
del
|
|
719
|
+
if dist_type_pcm == PCMDistribution.BETA_BND:
|
|
720
|
+
beta_min, beta_max = dist_parms_pcm[2:]
|
|
721
|
+
pcm_array = (beta_max - beta_min) * pcm_array + beta_min
|
|
722
|
+
del beta_min, beta_max
|
|
740
723
|
|
|
741
|
-
if
|
|
742
|
-
|
|
743
|
-
if
|
|
724
|
+
if dist_firm2_pcm == FM2Constraint.SYM:
|
|
725
|
+
pcm_array = np.column_stack((pcm_array,) * _frmshr_array.shape[1])
|
|
726
|
+
if dist_firm2_pcm == FM2Constraint.MNL:
|
|
744
727
|
# Impose FOCs from profit-maximization with MNL demand
|
|
745
|
-
if
|
|
728
|
+
if dist_type_pcm == PCMDistribution.EMPR:
|
|
746
729
|
print(
|
|
747
730
|
"NOTE: Estimated Firm 2 parameters will not be consistent with "
|
|
748
731
|
"the empirical distribution of margins in the source data. For "
|
|
749
732
|
"consistency, respecify pcm_spec.firm2_pcm_constraint = FM2Constraint.IID."
|
|
750
733
|
)
|
|
751
|
-
|
|
734
|
+
purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
752
735
|
|
|
753
|
-
|
|
736
|
+
pcm_array[:, [1]] = np.divide(
|
|
754
737
|
np.einsum(
|
|
755
738
|
"ij,ij,ij->ij",
|
|
756
739
|
_price_array[:, [0]],
|
|
757
|
-
|
|
758
|
-
1 -
|
|
740
|
+
pcm_array[:, [0]],
|
|
741
|
+
1 - purchase_prob_array[:, [0]],
|
|
759
742
|
),
|
|
760
743
|
np.einsum(
|
|
761
|
-
"ij,ij->ij", _price_array[:, [1]], 1 -
|
|
744
|
+
"ij,ij->ij", _price_array[:, [1]], 1 - purchase_prob_array[:, [1]]
|
|
762
745
|
),
|
|
763
746
|
)
|
|
764
747
|
|
|
765
|
-
|
|
748
|
+
mnl_test_array = (pcm_array[:, 1] >= 0) & (pcm_array[:, 1] <= 1)
|
|
766
749
|
else:
|
|
767
|
-
|
|
750
|
+
mnl_test_array = np.ones(len(pcm_array), dtype=bool)
|
|
768
751
|
|
|
769
|
-
return MarginDataSample(
|
|
752
|
+
return MarginDataSample(pcm_array, mnl_test_array)
|
|
770
753
|
|
|
771
754
|
|
|
772
755
|
def _beta_located(
|
|
@@ -790,8 +773,8 @@ def _beta_located(
|
|
|
790
773
|
|
|
791
774
|
"""
|
|
792
775
|
|
|
793
|
-
|
|
794
|
-
return np.array([_mu *
|
|
776
|
+
mul = -1 + _mu * (1 - _mu) / _sigma**2
|
|
777
|
+
return np.array([_mu * mul, (1 - _mu) * mul], float)
|
|
795
778
|
|
|
796
779
|
|
|
797
780
|
def beta_located_bound(_dist_parms: ArrayDouble, /) -> ArrayDouble:
|
|
@@ -822,5 +805,5 @@ def beta_located_bound(_dist_parms: ArrayDouble, /) -> ArrayDouble:
|
|
|
822
805
|
.. [8] NIST, Beta Distribution. https://www.itl.nist.gov/div898/handbook/eda/section3/eda366h.htm
|
|
823
806
|
""" # noqa: RUF002
|
|
824
807
|
|
|
825
|
-
|
|
826
|
-
return _beta_located((
|
|
808
|
+
bmu, bsigma, bmin, bmax = _dist_parms
|
|
809
|
+
return _beta_located((bmu - bmin) / (bmax - bmin), bsigma / (bmax - bmin))
|