mergeron 2024.739099.2__py3-none-any.whl → 2024.739104.1__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.

@@ -4,13 +4,14 @@ Non-public functions called in data_generation.py
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ from collections.abc import Sequence
7
8
  from typing import Literal
8
9
 
9
10
  import numpy as np
10
11
  from attrs import evolve
11
12
  from numpy.random import SeedSequence
12
13
 
13
- from .. import VERSION, ArrayBIGINT, ArrayDouble, RECConstants # noqa: TID252
14
+ from .. import VERSION, ArrayBIGINT, ArrayDouble, RECTypes # noqa: TID252
14
15
  from ..core.damodaran_margin_data import mgn_data_resampler # noqa: TID252
15
16
  from ..core.pseudorandom_numbers import ( # noqa: TID252
16
17
  DIST_PARMS_DEFAULT,
@@ -18,23 +19,25 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
18
19
  prng,
19
20
  )
20
21
  from . import (
22
+ EMPTY_ARRAY_DEFAULT,
21
23
  FCOUNT_WTS_DEFAULT,
22
24
  FM2Constants,
23
25
  MarginDataSample,
24
- PCMConstants,
26
+ PCMDistributions,
25
27
  PCMSpec,
26
- PriceConstants,
27
28
  PriceDataSample,
29
+ PriceSpec,
30
+ SeedSequenceData,
28
31
  ShareDataSample,
29
32
  ShareSpec,
30
- SHRConstants,
33
+ SHRDistributions,
31
34
  SSZConstants,
32
35
  )
33
36
 
34
37
  __version__ = VERSION
35
38
 
36
39
 
