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
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Non-public functions called in data_generation.py
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from importlib.metadata import version
|
|
8
|
-
from typing import Literal
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
from numpy.random import SeedSequence
|
|
12
|
-
from numpy.typing import NDArray
|
|
13
|
-
|
|
14
|
-
from .. import _PKG_NAME, RECConstants # noqa: TID252
|
|
15
|
-
from ..core.damodaran_margin_data import resample_mgn_data # noqa: TID252
|
|
16
|
-
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
17
|
-
DIST_PARMS_DEFAULT,
|
|
18
|
-
MultithreadedRNG,
|
|
19
|
-
prng,
|
|
20
|
-
)
|
|
21
|
-
from . import (
|
|
22
|
-
FCOUNT_WTS_DEFAULT,
|
|
23
|
-
TF,
|
|
24
|
-
FM2Constants,
|
|
25
|
-
MarginDataSample,
|
|
26
|
-
MarketSampleSpec,
|
|
27
|
-
PCMConstants,
|
|
28
|
-
PriceDataSample,
|
|
29
|
-
PRIConstants,
|
|
30
|
-
ShareDataSample,
|
|
31
|
-
SHRConstants,
|
|
32
|
-
SSZConstants,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
__version__ = version(_PKG_NAME)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _gen_share_data(
|
|
39
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
40
|
-
_fcount_rng_seed_seq: SeedSequence | None,
|
|
41
|
-
_mktshr_rng_seed_seq: SeedSequence,
|
|
42
|
-
_nthreads: int = 16,
|
|
43
|
-
/,
|
|
44
|
-
) -> ShareDataSample:
|
|
45
|
-
"""Helper function for generating share data.
|
|
46
|
-
|
|
47
|
-
Parameters
|
|
48
|
-
----------
|
|
49
|
-
_mkt_sample_spec
|
|
50
|
-
Class specifying parameters for share-, price-, and margin-data generation
|
|
51
|
-
_fcount_rng_seed_seq
|
|
52
|
-
Seed sequence for assuring independent and, optionally, redundant streams
|
|
53
|
-
_mktshr_rng_seed_seq
|
|
54
|
-
Seed sequence for assuring independent and, optionally, redundant streams
|
|
55
|
-
_nthreads
|
|
56
|
-
Must be specified for generating repeatable random streams
|
|
57
|
-
|
|
58
|
-
Returns
|
|
59
|
-
-------
|
|
60
|
-
Arrays representing shares, diversion ratios, etc. structured as a :ShareDataSample:
|
|
61
|
-
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
_recapture_spec, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts_raw = (
|
|
65
|
-
getattr(_mkt_sample_spec.share_spec, _f)
|
|
66
|
-
for _f in ("recapture_spec", "dist_type", "dist_parms", "firm_counts_weights")
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
_ssz = _mkt_sample_spec.sample_size
|
|
70
|
-
|
|
71
|
-
_r_bar = _mkt_sample_spec.recapture_rate or 0.80
|
|
72
|
-
|
|
73
|
-
match _dist_type_mktshr:
|
|
74
|
-
case SHRConstants.UNI:
|
|
75
|
-
_mkt_share_sample = _gen_market_shares_uniform(
|
|
76
|
-
_ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
case _ if _dist_type_mktshr.name.startswith("DIR_"):
|
|
80
|
-
_firm_count_prob_wts = (
|
|
81
|
-
None
|
|
82
|
-
if _firm_count_prob_wts_raw is None
|
|
83
|
-
else np.array(_firm_count_prob_wts_raw, dtype=np.float64)
|
|
84
|
-
)
|
|
85
|
-
_mkt_share_sample = _gen_market_shares_dirichlet_multisample(
|
|
86
|
-
_ssz,
|
|
87
|
-
_recapture_spec,
|
|
88
|
-
_dist_type_mktshr,
|
|
89
|
-
_dist_parms_mktshr,
|
|
90
|
-
_firm_count_prob_wts,
|
|
91
|
-
_fcount_rng_seed_seq,
|
|
92
|
-
_mktshr_rng_seed_seq,
|
|
93
|
-
_nthreads,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
case _:
|
|
97
|
-
raise ValueError(
|
|
98
|
-
f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# If recapture_spec == "inside-out", recalculate _aggregate_purchase_prob
|
|
102
|
-
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
103
|
-
if _recapture_spec == RECConstants.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)),
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
return _mkt_share_sample
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def _gen_market_shares_uniform(
|
|
115
|
-
_s_size: int = 10**6,
|
|
116
|
-
_dist_parms_mktshr: NDArray[np.floating[TF]] | None = DIST_PARMS_DEFAULT, # type: ignore
|
|
117
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
118
|
-
_nthreads: int = 16,
|
|
119
|
-
/,
|
|
120
|
-
) -> ShareDataSample:
|
|
121
|
-
"""Generate merging-firm shares from Uniform distribution on the 3-D simplex.
|
|
122
|
-
|
|
123
|
-
Parameters
|
|
124
|
-
----------
|
|
125
|
-
_s_size
|
|
126
|
-
size of sample to be drawn
|
|
127
|
-
_r_bar
|
|
128
|
-
market recapture rate
|
|
129
|
-
_mktshr_rng_seed_seq
|
|
130
|
-
seed for rng, so results can be made replicable
|
|
131
|
-
_nthreads
|
|
132
|
-
number of threads for random number generation
|
|
133
|
-
|
|
134
|
-
Returns
|
|
135
|
-
-------
|
|
136
|
-
market shares and other market statistics for each draw (market)
|
|
137
|
-
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
_frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
|
|
141
|
-
_dist_parms_mktshr: NDArray[np.floating[TF]] = (
|
|
142
|
-
DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr # type: ignore
|
|
143
|
-
)
|
|
144
|
-
_mrng = MultithreadedRNG(
|
|
145
|
-
_frmshr_array,
|
|
146
|
-
dist_type="Uniform",
|
|
147
|
-
dist_parms=_dist_parms_mktshr,
|
|
148
|
-
seed_sequence=_mktshr_rng_seed_seq,
|
|
149
|
-
nthreads=_nthreads,
|
|
150
|
-
)
|
|
151
|
-
_mrng.fill()
|
|
152
|
-
# Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 < 1
|
|
153
|
-
_frmshr_array = np.sort(_frmshr_array, axis=1)
|
|
154
|
-
_frmshr_array = np.column_stack((
|
|
155
|
-
_frmshr_array[:, 0],
|
|
156
|
-
_frmshr_array[:, 1] - _frmshr_array[:, 0],
|
|
157
|
-
))
|
|
158
|
-
|
|
159
|
-
# Keep only share combinations representing feasible mergers
|
|
160
|
-
_frmshr_array = _frmshr_array[_frmshr_array.min(axis=1) > 0]
|
|
161
|
-
|
|
162
|
-
# Let a third column have values of "np.nan", so HHI calculations return "np.nan"
|
|
163
|
-
_mktshr_array = np.pad(
|
|
164
|
-
_frmshr_array, ((0, 0), (0, 1)), "constant", constant_values=np.nan
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
_fcounts: NDArray[np.int64] = np.ones((_s_size, 1), np.int64) * np.nan # type: ignore
|
|
168
|
-
_nth_firm_share, _aggregate_purchase_prob = (
|
|
169
|
-
np.nan * np.ones((_s_size, 1), np.float64) for _ in range(2)
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
return ShareDataSample(
|
|
173
|
-
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _gen_market_shares_dirichlet_multisample(
|
|
178
|
-
_s_size: int = 10**6,
|
|
179
|
-
_recapture_spec: RECConstants = RECConstants.INOUT,
|
|
180
|
-
_dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
|
|
181
|
-
_dist_parms_dir: NDArray[np.floating[TF]] | None = None,
|
|
182
|
-
_firm_count_wts: NDArray[np.floating[TF]] | None = None, # type: ignore
|
|
183
|
-
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
184
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
185
|
-
_nthreads: int = 16,
|
|
186
|
-
/,
|
|
187
|
-
) -> ShareDataSample:
|
|
188
|
-
"""Dirichlet-distributed shares with multiple firm-counts.
|
|
189
|
-
|
|
190
|
-
Firm-counts may be specified as having Uniform distribution over the range
|
|
191
|
-
of firm counts, or a set of probability weights may be specified. In the
|
|
192
|
-
latter case the proportion of draws for each firm-count matches the
|
|
193
|
-
specified probability weight.
|
|
194
|
-
|
|
195
|
-
Parameters
|
|
196
|
-
----------
|
|
197
|
-
_s_size
|
|
198
|
-
sample size to be drawn
|
|
199
|
-
_r_bar
|
|
200
|
-
market recapture rate
|
|
201
|
-
_firm_count_wts
|
|
202
|
-
firm count weights array for sample to be drawn
|
|
203
|
-
_dist_type_dir
|
|
204
|
-
Whether Dirichlet is Flat or Asymmetric
|
|
205
|
-
_recapture_spec
|
|
206
|
-
r_1 = r_2 if "proportional", otherwise MNL-consistent
|
|
207
|
-
_fcount_rng_seed_seq
|
|
208
|
-
seed firm count rng, for replicable results
|
|
209
|
-
_mktshr_rng_seed_seq
|
|
210
|
-
seed market share rng, for replicable results
|
|
211
|
-
_nthreads
|
|
212
|
-
number of threads for parallelized random number generation
|
|
213
|
-
|
|
214
|
-
Returns
|
|
215
|
-
-------
|
|
216
|
-
array of market shares and other market statistics
|
|
217
|
-
|
|
218
|
-
"""
|
|
219
|
-
|
|
220
|
-
_firm_count_wts: NDArray[np.floating[TF]] = (
|
|
221
|
-
FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
_min_choice_wt = 0.03 if _dist_type_dir == SHRConstants.DIR_FLAT_CONSTR else 0.00
|
|
225
|
-
_fcount_keys, _choice_wts = zip(
|
|
226
|
-
*(
|
|
227
|
-
_f
|
|
228
|
-
for _f in zip(
|
|
229
|
-
2 + np.arange(len(_firm_count_wts)), # type: ignore
|
|
230
|
-
_firm_count_wts / _firm_count_wts.sum(),
|
|
231
|
-
strict=True,
|
|
232
|
-
)
|
|
233
|
-
if _f[1] > _min_choice_wt
|
|
234
|
-
)
|
|
235
|
-
)
|
|
236
|
-
_choice_wts = _choice_wts / sum(_choice_wts)
|
|
237
|
-
|
|
238
|
-
_fc_max = _fcount_keys[-1]
|
|
239
|
-
_dir_alphas_full = (
|
|
240
|
-
[1.0] * _fc_max if _dist_parms_dir is None else _dist_parms_dir[:_fc_max]
|
|
241
|
-
)
|
|
242
|
-
if _dist_type_dir == SHRConstants.DIR_ASYM:
|
|
243
|
-
_dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, _fc_max)
|
|
244
|
-
|
|
245
|
-
if _dist_type_dir == SHRConstants.DIR_COND:
|
|
246
|
-
|
|
247
|
-
def _gen_dir_alphas(_fcv: int) -> NDArray[np.float64]:
|
|
248
|
-
_dat = [2.5] * 2
|
|
249
|
-
if _fcv > len(_dat):
|
|
250
|
-
_dat += [1.0 / (_fcv - 2)] * (_fcv - 2)
|
|
251
|
-
return np.array(_dat, dtype=np.float64)
|
|
252
|
-
|
|
253
|
-
else:
|
|
254
|
-
|
|
255
|
-
def _gen_dir_alphas(_fcv: int) -> NDArray[np.float64]:
|
|
256
|
-
return np.array(_dir_alphas_full[:_fcv], dtype=np.float64) # type: ignore
|
|
257
|
-
|
|
258
|
-
_fcounts = prng(_fcount_rng_seed_seq).choice(
|
|
259
|
-
_fcount_keys, size=(_s_size, 1), p=_choice_wts
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
_mktshr_seed_seq_ch = (
|
|
263
|
-
_mktshr_rng_seed_seq.spawn(len(_fcount_keys))
|
|
264
|
-
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
265
|
-
else SeedSequence(pool_size=8).spawn(len(_fcounts))
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
_aggregate_purchase_prob, _nth_firm_share = (
|
|
269
|
-
np.empty((_s_size, 1)) for _ in range(2)
|
|
270
|
-
)
|
|
271
|
-
_mktshr_array = np.empty((_s_size, _fc_max), dtype=np.float64)
|
|
272
|
-
for _f_val, _f_sseq in zip(_fcount_keys, _mktshr_seed_seq_ch, strict=True):
|
|
273
|
-
_fcounts_match_rows = np.where(_fcounts == _f_val)[0]
|
|
274
|
-
_dir_alphas_test = _gen_dir_alphas(_f_val)
|
|
275
|
-
|
|
276
|
-
try:
|
|
277
|
-
_mktshr_sample_f = _gen_market_shares_dirichlet(
|
|
278
|
-
_dir_alphas_test,
|
|
279
|
-
len(_fcounts_match_rows),
|
|
280
|
-
_recapture_spec,
|
|
281
|
-
_f_sseq,
|
|
282
|
-
_nthreads,
|
|
283
|
-
)
|
|
284
|
-
except ValueError as _err:
|
|
285
|
-
print(_f_val, len(_fcounts_match_rows))
|
|
286
|
-
raise _err
|
|
287
|
-
|
|
288
|
-
# Push data for present sample to parent
|
|
289
|
-
_mktshr_array[_fcounts_match_rows] = np.pad(
|
|
290
|
-
_mktshr_sample_f.mktshr_array,
|
|
291
|
-
((0, 0), (0, _fc_max - _mktshr_sample_f.mktshr_array.shape[1])),
|
|
292
|
-
"constant",
|
|
293
|
-
)
|
|
294
|
-
_aggregate_purchase_prob[_fcounts_match_rows] = (
|
|
295
|
-
_mktshr_sample_f.aggregate_purchase_prob
|
|
296
|
-
)
|
|
297
|
-
_nth_firm_share[_fcounts_match_rows] = _mktshr_sample_f.nth_firm_share
|
|
298
|
-
|
|
299
|
-
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
300
|
-
_mktshr_array
|
|
301
|
-
):
|
|
302
|
-
raise ValueError(
|
|
303
|
-
"DATA GENERATION ERROR: {} {} {}".format(
|
|
304
|
-
"Generation of sample shares is inconsistent:",
|
|
305
|
-
"array of drawn shares must some to the number of draws",
|
|
306
|
-
"i.e., the sample size, which condition is not met.",
|
|
307
|
-
)
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return ShareDataSample(
|
|
311
|
-
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _gen_market_shares_dirichlet(
|
|
316
|
-
_dir_alphas: NDArray[np.floating[TF]],
|
|
317
|
-
_s_size: int = 10**6,
|
|
318
|
-
_recapture_spec: RECConstants = RECConstants.INOUT,
|
|
319
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
320
|
-
_nthreads: int = 16,
|
|
321
|
-
/,
|
|
322
|
-
) -> ShareDataSample:
|
|
323
|
-
"""Dirichlet-distributed shares with fixed firm-count.
|
|
324
|
-
|
|
325
|
-
Parameters
|
|
326
|
-
----------
|
|
327
|
-
_dir_alphas
|
|
328
|
-
Shape parameters for Dirichlet distribution
|
|
329
|
-
_s_size
|
|
330
|
-
sample size to be drawn
|
|
331
|
-
_r_bar
|
|
332
|
-
market recapture rate
|
|
333
|
-
_recapture_spec
|
|
334
|
-
r_1 = r_2 if RECConstants.FIXED, otherwise MNL-consistent. If
|
|
335
|
-
RECConstants.OUTIN; the number of columns in the output share array
|
|
336
|
-
is len(_dir_alphas) - 1.
|
|
337
|
-
_mktshr_rng_seed_seq
|
|
338
|
-
seed market share rng, for replicable results
|
|
339
|
-
_nthreads
|
|
340
|
-
number of threads for parallelized random number generation
|
|
341
|
-
|
|
342
|
-
Returns
|
|
343
|
-
-------
|
|
344
|
-
array of market shares and other market statistics
|
|
345
|
-
|
|
346
|
-
"""
|
|
347
|
-
|
|
348
|
-
if not isinstance(_dir_alphas, np.ndarray):
|
|
349
|
-
_dir_alphas = np.array(_dir_alphas)
|
|
350
|
-
|
|
351
|
-
if _recapture_spec == RECConstants.OUTIN:
|
|
352
|
-
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
353
|
-
|
|
354
|
-
_mktshr_seed_seq_ch = (
|
|
355
|
-
_mktshr_rng_seed_seq
|
|
356
|
-
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
357
|
-
else SeedSequence(pool_size=8)
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
_mktshr_array = np.empty((_s_size, len(_dir_alphas)))
|
|
361
|
-
_mrng = MultithreadedRNG(
|
|
362
|
-
_mktshr_array,
|
|
363
|
-
dist_type="Dirichlet",
|
|
364
|
-
dist_parms=_dir_alphas,
|
|
365
|
-
seed_sequence=_mktshr_seed_seq_ch,
|
|
366
|
-
nthreads=_nthreads,
|
|
367
|
-
)
|
|
368
|
-
_mrng.fill()
|
|
369
|
-
|
|
370
|
-
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
371
|
-
_mktshr_array
|
|
372
|
-
):
|
|
373
|
-
print(_dir_alphas, _iss, repr(_s_size), len(_mktshr_array))
|
|
374
|
-
print(repr(_mktshr_array[-10:, :]))
|
|
375
|
-
raise ValueError(
|
|
376
|
-
"DATA GENERATION ERROR: {} {} {}".format(
|
|
377
|
-
"Generation of sample shares is inconsistent:",
|
|
378
|
-
"array of drawn shares must sum to the number of draws",
|
|
379
|
-
"i.e., the sample size, which condition is not met.",
|
|
380
|
-
)
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
# If recapture_spec == 'inside_out', further calculations downstream
|
|
384
|
-
_aggregate_purchase_prob = np.nan * np.empty((_s_size, 1))
|
|
385
|
-
if _recapture_spec == RECConstants.OUTIN:
|
|
386
|
-
_aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
|
|
387
|
-
_mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
|
|
388
|
-
|
|
389
|
-
return ShareDataSample(
|
|
390
|
-
_mktshr_array,
|
|
391
|
-
(_mktshr_array.shape[-1] * np.ones((_s_size, 1))).astype(np.int64),
|
|
392
|
-
_mktshr_array[:, [-1]],
|
|
393
|
-
_aggregate_purchase_prob,
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def _gen_pr_data(
|
|
398
|
-
_frmshr_array: NDArray[np.floating[TF]],
|
|
399
|
-
_nth_firm_share: NDArray[np.floating[TF]],
|
|
400
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
401
|
-
_seed_seq: SeedSequence | None = None,
|
|
402
|
-
/,
|
|
403
|
-
) -> PriceDataSample:
|
|
404
|
-
_ssz = len(_frmshr_array)
|
|
405
|
-
|
|
406
|
-
_hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
|
|
407
|
-
|
|
408
|
-
_price_array, _price_ratio_array, _hsr_filing_test = (
|
|
409
|
-
np.ones_like(_frmshr_array),
|
|
410
|
-
np.empty_like(_frmshr_array),
|
|
411
|
-
np.empty(_ssz, dtype=bool),
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
_pr_max_ratio = 5.0
|
|
415
|
-
match _mkt_sample_spec.pr_sym_spec:
|
|
416
|
-
case PRIConstants.SYM:
|
|
417
|
-
_nth_firm_price = np.ones((_ssz, 1))
|
|
418
|
-
case PRIConstants.POS:
|
|
419
|
-
_price_array, _nth_firm_price = (
|
|
420
|
-
np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
421
|
-
)
|
|
422
|
-
case PRIConstants.NEG:
|
|
423
|
-
_price_array, _nth_firm_price = (
|
|
424
|
-
np.ceil((1 - _p) * _pr_max_ratio)
|
|
425
|
-
for _p in (_frmshr_array, _nth_firm_share)
|
|
426
|
-
)
|
|
427
|
-
case PRIConstants.ZERO:
|
|
428
|
-
_price_array_gen = prng(_seed_seq).choice(
|
|
429
|
-
1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
430
|
-
)
|
|
431
|
-
_price_array = _price_array_gen[:, :2]
|
|
432
|
-
_nth_firm_price = _price_array_gen[:, [2]]
|
|
433
|
-
# del _price_array_gen
|
|
434
|
-
case _:
|
|
435
|
-
raise ValueError(
|
|
436
|
-
f"Condition regarding price symmetry"
|
|
437
|
-
f' "{_mkt_sample_spec.pr_sym_spec.value}" is invalid.'
|
|
438
|
-
)
|
|
439
|
-
# del _pr_max_ratio
|
|
440
|
-
|
|
441
|
-
_price_array = _price_array.astype(np.float64)
|
|
442
|
-
_rev_array = _price_array * _frmshr_array
|
|
443
|
-
_nth_firm_rev = _nth_firm_price * _nth_firm_share
|
|
444
|
-
|
|
445
|
-
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
446
|
-
# the ratio has not changed since inception of the HSR filing test,
|
|
447
|
-
# so we treat it as a constant of merger policy.
|
|
448
|
-
_test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
|
|
449
|
-
|
|
450
|
-
match _hsr_filing_test_type:
|
|
451
|
-
case SSZConstants.HSR_TEN:
|
|
452
|
-
# See, https://www.ftc.gov/enforcement/premerger-notification-program/
|
|
453
|
-
# -> Procedures For Submitting Post-Consummation Filings
|
|
454
|
-
# -> Key Elements to Determine Whether a Post Consummation Filing is Required
|
|
455
|
-
# under heading, "Historical Thresholds"
|
|
456
|
-
# Revenue ratio has been 10-to-1 since inception
|
|
457
|
-
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
458
|
-
# ratio restriction on the merging firms' revenues
|
|
459
|
-
_rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
|
|
460
|
-
_hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
|
|
461
|
-
# del _rev_array, _rev_ratio
|
|
462
|
-
case SSZConstants.HSR_NTH:
|
|
463
|
-
# To get around the 10-to-1 ratio restriction, specify that the nth firm
|
|
464
|
-
# matches the smaller firm in the size test; then if the smaller merging firm
|
|
465
|
-
# matches the n-th firm in size, and the larger merging firm has at least
|
|
466
|
-
# 10 times the size of the nth firm, the size test is considered met.
|
|
467
|
-
# Alternatively, if the smaller merging firm has 10% or greater share,
|
|
468
|
-
# the value of transaction test is considered met.
|
|
469
|
-
_rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
|
|
470
|
-
_hsr_filing_test = (
|
|
471
|
-
np.einsum(
|
|
472
|
-
"ij->i",
|
|
473
|
-
1 * (_rev_ratio_to_nth > [1, _test_rev_ratio]),
|
|
474
|
-
dtype=np.int64,
|
|
475
|
-
)
|
|
476
|
-
== _rev_ratio_to_nth.shape[1]
|
|
477
|
-
) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
|
|
478
|
-
|
|
479
|
-
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
480
|
-
case _:
|
|
481
|
-
# Otherwise, all draws meet the filing test
|
|
482
|
-
_hsr_filing_test = np.ones(_ssz, dtype=bool)
|
|
483
|
-
|
|
484
|
-
return PriceDataSample(_price_array, _hsr_filing_test)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
def _gen_pcm_data(
|
|
488
|
-
_frmshr_array: NDArray[np.floating[TF]],
|
|
489
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
490
|
-
_price_array: NDArray[np.floating[TF]],
|
|
491
|
-
_aggregate_purchase_prob: NDArray[np.floating[TF]],
|
|
492
|
-
_pcm_rng_seed_seq: SeedSequence,
|
|
493
|
-
_nthreads: int = 16,
|
|
494
|
-
/,
|
|
495
|
-
) -> MarginDataSample:
|
|
496
|
-
_recapture_spec = _mkt_sample_spec.share_spec.recapture_spec
|
|
497
|
-
_dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
|
|
498
|
-
getattr(_mkt_sample_spec.pcm_spec, _f)
|
|
499
|
-
for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
|
|
500
|
-
)
|
|
501
|
-
_dist_type: Literal["Beta", "Uniform"] = (
|
|
502
|
-
"Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
_pcm_array = np.empty((len(_frmshr_array), 2), dtype=np.float64)
|
|
506
|
-
_mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
|
|
507
|
-
|
|
508
|
-
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
509
|
-
_dist_parms = np.ones(2, np.float64)
|
|
510
|
-
if _dist_type_pcm == PCMConstants.EMPR:
|
|
511
|
-
_pcm_array = resample_mgn_data(
|
|
512
|
-
_pcm_array.shape, # type: ignore
|
|
513
|
-
seed_sequence=_pcm_rng_seed_seq,
|
|
514
|
-
)
|
|
515
|
-
else:
|
|
516
|
-
if _dist_type_pcm == PCMConstants.UNI:
|
|
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
|
-
|
|
523
|
-
if _dist_parms_pcm is None:
|
|
524
|
-
_dist_parms_pcm = _dist_parms
|
|
525
|
-
|
|
526
|
-
elif _dist_type_pcm == PCMConstants.BETA_BND: # Bounded beta
|
|
527
|
-
if _dist_parms_pcm is None:
|
|
528
|
-
_dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
|
|
529
|
-
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
530
|
-
|
|
531
|
-
_pcm_rng = MultithreadedRNG(
|
|
532
|
-
_pcm_array,
|
|
533
|
-
dist_type=_dist_type,
|
|
534
|
-
dist_parms=_dist_parms,
|
|
535
|
-
seed_sequence=_pcm_rng_seed_seq,
|
|
536
|
-
nthreads=_nthreads,
|
|
537
|
-
)
|
|
538
|
-
_pcm_rng.fill()
|
|
539
|
-
del _pcm_rng
|
|
540
|
-
|
|
541
|
-
if _dist_type_pcm == PCMConstants.BETA_BND:
|
|
542
|
-
_beta_min, _beta_max = _dist_parms_pcm[2:] # type: ignore
|
|
543
|
-
_pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
|
|
544
|
-
del _beta_min, _beta_max
|
|
545
|
-
|
|
546
|
-
if _dist_firm2_pcm == FM2Constants.MNL:
|
|
547
|
-
# Impose FOCs from profit-maximization with MNL demand
|
|
548
|
-
_purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
549
|
-
|
|
550
|
-
_pcm_array[:, [1]] = np.divide(
|
|
551
|
-
np.einsum(
|
|
552
|
-
"ij,ij,ij->ij",
|
|
553
|
-
_price_array[:, [0]],
|
|
554
|
-
_pcm_array[:, [0]],
|
|
555
|
-
1 - _purchprob_array[:, [0]],
|
|
556
|
-
),
|
|
557
|
-
np.einsum("ij,ij->ij", _price_array[:, [1]], 1 - _purchprob_array[:, [1]]),
|
|
558
|
-
)
|
|
559
|
-
|
|
560
|
-
_mnl_test_array = _pcm_array[:, 1].__ge__(0) & _pcm_array[:, 1].__le__(1)
|
|
561
|
-
else:
|
|
562
|
-
_mnl_test_array = np.ones(len(_pcm_array), dtype=bool)
|
|
563
|
-
if _dist_firm2_pcm == FM2Constants.SYM:
|
|
564
|
-
_pcm_array[:, [1]] = _pcm_array[:, [0]]
|
|
565
|
-
|
|
566
|
-
return MarginDataSample(_pcm_array, _mnl_test_array)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def _beta_located(
|
|
570
|
-
_mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
|
|
571
|
-
) -> NDArray[np.float64]:
|
|
572
|
-
"""
|
|
573
|
-
Given mean and stddev, return shape parameters for corresponding Beta distribution
|
|
574
|
-
|
|
575
|
-
Solve the first two moments of the standard Beta to get the shape parameters.
|
|
576
|
-
|
|
577
|
-
Parameters
|
|
578
|
-
----------
|
|
579
|
-
_mu
|
|
580
|
-
mean
|
|
581
|
-
_sigma
|
|
582
|
-
standardd deviation
|
|
583
|
-
|
|
584
|
-
Returns
|
|
585
|
-
-------
|
|
586
|
-
shape parameters for Beta distribution
|
|
587
|
-
|
|
588
|
-
"""
|
|
589
|
-
|
|
590
|
-
_mul = -1 + _mu * (1 - _mu) / _sigma**2
|
|
591
|
-
return np.array([_mu * _mul, (1 - _mu) * _mul], dtype=np.float64)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
def beta_located_bound(_dist_parms: NDArray[np.floating[TF]], /) -> NDArray[np.float64]:
|
|
595
|
-
R"""
|
|
596
|
-
Return shape parameters for a non-standard beta, given the mean, stddev, range
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Recover the r.v.s as
|
|
600
|
-
:math:`\min + (\max - \min) \cdot \symup{Β}(a, b)`,
|
|
601
|
-
with `a` and `b` calculated from the specified mean (:math:`\mu`) and
|
|
602
|
-
variance (:math:`\sigma`). [8]_
|
|
603
|
-
|
|
604
|
-
Parameters
|
|
605
|
-
----------
|
|
606
|
-
_dist_parms
|
|
607
|
-
vector of :math:`\mu`, :math:`\sigma`, :math:`\mathtt{\min}`, and :math:`\mathtt{\max}` values
|
|
608
|
-
|
|
609
|
-
Returns
|
|
610
|
-
-------
|
|
611
|
-
shape parameters for Beta distribution
|
|
612
|
-
|
|
613
|
-
Notes
|
|
614
|
-
-----
|
|
615
|
-
For example, ``beta_located_bound(np.array([0.5, 0.2887, 0.0, 1.0]))``.
|
|
616
|
-
|
|
617
|
-
References
|
|
618
|
-
----------
|
|
619
|
-
.. [8] NIST, Beta Distribution. https://www.itl.nist.gov/div898/handbook/eda/section3/eda366h.htm
|
|
620
|
-
""" # noqa: RUF002
|
|
621
|
-
|
|
622
|
-
_bmu, _bsigma, _bmin, _bmax = _dist_parms
|
|
623
|
-
return _beta_located((_bmu - _bmin) / (_bmax - _bmin), _bsigma / (_bmax - _bmin))
|