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
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Non-public functions called in data_generation.py
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from attrs import evolve
|
|
12
|
+
from numpy.random import SeedSequence
|
|
13
|
+
|
|
14
|
+
from .. import DEFAULT_REC_RATIO, VERSION, ArrayDouble, RECForm # noqa: TID252
|
|
15
|
+
from ..core.empirical_margin_distribution import mgn_data_resampler # noqa: TID252
|
|
16
|
+
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
17
|
+
DEFAULT_DIST_PARMS,
|
|
18
|
+
MultithreadedRNG,
|
|
19
|
+
prng,
|
|
20
|
+
)
|
|
21
|
+
from . import (
|
|
22
|
+
DEFAULT_EMPTY_ARRAY,
|
|
23
|
+
DEFAULT_FCOUNT_WTS,
|
|
24
|
+
FM2Constraint,
|
|
25
|
+
MarginDataSample,
|
|
26
|
+
PCMDistribution,
|
|
27
|
+
PCMSpec,
|
|
28
|
+
PriceDataSample,
|
|
29
|
+
PriceSpec,
|
|
30
|
+
SeedSequenceData,
|
|
31
|
+
ShareDataSample,
|
|
32
|
+
ShareSpec,
|
|
33
|
+
SHRDistribution,
|
|
34
|
+
SSZConstant,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__version__ = VERSION
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def gen_share_data(
|
|
41
|
+
_sample_size: int,
|
|
42
|
+
_share_spec: ShareSpec,
|
|
43
|
+
_fcount_rng_seed_seq: SeedSequence | None,
|
|
44
|
+
_mktshr_rng_seed_seq: SeedSequence,
|
|
45
|
+
_nthreads: int = 16,
|
|
46
|
+
/,
|
|
47
|
+
) -> ShareDataSample:
|
|
48
|
+
"""Helper function for generating share data.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
_share_spec
|
|
53
|
+
Class specifying parameters for generating market share data
|
|
54
|
+
_fcount_rng_seed_seq
|
|
55
|
+
Seed sequence for assuring independent and, optionally, redundant streams
|
|
56
|
+
_mktshr_rng_seed_seq
|
|
57
|
+
Seed sequence for assuring independent and, optionally, redundant streams
|
|
58
|
+
_nthreads
|
|
59
|
+
Must be specified for generating repeatable random streams
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
Arrays representing shares, diversion ratios, etc. structured as a :ShareDataSample:
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
_recapture_form, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts = (
|
|
68
|
+
getattr(_share_spec, _f)
|
|
69
|
+
for _f in ("recapture_form", "dist_type", "dist_parms", "firm_counts_weights")
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
_ssz = _sample_size
|
|
73
|
+
|
|
74
|
+
if _dist_type_mktshr == SHRDistribution.UNI:
|
|
75
|
+
_mkt_share_sample = gen_market_shares_uniform(
|
|
76
|
+
_ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
elif _dist_type_mktshr.name.startswith("DIR_"):
|
|
80
|
+
_firm_count_prob_wts = (
|
|
81
|
+
None
|
|
82
|
+
if _firm_count_prob_wts is None
|
|
83
|
+
else np.array(_firm_count_prob_wts, dtype=np.float64)
|
|
84
|
+
)
|
|
85
|
+
_mkt_share_sample = gen_market_shares_dirichlet_multimarket(
|
|
86
|
+
_ssz,
|
|
87
|
+
_recapture_form,
|
|
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
|
+
else:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
|
|
102
|
+
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
103
|
+
_r_bar = _share_spec.recapture_ratio or DEFAULT_REC_RATIO
|
|
104
|
+
if _recapture_form == RECForm.INOUT:
|
|
105
|
+
_mkt_share_sample = ShareDataSample(
|
|
106
|
+
_mkt_share_sample.mktshr_array,
|
|
107
|
+
_mkt_share_sample.fcounts,
|
|
108
|
+
_mkt_share_sample.nth_firm_share,
|
|
109
|
+
_r_bar / (1 - (1 - _r_bar) * _frmshr_array.min(axis=1, keepdims=True)),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return _mkt_share_sample
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def gen_market_shares_uniform(
|
|
116
|
+
_s_size: int = 10**6,
|
|
117
|
+
_dist_parms_mktshr: ArrayDouble | None = DEFAULT_DIST_PARMS,
|
|
118
|
+
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
119
|
+
_nthreads: int = 16,
|
|
120
|
+
/,
|
|
121
|
+
) -> ShareDataSample:
|
|
122
|
+
"""Generate merging-firm shares from Uniform distribution on the 3-D simplex.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
_s_size
|
|
127
|
+
size of sample to be drawn
|
|
128
|
+
|
|
129
|
+
_mktshr_rng_seed_seq
|
|
130
|
+
seed for rng, so results can be made replicable
|
|
131
|
+
|
|
132
|
+
_nthreads
|
|
133
|
+
number of threads for random number generation
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
market shares and other market statistics for each draw (market)
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
_frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
|
|
142
|
+
|
|
143
|
+
_dist_parms_mktshr = _dist_parms_mktshr or DEFAULT_DIST_PARMS
|
|
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.sort(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
|
+
# This is a no-op for 64-bit floats, but is necessary for 32-bit floats
|
|
161
|
+
_frmshr_array = _frmshr_array[_frmshr_array.min(axis=1) > 0]
|
|
162
|
+
|
|
163
|
+
# Let a third column have values of "np.nan", so HHI calculations return "np.nan"
|
|
164
|
+
_mktshr_array = np.pad(
|
|
165
|
+
_frmshr_array, ((0, 0), (0, 1)), "constant", constant_values=np.nan
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
_fcounts = np.empty((_s_size, 1), np.int64)
|
|
169
|
+
_nth_firm_share, _aggregate_purchase_prob = (
|
|
170
|
+
np.empty(_fcounts.shape, np.float64)
|
|
171
|
+
for _ in ("nth_firm_share", "aggregate_purchase_prob")
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# This array is meant to be ignored, so a sentinel value is fine
|
|
175
|
+
_fcounts.fill(-9999)
|
|
176
|
+
|
|
177
|
+
_nth_firm_share.fill(np.nan)
|
|
178
|
+
_aggregate_purchase_prob.fill(np.nan)
|
|
179
|
+
|
|
180
|
+
return ShareDataSample(
|
|
181
|
+
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def gen_market_shares_dirichlet_multimarket(
|
|
186
|
+
_s_size: int = 10**6,
|
|
187
|
+
_recapture_form: RECForm = RECForm.INOUT,
|
|
188
|
+
_dist_type_dir: SHRDistribution = SHRDistribution.DIR_FLAT,
|
|
189
|
+
_dist_parms_dir: ArrayDouble | None = None,
|
|
190
|
+
_firm_count_wts: ArrayDouble | None = None,
|
|
191
|
+
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
192
|
+
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
193
|
+
_nthreads: int = 16,
|
|
194
|
+
/,
|
|
195
|
+
) -> ShareDataSample:
|
|
196
|
+
"""Dirichlet-distributed shares with multiple firm-counts.
|
|
197
|
+
|
|
198
|
+
Firm-counts may be specified as having Uniform distribution over the range
|
|
199
|
+
of firm counts, or a set of probability weights may be specified. In the
|
|
200
|
+
latter case the proportion of draws for each firm-count matches the
|
|
201
|
+
specified probability weight.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
_s_size
|
|
206
|
+
sample size to be drawn
|
|
207
|
+
|
|
208
|
+
_firm_count_wts
|
|
209
|
+
firm count weights array for sample to be drawn
|
|
210
|
+
|
|
211
|
+
_dist_type_dir
|
|
212
|
+
Whether Dirichlet is Flat or Asymmetric
|
|
213
|
+
|
|
214
|
+
_recapture_form
|
|
215
|
+
r_1 = r_2 if "proportional", otherwise MNL-consistent
|
|
216
|
+
|
|
217
|
+
_fcount_rng_seed_seq
|
|
218
|
+
seed firm count rng, for replicable results
|
|
219
|
+
|
|
220
|
+
_mktshr_rng_seed_seq
|
|
221
|
+
seed market share rng, for replicable results
|
|
222
|
+
|
|
223
|
+
_nthreads
|
|
224
|
+
number of threads for parallelized random number generation
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
array of market shares and other market statistics
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# _firm_count_wts: ArrayDouble = (
|
|
233
|
+
# DEFAULT_FCOUNT_WTS if _firm_count_wts is None else _firm_count_wts
|
|
234
|
+
# )
|
|
235
|
+
_firm_count_wts = DEFAULT_FCOUNT_WTS if _firm_count_wts is None else _firm_count_wts
|
|
236
|
+
|
|
237
|
+
_min_choice_wt = 0.03 if _dist_type_dir == SHRDistribution.DIR_FLAT_CONSTR else 0.00
|
|
238
|
+
_fcount_keys, _choice_wts = zip(
|
|
239
|
+
*(
|
|
240
|
+
_f
|
|
241
|
+
for _f in zip(
|
|
242
|
+
2 + np.arange(len(_firm_count_wts)),
|
|
243
|
+
_firm_count_wts / _firm_count_wts.sum(),
|
|
244
|
+
strict=True,
|
|
245
|
+
)
|
|
246
|
+
if _f[1] > _min_choice_wt
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
_choice_wts = _choice_wts / sum(_choice_wts)
|
|
250
|
+
|
|
251
|
+
_fc_max = _fcount_keys[-1]
|
|
252
|
+
_dir_alphas_full = (
|
|
253
|
+
[1.0] * _fc_max if _dist_parms_dir is None else _dist_parms_dir[:_fc_max]
|
|
254
|
+
)
|
|
255
|
+
if _dist_type_dir == SHRDistribution.DIR_ASYM:
|
|
256
|
+
_dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, _fc_max)
|
|
257
|
+
|
|
258
|
+
if _dist_type_dir == SHRDistribution.DIR_COND:
|
|
259
|
+
|
|
260
|
+
def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
|
|
261
|
+
_dat = [2.5] * 2
|
|
262
|
+
if _fcv > len(_dat):
|
|
263
|
+
_dat += [1.0 / (_fcv - 2)] * (_fcv - 2)
|
|
264
|
+
return np.array(_dat, dtype=np.float64)
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
|
|
268
|
+
def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
|
|
269
|
+
return np.array(_dir_alphas_full[:_fcv], dtype=np.float64)
|
|
270
|
+
|
|
271
|
+
_fcounts = prng(_fcount_rng_seed_seq).choice(
|
|
272
|
+
_fcount_keys, size=(_s_size, 1), p=_choice_wts
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
_mktshr_seed_seq_ch = (
|
|
276
|
+
_mktshr_rng_seed_seq.spawn(len(_fcount_keys))
|
|
277
|
+
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
278
|
+
else SeedSequence(pool_size=8).spawn(len(_fcounts))
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
_aggregate_purchase_prob, _nth_firm_share = (
|
|
282
|
+
np.empty((_s_size, 1)) for _ in range(2)
|
|
283
|
+
)
|
|
284
|
+
_mktshr_array = np.empty((_s_size, _fc_max), dtype=np.float64)
|
|
285
|
+
for _f_val, _f_sseq in zip(_fcount_keys, _mktshr_seed_seq_ch, strict=True):
|
|
286
|
+
_fcounts_match_rows = np.where(_fcounts == _f_val)[0]
|
|
287
|
+
_dir_alphas_test = _gen_dir_alphas(_f_val)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
_mktshr_sample_f = gen_market_shares_dirichlet(
|
|
291
|
+
_dir_alphas_test,
|
|
292
|
+
len(_fcounts_match_rows),
|
|
293
|
+
_recapture_form,
|
|
294
|
+
_f_sseq,
|
|
295
|
+
_nthreads,
|
|
296
|
+
)
|
|
297
|
+
except ValueError as _err:
|
|
298
|
+
print(_f_val, len(_fcounts_match_rows))
|
|
299
|
+
raise _err
|
|
300
|
+
|
|
301
|
+
# Push data for present sample to parent
|
|
302
|
+
_mktshr_array[_fcounts_match_rows] = np.pad(
|
|
303
|
+
_mktshr_sample_f.mktshr_array,
|
|
304
|
+
((0, 0), (0, _fc_max - _mktshr_sample_f.mktshr_array.shape[1])),
|
|
305
|
+
"constant",
|
|
306
|
+
)
|
|
307
|
+
_aggregate_purchase_prob[_fcounts_match_rows] = (
|
|
308
|
+
_mktshr_sample_f.aggregate_purchase_prob
|
|
309
|
+
)
|
|
310
|
+
_nth_firm_share[_fcounts_match_rows] = _mktshr_sample_f.nth_firm_share
|
|
311
|
+
|
|
312
|
+
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
313
|
+
_mktshr_array
|
|
314
|
+
):
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"DATA GENERATION ERROR: {} {} {}".format(
|
|
317
|
+
"Generation of sample shares is inconsistent:",
|
|
318
|
+
"array of drawn shares must some to the number of draws",
|
|
319
|
+
"i.e., the sample size, which condition is not met.",
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return ShareDataSample(
|
|
324
|
+
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def gen_market_shares_dirichlet(
|
|
329
|
+
_dir_alphas: ArrayDouble,
|
|
330
|
+
_s_size: int = 10**6,
|
|
331
|
+
_recapture_form: RECForm = RECForm.INOUT,
|
|
332
|
+
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
333
|
+
_nthreads: int = 16,
|
|
334
|
+
/,
|
|
335
|
+
) -> ShareDataSample:
|
|
336
|
+
"""Dirichlet-distributed shares with fixed firm-count.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
_dir_alphas
|
|
341
|
+
Shape parameters for Dirichlet distribution
|
|
342
|
+
|
|
343
|
+
_s_size
|
|
344
|
+
sample size to be drawn
|
|
345
|
+
|
|
346
|
+
_recapture_form
|
|
347
|
+
r_1 = r_2 if RECForm.FIXED, otherwise MNL-consistent. If
|
|
348
|
+
RECForm.OUTIN; the number of columns in the output share array
|
|
349
|
+
is len(_dir_alphas) - 1.
|
|
350
|
+
|
|
351
|
+
_mktshr_rng_seed_seq
|
|
352
|
+
seed market share rng, for replicable results
|
|
353
|
+
|
|
354
|
+
_nthreads
|
|
355
|
+
number of threads for parallelized random number generation
|
|
356
|
+
|
|
357
|
+
Returns
|
|
358
|
+
-------
|
|
359
|
+
array of market shares and other market statistics
|
|
360
|
+
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
if not isinstance(_dir_alphas, np.ndarray):
|
|
364
|
+
_dir_alphas = np.array(_dir_alphas)
|
|
365
|
+
|
|
366
|
+
if _recapture_form == RECForm.OUTIN:
|
|
367
|
+
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
368
|
+
|
|
369
|
+
_mktshr_seed_seq_ch = (
|
|
370
|
+
_mktshr_rng_seed_seq
|
|
371
|
+
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
372
|
+
else SeedSequence(pool_size=8)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
_mktshr_array = np.empty((_s_size, len(_dir_alphas)), dtype=np.float64)
|
|
376
|
+
_mrng = MultithreadedRNG(
|
|
377
|
+
_mktshr_array,
|
|
378
|
+
dist_type="Dirichlet",
|
|
379
|
+
dist_parms=_dir_alphas,
|
|
380
|
+
seed_sequence=_mktshr_seed_seq_ch,
|
|
381
|
+
nthreads=_nthreads,
|
|
382
|
+
)
|
|
383
|
+
_mrng.fill()
|
|
384
|
+
|
|
385
|
+
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
386
|
+
_mktshr_array
|
|
387
|
+
):
|
|
388
|
+
print(_dir_alphas, _iss, repr(_s_size), len(_mktshr_array))
|
|
389
|
+
print(repr(_mktshr_array[-10:, :]))
|
|
390
|
+
raise ValueError(
|
|
391
|
+
"DATA GENERATION ERROR: {} {} {}".format(
|
|
392
|
+
"Generation of sample shares is inconsistent:",
|
|
393
|
+
"array of drawn shares must sum to the number of draws",
|
|
394
|
+
"i.e., the sample size, which condition is not met.",
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# If recapture_form == 'inside_out', further calculations downstream
|
|
399
|
+
_aggregate_purchase_prob = np.empty((_s_size, 1), dtype=np.float64)
|
|
400
|
+
_aggregate_purchase_prob.fill(np.nan)
|
|
401
|
+
if _recapture_form == RECForm.OUTIN:
|
|
402
|
+
_aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]] # type: ignore
|
|
403
|
+
_mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob # type: ignore
|
|
404
|
+
|
|
405
|
+
return ShareDataSample(
|
|
406
|
+
_mktshr_array,
|
|
407
|
+
(_mktshr_array.shape[-1] * np.ones((_s_size, 1))).astype(np.int64),
|
|
408
|
+
_mktshr_array[:, [-1]],
|
|
409
|
+
_aggregate_purchase_prob,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def gen_divr_array(
|
|
414
|
+
_recapture_form: RECForm,
|
|
415
|
+
_recapture_ratio: float | None,
|
|
416
|
+
_frmshr_array: ArrayDouble,
|
|
417
|
+
_aggregate_purchase_prob: ArrayDouble = DEFAULT_EMPTY_ARRAY,
|
|
418
|
+
/,
|
|
419
|
+
) -> ArrayDouble:
|
|
420
|
+
"""
|
|
421
|
+
Given merging-firm shares and related parameters, return diverion ratios.
|
|
422
|
+
|
|
423
|
+
If recapture is specified as :attr:`mergeron.RECForm.OUTIN`, then the
|
|
424
|
+
choice-probability for the outside good must be supplied.
|
|
425
|
+
|
|
426
|
+
Parameters
|
|
427
|
+
----------
|
|
428
|
+
_recapture_form
|
|
429
|
+
Enum specifying Fixed (proportional), Inside-out, or Outside-in
|
|
430
|
+
|
|
431
|
+
_recapture_ratio
|
|
432
|
+
If recapture is proportional or inside-out, the recapture ratio
|
|
433
|
+
for the firm with the smaller share.
|
|
434
|
+
|
|
435
|
+
_frmshr_array
|
|
436
|
+
Merging-firm shares.
|
|
437
|
+
|
|
438
|
+
_aggregate_purchase_prob
|
|
439
|
+
1 minus probability that the outside good is chosen; converts
|
|
440
|
+
market shares to choice probabilities by multiplication.
|
|
441
|
+
|
|
442
|
+
Raises
|
|
443
|
+
------
|
|
444
|
+
ValueError
|
|
445
|
+
If the firm with the smaller share does not have the larger
|
|
446
|
+
diversion ratio between the merging firms.
|
|
447
|
+
|
|
448
|
+
Returns
|
|
449
|
+
-------
|
|
450
|
+
Merging-firm diversion ratios for mergers in the sample.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
_divr_array: ArrayDouble
|
|
455
|
+
if _recapture_form == RECForm.FIXED:
|
|
456
|
+
_divr_array = _recapture_ratio * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
_purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
460
|
+
_divr_array = _purchprob_array[:, ::-1] / (1 - _purchprob_array)
|
|
461
|
+
|
|
462
|
+
_divr_assert_test = (
|
|
463
|
+
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
464
|
+
| (np.argmin(_frmshr_array, axis=1) == np.argmax(_divr_array, axis=1))
|
|
465
|
+
)[:, None]
|
|
466
|
+
if not all(_divr_assert_test):
|
|
467
|
+
raise ValueError(
|
|
468
|
+
"{} {} {} {}".format(
|
|
469
|
+
"Data construction fails tests:",
|
|
470
|
+
"the index of min(s_1, s_2) must equal",
|
|
471
|
+
"the index of max(d_12, d_21), for all draws.",
|
|
472
|
+
"unless frmshr_array sums to 1.00.",
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return _divr_array
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def gen_margin_price_data(
|
|
480
|
+
_frmshr_array: ArrayDouble,
|
|
481
|
+
_nth_firm_share: ArrayDouble,
|
|
482
|
+
_aggregate_purchase_prob: ArrayDouble,
|
|
483
|
+
_pcm_spec: PCMSpec,
|
|
484
|
+
_price_spec: PriceSpec,
|
|
485
|
+
_hsr_filing_test_type: SSZConstant,
|
|
486
|
+
_pcm_rng_seed_seq: SeedSequence,
|
|
487
|
+
_pr_rng_seed_seq: SeedSequence | None = None,
|
|
488
|
+
_nthreads: int = 16,
|
|
489
|
+
/,
|
|
490
|
+
) -> tuple[MarginDataSample, PriceDataSample]:
|
|
491
|
+
"""Generate margin and price data for mergers in the sample.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
_frmshr_array
|
|
496
|
+
Merging-firm shares; see :class:`mergeron.gen.ShareSpec`.
|
|
497
|
+
|
|
498
|
+
_nth_firm_share
|
|
499
|
+
Share of the nth firm in the sample.
|
|
500
|
+
|
|
501
|
+
_aggregate_purchase_prob
|
|
502
|
+
1 minus probability that the outside good is chosen; converts
|
|
503
|
+
market shares to choice probabilities by multiplication.
|
|
504
|
+
|
|
505
|
+
_pcm_spec
|
|
506
|
+
Enum specifying whether to use asymmetric or flat margins. see
|
|
507
|
+
:class:`mergeron.gen.PCMSpec`.
|
|
508
|
+
|
|
509
|
+
_price_spec
|
|
510
|
+
Enum specifying whether to use symmetric, positive, or negative
|
|
511
|
+
margins; see :class:`mergeron.gen.PriceSpec`.
|
|
512
|
+
|
|
513
|
+
_hsr_filing_test_type
|
|
514
|
+
Enum specifying restriction, if any, to impose on market data sample
|
|
515
|
+
to model HSR filing requirements; see :class:`mergeron.gen.SSZConstant`.
|
|
516
|
+
|
|
517
|
+
_pcm_rng_seed_seq
|
|
518
|
+
Seed sequence for generating margin data.
|
|
519
|
+
|
|
520
|
+
_pr_rng_seed_seq
|
|
521
|
+
Seed sequence for generating price data.
|
|
522
|
+
|
|
523
|
+
_nthreads
|
|
524
|
+
Number of threads to use in generating price data.
|
|
525
|
+
|
|
526
|
+
Returns
|
|
527
|
+
-------
|
|
528
|
+
Simulated margin- and price-data arrays for mergers in the sample.
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
_margin_data = MarginDataSample(
|
|
532
|
+
np.empty_like(_frmshr_array), np.ones(len(_frmshr_array)) == 0
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
_price_array, _price_ratio_array = (
|
|
536
|
+
np.ones_like(_frmshr_array, np.float64),
|
|
537
|
+
np.empty_like(_frmshr_array, np.float64),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
_pr_max_ratio = 5.0
|
|
541
|
+
match _price_spec:
|
|
542
|
+
case PriceSpec.SYM:
|
|
543
|
+
_nth_firm_price = np.ones((len(_frmshr_array), 1), np.float64)
|
|
544
|
+
case PriceSpec.POS:
|
|
545
|
+
_price_array, _nth_firm_price = (
|
|
546
|
+
np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
547
|
+
)
|
|
548
|
+
case PriceSpec.NEG:
|
|
549
|
+
_price_array, _nth_firm_price = (
|
|
550
|
+
np.ceil((1 - _p) * _pr_max_ratio)
|
|
551
|
+
for _p in (_frmshr_array, _nth_firm_share)
|
|
552
|
+
)
|
|
553
|
+
case PriceSpec.ZERO:
|
|
554
|
+
_price_array_gen = prng(_pr_rng_seed_seq).choice(
|
|
555
|
+
1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
556
|
+
)
|
|
557
|
+
_price_array = _price_array_gen[:, :2]
|
|
558
|
+
_nth_firm_price = _price_array_gen[:, [2]] # type: ignore
|
|
559
|
+
# del _price_array_gen
|
|
560
|
+
case PriceSpec.CSY:
|
|
561
|
+
# TODO:
|
|
562
|
+
# evolve FM2Constraint (save running MNL test twice); evolve copy of _mkt_sample_spec=1q
|
|
563
|
+
# generate the margin data
|
|
564
|
+
# generate price and margin data
|
|
565
|
+
_frmshr_array_plus = np.hstack((_frmshr_array, _nth_firm_share))
|
|
566
|
+
_pcm_spec_here = evolve(_pcm_spec, firm2_pcm_constraint=FM2Constraint.IID)
|
|
567
|
+
_margin_data = _gen_margin_data(
|
|
568
|
+
_frmshr_array_plus,
|
|
569
|
+
np.ones_like(_frmshr_array_plus, np.float64),
|
|
570
|
+
_aggregate_purchase_prob,
|
|
571
|
+
_pcm_spec_here,
|
|
572
|
+
_pcm_rng_seed_seq,
|
|
573
|
+
_nthreads,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
_pcm_array, _mnl_test_array = (
|
|
577
|
+
getattr(_margin_data, _f) for _f in ("pcm_array", "mnl_test_array")
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
_price_array_here = 1 / (1 - _pcm_array)
|
|
581
|
+
_price_array = _price_array_here[:, :2]
|
|
582
|
+
_nth_firm_price = _price_array_here[:, [-1]]
|
|
583
|
+
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.MNL:
|
|
584
|
+
# Generate i.i.d. PCMs then take PCM0 and construct PCM1
|
|
585
|
+
# Regenerate MNL test
|
|
586
|
+
_purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
587
|
+
_pcm_array[:, 1] = np.divide(
|
|
588
|
+
(
|
|
589
|
+
_m1_nr := np.divide(
|
|
590
|
+
np.einsum(
|
|
591
|
+
"ij,ij,ij->ij",
|
|
592
|
+
_price_array[:, [0]],
|
|
593
|
+
_pcm_array[:, [0]],
|
|
594
|
+
1 - _purchase_prob_array[:, [0]],
|
|
595
|
+
),
|
|
596
|
+
1 - _purchase_prob_array[:, [1]],
|
|
597
|
+
)
|
|
598
|
+
),
|
|
599
|
+
1 + _m1_nr,
|
|
600
|
+
)
|
|
601
|
+
_mnl_test_array = (_pcm_array[:, [1]] >= 0) & (_pcm_array[:, [1]] <= 1)
|
|
602
|
+
|
|
603
|
+
_margin_data = MarginDataSample(_pcm_array[:, :2], _mnl_test_array)
|
|
604
|
+
del _price_array_here
|
|
605
|
+
case _:
|
|
606
|
+
raise ValueError(
|
|
607
|
+
f'Specification of price distribution, "{_price_spec.value}" is invalid.'
|
|
608
|
+
)
|
|
609
|
+
if _price_spec != PriceSpec.CSY:
|
|
610
|
+
_margin_data = _gen_margin_data(
|
|
611
|
+
_frmshr_array,
|
|
612
|
+
_price_array,
|
|
613
|
+
_aggregate_purchase_prob,
|
|
614
|
+
_pcm_spec,
|
|
615
|
+
_pcm_rng_seed_seq,
|
|
616
|
+
_nthreads,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
_price_array = _price_array.astype(np.float64)
|
|
620
|
+
_rev_array = _price_array * _frmshr_array
|
|
621
|
+
_nth_firm_rev = _nth_firm_price * _nth_firm_share
|
|
622
|
+
|
|
623
|
+
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
624
|
+
# the ratio has not changed since inception of the HSR filing test,
|
|
625
|
+
# so we treat it as a constant of merger enforcement policy.
|
|
626
|
+
_test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
|
|
627
|
+
|
|
628
|
+
match _hsr_filing_test_type:
|
|
629
|
+
case SSZConstant.HSR_TEN:
|
|
630
|
+
# See, https://www.ftc.gov/enforcement/premerger-notification-program/
|
|
631
|
+
# -> Procedures For Submitting Post-Consummation Filings
|
|
632
|
+
# -> Key Elements to Determine Whether a Post Consummation Filing is Required
|
|
633
|
+
# under heading, "Historical Thresholds"
|
|
634
|
+
# Revenue ratio has been 10-to-1 since inception
|
|
635
|
+
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
636
|
+
# ratio restriction on the merging firms' revenues
|
|
637
|
+
_rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
|
|
638
|
+
_hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
|
|
639
|
+
# del _rev_array, _rev_ratio
|
|
640
|
+
case SSZConstant.HSR_NTH:
|
|
641
|
+
# To get around the 10-to-1 ratio restriction, specify that the nth firm test:
|
|
642
|
+
# if the smaller merging firm matches or exceeds the n-th firm in size, and
|
|
643
|
+
# the larger merging firm has at least 10 times the size of the nth firm,
|
|
644
|
+
# the size test is considered met.
|
|
645
|
+
# Alternatively, if the smaller merging firm has 10% or greater share,
|
|
646
|
+
# the value of transaction test is considered met.
|
|
647
|
+
_rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
|
|
648
|
+
_hsr_filing_test = (
|
|
649
|
+
np.einsum(
|
|
650
|
+
"ij->i",
|
|
651
|
+
1 * (_rev_ratio_to_nth > [1, _test_rev_ratio]),
|
|
652
|
+
dtype=np.int64,
|
|
653
|
+
)
|
|
654
|
+
== _rev_ratio_to_nth.shape[1]
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
658
|
+
case _:
|
|
659
|
+
# Otherwise, all draws meet the filing test
|
|
660
|
+
_hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
|
|
661
|
+
_hsr_filing_test = _hsr_filing_test | (
|
|
662
|
+
_frmshr_array.min(axis=1) >= _test_rev_ratio_inv
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
return _margin_data, PriceDataSample(_price_array, _hsr_filing_test)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _gen_margin_data(
|
|
669
|
+
_frmshr_array: ArrayDouble,
|
|
670
|
+
_price_array: ArrayDouble,
|
|
671
|
+
_aggregate_purchase_prob: ArrayDouble,
|
|
672
|
+
_pcm_spec: PCMSpec,
|
|
673
|
+
_pcm_rng_seed_seq: SeedSequence,
|
|
674
|
+
_nthreads: int = 16,
|
|
675
|
+
/,
|
|
676
|
+
) -> MarginDataSample:
|
|
677
|
+
_dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
|
|
678
|
+
getattr(_pcm_spec, _f)
|
|
679
|
+
for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
_pcm_array = (
|
|
683
|
+
np.empty((len(_frmshr_array), 1), dtype=np.float64)
|
|
684
|
+
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.SYM
|
|
685
|
+
else np.empty_like(_frmshr_array, dtype=np.float64)
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
689
|
+
if _dist_type_pcm == PCMDistribution.EMPR:
|
|
690
|
+
_pcm_array = mgn_data_resampler(
|
|
691
|
+
_pcm_array.shape, seed_sequence=_pcm_rng_seed_seq
|
|
692
|
+
)
|
|
693
|
+
else:
|
|
694
|
+
_dist_type: Literal["Beta", "Uniform"]
|
|
695
|
+
if _dist_type_pcm in (PCMDistribution.BETA, PCMDistribution.BETA_BND):
|
|
696
|
+
_dist_type = "Beta"
|
|
697
|
+
_dist_parms_pcm = (
|
|
698
|
+
(
|
|
699
|
+
np.array([0, 1, 0, 1], np.float64)
|
|
700
|
+
if _dist_parms_pcm == PCMDistribution.BETA_BND
|
|
701
|
+
else np.ones(2, np.float64)
|
|
702
|
+
)
|
|
703
|
+
if _dist_parms_pcm is None
|
|
704
|
+
else _dist_parms_pcm
|
|
705
|
+
)
|
|
706
|
+
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
707
|
+
|
|
708
|
+
else:
|
|
709
|
+
_dist_type = "Uniform"
|
|
710
|
+
_dist_parms = (
|
|
711
|
+
DEFAULT_DIST_PARMS if _dist_parms_pcm is None else _dist_parms_pcm
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
_pcm_rng = MultithreadedRNG(
|
|
715
|
+
_pcm_array,
|
|
716
|
+
dist_type=_dist_type,
|
|
717
|
+
dist_parms=_dist_parms,
|
|
718
|
+
seed_sequence=_pcm_rng_seed_seq,
|
|
719
|
+
nthreads=_nthreads,
|
|
720
|
+
)
|
|
721
|
+
_pcm_rng.fill()
|
|
722
|
+
del _pcm_rng
|
|
723
|
+
|
|
724
|
+
if _dist_type_pcm == PCMDistribution.BETA_BND:
|
|
725
|
+
_beta_min, _beta_max = _dist_parms_pcm[2:]
|
|
726
|
+
_pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
|
|
727
|
+
del _beta_min, _beta_max
|
|
728
|
+
|
|
729
|
+
if _dist_firm2_pcm == FM2Constraint.SYM:
|
|
730
|
+
_pcm_array = np.column_stack((_pcm_array,) * _frmshr_array.shape[1])
|
|
731
|
+
if _dist_firm2_pcm == FM2Constraint.MNL:
|
|
732
|
+
# Impose FOCs from profit-maximization with MNL demand
|
|
733
|
+
if _dist_type_pcm == PCMDistribution.EMPR:
|
|
734
|
+
print(
|
|
735
|
+
"NOTE: Estimated Firm 2 parameters will not be consistent with "
|
|
736
|
+
"the empirical distribution of margins in the source data. For "
|
|
737
|
+
"consistency, respecify pcm_spec.firm2_pcm_constraint = FM2Constraint.IID."
|
|
738
|
+
)
|
|
739
|
+
_purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
740
|
+
|
|
741
|
+
_pcm_array[:, [1]] = np.divide(
|
|
742
|
+
np.einsum(
|
|
743
|
+
"ij,ij,ij->ij",
|
|
744
|
+
_price_array[:, [0]],
|
|
745
|
+
_pcm_array[:, [0]],
|
|
746
|
+
1 - _purchase_prob_array[:, [0]],
|
|
747
|
+
),
|
|
748
|
+
np.einsum(
|
|
749
|
+
"ij,ij->ij", _price_array[:, [1]], 1 - _purchase_prob_array[:, [1]]
|
|
750
|
+
),
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
_mnl_test_array = _pcm_array[:, 1].__ge__(0) & _pcm_array[:, 1].__le__(1)
|
|
754
|
+
else:
|
|
755
|
+
_mnl_test_array = np.ones(len(_pcm_array), dtype=bool)
|
|
756
|
+
|
|
757
|
+
return MarginDataSample(_pcm_array, _mnl_test_array)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _beta_located(
|
|
761
|
+
_mu: float | ArrayDouble, _sigma: float | ArrayDouble, /
|
|
762
|
+
) -> ArrayDouble:
|
|
763
|
+
"""
|
|
764
|
+
Given mean and stddev, return shape parameters for corresponding Beta distribution
|
|
765
|
+
|
|
766
|
+
Solve the first two moments of the standard Beta to get the shape parameters.
|
|
767
|
+
|
|
768
|
+
Parameters
|
|
769
|
+
----------
|
|
770
|
+
_mu
|
|
771
|
+
mean
|
|
772
|
+
_sigma
|
|
773
|
+
standardd deviation
|
|
774
|
+
|
|
775
|
+
Returns
|
|
776
|
+
-------
|
|
777
|
+
shape parameters for Beta distribution
|
|
778
|
+
|
|
779
|
+
"""
|
|
780
|
+
|
|
781
|
+
_mul = -1 + _mu * (1 - _mu) / _sigma**2
|
|
782
|
+
return np.array([_mu * _mul, (1 - _mu) * _mul], dtype=np.float64)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def beta_located_bound(_dist_parms: ArrayDouble, /) -> ArrayDouble:
|
|
786
|
+
R"""
|
|
787
|
+
Return shape parameters for a non-standard beta, given the mean, stddev, range
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
Recover the r.v.s as
|
|
791
|
+
:math:`\min + (\max - \min) \cdot \symup{Β}(a, b)`,
|
|
792
|
+
with `a` and `b` calculated from the specified mean (:math:`\mu`) and
|
|
793
|
+
variance (:math:`\sigma`). [8]_
|
|
794
|
+
|
|
795
|
+
Parameters
|
|
796
|
+
----------
|
|
797
|
+
_dist_parms
|
|
798
|
+
vector of :math:`\mu`, :math:`\sigma`, :math:`\mathtt{\min}`, and :math:`\mathtt{\max}` values
|
|
799
|
+
|
|
800
|
+
Returns
|
|
801
|
+
-------
|
|
802
|
+
shape parameters for Beta distribution
|
|
803
|
+
|
|
804
|
+
Notes
|
|
805
|
+
-----
|
|
806
|
+
For example, ``beta_located_bound(np.array([0.5, 0.2887, 0.0, 1.0]))``.
|
|
807
|
+
|
|
808
|
+
References
|
|
809
|
+
----------
|
|
810
|
+
.. [8] NIST, Beta Distribution. https://www.itl.nist.gov/div898/handbook/eda/section3/eda366h.htm
|
|
811
|
+
""" # noqa: RUF002
|
|
812
|
+
|
|
813
|
+
_bmu, _bsigma, _bmin, _bmax = _dist_parms
|
|
814
|
+
return _beta_located((_bmu - _bmin) / (_bmax - _bmin), _bsigma / (_bmax - _bmin))
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def parse_seed_seq_list(
|
|
818
|
+
_sseq_list: Sequence[SeedSequence] | None,
|
|
819
|
+
_mktshr_dist_type: SHRDistribution,
|
|
820
|
+
_price_spec: PriceSpec,
|
|
821
|
+
/,
|
|
822
|
+
) -> SeedSequenceData:
|
|
823
|
+
"""Initialize RNG seed sequences to ensure independence of distinct random streams.
|
|
824
|
+
|
|
825
|
+
The tuple of SeedSequences, is parsed in the following order
|
|
826
|
+
for generating the relevant random variates:
|
|
827
|
+
1.) quantity shares
|
|
828
|
+
2.) price-cost margins
|
|
829
|
+
3.) firm-counts, if :code:`MarketSpec.share_spec.dist_type` is a Dirichlet distribution
|
|
830
|
+
4.) prices, if :code:`MarketSpec.price_spec ==`:attr:`mergeron.gen.PriceSpec.ZERO`.
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
Parameters
|
|
835
|
+
----------
|
|
836
|
+
_sseq_list
|
|
837
|
+
List of RNG seed sequences
|
|
838
|
+
|
|
839
|
+
_mktshr_dist_type
|
|
840
|
+
Market share distribution type
|
|
841
|
+
|
|
842
|
+
_price_spec
|
|
843
|
+
Price specification
|
|
844
|
+
|
|
845
|
+
Returns
|
|
846
|
+
-------
|
|
847
|
+
Seed sequence data
|
|
848
|
+
|
|
849
|
+
"""
|
|
850
|
+
_seed_count = 2 if _mktshr_dist_type == SHRDistribution.UNI else 3
|
|
851
|
+
_seed_count += 1 if _price_spec == PriceSpec.ZERO else 0
|
|
852
|
+
|
|
853
|
+
_fcount_rng_seed_seq: SeedSequence | None = None
|
|
854
|
+
_pr_rng_seed_seq: SeedSequence | None = None
|
|
855
|
+
|
|
856
|
+
_sseq_list = (
|
|
857
|
+
_sseq_list
|
|
858
|
+
if _sseq_list
|
|
859
|
+
else tuple(SeedSequence(pool_size=8) for _ in range(_seed_count))
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
if (_l := len(_sseq_list)) < _seed_count:
|
|
863
|
+
raise ValueError(
|
|
864
|
+
f"Seed sequence list must contain {_seed_count} seed sequences; "
|
|
865
|
+
f"only {_l} given."
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
_mktshr_rng_seed_seq, _pcm_rng_seed_seq = _sseq_list[:2]
|
|
869
|
+
_fcount_rng_seed_seq = (
|
|
870
|
+
None if _mktshr_dist_type == SHRDistribution.UNI else _sseq_list[2]
|
|
871
|
+
)
|
|
872
|
+
_pr_rng_seed_seq = _sseq_list[-1] if _price_spec == PriceSpec.ZERO else None
|
|
873
|
+
|
|
874
|
+
return SeedSequenceData(
|
|
875
|
+
_mktshr_rng_seed_seq, _pcm_rng_seed_seq, _fcount_rng_seed_seq, _pr_rng_seed_seq
|
|
876
|
+
)
|