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.

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