37
- def _gen_share_data(
40
+ def gen_share_data(
38
41
  _sample_size: int,
39
42
  _share_spec: ShareSpec,
40
43
  _fcount_rng_seed_seq: SeedSequence | None,
@@ -68,8 +71,8 @@ def _gen_share_data(
68
71
 
69
72
  _ssz = _sample_size
70
73
 
71
- if _dist_type_mktshr == SHRConstants.UNI:
72
- _mkt_share_sample = _gen_market_shares_uniform(
74
+ if _dist_type_mktshr == SHRDistributions.UNI:
75
+ _mkt_share_sample = gen_market_shares_uniform(
73
76
  _ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
74
77
  )
75
78
 
@@ -79,7 +82,7 @@ def _gen_share_data(
79
82
  if _firm_count_prob_wts is None
80
83
  else np.array(_firm_count_prob_wts, dtype=np.float64)
81
84
  )
82
- _mkt_share_sample = _gen_market_shares_dirichlet_multisample(
85
+ _mkt_share_sample = gen_market_shares_dirichlet_multimarket(
83
86
  _ssz,
84
87
  _recapture_form,
85
88
  _dist_type_mktshr,
@@ -97,8 +100,8 @@ def _gen_share_data(
97
100
 
98
101
  # If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
99
102
  _frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
100
- _r_bar = _share_spec.recapture_rate or 0.85
101
- if _recapture_form == RECConstants.INOUT:
103
+ _r_bar = _share_spec.recapture_rate or 0.8
104
+ if _recapture_form == RECTypes.INOUT:
102
105
  _mkt_share_sample = ShareDataSample(
103
106
  _mkt_share_sample.mktshr_array,
104
107
  _mkt_share_sample.fcounts,
@@ -109,7 +112,7 @@ def _gen_share_data(
109
112
  return _mkt_share_sample
110
113
 
111
114
 
112
- def _gen_market_shares_uniform(
115
+ def gen_market_shares_uniform(
113
116
  _s_size: int = 10**6,
114
117
  _dist_parms_mktshr: ArrayDouble | None = DIST_PARMS_DEFAULT,
115
118
  _mktshr_rng_seed_seq: SeedSequence | None = None,
@@ -122,10 +125,10 @@ def _gen_market_shares_uniform(
122
125
  ----------
123
126
  _s_size
124
127
  size of sample to be drawn
125
- _r_bar
126
- market recapture rate
128
+
127
129
  _mktshr_rng_seed_seq
128
130
  seed for rng, so results can be made replicable
131
+
129
132
  _nthreads
130
133
  number of threads for random number generation
131
134
 
@@ -172,10 +175,10 @@ def _gen_market_shares_uniform(
172
175
  )
173
176
 
174
177
 
175
- def _gen_market_shares_dirichlet_multisample(
178
+ def gen_market_shares_dirichlet_multimarket(
176
179
  _s_size: int = 10**6,
177
- _recapture_form: RECConstants = RECConstants.INOUT,
178
- _dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
180
+ _recapture_form: RECTypes = RECTypes.INOUT,
181
+ _dist_type_dir: SHRDistributions = SHRDistributions.DIR_FLAT,
179
182
  _dist_parms_dir: ArrayDouble | None = None,
180
183
  _firm_count_wts: ArrayDouble | None = None,
181
184
  _fcount_rng_seed_seq: SeedSequence | None = None,
@@ -194,18 +197,22 @@ def _gen_market_shares_dirichlet_multisample(
194
197
  ----------
195
198
  _s_size
196
199
  sample size to be drawn
197
- _r_bar
198
- market recapture rate
200
+
199
201
  _firm_count_wts
200
202
  firm count weights array for sample to be drawn
203
+
201
204
  _dist_type_dir
202
205
  Whether Dirichlet is Flat or Asymmetric
206
+
203
207
  _recapture_form
204
208
  r_1 = r_2 if "proportional", otherwise MNL-consistent
209
+
205
210
  _fcount_rng_seed_seq
206
211
  seed firm count rng, for replicable results
212
+
207
213
  _mktshr_rng_seed_seq
208
214
  seed market share rng, for replicable results
215
+
209
216
  _nthreads
210
217
  number of threads for parallelized random number generation
211
218
 
@@ -219,7 +226,9 @@ def _gen_market_shares_dirichlet_multisample(
219
226
  FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
220
227
  )
221
228
 
222
- _min_choice_wt = 0.03 if _dist_type_dir == SHRConstants.DIR_FLAT_CONSTR else 0.00
229
+ _min_choice_wt = (
230
+ 0.03 if _dist_type_dir == SHRDistributions.DIR_FLAT_CONSTR else 0.00
231
+ )
223
232
  _fcount_keys, _choice_wts = zip(
224
233
  *(
225
234
  _f
@@ -237,10 +246,10 @@ def _gen_market_shares_dirichlet_multisample(
237
246
  _dir_alphas_full = (
238
247
  [1.0] * _fc_max if _dist_parms_dir is None else _dist_parms_dir[:_fc_max]
239
248
  )
240
- if _dist_type_dir == SHRConstants.DIR_ASYM:
249
+ if _dist_type_dir == SHRDistributions.DIR_ASYM:
241
250
  _dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, _fc_max)
242
251
 
243
- if _dist_type_dir == SHRConstants.DIR_COND:
252
+ if _dist_type_dir == SHRDistributions.DIR_COND:
244
253
 
245
254
  def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
246
255
  _dat = [2.5] * 2
@@ -272,7 +281,7 @@ def _gen_market_shares_dirichlet_multisample(
272
281
  _dir_alphas_test = _gen_dir_alphas(_f_val)
273
282
 
274
283
  try:
275
- _mktshr_sample_f = _gen_market_shares_dirichlet(
284
+ _mktshr_sample_f = gen_market_shares_dirichlet(
276
285
  _dir_alphas_test,
277
286
  len(_fcounts_match_rows),
278
287
  _recapture_form,
@@ -310,10 +319,10 @@ def _gen_market_shares_dirichlet_multisample(
310
319
  )
311
320
 
312
321
 
313
- def _gen_market_shares_dirichlet(
322
+ def gen_market_shares_dirichlet(
314
323
  _dir_alphas: ArrayDouble,
315
324
  _s_size: int = 10**6,
316
- _recapture_form: RECConstants = RECConstants.INOUT,
325
+ _recapture_form: RECTypes = RECTypes.INOUT,
317
326
  _mktshr_rng_seed_seq: SeedSequence | None = None,
318
327
  _nthreads: int = 16,
319
328
  /,
@@ -324,16 +333,18 @@ def _gen_market_shares_dirichlet(
324
333
  ----------
325
334
  _dir_alphas
326
335
  Shape parameters for Dirichlet distribution
336
+
327
337
  _s_size
328
338
  sample size to be drawn
329
- _r_bar
330
- market recapture rate
339
+
331
340
  _recapture_form
332
- r_1 = r_2 if RECConstants.FIXED, otherwise MNL-consistent. If
333
- RECConstants.OUTIN; the number of columns in the output share array
341
+ r_1 = r_2 if RECTypes.FIXED, otherwise MNL-consistent. If
342
+ RECTypes.OUTIN; the number of columns in the output share array
334
343
  is len(_dir_alphas) - 1.
344
+
335
345
  _mktshr_rng_seed_seq
336
346
  seed market share rng, for replicable results
347
+
337
348
  _nthreads
338
349
  number of threads for parallelized random number generation
339
350
 
@@ -346,7 +357,7 @@ def _gen_market_shares_dirichlet(
346
357
  if not isinstance(_dir_alphas, np.ndarray):
347
358
  _dir_alphas = np.array(_dir_alphas)
348
359
 
349
- if _recapture_form == RECConstants.OUTIN:
360
+ if _recapture_form == RECTypes.OUTIN:
350
361
  _dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
351
362
 
352
363
  _mktshr_seed_seq_ch = (
@@ -380,7 +391,7 @@ def _gen_market_shares_dirichlet(
380
391
 
381
392
  # If recapture_form == 'inside_out', further calculations downstream
382
393
  _aggregate_purchase_prob = np.nan * np.empty((_s_size, 1))
383
- if _recapture_form == RECConstants.OUTIN:
394
+ if _recapture_form == RECTypes.OUTIN:
384
395
  _aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
385
396
  _mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
386
397
 
@@ -392,18 +403,124 @@ def _gen_market_shares_dirichlet(
392
403
  )
393
404
 
394
405
 
395
- def _gen_margin_price_data(
406
+ def gen_divr_array(
407
+ _recapture_form: RECTypes,
408
+ _recapture_rate: float | None,
409
+ _frmshr_array: ArrayDouble,
410
+ _aggregate_purchase_prob: ArrayDouble = EMPTY_ARRAY_DEFAULT,
411
+ /,
412
+ ) -> ArrayDouble:
413
+ """
414
+ Given merging-firm shares and related parameters, return diverion ratios.
415
+
416
+ If recapture is specified as :attr:`mergeron.RECTypes.OUTIN`, then the
417
+ choice-probability for the outside good must be supplied.
418
+
419
+ Parameters
420
+ ----------
421
+ _recapture_form
422
+ Enum specifying Fixed (proportional), Inside-out, or Outside-in
423
+
424
+ _recapture_rate
425
+ If recapture is proportional or inside-out, the recapture rate
426
+ for the firm with the smaller share.
427
+
428
+ _frmshr_array
429
+ Merging-firm shares.
430
+
431
+ _aggregate_purchase_prob
432
+ 1 minus probability that the outside good is chosen; converts
433
+ market shares to choice probabilities by multiplication.
434
+
435
+ Raises
436
+ ------
437
+ ValueError
438
+ If the firm with the smaller share does not have the larger
439
+ diversion ratio between the merging firms.
440
+
441
+ Returns
442
+ -------
443
+ Merging-firm diversion ratios for mergers in the sample.
444
+
445
+ """
446
+
447
+ _divr_array: ArrayDouble
448
+ if _recapture_form == RECTypes.FIXED:
449
+ _divr_array = _recapture_rate * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
450
+
451
+ else:
452
+ _purchprob_array = _aggregate_purchase_prob * _frmshr_array
453
+ _divr_array = _purchprob_array[:, ::-1] / (1 - _purchprob_array)
454
+
455
+ _divr_assert_test = (
456
+ (np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
457
+ | (np.argmin(_frmshr_array, axis=1) == np.argmax(_divr_array, axis=1))
458
+ )[:, None]
459
+ if not all(_divr_assert_test):
460
+ raise ValueError(
461
+ "{} {} {} {}".format(
462
+ "Data construction fails tests:",
463
+ "the index of min(s_1, s_2) must equal",
464
+ "the index of max(d_12, d_21), for all draws.",
465
+ "unless frmshr_array sums to 1.00.",
466
+ )
467
+ )
468
+
469
+ return _divr_array
470
+
471
+
472
+ def gen_margin_price_data(
396
473
  _frmshr_array: ArrayDouble,
397
474
  _nth_firm_share: ArrayDouble,
398
475
  _aggregate_purchase_prob: ArrayDouble,
399
476
  _pcm_spec: PCMSpec,
400
- _price_spec: PriceConstants,
477
+ _price_spec: PriceSpec,
401
478
  _hsr_filing_test_type: SSZConstants,
402
479
  _pcm_rng_seed_seq: SeedSequence,
403
480
  _pr_rng_seed_seq: SeedSequence | None = None,
404
481
  _nthreads: int = 16,
405
482
  /,
406
483
  ) -> tuple[MarginDataSample, PriceDataSample]:
484
+ """Generate margin and price data for mergers in the sample.
485
+
486
+ Parameters
487
+ ----------
488
+ _frmshr_array
489
+ Merging-firm shares; see :class:`mergeron.gen.ShareSpec`.
490
+
491
+ _nth_firm_share
492
+ Share of the nth firm in the sample.
493
+
494
+ _aggregate_purchase_prob
495
+ 1 minus probability that the outside good is chosen; converts
496
+ market shares to choice probabilities by multiplication.
497
+
498
+ _pcm_spec
499
+ Enum specifying whether to use asymmetric or flat margins. see
500
+ :class:`mergeron.gen.PCMSpec`.
501
+
502
+ _price_spec
503
+ Enum specifying whether to use symmetric, positive, or negative
504
+ margins; see :class:`mergeron.gen.PriceSpec`.
505
+
506
+ _hsr_filing_test_type
507
+ Enum specifying restriction, if any, to impose on market data sample
508
+ to model HSR filing requirements; see :class:`mergeron.gen.SSZConstants`.
509
+
510
+ _pcm_rng_seed_seq
511
+ Seed sequence for generating margin data.
512
+
513
+ _pr_rng_seed_seq
514
+ Seed sequence for generating price data.
515
+
516
+ _nthreads
517
+ Number of threads to use in generating price data.
518
+
519
+ Returns
520
+ -------
521
+ Simulated margin- and price-data arrays for mergers in the sample.
522
+ """
523
+
407
524
  _price_array, _price_ratio_array = (
408
525
  np.ones_like(_frmshr_array, np.float64),
409
526
  np.empty_like(_frmshr_array, np.float64),
@@ -411,25 +528,25 @@ def _gen_margin_price_data(
411
528
 
412
529
  _pr_max_ratio = 5.0
413
530
  match _price_spec:
414
- case PriceConstants.SYM:
531
+ case PriceSpec.SYM:
415
532
  _nth_firm_price = np.ones((len(_frmshr_array), 1))
416
- case PriceConstants.POS:
533
+ case PriceSpec.POS:
417
534
  _price_array, _nth_firm_price = (
418
535
  np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
419
536
  )
420
- case PriceConstants.NEG:
537
+ case PriceSpec.NEG:
421
538
  _price_array, _nth_firm_price = (
422
539
  np.ceil((1 - _p) * _pr_max_ratio)
423
540
  for _p in (_frmshr_array, _nth_firm_share)
424
541
  )
425
- case PriceConstants.ZERO:
542
+ case PriceSpec.ZERO:
426
543
  _price_array_gen = prng(_pr_rng_seed_seq).choice(
427
544
  1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
428
545
  )
429
546
  _price_array = _price_array_gen[:, :2]
430
547
  _nth_firm_price = _price_array_gen[:, [2]]
431
548
  # del _price_array_gen
432
- case PriceConstants.CSY:
549
+ case PriceSpec.CSY:
433
550
  # TODO:
434
551
  # evolve FM2Constraint (save running MNL test twice); evolve copy of _mkt_sample_spec=1q
435
552
  # generate the margin data
@@ -479,7 +596,7 @@ def _gen_margin_price_data(
479
596
  f"Specitication of price distribution"
480
597
  f' "{_price_spec.value}" is invalid.'
481
598
  )
482
- if _price_spec != PriceConstants.CSY:
599
+ if _price_spec != PriceSpec.CSY:
483
600
  _margin_data = _gen_margin_data(
484
601
  _frmshr_array,
485
602
  _price_array,
@@ -535,94 +652,6 @@ def _gen_margin_price_data(
535
652
  return _margin_data, PriceDataSample(_price_array, _hsr_filing_test)
536
653
 
537
654
 
538
- # marked for deletion
539
- def _gen_price_data(
540
- _frmshr_array: ArrayDouble,
541
- _nth_firm_share: ArrayDouble,
542
- _price_spec: PriceConstants,
543
- _hsr_filing_test_type: SSZConstants,
544
- _seed_seq: SeedSequence | None = None,
545
- /,
546
- ) -> PriceDataSample:
547
- _price_array, _price_ratio_array, _hsr_filing_test = (
548
- np.ones_like(_frmshr_array, np.float64),
549
- np.empty_like(_frmshr_array, np.float64),
550
- np.empty(len(_frmshr_array), bool),
551
- )
552
-
553
- _pr_max_ratio = 5.0
554
- match _price_spec:
555
- case PriceConstants.SYM:
556
- _nth_firm_price = np.ones((len(_frmshr_array), 1))
557
- case PriceConstants.POS:
558
- _price_array, _nth_firm_price = (
559
- np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
560
- )
561
- case PriceConstants.NEG:
562
- _price_array, _nth_firm_price = (
563
- np.ceil((1 - _p) * _pr_max_ratio)
564
- for _p in (_frmshr_array, _nth_firm_share)
565
- )
566
- case PriceConstants.ZERO:
567
- _price_array_gen = prng(_seed_seq).choice(
568
- 1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
569
- )
570
- _price_array = _price_array_gen[:, :2]
571
- _nth_firm_price = _price_array_gen[:, [2]]
572
- # del _price_array_gen
573
- case PriceConstants.CSY:
574
- raise ValueError("Market-wide cost-symmetry is not yet implemented.")
575
- case _:
576
- raise ValueError(
577
- f"Specitication of price distribution"
578
- f' "{_price_spec.value}" is invalid.'
579
- )
580
-
581
- _price_array = _price_array.astype(np.float64)
582
- _rev_array = _price_array * _frmshr_array
583
- _nth_firm_rev = _nth_firm_price * _nth_firm_share
584
-
585
- # Although `_test_rev_ratio_inv` is not fixed at 10%,
586
- # the ratio has not changed since inception of the HSR filing test,
587
- # so we treat it as a constant of merger enforcement policy.
588
- _test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
589
-
590
- match _hsr_filing_test_type:
591
- case SSZConstants.HSR_TEN:
592
- # See, https://www.ftc.gov/enforcement/premerger-notification-program/
593
- # -> Procedures For Submitting Post-Consummation Filings
594
- # -> Key Elements to Determine Whether a Post Consummation Filing is Required
595
- # under heading, "Historical Thresholds"
596
- # Revenue ratio has been 10-to-1 since inception
597
- # Thus, a simple form of the HSR filing test would impose a 10-to-1
598
- # ratio restriction on the merging firms' revenues
599
- _rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
600
- _hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
601
- # del _rev_array, _rev_ratio
602
- case SSZConstants.HSR_NTH:
603
- # To get around the 10-to-1 ratio restriction, specify that the nth firm test:
604
- # if the smaller merging firm matches or exceeds the n-th firm in size, and
605
- # the larger merging firm has at least 10 times the size of the nth firm,
606
- # the size test is considered met.
607
- # Alternatively, if the smaller merging firm has 10% or greater share,
608
- # the value of transaction test is considered met.
609
- _rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
610
- _hsr_filing_test = (
611
- np.einsum(
612
- "ij->i",
613
- 1 * (_rev_ratio_to_nth > [1, _test_rev_ratio]),
614
- dtype=np.int64,
615
- )
616
- == _rev_ratio_to_nth.shape[1]
617
- ) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
618
-
619
- case _:
620
- # Otherwise, all draws meet the filing test
621
- _hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
622
-
623
- return PriceDataSample(_price_array, _hsr_filing_test)
624
-
625
-
626
655
  def _gen_margin_data(
627
656
  _frmshr_array: ArrayDouble,
628
657
  _price_array: ArrayDouble,
@@ -645,23 +674,23 @@ def _gen_margin_data(
645
674
  )
646
675
 
647
676
  _beta_min, _beta_max = [None] * 2 # placeholder
648
- if _dist_type_pcm == PCMConstants.EMPR:
677
+ if _dist_type_pcm == PCMDistributions.EMPR:
649
678
  _pcm_array = mgn_data_resampler(
650
679
  _pcm_array.shape, # type: ignore
651
680
  seed_sequence=_pcm_rng_seed_seq,
652
681
  )
653
682
  else:
654
- _dist_type = "Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
655
- if _dist_type_pcm == PCMConstants.BETA:
683
+ _dist_type = "Uniform" if _dist_type_pcm == PCMDistributions.UNI else "Beta"
684
+ if _dist_type_pcm == PCMDistributions.BETA:
656
685
  if _dist_parms_pcm is None:
657
686
  _dist_parms_pcm = np.ones(2, np.float64)
658
687
 
659
- elif _dist_type_pcm == PCMConstants.BETA_BND: # Bounded beta
688
+ elif _dist_type_pcm == PCMDistributions.BETA_BND: # Bounded beta
660
689
  if _dist_parms_pcm is None:
661
690
  _dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
662
691
  _dist_parms = beta_located_bound(_dist_parms_pcm)
663
692
  else:
664
- # _dist_type_pcm == PCMConstants.UNI
693
+ # _dist_type_pcm == PCMDistributions.UNI
665
694
  _dist_parms = (
666
695
  DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
667
696
  )
@@ -676,7 +705,7 @@ def _gen_margin_data(
676
705
  _pcm_rng.fill()
677
706
  del _pcm_rng
678
707
 
679
- if _dist_type_pcm == PCMConstants.BETA_BND:
708
+ if _dist_type_pcm == PCMDistributions.BETA_BND:
680
709
  _beta_min, _beta_max = _dist_parms_pcm[2:]
681
710
  _pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
682
711
  del _beta_min, _beta_max
@@ -685,7 +714,7 @@ def _gen_margin_data(
685
714
  _pcm_array = np.column_stack((_pcm_array,) * _frmshr_array.shape[1])
686
715
  if _dist_firm2_pcm == FM2Constants.MNL:
687
716
  # Impose FOCs from profit-maximization with MNL demand
688
- if _dist_type_pcm == PCMConstants.EMPR:
717
+ if _dist_type_pcm == PCMDistributions.EMPR:
689
718
  print(
690
719
  "NOTE: Estimated Firm 2 parameters will not be consistent with "
691
720
  "the empirical distribution of margins in the source data. For "
@@ -767,3 +796,65 @@ def beta_located_bound(_dist_parms: ArrayDouble, /) -> ArrayDouble:
767
796
 
768
797
  _bmu, _bsigma, _bmin, _bmax = _dist_parms
769
798
  return _beta_located((_bmu - _bmin) / (_bmax - _bmin), _bsigma / (_bmax - _bmin))
799
+
800
+
801
+ def parse_seed_seq_list(
802
+ _sseq_list: Sequence[SeedSequence] | None,
803
+ _mktshr_dist_type: SHRDistributions,
804
+ _price_spec: PriceSpec,
805
+ /,
806
+ ) -> SeedSequenceData:
807
+ """Initialize RNG seed sequences to ensure independence of distinct random streams.
808
+
809
+ The tuple of SeedSequences, is parsed in the following order
810
+ for generating the relevant random variates:
811
+ 1.) quantity shares
812
+ 2.) price-cost margins
813
+ 3.) firm-counts, if :code:`MarketSpec.share_spec.dist_type` is a Dirichlet distribution
814
+ 4.) prices, if :code:`MarketSpec.price_spec ==`:attr:`mergeron.gen.PriceSpec.ZERO`.
815
+
816
+
817
+
818
+ Parameters
819
+ ----------
820
+ _sseq_list
821
+ List of RNG seed sequences
822
+
823
+ _mktshr_dist_type
824
+ Market share distribution type
825
+
826
+ _price_spec
827
+ Price specification
828
+
829
+ Returns
830
+ -------
831
+ Seed sequence data
832
+
833
+ """
834
+ _fcount_rng_seed_seq: SeedSequence | None = None
835
+ _pr_rng_seed_seq: SeedSequence | None = None
836
+
837
+ if _price_spec == PriceSpec.ZERO:
838
+ _sseq_list, _pr_rng_seed_seq = (
839
+ (_sseq_list[:-1], _sseq_list[-1])
840
+ if _sseq_list
841
+ else (None, SeedSequence(pool_size=8))
842
+ )
843
+
844
+ _seed_count = 2 if _mktshr_dist_type == SHRDistributions.UNI else 3
845
+
846
+ if _sseq_list:
847
+ if len(_sseq_list) < _seed_count:
848
+ raise ValueError(
849
+ f"seed sequence list must contain {_seed_count} seed sequences"
850
+ )
851
+ else:
852
+ _sseq_list = tuple(SeedSequence(pool_size=8) for _ in range(_seed_count))
853
+
854
+ (_mktshr_rng_seed_seq, _pcm_rng_seed_seq, _fcount_rng_seed_seq) = (
855
+ _sseq_list if _seed_count == 3 else (*_sseq_list, None) # type: ignore
856
+ )
857
+
858
+ return SeedSequenceData(
859
+ _mktshr_rng_seed_seq, _pcm_rng_seed_seq, _fcount_rng_seed_seq, _pr_rng_seed_seq
860
+ )
@@ -21,7 +21,6 @@ from scipy.stats import beta, norm # type: ignore
21
21
  from .. import ( # noqa: TID252
22
22
  _PKG_NAME,
23
23
  DATA_DIR,
24
- TI,
25
24
  VERSION,
26
25
  ArrayBIGINT,
27
26
  ArrayDouble,
@@ -165,32 +164,6 @@ hhi_delta_ranger, hhi_zone_post_ranger = (
165
164
  for _f in (HHI_DELTA_KNOTS, HHI_POST_ZONE_KNOTS)
166
165
  )
167
166
 
168
- HMG_PRESUMPTION_ZONE_DICT = {
169
- HHI_POST_ZONE_KNOTS[0]: {
170
- HHI_DELTA_KNOTS[0]: (0, 0, 0),
171
- HHI_DELTA_KNOTS[1]: (0, 0, 0),
172
- HHI_DELTA_KNOTS[2]: (0, 0, 0),
173
- },
174
- HHI_POST_ZONE_KNOTS[1]: {
175
- HHI_DELTA_KNOTS[0]: (0, 1, 1),
176
- HHI_DELTA_KNOTS[1]: (1, 1, 2),
177
- HHI_DELTA_KNOTS[2]: (1, 1, 2),
178
- },
179
- HHI_POST_ZONE_KNOTS[2]: {
180
- HHI_DELTA_KNOTS[0]: (0, 2, 1),
181
- HHI_DELTA_KNOTS[1]: (1, 2, 3),
182
- HHI_DELTA_KNOTS[2]: (2, 2, 4),
183
- },
184
- }
185
-
186
- ZONE_VALS = np.unique(
187
- np.vstack([
188
- tuple(HMG_PRESUMPTION_ZONE_DICT[_k].values())
189
- for _k in HMG_PRESUMPTION_ZONE_DICT
190
- ]),
191
- axis=0,
192
- )
193
-
194
167
  ZONE_STRINGS = {
195
168
  0: R"Green Zone (Safeharbor)",
196
169
  1: R"Yellow Zone",
@@ -234,6 +207,31 @@ ZONE_DETAIL_STRINGS_DELTA_LATEX = {
234
207
  4: Rf"\Delta HHI \geqslant \text{{{HHI_DELTA_KNOTS[2]} pts.}}",
235
208
  }
236
209
 
210
+ HMG_PRESUMPTION_ZONE_MAP = {
211
+ HHI_POST_ZONE_KNOTS[0]: {
212
+ HHI_DELTA_KNOTS[0]: (0, 0, 0),
213
+ HHI_DELTA_KNOTS[1]: (0, 0, 0),
214
+ HHI_DELTA_KNOTS[2]: (0, 0, 0),
215
+ },
216
+ HHI_POST_ZONE_KNOTS[1]: {
217
+ HHI_DELTA_KNOTS[0]: (0, 1, 1),
218
+ HHI_DELTA_KNOTS[1]: (1, 1, 2),
219
+ HHI_DELTA_KNOTS[2]: (1, 1, 2),
220
+ },
221
+ HHI_POST_ZONE_KNOTS[2]: {
222
+ HHI_DELTA_KNOTS[0]: (0, 2, 1),
223
+ HHI_DELTA_KNOTS[1]: (1, 2, 3),
224
+ HHI_DELTA_KNOTS[2]: (2, 2, 4),
225
+ },
226
+ }
227
+
228
+ ZONE_VALS = np.unique(
229
+ np.vstack([
230
+ tuple(HMG_PRESUMPTION_ZONE_MAP[_k].values()) for _k in HMG_PRESUMPTION_ZONE_MAP
231
+ ]),
232
+ axis=0,
233
+ )
234
+
237
235
 
238
236
  def enf_stats_output(
239
237
  _data_array_dict: fid.INVData,
@@ -475,7 +473,7 @@ def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
475
473
  else (_hhi_delta_ranged == _hhi_zone_delta_lim)
476
474
  )
477
475
 
478
- _zone_val = HMG_PRESUMPTION_ZONE_DICT[_hhi_zone_post_lim][
476
+ _zone_val = HMG_PRESUMPTION_ZONE_MAP[_hhi_zone_post_lim][
479
477
  _hhi_zone_delta_lim
480
478
  ]
481
479
 
@@ -723,8 +721,8 @@ def stats_print_rows(
723
721
 
724
722
 
725
723
  def propn_ci(
726
- _npos: ArrayINT[TI] | int = 4,
727
- _nobs: ArrayINT[TI] | int = 10,
724
+ _npos: ArrayINT | int = 4,
725
+ _nobs: ArrayINT | int = 10,
728
726
  /,
729
727
  *,
730
728
  alpha: float = 0.05,