mergeron 2024.738953.1__py3-none-any.whl → 2025.739265.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mergeron might be problematic. Click here for more details.

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