mergeron 2024.738963.0__tar.gz → 2024.738972.0__tar.gz

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 (31) hide show
  1. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/PKG-INFO +1 -1
  2. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/pyproject.toml +1 -1
  3. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/__init__.py +2 -2
  4. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/guidelines_boundaries.py +16 -13
  5. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/guidelines_boundaries_specialized_functions.py +11 -6
  6. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/gen/__init__.py +21 -29
  7. mergeron-2024.738963.0/src/mergeron/gen/_data_generation_functions_nonpublic.py → mergeron-2024.738972.0/src/mergeron/gen/_data_generation_functions.py +77 -19
  8. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/gen/data_generation.py +17 -14
  9. mergeron-2024.738972.0/src/mergeron/gen/market_sample.py +79 -0
  10. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/gen/upp_tests.py +99 -66
  11. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/README.rst +0 -0
  12. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/License.txt +0 -0
  13. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/__init__.py +0 -0
  14. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/InCommon RSA Server CA cert chain.pem +0 -0
  15. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/damodaran_margin_data.py +0 -0
  16. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/excel_helper.py +0 -0
  17. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/ftc_invdata.msgpack +0 -0
  18. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
  19. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/proportions_tests.py +0 -0
  20. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  21. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/ext/__init__.py +0 -0
  22. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/ext/tol_colors.py +0 -0
  23. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/gen/investigations_stats.py +0 -0
  24. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  25. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  26. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  27. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
  28. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/mergeron.cls +0 -0
  29. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -0
  30. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -0
  31. {mergeron-2024.738963.0 → mergeron-2024.738972.0}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mergeron
3
- Version: 2024.738963.0
3
+ Version: 2024.738972.0
4
4
  Summary: Analysis of standards defined in Horizontal Merger Guidelines
5
5
  License: MIT
6
6
  Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "mergeron"
3
3
  # See ./get_version_str.py
4
- version = "2024.738963.0"
4
+ version = "2024.738972.0"
5
5
  description = "Analysis of standards defined in Horizontal Merger Guidelines"
