mergeron 2024.738936.2__py3-none-any.whl → 2024.738940.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/core/excel_helper.py +4 -2
- mergeron/core/guidelines_standards.py +137 -83
- mergeron/examples/concentration_as_diversion.py +30 -30
- mergeron/examples/investigations_stats_sim_tables.py +22 -29
- mergeron/examples/safeharbor_boundaries_for_mergers_with_asymmetric_shares.py +13 -13
- mergeron/examples/safeharbor_boundaries_for_symmetric_firm_mergers.py +3 -3
- mergeron/examples/sound_guppi_safeharbor.py +31 -28
- mergeron/examples/testIntrinsicClearanceRates.py +12 -12
- mergeron/examples/visualize_guidelines_tests.py +34 -29
- mergeron/gen/__init__.py +463 -0
- mergeron/gen/_data_generation_functions_nonpublic.py +626 -0
- mergeron/gen/data_generation.py +45 -989
- mergeron/gen/guidelines_tests.py +122 -99
- mergeron/gen/investigations_stats.py +10 -12
- mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +1 -1
- mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +1 -1
- {mergeron-2024.738936.2.dist-info → mergeron-2024.738940.0.dist-info}/METADATA +1 -1
- {mergeron-2024.738936.2.dist-info → mergeron-2024.738940.0.dist-info}/RECORD +19 -18
- {mergeron-2024.738936.2.dist-info → mergeron-2024.738940.0.dist-info}/WHEEL +0 -0
mergeron/gen/data_generation.py
CHANGED
|
@@ -11,400 +11,29 @@ from .. import _PKG_NAME # noqa: TID252
|
|
|
11
11
|
|
|
12
12
|
__version__ = version(_PKG_NAME)
|
|
13
13
|
|
|
14
|
-
import enum
|
|
15
|
-
from typing import Literal, NamedTuple, TypeVar
|
|
16
|
-
|
|
17
14
|
import attrs
|
|
18
15
|
import numpy as np
|
|
19
16
|
from numpy.random import SeedSequence
|
|
20
|
-
from numpy.typing import
|
|
21
|
-
|
|
22
|
-
from
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
from numpy.typing import NDArray
|
|
18
|
+
|
|
19
|
+
from . import (
|
|
20
|
+
EMPTY_ARRAY_DEFAULT,
|
|
21
|
+
TF,
|
|
22
|
+
FM2Constants,
|
|
23
|
+
MarketDataSample,
|
|
24
|
+
MarketSampleSpec,
|
|
25
|
+
PRIConstants,
|
|
26
|
+
RECConstants,
|
|
27
|
+
SHRConstants,
|
|
28
|
+
SSZConstants,
|
|
29
|
+
)
|
|
30
|
+
from ._data_generation_functions_nonpublic import (
|
|
31
|
+
_gen_market_shares_dirichlet, # noqa: F401 easter-egg for external modules
|
|
32
|
+
_gen_market_shares_uniform, # noqa: F401 easter-egg for external modules
|
|
33
|
+
_gen_pcm_data,
|
|
34
|
+
_gen_pr_data,
|
|
35
|
+
_gen_share_data,
|
|
27
36
|
)
|
|
28
|
-
|
|
29
|
-
EMPTY_ARRAY_DEFAULT = np.zeros(2)
|
|
30
|
-
FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
|
|
31
|
-
|
|
32
|
-
TF = TypeVar("TF", bound=NBitBase)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@enum.unique
|
|
36
|
-
class PRIConstants(tuple[bool, str | None], enum.ReprEnum):
|
|
37
|
-
"""Price specification.
|
|
38
|
-
|
|
39
|
-
Whether prices are symmetric and, if not, the direction of correlation, if any.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
SYM = (True, None)
|
|
43
|
-
ZERO = (False, None)
|
|
44
|
-
NEG = (False, "negative share-correlation")
|
|
45
|
-
POS = (False, "positive share-correlation")
|
|
46
|
-
CSY = (False, "market-wide cost-symmetry")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@enum.unique
|
|
50
|
-
class SHRConstants(enum.StrEnum):
|
|
51
|
-
"""Market share distributions."""
|
|
52
|
-
|
|
53
|
-
UNI = "Uniform"
|
|
54
|
-
"""Uniform distribution over the 3-simplex"""
|
|
55
|
-
|
|
56
|
-
DIR_FLAT = "Flat Dirichlet"
|
|
57
|
-
"""Shape parameter for all merging-firm-shares is unity (1)"""
|
|
58
|
-
|
|
59
|
-
DIR_FLAT_CONSTR = "Flat Dirichlet - Constrained"
|
|
60
|
-
"""Impose minimum probablility weight on each firm-count
|
|
61
|
-
|
|
62
|
-
Only firm-counts with probability weight of no less than 3%
|
|
63
|
-
are included for data generation.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
DIR_ASYM = "Asymmetric Dirichlet"
|
|
67
|
-
"""Share distribution for merging-firm shares has a higher peak share
|
|
68
|
-
|
|
69
|
-
Shape parameter for merging-firm-share is 2.5, and 1.0 for all others.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
DIR_COND = "Conditional Dirichlet"
|
|
73
|
-
"""Shape parameters for non-merging firms is proportional
|
|
74
|
-
|
|
75
|
-
Shape parameters for merging-firm-share are 2.0 each; and
|
|
76
|
-
are equiproportional and add to 2.0 for all non-merging-firm-shares.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@enum.unique
|
|
81
|
-
class RECConstants(enum.StrEnum):
|
|
82
|
-
"""Recapture rate - derivation methods."""
|
|
83
|
-
|
|
84
|
-
INOUT = "inside-out"
|
|
85
|
-
OUTIN = "outside-in"
|
|
86
|
-
FIXED = "proportional"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class ShareSpec(NamedTuple):
|
|
90
|
-
"""Market share specification
|
|
91
|
-
|
|
92
|
-
Notes
|
|
93
|
-
-----
|
|
94
|
-
If recapture is determined "outside-in", market shares cannot have
|
|
95
|
-
Uniform distribution.
|
|
96
|
-
|
|
97
|
-
If sample with varying firm counts is required, market shares must
|
|
98
|
-
be specified as having a supported Dirichlet distribution.
|
|
99
|
-
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
recapture_spec: RECConstants
|
|
103
|
-
"""see RECConstants"""
|
|
104
|
-
|
|
105
|
-
dist_type: SHRConstants
|
|
106
|
-
"""see SHRConstants"""
|
|
107
|
-
|
|
108
|
-
dist_parms: NDArray[np.float64] | None
|
|
109
|
-
"""Parameters for tailoring market-share distribution
|
|
110
|
-
|
|
111
|
-
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
112
|
-
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
113
|
-
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
114
|
-
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
115
|
-
no less than the length of firm-count weights below; defaults depend on
|
|
116
|
-
type of Dirichlet-distribution specified.
|
|
117
|
-
|
|
118
|
-
"""
|
|
119
|
-
firm_counts_prob_weights: NDArray[np.float64 | np.int64] | None
|
|
120
|
-
"""relative or absolute frequencies of firm counts
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Given frequencies are exogenous to generated market data sample;
|
|
124
|
-
defaults to FCOUNT_WTS_DEFAULT, which specifies firm-counts of 2 to 6
|
|
125
|
-
with weights in descending order from 5 to 1."""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@enum.unique
|
|
129
|
-
class PCMConstants(enum.StrEnum):
|
|
130
|
-
"""Margin distributions."""
|
|
131
|
-
|
|
132
|
-
UNI = "Uniform"
|
|
133
|
-
BETA = "Beta"
|
|
134
|
-
BETA_BND = "Bounded Beta"
|
|
135
|
-
EMPR = "Damodaran margin data"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@enum.unique
|
|
139
|
-
class FM2Constants(enum.StrEnum):
|
|
140
|
-
"""Firm 2 margins - derivation methods."""
|
|
141
|
-
|
|
142
|
-
IID = "i.i.d"
|
|
143
|
-
MNL = "MNL-dep"
|
|
144
|
-
SYM = "symmetric"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
class PCMSpec(NamedTuple):
|
|
148
|
-
"""Price-cost margin (PCM) specification
|
|
149
|
-
|
|
150
|
-
If price-cost margins are specified as having Beta distribution,
|
|
151
|
-
`dist_parms` is specified as a pair of positive, non-zero shape parameters of
|
|
152
|
-
the standard Beta distribution. Specifying shape parameters :code:`np.array([1, 1])`
|
|
153
|
-
is known equivalent to specifying uniform distribution over
|
|
154
|
-
the interval :math:`[0, 1]`. If price-cost margins are specified as having
|
|
155
|
-
Bounded-Beta distribution, `dist_parms` is specified as
|
|
156
|
-
the tuple, (`mean`, `std deviation`, `min`, `max`), where `min` and `max`
|
|
157
|
-
are lower- and upper-bounds respectively within the interval :math:`[0, 1]`.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
dist_type: PCMConstants
|
|
163
|
-
"""See PCMConstants"""
|
|
164
|
-
|
|
165
|
-
firm2_margin_restrictions: FM2Constants
|
|
166
|
-
"""See FM2Constants"""
|
|
167
|
-
|
|
168
|
-
dist_parms: NDArray[np.float64] | None
|
|
169
|
-
"""Parameter specification for tailoring PCM distribution
|
|
170
|
-
|
|
171
|
-
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
172
|
-
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
173
|
-
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
174
|
-
for empirical distribution based on Damodaran margin data, optional, ignored
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@enum.unique
|
|
179
|
-
class SSZConstants(float, enum.ReprEnum):
|
|
180
|
-
"""
|
|
181
|
-
Scale factors to offset sample size reduction.
|
|
182
|
-
|
|
183
|
-
Sample size reduction occurs when imposing a HSR filing test
|
|
184
|
-
or equilibrium condition under MNL demand.
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
HSR_NTH = 1.666667
|
|
188
|
-
"""
|
|
189
|
-
For HSR filing requirement.
|
|
190
|
-
|
|
191
|
-
When filing requirement is assumed met if maximum merging-firm shares exceeds
|
|
192
|
-
ten (10) times the n-th firm's share and minimum merging-firm share is
|
|
193
|
-
no less than n-th firm's share. To assure that the number of draws available
|
|
194
|
-
after applying the given restriction, the initial number of draws is larger than
|
|
195
|
-
the sample size by the given scale factor.
|
|
196
|
-
"""
|
|
197
|
-
|
|
198
|
-
HSR_TEN = 1.234567
|
|
199
|
-
"""
|
|
200
|
-
For alternative HSR filing requirement,
|
|
201
|
-
|
|
202
|
-
When filing requirement is assumed met if merging-firm shares exceed 10:1 ratio
|
|
203
|
-
to each other.
|
|
204
|
-
"""
|
|
205
|
-
|
|
206
|
-
MNL_DEP = 1.25
|
|
207
|
-
"""
|
|
208
|
-
For restricted PCM's.
|
|
209
|
-
|
|
210
|
-
When merging firm's PCMs are constrained for consistency with f.o.c.s from
|
|
211
|
-
profit maximization under Nash-Bertrand oligopoly with MNL demand.
|
|
212
|
-
"""
|
|
213
|
-
|
|
214
|
-
ONE = 1.00
|
|
215
|
-
"""When initial set of draws is not restricted in any way."""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
# share_spec dist_type validator:
|
|
219
|
-
def _share_spec_validator(
|
|
220
|
-
_instance: MarketSampleSpec, _attribute: attrs.Attribute, _value: ShareSpec, /
|
|
221
|
-
) -> None:
|
|
222
|
-
_r_bar = _instance.recapture_rate
|
|
223
|
-
if _value.dist_type == SHRConstants.UNI:
|
|
224
|
-
if _value.recapture_spec == RECConstants.OUTIN:
|
|
225
|
-
raise ValueError(
|
|
226
|
-
f"Invalid recapture specification, {_value.recapture_spec!r} "
|
|
227
|
-
"for market share specification with Uniform distribution. "
|
|
228
|
-
"Redefine the market-sample specification, modifying the ."
|
|
229
|
-
"market-share specification or the recapture specification."
|
|
230
|
-
)
|
|
231
|
-
elif _value.firm_counts_prob_weights is not None:
|
|
232
|
-
raise ValueError(
|
|
233
|
-
"Generated data for markets with specified firm-counts or "
|
|
234
|
-
"varying firm counts are not feasible with market shares "
|
|
235
|
-
"with Uniform distribution. Consider revising the "
|
|
236
|
-
r"distribution type to {SHRConstants.DIR_FLAT}, which gives "
|
|
237
|
-
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
238
|
-
"for firm-count, :math:`n`."
|
|
239
|
-
)
|
|
240
|
-
# Outside-in calibration only valid for Dir-distributed shares
|
|
241
|
-
elif _value.recapture_spec != RECConstants.OUTIN and (
|
|
242
|
-
_r_bar is None or not isinstance(_r_bar, float)
|
|
243
|
-
):
|
|
244
|
-
raise ValueError(
|
|
245
|
-
f"Recapture specification, {_value.recapture_spec!r} requires that "
|
|
246
|
-
"the market sample specification inclues a recapture rate."
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _pcm_spec_validator(
|
|
251
|
-
_instance: MarketSampleSpec, _attribute: attrs.Attribute, _value: PCMSpec, /
|
|
252
|
-
) -> None:
|
|
253
|
-
if (
|
|
254
|
-
_instance.share_spec.recapture_spec == RECConstants.FIXED
|
|
255
|
-
and _value.firm2_margin_restrictions == FM2Constants.MNL
|
|
256
|
-
):
|
|
257
|
-
raise ValueError(
|
|
258
|
-
"{} {} {}".format(
|
|
259
|
-
f'Specification of "recapture_spec", "{_instance.share_spec.recapture_spec}"',
|
|
260
|
-
"requires Firm 2 margin must have property, ",
|
|
261
|
-
f'"{FM2Constants.IID}" or "{FM2Constants.SYM}".',
|
|
262
|
-
)
|
|
263
|
-
)
|
|
264
|
-
elif _value.dist_type.name.startswith("BETA"):
|
|
265
|
-
if _value.dist_parms is None:
|
|
266
|
-
pass
|
|
267
|
-
elif np.array_equal(_value.dist_parms, DIST_PARMS_DEFAULT):
|
|
268
|
-
raise ValueError(
|
|
269
|
-
f"The distribution parameters, {DIST_PARMS_DEFAULT!r} "
|
|
270
|
-
"are not valid with margin distribution, {_dist_type_pcm!r}"
|
|
271
|
-
)
|
|
272
|
-
elif (
|
|
273
|
-
_value.dist_type == PCMConstants.BETA
|
|
274
|
-
and len(_value.dist_parms) != len(("max", "min"))
|
|
275
|
-
) or (
|
|
276
|
-
_value.dist_type == PCMConstants.BETA_BND
|
|
277
|
-
and len(_value.dist_parms) != len(("mu", "sigma", "max", "min"))
|
|
278
|
-
):
|
|
279
|
-
raise ValueError(
|
|
280
|
-
f"Given number, {len(_value.dist_parms)} of parameters "
|
|
281
|
-
f'for PCM with distribution, "{_value.dist_type}" is incorrect.'
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@attrs.define(slots=True, frozen=True)
|
|
286
|
-
class MarketSampleSpec:
|
|
287
|
-
"""Parameter specification for market data generation."""
|
|
288
|
-
|
|
289
|
-
sample_size: int = 10**6
|
|
290
|
-
"""sample size generated"""
|
|
291
|
-
|
|
292
|
-
recapture_rate: float | None = None
|
|
293
|
-
"""market recapture rate
|
|
294
|
-
|
|
295
|
-
Is None if market share specification includes
|
|
296
|
-
generation of outside good choice probabilities (RECConstants.OUTIN).
|
|
297
|
-
"""
|
|
298
|
-
pr_sym_spec: PRIConstants = PRIConstants.SYM
|
|
299
|
-
"""Price specification, see PRIConstants"""
|
|
300
|
-
|
|
301
|
-
share_spec: ShareSpec = attrs.field(
|
|
302
|
-
kw_only=True,
|
|
303
|
-
default=ShareSpec(RECConstants.INOUT, SHRConstants.UNI, None, None),
|
|
304
|
-
validator=_share_spec_validator,
|
|
305
|
-
)
|
|
306
|
-
"""See definition of ShareSpec"""
|
|
307
|
-
|
|
308
|
-
pcm_spec: PCMSpec = attrs.field(
|
|
309
|
-
kw_only=True,
|
|
310
|
-
default=PCMSpec(PCMConstants.UNI, FM2Constants.IID, None),
|
|
311
|
-
validator=_pcm_spec_validator,
|
|
312
|
-
)
|
|
313
|
-
"""See definition of PCMSpec"""
|
|
314
|
-
|
|
315
|
-
hsr_filing_test_type: SSZConstants = attrs.field(
|
|
316
|
-
kw_only=True, default=SSZConstants.ONE
|
|
317
|
-
)
|
|
318
|
-
"""Method for modeling HSR filing threholds, see SSZConstants"""
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
class MarketsSample(NamedTuple):
|
|
322
|
-
"""Container for generated markets data sample."""
|
|
323
|
-
|
|
324
|
-
frmshr_array: NDArray[np.floating]
|
|
325
|
-
"""Merging-firm shares (with two merging firms)"""
|
|
326
|
-
|
|
327
|
-
pcm_array: NDArray[np.floating]
|
|
328
|
-
"""Merging-firms' prices (normalized to 1, in default specification)"""
|
|
329
|
-
|
|
330
|
-
price_array: NDArray[np.floating]
|
|
331
|
-
"""Merging-firms' price-cost margins (PCM)"""
|
|
332
|
-
|
|
333
|
-
fcounts: NDArray[np.integer]
|
|
334
|
-
"""Number of firms in market"""
|
|
335
|
-
|
|
336
|
-
ratio_choice_prob_to_mktshr: NDArray[np.floating]
|
|
337
|
-
"""
|
|
338
|
-
One (1) minus probability that the outside good is chosen
|
|
339
|
-
|
|
340
|
-
Converts market shares to choice probabilities by multiplication.
|
|
341
|
-
"""
|
|
342
|
-
|
|
343
|
-
nth_firm_share: NDArray[np.floating]
|
|
344
|
-
"""Market-share of n-th firm
|
|
345
|
-
|
|
346
|
-
Relevant for testing for draws the do or
|
|
347
|
-
do not meet HSR filing thresholds.
|
|
348
|
-
"""
|
|
349
|
-
|
|
350
|
-
divr_array: NDArray[np.floating]
|
|
351
|
-
"""Diversion ratio between the merging firms"""
|
|
352
|
-
|
|
353
|
-
hhi_post: NDArray[np.floating]
|
|
354
|
-
"""Post-merger change in Herfindahl-Hirschmann Index (HHI)"""
|
|
355
|
-
|
|
356
|
-
hhi_delta: NDArray[np.floating]
|
|
357
|
-
"""Change in HHI from combination of merging firms"""
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
class ShareDataSample(NamedTuple):
|
|
361
|
-
"""Container for generated market shares.
|
|
362
|
-
|
|
363
|
-
Includes related measures of market structure
|
|
364
|
-
and aggregate purchase probability.
|
|
365
|
-
"""
|
|
366
|
-
|
|
367
|
-
mktshr_array: NDArray[np.float64]
|
|
368
|
-
"""All-firm shares (with two merging firms)"""
|
|
369
|
-
|
|
370
|
-
fcounts: NDArray[np.int64]
|
|
371
|
-
"""All-firm-count for each draw"""
|
|
372
|
-
|
|
373
|
-
nth_firm_share: NDArray[np.float64]
|
|
374
|
-
"""Market-share of n-th firm"""
|
|
375
|
-
|
|
376
|
-
aggregate_purchase_prob: NDArray[np.float64]
|
|
377
|
-
"""Converts market shares to choice probabilities by multiplication."""
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
class PriceDataSample(NamedTuple):
|
|
381
|
-
"""Container for generated price array, and related."""
|
|
382
|
-
|
|
383
|
-
price_array: NDArray[np.floating]
|
|
384
|
-
"""Merging-firms' prices"""
|
|
385
|
-
|
|
386
|
-
hsr_filing_test: NDArray[np.bool_]
|
|
387
|
-
"""Flags draws as meeting HSR filing thresholds or not"""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
class MarginDataSample(NamedTuple):
|
|
391
|
-
"""Container for generated margin array and related MNL test array."""
|
|
392
|
-
|
|
393
|
-
pcm_array: NDArray[np.float64]
|
|
394
|
-
"""Merging-firms' PCMs"""
|
|
395
|
-
|
|
396
|
-
mnl_test_array: NDArray[np.bool_]
|
|
397
|
-
"""Flags infeasible observations as False and rest as True
|
|
398
|
-
|
|
399
|
-
Applying restrictions from Bertrand-Nash oligopoly
|
|
400
|
-
with MNL demand results in draws of Firm 2 PCM falling
|
|
401
|
-
outside the feabile interval,:math:`[0, 1]`, depending
|
|
402
|
-
on the configuration of merging firm shares. Such draws
|
|
403
|
-
are are flagged as infeasible (False)in :code:`mnl_test_array` while
|
|
404
|
-
draws with PCM values within the feasible range are
|
|
405
|
-
flagged as True. Used from filtering-out draws with
|
|
406
|
-
infeasible PCM.
|
|
407
|
-
"""
|
|
408
37
|
|
|
409
38
|
|
|
410
39
|
def gen_market_sample(
|
|
@@ -413,7 +42,7 @@ def gen_market_sample(
|
|
|
413
42
|
*,
|
|
414
43
|
seed_seq_list: list[SeedSequence] | None = None,
|
|
415
44
|
nthreads: int = 16,
|
|
416
|
-
) ->
|
|
45
|
+
) -> MarketDataSample:
|
|
417
46
|
"""
|
|
418
47
|
Generate share, diversion ratio, price, and margin data based on supplied parameters
|
|
419
48
|
|
|
@@ -425,8 +54,8 @@ def gen_market_sample(
|
|
|
425
54
|
for generating the relevant random variates:
|
|
426
55
|
1.) quantity shares
|
|
427
56
|
2.) price-cost margins
|
|
428
|
-
3.) firm-counts, from :code:`[2, 2 + len(
|
|
429
|
-
weighted by :code:`
|
|
57
|
+
3.) firm-counts, from :code:`[2, 2 + len(firm_counts_weights)]`,
|
|
58
|
+
weighted by :code:`firm_counts_weights`, where relevant
|
|
430
59
|
4.) prices, if :code:`pr_sym_spec == PRIConstants.ZERO`.
|
|
431
60
|
|
|
432
61
|
Parameters
|
|
@@ -448,8 +77,9 @@ def gen_market_sample(
|
|
|
448
77
|
|
|
449
78
|
_mkt_sample_spec = _mkt_sample_spec or MarketSampleSpec()
|
|
450
79
|
|
|
451
|
-
_recapture_spec
|
|
452
|
-
|
|
80
|
+
_recapture_spec = _mkt_sample_spec.share_spec.recapture_spec
|
|
81
|
+
_dist_type_mktshr = _mkt_sample_spec.share_spec.dist_type
|
|
82
|
+
_dist_firm2_pcm = _mkt_sample_spec.pcm_spec.firm2_pcm_constraint
|
|
453
83
|
_hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
|
|
454
84
|
|
|
455
85
|
(
|
|
@@ -476,21 +106,28 @@ def gen_market_sample(
|
|
|
476
106
|
_mkt_sample_spec_here, _fcount_rng_seed_seq, _mktshr_rng_seed_seq, nthreads
|
|
477
107
|
)
|
|
478
108
|
|
|
109
|
+
_mktshr_array, _fcounts, _aggregate_purchase_prob, _nth_firm_share = (
|
|
110
|
+
getattr(_mktshr_data, _f)
|
|
111
|
+
for _f in (
|
|
112
|
+
"mktshr_array",
|
|
113
|
+
"fcounts",
|
|
114
|
+
"aggregate_purchase_prob",
|
|
115
|
+
"nth_firm_share",
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
479
119
|
# Generate merging-firm price data
|
|
480
|
-
_price_data =
|
|
120
|
+
_price_data = _gen_pr_data(
|
|
481
121
|
_mktshr_data.mktshr_array[:, :2],
|
|
482
122
|
_mktshr_data.nth_firm_share,
|
|
483
123
|
_mkt_sample_spec_here,
|
|
484
124
|
_pr_rng_seed_seq,
|
|
485
125
|
)
|
|
486
126
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
_price_array = _price_data.price_array
|
|
492
|
-
_hsr_filing_test = _price_data.hsr_filing_test
|
|
493
|
-
del _mktshr_data, _price_data
|
|
127
|
+
_price_array, _hsr_filing_test = (
|
|
128
|
+
getattr(_price_data, _f) for _f in ("price_array", "hsr_filing_test")
|
|
129
|
+
)
|
|
130
|
+
|
|
494
131
|
if _hsr_filing_test_type != SSZConstants.ONE:
|
|
495
132
|
_mktshr_array = _mktshr_array[_hsr_filing_test]
|
|
496
133
|
_fcounts = _fcounts[_hsr_filing_test]
|
|
@@ -507,7 +144,7 @@ def gen_market_sample(
|
|
|
507
144
|
)
|
|
508
145
|
|
|
509
146
|
# Generate margin data
|
|
510
|
-
|
|
147
|
+
_pcm_data = _gen_pcm_data(
|
|
511
148
|
_mktshr_array[:, :2],
|
|
512
149
|
_mkt_sample_spec_here,
|
|
513
150
|
_price_array,
|
|
@@ -515,6 +152,9 @@ def gen_market_sample(
|
|
|
515
152
|
_pcm_rng_seed_seq,
|
|
516
153
|
nthreads,
|
|
517
154
|
)
|
|
155
|
+
_pcm_array, _mnl_test_rows = (
|
|
156
|
+
getattr(_pcm_data, _f) for _f in ("pcm_array", "mnl_test_array")
|
|
157
|
+
)
|
|
518
158
|
|
|
519
159
|
_s_size = _mkt_sample_spec.sample_size # originally-specified sample size
|
|
520
160
|
if _dist_firm2_pcm == FM2Constants.MNL:
|
|
@@ -535,7 +175,7 @@ def gen_market_sample(
|
|
|
535
175
|
_hhi_delta + np.einsum("ij,ij->i", _mktshr_array, _mktshr_array)[:, None]
|
|
536
176
|
)
|
|
537
177
|
|
|
538
|
-
return
|
|
178
|
+
return MarketDataSample(
|
|
539
179
|
_frmshr_array,
|
|
540
180
|
_pcm_array,
|
|
541
181
|
_price_array,
|
|
@@ -585,454 +225,6 @@ def parse_seed_seq_list(
|
|
|
585
225
|
)
|
|
586
226
|
|
|
587
227
|
|
|
588
|
-
def _gen_share_data(
|
|
589
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
590
|
-
_fcount_rng_seed_seq: SeedSequence | None,
|
|
591
|
-
_mktshr_rng_seed_seq: SeedSequence,
|
|
592
|
-
_nthreads: int = 16,
|
|
593
|
-
/,
|
|
594
|
-
) -> ShareDataSample:
|
|
595
|
-
"""Helper function for generating share data.
|
|
596
|
-
|
|
597
|
-
Parameters
|
|
598
|
-
----------
|
|
599
|
-
_mkt_sample_spec
|
|
600
|
-
Class specifying parameters for share-, price-, and margin-data generation
|
|
601
|
-
_fcount_rng_seed_seq
|
|
602
|
-
Seed sequence for assuring independent and, optionally, redundant streams
|
|
603
|
-
_mktshr_rng_seed_seq
|
|
604
|
-
Seed sequence for assuring independent and, optionally, redundant streams
|
|
605
|
-
_nthreads
|
|
606
|
-
Must be specified for generating repeatable random streams
|
|
607
|
-
|
|
608
|
-
Returns
|
|
609
|
-
-------
|
|
610
|
-
Arrays representing shares, diversion ratios, etc. structured as a :ShareDataSample:
|
|
611
|
-
|
|
612
|
-
"""
|
|
613
|
-
|
|
614
|
-
_recapture_spec, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts_raw = (
|
|
615
|
-
_mkt_sample_spec.share_spec
|
|
616
|
-
)
|
|
617
|
-
|
|
618
|
-
_ssz = _mkt_sample_spec.sample_size
|
|
619
|
-
|
|
620
|
-
_r_bar = _mkt_sample_spec.recapture_rate or 0.80
|
|
621
|
-
|
|
622
|
-
match _dist_type_mktshr:
|
|
623
|
-
case SHRConstants.UNI:
|
|
624
|
-
_mkt_share_sample = _gen_market_shares_uniform(
|
|
625
|
-
_ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
case _ if _dist_type_mktshr.name.startswith("DIR_"):
|
|
629
|
-
_firm_count_prob_wts = (
|
|
630
|
-
None
|
|
631
|
-
if _firm_count_prob_wts_raw is None
|
|
632
|
-
else np.array(_firm_count_prob_wts_raw, dtype=np.float64)
|
|
633
|
-
)
|
|
634
|
-
_mkt_share_sample = _gen_market_shares_dirichlet_multisample(
|
|
635
|
-
_ssz,
|
|
636
|
-
_recapture_spec,
|
|
637
|
-
_dist_type_mktshr,
|
|
638
|
-
_dist_parms_mktshr,
|
|
639
|
-
_firm_count_prob_wts,
|
|
640
|
-
_fcount_rng_seed_seq,
|
|
641
|
-
_mktshr_rng_seed_seq,
|
|
642
|
-
_nthreads,
|
|
643
|
-
)
|
|
644
|
-
|
|
645
|
-
case _:
|
|
646
|
-
raise ValueError(
|
|
647
|
-
f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
# If recapture_spec == "inside-out", recalculate _aggregate_purchase_prob
|
|
651
|
-
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
652
|
-
if _recapture_spec == RECConstants.INOUT:
|
|
653
|
-
_mkt_share_sample = ShareDataSample(
|
|
654
|
-
_mkt_share_sample.mktshr_array,
|
|
655
|
-
_mkt_share_sample.fcounts,
|
|
656
|
-
_mkt_share_sample.nth_firm_share,
|
|
657
|
-
_r_bar / (1 - (1 - _r_bar) * _frmshr_array.min(axis=1, keepdims=True)),
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
return _mkt_share_sample
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
def _gen_market_shares_uniform(
|
|
664
|
-
_s_size: int = 10**6,
|
|
665
|
-
_dist_parms_mktshr: NDArray[np.floating[TF]] | None = DIST_PARMS_DEFAULT,
|
|
666
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
667
|
-
_nthreads: int = 16,
|
|
668
|
-
/,
|
|
669
|
-
) -> ShareDataSample:
|
|
670
|
-
"""Generate merging-firm shares from Uniform distribution on the 3-D simplex.
|
|
671
|
-
|
|
672
|
-
Parameters
|
|
673
|
-
----------
|
|
674
|
-
_s_size
|
|
675
|
-
size of sample to be drawn
|
|
676
|
-
_r_bar
|
|
677
|
-
market recapture rate
|
|
678
|
-
_mktshr_rng_seed_seq
|
|
679
|
-
seed for rng, so results can be made replicable
|
|
680
|
-
_nthreads
|
|
681
|
-
number of threads for random number generation
|
|
682
|
-
|
|
683
|
-
Returns
|
|
684
|
-
-------
|
|
685
|
-
market shares and other market statistics for each draw (market)
|
|
686
|
-
|
|
687
|
-
"""
|
|
688
|
-
|
|
689
|
-
_frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
|
|
690
|
-
_dist_parms_mktshr = (
|
|
691
|
-
DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr # type: ignore
|
|
692
|
-
)
|
|
693
|
-
_mrng = MultithreadedRNG(
|
|
694
|
-
_frmshr_array,
|
|
695
|
-
dist_type="Uniform",
|
|
696
|
-
dist_parms=_dist_parms_mktshr,
|
|
697
|
-
seed_sequence=_mktshr_rng_seed_seq,
|
|
698
|
-
nthreads=_nthreads,
|
|
699
|
-
)
|
|
700
|
-
_mrng.fill()
|
|
701
|
-
# Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 < 1
|
|
702
|
-
_frmshr_array = np.sort(_frmshr_array, axis=1)
|
|
703
|
-
_frmshr_array = np.column_stack((
|
|
704
|
-
_frmshr_array[:, 0],
|
|
705
|
-
_frmshr_array[:, 1] - _frmshr_array[:, 0],
|
|
706
|
-
))
|
|
707
|
-
|
|
708
|
-
# Keep only share combinations representing feasible mergers
|
|
709
|
-
_frmshr_array = _frmshr_array[_frmshr_array.min(axis=1) > 0]
|
|
710
|
-
|
|
711
|
-
# Let a third column have values of "np.nan", so HHI calculations return "np.nan"
|
|
712
|
-
_mktshr_array = np.pad(
|
|
713
|
-
_frmshr_array, ((0, 0), (0, 1)), "constant", constant_values=np.nan
|
|
714
|
-
)
|
|
715
|
-
|
|
716
|
-
_fcounts: NDArray[np.int64] = np.ones((_s_size, 1), np.int64) * np.nan # type: ignore
|
|
717
|
-
_nth_firm_share, _aggregate_purchase_prob = (
|
|
718
|
-
np.nan * np.ones((_s_size, 1), np.float64) for _ in range(2)
|
|
719
|
-
)
|
|
720
|
-
|
|
721
|
-
return ShareDataSample(
|
|
722
|
-
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
723
|
-
)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
def _gen_market_shares_dirichlet_multisample(
|
|
727
|
-
_s_size: int = 10**6,
|
|
728
|
-
_recapture_spec: RECConstants = RECConstants.INOUT,
|
|
729
|
-
_dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
|
|
730
|
-
_dist_parms_dir: NDArray[np.floating[TF]] | None = None,
|
|
731
|
-
_firm_count_wts: NDArray[np.floating[TF]] | None = None, # type: ignore
|
|
732
|
-
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
733
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
734
|
-
_nthreads: int = 16,
|
|
735
|
-
/,
|
|
736
|
-
) -> ShareDataSample:
|
|
737
|
-
"""Dirichlet-distributed shares with multiple firm-counts.
|
|
738
|
-
|
|
739
|
-
Firm-counts may be specified as having Uniform distribution over the range
|
|
740
|
-
of firm counts, or a set of probability weights may be specified. In the
|
|
741
|
-
latter case the proportion of draws for each firm-count matches the
|
|
742
|
-
specified probability weight.
|
|
743
|
-
|
|
744
|
-
Parameters
|
|
745
|
-
----------
|
|
746
|
-
_s_size
|
|
747
|
-
sample size to be drawn
|
|
748
|
-
_r_bar
|
|
749
|
-
market recapture rate
|
|
750
|
-
_firm_count_wts
|
|
751
|
-
firm count weights array for sample to be drawn
|
|
752
|
-
_dist_type_dir
|
|
753
|
-
Whether Dirichlet is Flat or Asymmetric
|
|
754
|
-
_recapture_spec
|
|
755
|
-
r_1 = r_2 if "proportional", otherwise MNL-consistent
|
|
756
|
-
_fcount_rng_seed_seq
|
|
757
|
-
seed firm count rng, for replicable results
|
|
758
|
-
_mktshr_rng_seed_seq
|
|
759
|
-
seed market share rng, for replicable results
|
|
760
|
-
_nthreads
|
|
761
|
-
number of threads for parallelized random number generation
|
|
762
|
-
|
|
763
|
-
Returns
|
|
764
|
-
-------
|
|
765
|
-
array of market shares and other market statistics
|
|
766
|
-
|
|
767
|
-
"""
|
|
768
|
-
|
|
769
|
-
_firm_count_wts: np.float64 = (
|
|
770
|
-
FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
|
|
771
|
-
) # type: ignore
|
|
772
|
-
|
|
773
|
-
_min_choice_wt = 0.03 if _dist_type_dir == SHRConstants.DIR_FLAT_CONSTR else 0.00
|
|
774
|
-
_fcount_keys, _choice_wts = zip(
|
|
775
|
-
*(
|
|
776
|
-
_f
|
|
777
|
-
for _f in zip(
|
|
778
|
-
2 + np.arange(len(_firm_count_wts)), # type: ignore
|
|
779
|
-
_firm_count_wts / _firm_count_wts.sum(),
|
|
780
|
-
strict=True,
|
|
781
|
-
)
|
|
782
|
-
if _f[1] > _min_choice_wt
|
|
783
|
-
)
|
|
784
|
-
)
|
|
785
|
-
_choice_wts = _choice_wts / sum(_choice_wts)
|
|
786
|
-
|
|
787
|
-
_fc_max = _fcount_keys[-1]
|
|
788
|
-
_dir_alphas_full = (
|
|
789
|
-
[1.0] * _fc_max if _dist_parms_dir is None else _dist_parms_dir[:_fc_max]
|
|
790
|
-
)
|
|
791
|
-
if _dist_type_dir == SHRConstants.DIR_ASYM:
|
|
792
|
-
_dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, _fc_max)
|
|
793
|
-
|
|
794
|
-
if _dist_type_dir == SHRConstants.DIR_COND:
|
|
795
|
-
|
|
796
|
-
def _gen_dir_alphas(_fcv: int) -> NDArray[np.float64]:
|
|
797
|
-
_dat = [2.5] * 2
|
|
798
|
-
if _fcv > len(_dat):
|
|
799
|
-
_dat += [1.0 / (_fcv - 2)] * (_fcv - 2)
|
|
800
|
-
return np.array(_dat, dtype=np.float64)
|
|
801
|
-
|
|
802
|
-
else:
|
|
803
|
-
|
|
804
|
-
def _gen_dir_alphas(_fcv: int) -> NDArray[np.float64]:
|
|
805
|
-
return np.array(_dir_alphas_full[:_fcv], dtype=np.float64)
|
|
806
|
-
|
|
807
|
-
_fcounts = prng(_fcount_rng_seed_seq).choice(
|
|
808
|
-
_fcount_keys, size=(_s_size, 1), p=_choice_wts
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
_mktshr_seed_seq_ch = (
|
|
812
|
-
_mktshr_rng_seed_seq.spawn(len(_fcount_keys))
|
|
813
|
-
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
814
|
-
else SeedSequence(pool_size=8).spawn(len(_fcounts))
|
|
815
|
-
)
|
|
816
|
-
|
|
817
|
-
_aggregate_purchase_prob, _nth_firm_share = (
|
|
818
|
-
np.empty((_s_size, 1)) for _ in range(2)
|
|
819
|
-
)
|
|
820
|
-
_mktshr_array = np.empty((_s_size, _fc_max), dtype=np.float64)
|
|
821
|
-
for _f_val, _f_sseq in zip(_fcount_keys, _mktshr_seed_seq_ch, strict=True):
|
|
822
|
-
_fcounts_match_rows = np.where(_fcounts == _f_val)[0]
|
|
823
|
-
_dir_alphas_test = _gen_dir_alphas(_f_val)
|
|
824
|
-
|
|
825
|
-
try:
|
|
826
|
-
_mktshr_sample_f = _gen_market_shares_dirichlet(
|
|
827
|
-
_dir_alphas_test,
|
|
828
|
-
len(_fcounts_match_rows),
|
|
829
|
-
_recapture_spec,
|
|
830
|
-
_f_sseq,
|
|
831
|
-
_nthreads,
|
|
832
|
-
)
|
|
833
|
-
except ValueError as _err:
|
|
834
|
-
print(_f_val, len(_fcounts_match_rows))
|
|
835
|
-
raise _err
|
|
836
|
-
|
|
837
|
-
# Push data for present sample to parent
|
|
838
|
-
_mktshr_array[_fcounts_match_rows] = np.pad(
|
|
839
|
-
_mktshr_sample_f.mktshr_array,
|
|
840
|
-
((0, 0), (0, _fc_max - _mktshr_sample_f.mktshr_array.shape[1])),
|
|
841
|
-
"constant",
|
|
842
|
-
)
|
|
843
|
-
_aggregate_purchase_prob[_fcounts_match_rows] = (
|
|
844
|
-
_mktshr_sample_f.aggregate_purchase_prob
|
|
845
|
-
)
|
|
846
|
-
_nth_firm_share[_fcounts_match_rows] = _mktshr_sample_f.nth_firm_share
|
|
847
|
-
|
|
848
|
-
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
849
|
-
_mktshr_array
|
|
850
|
-
):
|
|
851
|
-
raise ValueError(
|
|
852
|
-
"DATA GENERATION ERROR: {} {} {}".format(
|
|
853
|
-
"Generation of sample shares is inconsistent:",
|
|
854
|
-
"array of drawn shares must some to the number of draws",
|
|
855
|
-
"i.e., the sample size, which condition is not met.",
|
|
856
|
-
)
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
return ShareDataSample(
|
|
860
|
-
_mktshr_array, _fcounts, _nth_firm_share, _aggregate_purchase_prob
|
|
861
|
-
)
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
def _gen_market_shares_dirichlet(
|
|
865
|
-
_dir_alphas: NDArray[np.floating[TF]],
|
|
866
|
-
_s_size: int = 10**6,
|
|
867
|
-
_recapture_spec: RECConstants = RECConstants.INOUT,
|
|
868
|
-
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
869
|
-
_nthreads: int = 16,
|
|
870
|
-
/,
|
|
871
|
-
) -> ShareDataSample:
|
|
872
|
-
"""Dirichlet-distributed shares with fixed firm-count.
|
|
873
|
-
|
|
874
|
-
Parameters
|
|
875
|
-
----------
|
|
876
|
-
_dir_alphas
|
|
877
|
-
Shape parameters for Dirichlet distribution
|
|
878
|
-
_s_size
|
|
879
|
-
sample size to be drawn
|
|
880
|
-
_r_bar
|
|
881
|
-
market recapture rate
|
|
882
|
-
_recapture_spec
|
|
883
|
-
r_1 = r_2 if RECConstants.FIXED, otherwise MNL-consistent. If
|
|
884
|
-
RECConstants.OUTIN; the number of columns in the output share array
|
|
885
|
-
is len(_dir_alphas) - 1.
|
|
886
|
-
_mktshr_rng_seed_seq
|
|
887
|
-
seed market share rng, for replicable results
|
|
888
|
-
_nthreads
|
|
889
|
-
number of threads for parallelized random number generation
|
|
890
|
-
|
|
891
|
-
Returns
|
|
892
|
-
-------
|
|
893
|
-
array of market shares and other market statistics
|
|
894
|
-
|
|
895
|
-
"""
|
|
896
|
-
|
|
897
|
-
if not isinstance(_dir_alphas, np.ndarray):
|
|
898
|
-
_dir_alphas = np.array(_dir_alphas)
|
|
899
|
-
|
|
900
|
-
if _recapture_spec == RECConstants.OUTIN:
|
|
901
|
-
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
902
|
-
|
|
903
|
-
_mktshr_seed_seq_ch = (
|
|
904
|
-
_mktshr_rng_seed_seq
|
|
905
|
-
if isinstance(_mktshr_rng_seed_seq, SeedSequence)
|
|
906
|
-
else SeedSequence(pool_size=8)
|
|
907
|
-
)
|
|
908
|
-
|
|
909
|
-
_mktshr_array = np.empty((_s_size, len(_dir_alphas)))
|
|
910
|
-
_mrng = MultithreadedRNG(
|
|
911
|
-
_mktshr_array,
|
|
912
|
-
dist_type="Dirichlet",
|
|
913
|
-
dist_parms=_dir_alphas,
|
|
914
|
-
seed_sequence=_mktshr_seed_seq_ch,
|
|
915
|
-
nthreads=_nthreads,
|
|
916
|
-
)
|
|
917
|
-
_mrng.fill()
|
|
918
|
-
|
|
919
|
-
if (_iss := np.round(np.einsum("ij->", _mktshr_array))) != _s_size or _iss != len(
|
|
920
|
-
_mktshr_array
|
|
921
|
-
):
|
|
922
|
-
print(_dir_alphas, _iss, repr(_s_size), len(_mktshr_array))
|
|
923
|
-
print(repr(_mktshr_array[-10:, :]))
|
|
924
|
-
raise ValueError(
|
|
925
|
-
"DATA GENERATION ERROR: {} {} {}".format(
|
|
926
|
-
"Generation of sample shares is inconsistent:",
|
|
927
|
-
"array of drawn shares must sum to the number of draws",
|
|
928
|
-
"i.e., the sample size, which condition is not met.",
|
|
929
|
-
)
|
|
930
|
-
)
|
|
931
|
-
|
|
932
|
-
# If recapture_spec == 'inside_out', further calculations downstream
|
|
933
|
-
_aggregate_purchase_prob = np.nan * np.empty((_s_size, 1))
|
|
934
|
-
if _recapture_spec == RECConstants.OUTIN:
|
|
935
|
-
_aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
|
|
936
|
-
_mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
|
|
937
|
-
|
|
938
|
-
return ShareDataSample(
|
|
939
|
-
_mktshr_array,
|
|
940
|
-
(_mktshr_array.shape[-1] * np.ones((_s_size, 1))).astype(np.int64),
|
|
941
|
-
_mktshr_array[:, [-1]],
|
|
942
|
-
_aggregate_purchase_prob,
|
|
943
|
-
)
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
def _gen_pr_ratio(
|
|
947
|
-
_frmshr_array: NDArray[np.floating[TF]],
|
|
948
|
-
_nth_firm_share: NDArray[np.floating[TF]],
|
|
949
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
950
|
-
_seed_seq: SeedSequence | None = None,
|
|
951
|
-
/,
|
|
952
|
-
) -> PriceDataSample:
|
|
953
|
-
_ssz = len(_frmshr_array)
|
|
954
|
-
|
|
955
|
-
_hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
|
|
956
|
-
|
|
957
|
-
_price_array, _price_ratio_array, _hsr_filing_test = (
|
|
958
|
-
np.ones_like(_frmshr_array),
|
|
959
|
-
np.empty_like(_frmshr_array),
|
|
960
|
-
np.empty(_ssz, dtype=bool),
|
|
961
|
-
)
|
|
962
|
-
|
|
963
|
-
_pr_max_ratio = 5.0
|
|
964
|
-
match _mkt_sample_spec.pr_sym_spec:
|
|
965
|
-
case PRIConstants.SYM:
|
|
966
|
-
_nth_firm_price = np.ones((_ssz, 1))
|
|
967
|
-
case PRIConstants.POS:
|
|
968
|
-
_price_array, _nth_firm_price = (
|
|
969
|
-
np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
970
|
-
)
|
|
971
|
-
case PRIConstants.NEG:
|
|
972
|
-
_price_array, _nth_firm_price = (
|
|
973
|
-
np.ceil((1 - _p) * _pr_max_ratio)
|
|
974
|
-
for _p in (_frmshr_array, _nth_firm_share)
|
|
975
|
-
)
|
|
976
|
-
case PRIConstants.ZERO:
|
|
977
|
-
_price_array_gen = prng(_seed_seq).choice(
|
|
978
|
-
1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
979
|
-
)
|
|
980
|
-
_price_array = _price_array_gen[:, :2]
|
|
981
|
-
_nth_firm_price = _price_array_gen[:, [2]]
|
|
982
|
-
# del _price_array_gen
|
|
983
|
-
case _:
|
|
984
|
-
raise ValueError(
|
|
985
|
-
f"Condition regarding price symmetry"
|
|
986
|
-
f' "{_mkt_sample_spec.pr_sym_spec.value}" is invalid.'
|
|
987
|
-
)
|
|
988
|
-
# del _pr_max_ratio
|
|
989
|
-
|
|
990
|
-
_price_ratio_array = _price_array / _price_array[:, ::-1]
|
|
991
|
-
_rev_array = _price_array * _frmshr_array
|
|
992
|
-
_nth_firm_rev = _nth_firm_price * _nth_firm_share
|
|
993
|
-
|
|
994
|
-
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
995
|
-
# the ratio has not changed since inception of the HSR filing test,
|
|
996
|
-
# so we treat it as a constant of merger policy.
|
|
997
|
-
_test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
|
|
998
|
-
|
|
999
|
-
match _hsr_filing_test_type:
|
|
1000
|
-
case SSZConstants.HSR_TEN:
|
|
1001
|
-
# See, https://www.ftc.gov/enforcement/premerger-notification-program/
|
|
1002
|
-
# -> Procedures For Submitting Post-Consummation Filings
|
|
1003
|
-
# -> Key Elements to Determine Whether a Post Consummation Filing is Required
|
|
1004
|
-
# under heading, "Historical Thresholds"
|
|
1005
|
-
# Revenue ratio has been 10-to-1 since inception
|
|
1006
|
-
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
1007
|
-
# ratio restriction on the merging firms' revenues
|
|
1008
|
-
_rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
|
|
1009
|
-
_hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
|
|
1010
|
-
# del _rev_array, _rev_ratio
|
|
1011
|
-
case SSZConstants.HSR_NTH:
|
|
1012
|
-
# To get around the 10-to-1 ratio restriction, specify that the nth firm
|
|
1013
|
-
# matches the smaller firm in the size test; then if the smaller merging firm
|
|
1014
|
-
# matches the n-th firm in size, and the larger merging firm has at least
|
|
1015
|
-
# 10 times the size of the nth firm, the size test is considered met.
|
|
1016
|
-
# Alternatively, if the smaller merging firm has 10% or greater share,
|
|
1017
|
-
# the value of transaction test is considered met.
|
|
1018
|
-
_rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
|
|
1019
|
-
_hsr_filing_test = (
|
|
1020
|
-
np.einsum(
|
|
1021
|
-
"ij->i",
|
|
1022
|
-
1 * (_rev_ratio_to_nth > [1, _test_rev_ratio]),
|
|
1023
|
-
dtype=np.int64,
|
|
1024
|
-
)
|
|
1025
|
-
== _rev_ratio_to_nth.shape[1]
|
|
1026
|
-
) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
|
|
1027
|
-
|
|
1028
|
-
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
1029
|
-
case _:
|
|
1030
|
-
# Otherwise, all draws meet the filing test
|
|
1031
|
-
_hsr_filing_test = np.ones(_ssz, dtype=bool)
|
|
1032
|
-
|
|
1033
|
-
return PriceDataSample(_price_array, _hsr_filing_test)
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
228
|
def gen_divr_array(
|
|
1037
229
|
_frmshr_array: NDArray[np.floating[TF]],
|
|
1038
230
|
_r_bar: float,
|
|
@@ -1091,139 +283,3 @@ def gen_divr_array(
|
|
|
1091
283
|
)
|
|
1092
284
|
|
|
1093
285
|
return _divr_array
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
def _gen_pcm_data(
|
|
1097
|
-
_frmshr_array: NDArray[np.floating[TF]],
|
|
1098
|
-
_mkt_sample_spec: MarketSampleSpec,
|
|
1099
|
-
_price_array: NDArray[np.floating[TF]],
|
|
1100
|
-
_aggregate_purchase_prob: NDArray[np.floating[TF]],
|
|
1101
|
-
_pcm_rng_seed_seq: SeedSequence,
|
|
1102
|
-
_nthreads: int = 16,
|
|
1103
|
-
/,
|
|
1104
|
-
) -> MarginDataSample:
|
|
1105
|
-
_recapture_spec, _, _, _ = _mkt_sample_spec.share_spec
|
|
1106
|
-
_dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = _mkt_sample_spec.pcm_spec
|
|
1107
|
-
_dist_type: Literal["Beta", "Uniform"] = (
|
|
1108
|
-
"Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
|
|
1109
|
-
)
|
|
1110
|
-
|
|
1111
|
-
_pcm_array = np.empty((len(_frmshr_array), 2), dtype=np.float64)
|
|
1112
|
-
_mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
|
|
1113
|
-
|
|
1114
|
-
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
1115
|
-
_dist_parms = np.ones(2, np.float64)
|
|
1116
|
-
if _dist_type_pcm == PCMConstants.EMPR:
|
|
1117
|
-
_pcm_array = resample_mgn_data(
|
|
1118
|
-
_pcm_array.shape, # type: ignore
|
|
1119
|
-
seed_sequence=_pcm_rng_seed_seq,
|
|
1120
|
-
)
|
|
1121
|
-
else:
|
|
1122
|
-
if _dist_type_pcm == PCMConstants.UNI:
|
|
1123
|
-
_dist_parms = (
|
|
1124
|
-
DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
|
|
1125
|
-
)
|
|
1126
|
-
elif _dist_type_pcm == PCMConstants.BETA:
|
|
1127
|
-
# Error-checking (could move to validators in definition of MarketSampleSpec)
|
|
1128
|
-
|
|
1129
|
-
if _dist_parms_pcm is None:
|
|
1130
|
-
_dist_parms_pcm = _dist_parms
|
|
1131
|
-
|
|
1132
|
-
elif _dist_type_pcm == PCMConstants.BETA_BND: # Bounded beta
|
|
1133
|
-
if _dist_parms_pcm is None:
|
|
1134
|
-
_dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
|
|
1135
|
-
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
1136
|
-
|
|
1137
|
-
_pcm_rng = MultithreadedRNG(
|
|
1138
|
-
_pcm_array,
|
|
1139
|
-
dist_type=_dist_type,
|
|
1140
|
-
dist_parms=_dist_parms,
|
|
1141
|
-
seed_sequence=_pcm_rng_seed_seq,
|
|
1142
|
-
nthreads=_nthreads,
|
|
1143
|
-
)
|
|
1144
|
-
_pcm_rng.fill()
|
|
1145
|
-
del _pcm_rng
|
|
1146
|
-
|
|
1147
|
-
if _dist_type_pcm == PCMConstants.BETA_BND:
|
|
1148
|
-
_beta_min, _beta_max = _dist_parms_pcm[2:] # type: ignore
|
|
1149
|
-
_pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
|
|
1150
|
-
del _beta_min, _beta_max
|
|
1151
|
-
|
|
1152
|
-
if _dist_firm2_pcm == FM2Constants.MNL:
|
|
1153
|
-
# Impose FOCs from profit-maximization with MNL demand
|
|
1154
|
-
_purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
1155
|
-
|
|
1156
|
-
_pcm_array[:, [1]] = np.divide(
|
|
1157
|
-
np.einsum(
|
|
1158
|
-
"ij,ij,ij->ij",
|
|
1159
|
-
_price_array[:, [0]],
|
|
1160
|
-
_pcm_array[:, [0]],
|
|
1161
|
-
1 - _purchprob_array[:, [0]],
|
|
1162
|
-
),
|
|
1163
|
-
np.einsum("ij,ij->ij", _price_array[:, [1]], 1 - _purchprob_array[:, [1]]),
|
|
1164
|
-
)
|
|
1165
|
-
|
|
1166
|
-
_mnl_test_array = _pcm_array[:, 1].__ge__(0) & _pcm_array[:, 1].__le__(1)
|
|
1167
|
-
else:
|
|
1168
|
-
_mnl_test_array = np.ones(len(_pcm_array), dtype=bool)
|
|
1169
|
-
if _dist_firm2_pcm == FM2Constants.SYM:
|
|
1170
|
-
_pcm_array[:, [1]] = _pcm_array[:, [0]]
|
|
1171
|
-
|
|
1172
|
-
return MarginDataSample(_pcm_array, _mnl_test_array)
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
def _beta_located(
|
|
1176
|
-
_mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
|
|
1177
|
-
) -> NDArray[np.float64]:
|
|
1178
|
-
"""
|
|
1179
|
-
Given mean and stddev, return shape parameters for corresponding Beta distribution
|
|
1180
|
-
|
|
1181
|
-
Solve the first two moments of the standard Beta to get the shape parameters.
|
|
1182
|
-
|
|
1183
|
-
Parameters
|
|
1184
|
-
----------
|
|
1185
|
-
_mu
|
|
1186
|
-
mean
|
|
1187
|
-
_sigma
|
|
1188
|
-
standardd deviation
|
|
1189
|
-
|
|
1190
|
-
Returns
|
|
1191
|
-
-------
|
|
1192
|
-
shape parameters for Beta distribution
|
|
1193
|
-
|
|
1194
|
-
"""
|
|
1195
|
-
|
|
1196
|
-
_mul = -1 + _mu * (1 - _mu) / _sigma**2
|
|
1197
|
-
return np.array([_mu * _mul, (1 - _mu) * _mul], dtype=np.float64)
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
def beta_located_bound(_dist_parms: NDArray[np.floating[TF]], /) -> NDArray[np.float64]:
|
|
1201
|
-
R"""
|
|
1202
|
-
Return shape parameters for a non-standard beta, given the mean, stddev, range
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
Recover the r.v.s as
|
|
1206
|
-
:math:`\min + (\max - \min) \cdot \symup{Β}(a, b)`,
|
|
1207
|
-
with `a` and `b` calculated from the specified mean (:math:`\mu`) and
|
|
1208
|
-
variance (:math:`\sigma`). [8]_
|
|
1209
|
-
|
|
1210
|
-
Parameters
|
|
1211
|
-
----------
|
|
1212
|
-
_dist_parms
|
|
1213
|
-
vector of :math:`\mu`, :math:`\sigma`, :math:`\mathtt{\min}`, and :math:`\mathtt{\max}` values
|
|
1214
|
-
|
|
1215
|
-
Returns
|
|
1216
|
-
-------
|
|
1217
|
-
shape parameters for Beta distribution
|
|
1218
|
-
|
|
1219
|
-
Notes
|
|
1220
|
-
-----
|
|
1221
|
-
For example, ``beta_located_bound(np.array([0.5, 0.2887, 0.0, 1.0]))``.
|
|
1222
|
-
|
|
1223
|
-
References
|
|
1224
|
-
----------
|
|
1225
|
-
.. [8] NIST, Beta Distribution. https://www.itl.nist.gov/div898/handbook/eda/section3/eda366h.htm
|
|
1226
|
-
""" # noqa: RUF002
|
|
1227
|
-
|
|
1228
|
-
_bmu, _bsigma, _bmin, _bmax = _dist_parms
|
|
1229
|
-
return _beta_located((_bmu - _bmin) / (_bmax - _bmin), _bsigma / (_bmax - _bmin))
|