6
6
  keywords = [
7
7
  "merger policy analysis",
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from importlib.metadata import version
4
4
 
5
- from attrs import Attribute, define, field, validators
5
+ from attrs import Attribute, field, frozen, validators
6
6
 
7
7
  from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
8
8
 
@@ -38,7 +38,7 @@ def _rec_spec_validator(
38
38
  )
39
39
 
40
40
 
41
- @define(slots=True, frozen=True)
41
+ @frozen
42
42
  class UPPBoundarySpec:
43
43
  share_ratio: float = field(
44
44
  kw_only=False,
@@ -5,13 +5,12 @@ with a canvas on which to draw boundaries for Guidelines standards.
5
5
  """
6
6
 
7
7
  import decimal
8
- from collections.abc import Callable
9
8
  from dataclasses import dataclass
10
9
  from importlib.metadata import version
11
10
  from typing import Any, Literal, TypeAlias
12
11
 
13
12
  import numpy as np
14
- from attrs import define, field
13
+ from attrs import field, frozen
15
14
  from mpmath import mp, mpf # type: ignore
16
15
  from numpy.typing import NDArray
17
16
 
@@ -24,7 +23,7 @@ __version__ = version(_PKG_NAME)
24
23
  mp.prec = 80
25
24
  mp.trap_complex = True
26
25
 
27
- HMGPubYear: TypeAlias = Literal[1992, 2010, 2023]
26
+ HMGPubYear: TypeAlias = Literal[1992, 2004, 2010, 2023]
28
27
 
29
28
 
30
29
  @dataclass(slots=True, frozen=True)
@@ -43,25 +42,28 @@ class GuidelinesBoundary:
43
42
  area: float
44
43
 
45
44
 
46
- @dataclass(slots=True, frozen=True)
47
- class GuidelinesBoundaryCallable:
48
- boundary_function: Callable[[NDArray[np.float64]], NDArray[np.float64]]
49
- area: float
50
- s_naught: float = 0
51
-
52
-
53
- @define(slots=True, frozen=True)
45
+ @frozen
54
46
  class GuidelinesThresholds:
55
47
  """
56
48
  Guidelines threholds by Guidelines publication year
57
49
 
58
50
  ΔHHI, Recapture Rate, GUPPI, Diversion ratio, CMCR, and IPR thresholds
59
- constructed from concentration standards.
51
+ constructed from concentration standards in Guidelines published in
52
+ 1992, 2004, 2010, and 2023.
53
+
54
+ The 2004 Guidelines refernced here are the EU Commission
55
+ guidelines on assessment of horizontal mergers. These
56
+ guidelines also define a presumption for mergers with
57
+ post-merger HHI in [1000, 2000) and ΔHHI >= 250 points,
58
+ whi is not modeled here.
59
+
60
+ All other Guidelines modeled here are U.S. merger guidelines.
61
+
60
62
  """
61
63
 
62
64
  pub_year: HMGPubYear
63
65
  """
64
- Year of publication of the U.S. Horizontal Merger Guidelines (HMG)
66
+ Year of publication of the Guidelines
65
67
  """
66
68
 
67
69
  safeharbor: HMGThresholds = field(kw_only=True, default=None)
@@ -99,6 +101,7 @@ class GuidelinesThresholds:
99
101
  _hhi_p, _dh_s, _dh_p = {
100
102
  1992: (0.18, 0.005, 0.01),
101
103
  2010: (0.25, 0.01, 0.02),
104
+ 2004: (0.20, 0.015, 0.015),
102
105
  2023: (0.18, 0.01, 0.01),
103
106
  }[self.pub_year]
104
107
 
@@ -6,21 +6,19 @@ to have poor performance
6
6
 
7
7
  """
8
8
 
9
+ from collections.abc import Callable
10
+ from dataclasses import dataclass
9
11
  from importlib.metadata import version
10
12
  from typing import Literal
11
13
 
12
14
  import numpy as np
13
15
  from mpmath import mp, mpf # type: ignore
16
+ from numpy.typing import NDArray
14
17
  from scipy.spatial.distance import minkowski as distance_function # type: ignore
15
18
  from sympy import lambdify, simplify, solve, symbols
16
19
 
17
20
  from .. import _PKG_NAME # noqa: TID252
18
- from .guidelines_boundaries import (
19
- GuidelinesBoundary,
20
- GuidelinesBoundaryCallable,
21
- _shrratio_boundary_intcpt,
22
- lerp,
23
- )
21
+ from .guidelines_boundaries import GuidelinesBoundary, _shrratio_boundary_intcpt, lerp
24
22
 
25
23
  __version__ = version(_PKG_NAME)
26
24
 
@@ -29,6 +27,13 @@ mp.prec = 80
29
27
  mp.trap_complex = True
30
28
 
31
29
 
30
+ @dataclass(slots=True, frozen=True)
31
+ class GuidelinesBoundaryCallable:
32
+ boundary_function: Callable[[NDArray[np.float64]], NDArray[np.float64]]
33
+ area: float
34
+ s_naught: float = 0
35
+
36
+
32
37
  def delta_hhi_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCallable:
33
38
  """
34
39
  Generate the list of share combination on the ΔHHI boundary.
@@ -5,23 +5,21 @@ Defines constants and containers for industry data generation and testing
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from importlib.metadata import version
9
-
10
- from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
11
-
12
- __version__ = version(_PKG_NAME)
13
-
14
-
15
8
  import enum
16
9
  from dataclasses import dataclass
17
- from typing import ClassVar, Literal, Protocol, TypeVar, Union
10
+ from importlib.metadata import version
11
+ from typing import ClassVar, Protocol, TypeVar
18
12
 
19
13
  import numpy as np
20
- from attrs import Attribute, define, field, validators
14
+ from attrs import Attribute, define, field, frozen, validators
21
15
  from numpy.typing import NBitBase, NDArray
22
16
 
17
+ from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
23
18
  from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
24
19
 
20
+ __version__ = version(_PKG_NAME)
21
+
22
+
25
23
  EMPTY_ARRAY_DEFAULT = np.zeros(2)
26
24
  FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
27
25
 
@@ -74,7 +72,7 @@ class SHRConstants(enum.StrEnum):
74
72
  """
75
73
 
76
74
 
77
- @define(slots=True, frozen=True)
75
+ @frozen
78
76
  class ShareSpec:
79
77
  """Market share specification
80
78
 
@@ -152,7 +150,7 @@ class FM2Constants(enum.StrEnum):
152
150
  SYM = "symmetric"
153
151
 
154
152
 
155
- @define(slots=True, frozen=True)
153
+ @frozen
156
154
  class PCMSpec:
157
155
  """Price-cost margin (PCM) specification
158
156
 
@@ -224,9 +222,9 @@ class SSZConstants(float, enum.ReprEnum):
224
222
  """When initial set of draws is not restricted in any way."""
225
223
 
226
224
 
227
- # Validators for selected attributes of MarketSampleSpec
225
+ # Validators for selected attributes of MarketSpec
228
226
  def _sample_size_validator(
229
- _object: MarketSampleSpec, _attribute: Attribute[int], _value: int, /
227
+ _object: MarketSpec, _attribute: Attribute[int], _value: int, /
230
228
  ) -> None:
231
229
  if _value < 10**6:
232
230
  raise ValueError(
@@ -235,7 +233,7 @@ def _sample_size_validator(
235
233
 
236
234
 
237
235
  def _share_spec_validator(
238
- _instance: MarketSampleSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
236
+ _instance: MarketSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
239
237
  ) -> None:
240
238
  _r_bar = _value.recapture_rate
241
239
  if _r_bar and not (0 < _r_bar <= 1):
@@ -274,7 +272,7 @@ def _share_spec_validator(
274
272
 
275
273
 
276
274
  def _pcm_spec_validator(
277
- _instance: MarketSampleSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
275
+ _instance: MarketSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
278
276
  ) -> None:
279
277
  if (
280
278
  _instance.share_spec.recapture_form == RECConstants.FIXED
@@ -308,15 +306,10 @@ def _pcm_spec_validator(
308
306
  )
309
307
 
310
308
 
311
- @define(slots=True, frozen=True)
312
- class MarketSampleSpec:
309
+ @define(slots=False)
310
+ class MarketSpec:
313
311
  """Parameter specification for market data generation."""
314
312
 
315
- sample_size: int = field(
316
- default=10**6, validator=(validators.instance_of(int), _sample_size_validator)
317
- )
318
- """sample size generated"""
319
-
320
313
  share_spec: ShareSpec = field(
321
314
  kw_only=True,
322
315
  default=ShareSpec(RECConstants.INOUT, 0.855, SHRConstants.UNI, None, None),
@@ -353,17 +346,16 @@ class INVResolution(enum.StrEnum):
353
346
  BOTH = "both"
354
347
 
355
348
 
356
- @define(slots=True, frozen=True)
349
+ @frozen
357
350
  class UPPTestRegime:
358
351
  resolution: INVResolution = field(
359
352
  default=INVResolution.ENFT, validator=validators.instance_of(INVResolution)
360
353
  )
361
354
  guppi_aggregator: UPPAggrSelector = field(
362
- default=UPPAggrSelector.MAX, validator=validators.instance_of(UPPAggrSelector)
355
+ default=UPPAggrSelector.MIN, validator=validators.instance_of(UPPAggrSelector)
363
356
  )
364
357
  divr_aggregator: UPPAggrSelector | None = field(
365
- default=guppi_aggregator,
366
- validator=validators.instance_of((UPPAggrSelector, type(None))),
358
+ default=None, validator=validators.instance_of((UPPAggrSelector, type(None)))
367
359
  )
368
360
 
369
361
 
@@ -469,7 +461,7 @@ class MarginDataSample:
469
461
 
470
462
  @dataclass(slots=True, frozen=True)
471
463
  class UPPTestsRaw:
472
- """arrays marking test failures and successes
464
+ """Container for arrays marking test failures and successes
473
465
 
474
466
  A test success is a draw ("market") that meeets the
475
467
  specified test criterion, and a test failure is
@@ -494,9 +486,9 @@ class UPPTestsRaw:
494
486
 
495
487
  @dataclass(slots=True, frozen=True)
496
488
  class UPPTestsCounts:
497
- """counts of markets resolved as specified
489
+ """Counts of markets resolved as specified
498
490
 
499
- Resolution is specified in a UPPTestRegime object.
491
+ Resolution may be either "enforcement" or "clearance".
500
492
  """
501
493
 
502
494
  by_firm_count: NDArray[np.int64]
@@ -19,11 +19,12 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
19
19
  prng,
20
20
  )
21
21
  from . import (
22
+ EMPTY_ARRAY_DEFAULT,
22
23
  FCOUNT_WTS_DEFAULT,
23
24
  TF,
24
25
  FM2Constants,
25
26
  MarginDataSample,
26
- MarketSampleSpec,
27
+ MarketSpec,
27
28
  PCMConstants,
28
29
  PriceDataSample,
29
30
  PRIConstants,
@@ -36,7 +37,8 @@ __version__ = version(_PKG_NAME)
36
37
 
37
38
 
38
39
  def _gen_share_data(
39
- _mkt_sample_spec: MarketSampleSpec,
40
+ _sample_size: int,
41
+ _mkt_sample_spec: MarketSpec,
40
42
  _fcount_rng_seed_seq: SeedSequence | None,
41
43
  _mktshr_rng_seed_seq: SeedSequence,
42
44
  _nthreads: int = 16,
@@ -66,7 +68,7 @@ def _gen_share_data(
66
68
  for _f in ("recapture_form", "dist_type", "dist_parms", "firm_counts_weights")
67
69
  )
68
70
 
69
- _ssz = _mkt_sample_spec.sample_size
71
+ _ssz = _sample_size
70
72
 
71
73
  match _dist_type_mktshr:
72
74
  case SHRConstants.UNI:
@@ -396,24 +398,22 @@ def _gen_market_shares_dirichlet(
396
398
  def _gen_price_data(
397
399
  _frmshr_array: NDArray[np.float64],
398
400
  _nth_firm_share: NDArray[np.float64],
399
- _mkt_sample_spec: MarketSampleSpec,
401
+ _mkt_sample_spec: MarketSpec,
400
402
  _seed_seq: SeedSequence | None = None,
401
403
  /,
402
404
  ) -> PriceDataSample:
403
- _ssz = len(_frmshr_array)
404
-
405
405
  _hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
406
406
 
407
407
  _price_array, _price_ratio_array, _hsr_filing_test = (
408
408
  np.ones_like(_frmshr_array, np.float64),
409
409
  np.empty_like(_frmshr_array, np.float64),
410
- np.empty(_ssz, bool),
410
+ np.empty(len(_frmshr_array), bool),
411
411
  )
412
412
 
413
413
  _pr_max_ratio = 5.0
414
414
  match _mkt_sample_spec.price_spec:
415
415
  case PRIConstants.SYM:
416
- _nth_firm_price = np.ones((_ssz, 1))
416
+ _nth_firm_price = np.ones((len(_frmshr_array), 1))
417
417
  case PRIConstants.POS:
418
418
  _price_array, _nth_firm_price = (
419
419
  np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
@@ -477,14 +477,14 @@ def _gen_price_data(
477
477
  # del _nth_firm_rev, _rev_ratio_to_nth
478
478
  case _:
479
479
  # Otherwise, all draws meet the filing test
480
- _hsr_filing_test = np.ones(_ssz, dtype=bool)
480
+ _hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
481
481
 
482
482
  return PriceDataSample(_price_array, _hsr_filing_test)
483
483
 
484
484
 
485
485
  def _gen_pcm_data(
486
486
  _frmshr_array: NDArray[np.floating[TF]],
487
- _mkt_sample_spec: MarketSampleSpec,
487
+ _mkt_sample_spec: MarketSpec,
488
488
  _price_array: NDArray[np.floating[TF]],
489
489
  _aggregate_purchase_prob: NDArray[np.floating[TF]],
490
490
  _pcm_rng_seed_seq: SeedSequence,
@@ -504,27 +504,25 @@ def _gen_pcm_data(
504
504
  _mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
505
505
 
506
506
  _beta_min, _beta_max = [None] * 2 # placeholder
507
- _dist_parms = np.ones(2, np.float64)
508
507
  if _dist_type_pcm == PCMConstants.EMPR:
509
508
  _pcm_array = resample_mgn_data(
510
509
  _pcm_array.shape, # type: ignore
511
510
  seed_sequence=_pcm_rng_seed_seq,
512
511
  )
513
512
  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
-
513
+ if _dist_type_pcm == PCMConstants.BETA:
521
514
  if _dist_parms_pcm is None:
522
- _dist_parms_pcm = _dist_parms
515
+ _dist_parms_pcm = np.ones(2, np.float64)
523
516
 
524
517
  elif _dist_type_pcm == PCMConstants.BETA_BND: # Bounded beta
525
518
  if _dist_parms_pcm is None:
526
519
  _dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
527
520
  _dist_parms = beta_located_bound(_dist_parms_pcm)
521
+ else:
522
+ # _dist_type_pcm == PCMConstants.UNI
523
+ _dist_parms = (
524
+ DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
525
+ )
528
526
 
529
527
  _pcm_rng = MultithreadedRNG(
530
528
  _pcm_array,
@@ -564,6 +562,66 @@ def _gen_pcm_data(
564
562
  return MarginDataSample(_pcm_array, _mnl_test_array)
565
563
 
566
564
 
565
+ def _gen_divr_array(
566
+ _recapture_form: RECConstants,
567
+ _recapture_rate: float | None,
568
+ _frmshr_array: NDArray[np.float64],
569
+ _aggregate_purchase_prob: NDArray[np.float64] = EMPTY_ARRAY_DEFAULT,
570
+ /,
571
+ ) -> NDArray[np.float64]:
572
+ """
573
+ Given merging-firm shares and related parameters, return diverion ratios.
574
+
575
+ If recapture is specified as "Outside-in" (RECConstants.OUTIN), then the
576
+ choice-probability for the outside good must be supplied.
577
+
578
+ Parameters
579
+ ----------
580
+ _recapture_form
581
+ Enum specifying Fixed (proportional), Inside-out, or Outside-in
582
+
583
+ _recapture_rate
584
+ If recapture is proportional or inside-out, the recapture rate
585
+ for the firm with the smaller share.
586
+
587
+ _frmshr_array
588
+ Merging-firm shares.
589
+
590
+ _aggregate_purchase_prob
591
+ 1 minus probability that the outside good is chosen; converts
592
+ market shares to choice probabilities by multiplication.
593
+
594
+ Returns
595
+ -------
596
+ Merging-firm diversion ratios for mergers in the sample.
597
+
598
+ """
599
+
600
+ _divr_array: NDArray[np.float64]
601
+ if _recapture_form == RECConstants.FIXED:
602
+ _divr_array = _recapture_rate * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
603
+
604
+ else:
605
+ _purchprob_array = _aggregate_purchase_prob * _frmshr_array
606
+ _divr_array = _purchprob_array[:, ::-1] / (1 - _purchprob_array)
607
+
608
+ _divr_assert_test = (
609
+ (np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
610
+ | (np.argmin(_frmshr_array, axis=1) == np.argmax(_divr_array, axis=1))
611
+ )[:, None]
612
+ if not all(_divr_assert_test):
613
+ raise ValueError(
614
+ "{} {} {} {}".format(
615
+ "Data construction fails tests:",
616
+ "the index of min(s_1, s_2) must equal",
617
+ "the index of max(d_12, d_21), for all draws.",
618
+ "unless frmshr_array sums to 1.00.",
619
+ )
620
+ )
621
+
622
+ return _divr_array
623
+
624
+
567
625
  def _beta_located(
568
626
  _mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
569
627
  ) -> NDArray[np.float64]:
@@ -7,7 +7,6 @@ from __future__ import annotations
7
7
 
8
8
  from importlib.metadata import version
9
9
 
10
- import attrs
11
10
  import numpy as np
12
11
  from numpy.random import SeedSequence
13
12
  from numpy.typing import NDArray
@@ -17,12 +16,12 @@ from . import (
17
16
  EMPTY_ARRAY_DEFAULT,
18
17
  FM2Constants,
19
18
  MarketDataSample,
20
- MarketSampleSpec,
19
+ MarketSpec,
21
20
  PRIConstants,
22
21
  SHRConstants,
23
22
  SSZConstants,
24
23
  )
25
- from ._data_generation_functions_nonpublic import (
24
+ from ._data_generation_functions import (
26
25
  _gen_market_shares_dirichlet, # noqa: F401 easter-egg for external modules
27
26
  _gen_market_shares_uniform, # noqa: F401 easter-egg for external modules
28
27
  _gen_pcm_data,
@@ -34,9 +33,10 @@ __version__ = version(_PKG_NAME)
34
33
 
35
34
 
36
35
  def gen_market_sample(
37
- _mkt_sample_spec: MarketSampleSpec,
36
+ _mkt_sample_spec: MarketSpec,
38
37
  /,
39
38
  *,
39
+ sample_size: int = 10**6,
40
40
  seed_seq_list: list[SeedSequence] | None = None,
41
41
  nthreads: int = 16,
42
42
  ) -> MarketDataSample:
@@ -59,6 +59,8 @@ def gen_market_sample(
59
59
  ----------
60
60
  _mkt_sample_spec
61
61
  class specifying parameters for data generation
62
+ sample_size
63
+ number of draws to generate
62
64
  seed_seq_list
63
65
  tuple of SeedSequences to ensure replicable data generation with
64
66
  appropriately independent random streams
@@ -72,7 +74,7 @@ def gen_market_sample(
72
74
 
73
75
  """
74
76
 
75
- _mkt_sample_spec = _mkt_sample_spec or MarketSampleSpec()
77
+ _mkt_sample_spec = _mkt_sample_spec or MarketSpec()
76
78
 
77
79
  _recapture_form = _mkt_sample_spec.share_spec.recapture_form
78
80
  _recapture_rate = _mkt_sample_spec.share_spec.recapture_rate
@@ -89,19 +91,20 @@ def gen_market_sample(
89
91
  seed_seq_list, _dist_type_mktshr, _mkt_sample_spec.price_spec
90
92
  )
91
93
 
92
- _shr_sample_size = 1.0 * _mkt_sample_spec.sample_size
94
+ _shr_sample_size = 1.0 * sample_size
93
95
  # Scale up sample size to offset discards based on specified criteria
94
96
  _shr_sample_size *= _hsr_filing_test_type
95
97
  if _dist_firm2_pcm == FM2Constants.MNL:
96
98
  _shr_sample_size *= SSZConstants.MNL_DEP
97
- _mkt_sample_spec_here = attrs.evolve(
98
- _mkt_sample_spec, sample_size=int(_shr_sample_size)
99
- )
100
- del _shr_sample_size
99
+ _shr_sample_size = int(_shr_sample_size)
101
100
 
102
101
  # Generate share data
103
102
  _mktshr_data = _gen_share_data(
104
- _mkt_sample_spec_here, _fcount_rng_seed_seq, _mktshr_rng_seed_seq, nthreads
103
+ _shr_sample_size,
104
+ _mkt_sample_spec,
105
+ _fcount_rng_seed_seq,
106
+ _mktshr_rng_seed_seq,
107
+ nthreads,
105
108
  )
106
109
 
107
110
  _mktshr_array, _fcounts, _aggregate_purchase_prob, _nth_firm_share = (
@@ -116,7 +119,7 @@ def gen_market_sample(
116
119
 
117
120
  # Generate merging-firm price data
118
121
  _price_data = _gen_price_data(
119
- _mktshr_array[:, :2], _nth_firm_share, _mkt_sample_spec_here, _pr_rng_seed_seq
122
+ _mktshr_array[:, :2], _nth_firm_share, _mkt_sample_spec, _pr_rng_seed_seq
120
123
  )
121
124
 
122
125
  _price_array, _hsr_filing_test = (
@@ -138,7 +141,7 @@ def gen_market_sample(
138
141
  # Generate margin data
139
142
  _pcm_data = _gen_pcm_data(
140
143
  _mktshr_array[:, :2],
141
- _mkt_sample_spec_here,
144
+ _mkt_sample_spec,
142
145
  _price_array,
143
146
  _aggregate_purchase_prob,
144
147
  _pcm_rng_seed_seq,
@@ -148,7 +151,7 @@ def gen_market_sample(
148
151
  getattr(_pcm_data, _f) for _f in ("pcm_array", "mnl_test_array")
149
152
  )
150
153
 
151
- _s_size = _mkt_sample_spec.sample_size # originally-specified sample size
154
+ _s_size = sample_size # originally-specified sample size
152
155
  if _dist_firm2_pcm == FM2Constants.MNL:
153
156
  _mktshr_array = _mktshr_array[_mnl_test_rows][:_s_size]
154
157
  _pcm_array = _pcm_array[_mnl_test_rows][:_s_size]
@@ -0,0 +1,79 @@
1
+ """
2
+ Methods to generate data for analyzing merger enforcement policy.
3
+
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from importlib.metadata import version
9
+
10
+ from attrs import define
11
+ from numpy.random import SeedSequence
12
+
13
+ from .. import _PKG_NAME # noqa: TID252
14
+ from ..core import guidelines_boundaries as gbl # noqa: TID252
15
+ from . import MarketSpec, UPPTestRegime
16
+ from .data_generation import gen_market_sample
17
+ from .upp_tests import SaveData, invres_cnts, save_data_to_hdf5, sim_invres_cnts_ll
18
+
19
+ __version__ = version(_PKG_NAME)
20
+
21
+
22
+ @define(slots=False)
23
+ class MarketSample(MarketSpec):
24
+ def generate_sample(
25
+ self,
26
+ /,
27
+ *,
28
+ sample_size: int = 10**6,
29
+ seed_seq_list: list[SeedSequence] | None,
30
+ nthreads: int,
31
+ save_data_to_file: SaveData = False,
32
+ ) -> None:
33
+ self.data = gen_market_sample(
34
+ self,
35
+ sample_size=sample_size,
36
+ seed_seq_list=seed_seq_list,
37
+ nthreads=nthreads,
38
+ )
39
+ _invalid_array_names = (
40
+ ("fcounts", "choice_prob_outgd", "nth_firm_share", "hhi_post")
41
+ if self.share_spec.dist_type == "Uniform"
42
+ else ()
43
+ )
44
+ if save_data_to_file:
45
+ save_data_to_hdf5(
46
+ self.data,
47
+ excluded_attrs=_invalid_array_names,
48
+ save_data_to_file=save_data_to_file,
49
+ )
50
+
51
+ def estimate_invres_counts(
52
+ self,
53
+ _invres_parm_vec: gbl.HMGThresholds,
54
+ _upp_test_regime: UPPTestRegime,
55
+ /,
56
+ *,
57
+ sample_size: int = 10**6,
58
+ seed_seq_list: list[SeedSequence] | None,
59
+ nthreads: int,
60
+ save_data_to_file: SaveData = False,
61
+ ) -> None:
62
+ if getattr(self, "market_data_sample", None) is None:
63
+ self.invres_counts = sim_invres_cnts_ll(
64
+ self,
65
+ _invres_parm_vec,
66
+ _upp_test_regime,
67
+ save_data_to_file=save_data_to_file,
68
+ sample_size=sample_size,
69
+ seed_seq_list=seed_seq_list,
70
+ nthreads=nthreads,
71
+ )
72
+ else:
73
+ self.invres_counts = invres_cnts(
74
+ self.data, _invres_parm_vec, _upp_test_regime
75
+ )
76
+ if save_data_to_file:
77
+ save_data_to_hdf5(
78
+ self.invres_counts, save_data_to_file=save_data_to_file
79
+ )
@@ -12,7 +12,6 @@ from typing import Literal, TypeAlias, TypedDict
12
12
 
13
13
  import numpy as np
14
14
  import tables as ptb # type: ignore
15
- from attrs import evolve
16
15
  from joblib import Parallel, cpu_count, delayed # type: ignore
17
16
  from numpy.random import SeedSequence
18
17
  from numpy.typing import NDArray
@@ -26,7 +25,7 @@ from . import (
26
25
  DataclassInstance,
27
26
  INVResolution,
28
27
  MarketDataSample,
29
- MarketSampleSpec,
28
+ MarketSpec,
30
29
  UPPTestRegime,
31
30
  UPPTestsCounts,
32
31
  UPPTestsRaw,
@@ -43,21 +42,27 @@ ptb.parameters.MAX_BLOSC_THREADS = 4
43
42
  SaveData: TypeAlias = Literal[False] | tuple[Literal[True], ptb.File, ptb.Group]
44
43
 
45
44
 
46
- class IVNRESCntsArgs(TypedDict, total=False):
45
+ class INVRESCntsArgs(TypedDict, total=False):
47
46
  "Keyword arguments of function, :code:`sim_invres_cnts`"
48
47
 
49
- sim_test_regime: UPPTestRegime
50
48
  saved_array_name_suffix: str
51
49
  save_data_to_file: SaveData
52
- seed_seq_list: list[SeedSequence]
50
+ sample_size: int
51
+ seed_seq_list: list[SeedSequence] | None
53
52
  nthreads: int
54
53
 
55
54
 
56
55
  def sim_invres_cnts_ll(
57
- _mkt_sample_spec: MarketSampleSpec,
56
+ _mkt_sample_spec: MarketSpec,
58
57
  _invres_parm_vec: gbl.HMGThresholds,
59
- _sim_invres_cnts_kwargs: IVNRESCntsArgs,
58
+ _sim_test_regime: UPPTestRegime,
60
59
  /,
60
+ *,
61
+ saved_array_name_suffix: str = "",
62
+ save_data_to_file: SaveData = False,
63
+ sample_size: int = 10**6,
64
+ seed_seq_list: list[SeedSequence] | None = None,
65
+ nthreads: int = 16,
61
66
  ) -> UPPTestsCounts:
62
67
  """A function to parallelize data-generation and testing
63
68
 
@@ -78,22 +83,34 @@ def sim_invres_cnts_ll(
78
83
  _mkt_sample_spec
79
84
  Configuration to use for generating sample data to test
80
85
 
81
- _sim_invres_cnts_kwargs
82
- Arguments to downstream test function `sim_invres_cnts`
86
+ _sim_test_regime
87
+ Configuration to use for testing
88
+
89
+ saved_array_name_suffix
90
+ Suffix to add to the array names in the HDF5 file
91
+
92
+ save_data_to_file
93
+ Whether to save data to an HDF5 file, and where to save it
94
+
95
+ sample_size
96
+ Number of draws to simulate
97
+
98
+ seed_seq_list
99
+ List of seed sequences, to assure independent samples in each thread
100
+
101
+ nthreads
102
+ Number of parallel processes to use
83
103
 
84
104
  Returns
85
105
  -------
86
106
  Arrays of UPPTestCounts
87
107
 
88
108
  """
89
- _sample_sz = _mkt_sample_spec.sample_size
109
+ _sample_sz = sample_size
90
110
  _subsample_sz = 10**6
91
111
  _iter_count = int(_sample_sz / _subsample_sz) if _subsample_sz < _sample_sz else 1
92
112
  _thread_count = cpu_count()
93
113
 
94
- # Crate a copy, to avoid side effects in the outer scope
95
- _mkt_sample_spec_here = evolve(_mkt_sample_spec, sample_size=_subsample_sz)
96
-
97
114
  if (
98
115
  _mkt_sample_spec.share_spec.recapture_form != RECConstants.OUTIN
99
116
  and _mkt_sample_spec.share_spec.recapture_rate != _invres_parm_vec.rec
@@ -107,26 +124,24 @@ def sim_invres_cnts_ll(
107
124
  )
108
125
 
109
126
  _rng_seed_seq_list = [None] * _iter_count
110
- if _sim_invres_cnts_kwargs:
111
- if _sseql := _sim_invres_cnts_kwargs.get("seed_seq_list"):
112
- _rng_seed_seq_list = list(
113
- zip(*[g.spawn(_iter_count) for g in _sseql], strict=True) # type: ignore
114
- )
127
+ if seed_seq_list:
128
+ _rng_seed_seq_list = list(
129
+ zip(*[g.spawn(_iter_count) for g in seed_seq_list], strict=True) # type: ignore
130
+ )
115
131
 
116
- _sim_invres_cnts_kwargs: IVNRESCntsArgs = { # type: ignore
117
- _k: _v
118
- for _k, _v in _sim_invres_cnts_kwargs.items()
119
- if _k != "seed_seq_list"
120
- }
121
- else:
122
- _sim_invres_cnts_kwargs = {}
132
+ _sim_invres_cnts_kwargs: INVRESCntsArgs = INVRESCntsArgs({
133
+ "sample_size": _subsample_sz,
134
+ "save_data_to_file": save_data_to_file,
135
+ "nthreads": nthreads,
136
+ })
123
137
 
124
138
  _res_list = Parallel(n_jobs=_thread_count, prefer="threads")(
125
139
  delayed(sim_invres_cnts)(
126
- _mkt_sample_spec_here,
140
+ _mkt_sample_spec,
127
141
  _invres_parm_vec,
142
+ _sim_test_regime,
128
143
  **_sim_invres_cnts_kwargs,
129
- saved_array_name_suffix=f"{_iter_id:0{2 + int(np.ceil(np.log10(_iter_count)))}d}",
144
+ saved_array_name_suffix=f"{saved_array_name_suffix}_{_iter_id:0{2 + int(np.ceil(np.log10(_iter_count)))}d}",
130
145
  seed_seq_list=_rng_seed_seq_list_ch,
131
146
  )
132
147
  for _iter_id, _rng_seed_seq_list_ch in enumerate(_rng_seed_seq_list)
@@ -151,19 +166,23 @@ def sim_invres_cnts_ll(
151
166
 
152
167
 
153
168
  def sim_invres_cnts(
154
- _mkt_sample_spec: MarketSampleSpec,
169
+ _mkt_sample_spec: MarketSpec,
155
170
  _upp_test_parms: gbl.HMGThresholds,
171
+ _sim_test_regime: UPPTestRegime,
156
172
  /,
157
173
  *,
158
- sim_test_regime: UPPTestRegime,
159
174
  saved_array_name_suffix: str = "",
160
175
  save_data_to_file: SaveData = False,
176
+ sample_size: int = 10**6,
161
177
  seed_seq_list: list[SeedSequence] | None = None,
162
178
  nthreads: int = 16,
163
179
  ) -> UPPTestsCounts:
164
180
  # Generate market data
165
- _market_data = dgl.gen_market_sample(
166
- _mkt_sample_spec, seed_seq_list=seed_seq_list, nthreads=nthreads
181
+ _market_data_sample = dgl.gen_market_sample(
182
+ _mkt_sample_spec,
183
+ sample_size=sample_size,
184
+ seed_seq_list=seed_seq_list,
185
+ nthreads=nthreads,
167
186
  )
168
187
 
169
188
  _invalid_array_names = (
@@ -173,28 +192,42 @@ def sim_invres_cnts(
173
192
  )
174
193
 
175
194
  save_data_to_hdf5(
176
- _market_data,
177
- saved_array_name_suffix,
178
- _invalid_array_names,
195
+ _market_data_sample,
196
+ saved_array_name_suffix=saved_array_name_suffix,
197
+ excluded_attrs=_invalid_array_names,
179
198
  save_data_to_file=save_data_to_file,
180
199
  )
181
200
 
182
- _upp_tests_data = gen_upp_arrays(
183
- _market_data,
184
- _upp_test_parms,
185
- sim_test_regime,
201
+ _upp_test_arrays = invres_cnts(
202
+ _market_data_sample, _upp_test_parms, _sim_test_regime
203
+ )
204
+
205
+ save_data_to_hdf5(
206
+ _upp_test_arrays,
186
207
  saved_array_name_suffix=saved_array_name_suffix,
187
208
  save_data_to_file=save_data_to_file,
188
209
  )
189
210
 
211
+ return _upp_test_arrays
212
+
213
+
214
+ def invres_cnts(
215
+ _market_data_sample: MarketDataSample,
216
+ _upp_test_parms: gbl.HMGThresholds,
217
+ _upp_test_regime: UPPTestRegime,
218
+ /,
219
+ ) -> UPPTestsCounts:
220
+ _upp_test_arrays = gen_upp_test_arrays(
221
+ _market_data_sample, _upp_test_parms, _upp_test_regime
222
+ )
223
+
190
224
  _fcounts, _hhi_delta, _hhi_post = (
191
- getattr(_market_data, _g) for _g in ["fcounts", "hhi_delta", "hhi_post"]
225
+ getattr(_market_data_sample, _g) for _g in ("fcounts", "hhi_delta", "hhi_post")
192
226
  )
193
- del _market_data
194
227
 
195
228
  _stats_rowlen = 6
196
229
  # Clearance/enforcement counts --- by firm count
197
- _firm_counts_weights = _mkt_sample_spec.share_spec.firm_counts_weights
230
+ _firm_counts_weights = np.unique(_fcounts)
198
231
  if _firm_counts_weights is not None and np.all(_firm_counts_weights >= 0):
199
232
  _max_firm_count = len(_firm_counts_weights)
200
233
 
@@ -210,9 +243,9 @@ def sim_invres_cnts(
210
243
  *[
211
244
  np.einsum(
212
245
  "ij->",
213
- 1 * (_firm_count_test & getattr(_upp_tests_data, _f)),
246
+ 1 * (_firm_count_test & getattr(_upp_test_arrays, _f)),
214
247
  )
215
- for _f in _upp_tests_data.__dataclass_fields__
248
+ for _f in _upp_test_arrays.__dataclass_fields__
216
249
  ],
217
250
  ]),
218
251
  ))
@@ -236,9 +269,9 @@ def sim_invres_cnts(
236
269
  np.einsum("ij->", 1 * _hhi_delta_test),
237
270
  *[
238
271
  np.einsum(
239
- "ij->", 1 * (_hhi_delta_test & getattr(_upp_tests_data, _f))
272
+ "ij->", 1 * (_hhi_delta_test & getattr(_upp_test_arrays, _f))
240
273
  )
241
- for _f in _upp_tests_data.__dataclass_fields__
274
+ for _f in _upp_test_arrays.__dataclass_fields__
242
275
  ],
243
276
  ]),
244
277
  ))
@@ -273,9 +306,9 @@ def sim_invres_cnts(
273
306
  np.einsum("ij->", 1 * _conc_test),
274
307
  *[
275
308
  np.einsum(
276
- "ij->", 1 * (_conc_test & getattr(_upp_tests_data, _f))
309
+ "ij->", 1 * (_conc_test & getattr(_upp_test_arrays, _f))
277
310
  )
278
- for _f in _upp_tests_data.__dataclass_fields__
311
+ for _f in _upp_test_arrays.__dataclass_fields__
279
312
  ],
280
313
  ]),
281
314
  ))
@@ -293,19 +326,26 @@ def sim_invres_cnts(
293
326
  )
294
327
 
295
328
 
296
- def gen_upp_arrays(
329
+ def gen_upp_test_arrays(
297
330
  _market_data: MarketDataSample,
298
331
  _upp_test_parms: gbl.HMGThresholds,
299
332
  _sim_test_regime: UPPTestRegime,
300
333
  /,
301
- *,
302
- saved_array_name_suffix: str = "",
303
- save_data_to_file: SaveData = False,
304
334
  ) -> UPPTestsRaw:
305
335
  """
306
336
  Generate UPP tests arrays for given configuration and market sample
307
337
 
308
338
  Given a standards vector, market
339
+
340
+ Parameters
341
+ ----------
342
+ _market_data
343
+ market data sample
344
+ _upp_test_parms
345
+ guidelines thresholds for testing UPP and related statistics
346
+ _sim_test_regime
347
+ configuration to use for generating UPP tests
348
+
309
349
  """
310
350
  _g_bar, _divr_bar, _cmcr_bar, _ipr_bar = (
311
351
  getattr(_upp_test_parms, _f) for _f in ("guppi", "divr", "cmcr", "ipr")
@@ -407,29 +447,21 @@ def gen_upp_arrays(
407
447
  _divr_test_vector = _market_data.divr_array.max(axis=1, keepdims=True)
408
448
 
409
449
  if _invres_resolution == INVResolution.ENFT:
410
- _upp_tests_data = UPPTestsRaw(
450
+ _upp_test_arrays = UPPTestsRaw(
411
451
  _guppi_test_vector >= _g_bar,
412
452
  (_guppi_test_vector >= _g_bar) | (_divr_test_vector >= _divr_bar),
413
453
  _cmcr_test_vector >= _cmcr_bar,
414
454
  _ipr_test_vector >= _ipr_bar,
415
455
  )
416
456
  else:
417
- _upp_tests_data = UPPTestsRaw(
457
+ _upp_test_arrays = UPPTestsRaw(
418
458
  _guppi_test_vector < _g_bar,
419
459
  (_guppi_test_vector < _g_bar) & (_divr_test_vector < _divr_bar),
420
460
  _cmcr_test_vector < _cmcr_bar,
421
461
  _ipr_test_vector < _ipr_bar,
422
462
  )
423
- del _guppi_test_vector, _divr_test_vector, _cmcr_test_vector, _ipr_test_vector
424
-
425
- save_data_to_hdf5(
426
- _upp_tests_data,
427
- saved_array_name_suffix,
428
- (),
429
- save_data_to_file=save_data_to_file,
430
- )
431
463
 
432
- return _upp_tests_data
464
+ return _upp_test_arrays
433
465
 
434
466
 
435
467
  def initialize_hd5(
@@ -449,24 +481,25 @@ def initialize_hd5(
449
481
 
450
482
  def save_data_to_hdf5(
451
483
  _dclass: DataclassInstance,
452
- _saved_array_name_suffix: str = "",
453
- _excl_attrs: Sequence[str] = (),
454
484
  /,
455
485
  *,
486
+ saved_array_name_suffix: str | None = "",
487
+ excluded_attrs: Sequence[str] | None = (),
456
488
  save_data_to_file: SaveData = False,
457
489
  ) -> None:
458
490
  if save_data_to_file:
459
491
  _, _h5_file, _h5_group = save_data_to_file
460
492
  # Save market data arrays
493
+ excluded_attrs = excluded_attrs or ()
461
494
  for _array_name in _dclass.__dataclass_fields__:
462
- if _array_name in _excl_attrs:
495
+ if _array_name in excluded_attrs:
463
496
  continue
464
497
  save_array_to_hdf5(
465
498
  getattr(_dclass, _array_name),
466
499
  _array_name,
467
500
  _h5_group,
468
501
  _h5_file,
469
- saved_array_name_suffix=_saved_array_name_suffix,
502
+ saved_array_name_suffix=saved_array_name_suffix,
470
503
  )
471
504
 
472
505