mergeron 2024.738973.0__py3-none-any.whl → 2024.739079.10__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 (37) hide show
  1. mergeron/__init__.py +28 -3
  2. mergeron/core/__init__.py +2 -77
  3. mergeron/core/damodaran_margin_data.py +66 -52
  4. mergeron/core/excel_helper.py +39 -37
  5. mergeron/core/ftc_merger_investigations_data.py +66 -35
  6. mergeron/core/guidelines_boundaries.py +261 -234
  7. mergeron/core/guidelines_boundary_functions.py +182 -27
  8. mergeron/core/guidelines_boundary_functions_extra.py +17 -14
  9. mergeron/core/proportions_tests.py +2 -4
  10. mergeron/core/pseudorandom_numbers.py +6 -11
  11. mergeron/data/__init__.py +3 -0
  12. mergeron/data/damodaran_margin_data.xls +0 -0
  13. mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  14. mergeron/{jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 → data/jinja2_LaTeX_templates/setup_tikz_tables.tex} +45 -50
  15. mergeron/demo/__init__.py +3 -0
  16. mergeron/demo/visualize_empirical_margin_distribution.py +88 -0
  17. mergeron/ext/__init__.py +2 -4
  18. mergeron/ext/tol_colors.py +3 -3
  19. mergeron/gen/__init__.py +53 -46
  20. mergeron/gen/_data_generation_functions.py +28 -93
  21. mergeron/gen/data_generation.py +20 -24
  22. mergeron/gen/{investigations_stats.py → enforcement_stats.py} +59 -57
  23. mergeron/gen/market_sample.py +6 -10
  24. mergeron/gen/upp_tests.py +29 -26
  25. mergeron-2024.739079.10.dist-info/METADATA +109 -0
  26. mergeron-2024.739079.10.dist-info/RECORD +36 -0
  27. mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
  28. mergeron-2024.738973.0.dist-info/METADATA +0 -108
  29. mergeron-2024.738973.0.dist-info/RECORD +0 -32
  30. /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
  31. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  32. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  33. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  34. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
  35. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron.cls +0 -0
  36. /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron_table_collection_template.tex.jinja2 +0 -0
  37. {mergeron-2024.738973.0.dist-info → mergeron-2024.739079.10.dist-info}/WHEEL +0 -0
@@ -0,0 +1,88 @@
1
+ """
2
+ Plot the empirical distribution derived using the Gaussian KDE with
3
+ margin data downloaded from Prof. Damodaran's website at NYU.
4
+
5
+
6
+ """
7
+
8
+ import warnings
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ from matplotlib.ticker import StrMethodFormatter
13
+ from numpy.random import PCG64DXSM, Generator, SeedSequence
14
+ from scipy import stats # type: ignore
15
+
16
+ import mergeron.core.damodaran_margin_data as dmgn
17
+ from mergeron import DATA_DIR
18
+ from mergeron.core.guidelines_boundary_functions import boundary_plot
19
+
20
+ SAMPLE_SIZE = 10**6
21
+ BIN_COUNT = 25
22
+ mgn_data_obs, mgn_data_wts, mgn_data_stats = dmgn.mgn_data_builder()
23
+ print(repr(mgn_data_obs))
24
+ print(repr(mgn_data_stats))
25
+
26
+ plt, mgn_fig, mgn_ax, set_axis_def = boundary_plot(mktshares_plot_flag=False)
27
+ mgn_fig.set_figheight(6.5)
28
+ mgn_fig.set_figwidth(9.0)
29
+
30
+ _, mgn_bins, _ = mgn_ax.hist(
31
+ x=mgn_data_obs,
32
+ weights=mgn_data_wts,
33
+ bins=BIN_COUNT,
34
+ alpha=0.4,
35
+ density=True,
36
+ label="Downloaded data",
37
+ color="#004488", # Paul Tol's High Contrast Blue
38
+ )
39
+
40
+ with warnings.catch_warnings():
41
+ warnings.filterwarnings("ignore", category=UserWarning)
42
+ # Don't warn regarding the below; ticklabels have been fixed before this point
43
+ mgn_ax.set_yticklabels([
44
+ f"{float(_g.get_text()) * np.diff(mgn_bins)[-1]:.0%}"
45
+ for _g in mgn_ax.get_yticklabels()
46
+ ])
47
+
48
+ mgn_kde = stats.gaussian_kde(mgn_data_obs, weights=mgn_data_wts, bw_method="silverman")
49
+ mgn_kde.set_bandwidth(bw_method=mgn_kde.factor / 3.0)
50
+
51
+ mgn_xvec = np.linspace(0, BIN_COUNT, 10**5) / BIN_COUNT
52
+ mgn_ax.plot(
53
+ mgn_xvec,
54
+ mgn_kde(mgn_xvec),
55
+ color="#004488",
56
+ rasterized=True,
57
+ label="Estimated Density",
58
+ )
59
+
60
+ mgn_ax.hist(
61
+ x=mgn_kde.resample(
62
+ SAMPLE_SIZE, seed=Generator(PCG64DXSM(SeedSequence(pool_size=8)))
63
+ )[0],
64
+ color="#DDAA33", # Paul Tol's High Contrast Yellow
65
+ alpha=0.6,
66
+ bins=BIN_COUNT,
67
+ density=True,
68
+ label="Generated data",
69
+ )
70
+
71
+ mgn_ax.legend(
72
+ loc="best",
73
+ fancybox=False,
74
+ shadow=False,
75
+ frameon=True,
76
+ facecolor="white",
77
+ edgecolor="white",
78
+ framealpha=1,
79
+ fontsize="small",
80
+ )
81
+
82
+ mgn_ax.set_xlim(0.0, 1.0)
83
+ mgn_ax.xaxis.set_major_formatter(StrMethodFormatter("{x:>3.0%}"))
84
+ mgn_ax.set_xlabel("Price Cost Margin", fontsize=10)
85
+ mgn_ax.set_ylabel("Relative Frequency", fontsize=10)
86
+
87
+ mgn_fig.tight_layout()
88
+ plt.savefig(DATA_DIR / f"{Path(__file__).stem}.pdf")
mergeron/ext/__init__.py CHANGED
@@ -1,5 +1,3 @@
1
- from importlib.metadata import version
1
+ from .. import VERSION # noqa: TID252
2
2
 
3
- from .. import _PKG_NAME # noqa: TID252
4
-
5
- __version__ = version(_PKG_NAME)
3
+ __version__ = VERSION
@@ -796,7 +796,7 @@ def main() -> None:
796
796
  schemes: Sequence[str] = tol_cset()
797
797
  fig, axes = plt.subplots(ncols=len(schemes), figsize=(9, 3))
798
798
  fig.subplots_adjust(top=0.9, bottom=0.02, left=0.02, right=0.92)
799
- for ax, scheme in zip(axes, schemes):
799
+ for ax, scheme in zip(axes, schemes): # type: ignore
800
800
  cset = tol_cset(scheme)
801
801
  names = cset._fields # type: ignore
802
802
  colors = list(cset)
@@ -813,7 +813,7 @@ def main() -> None:
813
813
  gradient = np.vstack((gradient, gradient))
814
814
  fig, axes = plt.subplots(nrows=len(schemes))
815
815
  fig.subplots_adjust(top=0.98, bottom=0.02, left=0.2, right=0.99)
816
- for ax, scheme in zip(axes, schemes):
816
+ for ax, scheme in zip(axes, schemes): # type: ignore
817
817
  pos = list(ax.get_position().bounds)
818
818
  ax.set_axis_off()
819
819
  ax.imshow(gradient, aspect=4, cmap=tol_cmap(scheme))
@@ -832,7 +832,7 @@ def main() -> None:
832
832
  gradient = np.vstack((gradient, gradient))
833
833
  fig, axes = plt.subplots(nrows=23)
834
834
  fig.subplots_adjust(top=0.98, bottom=0.02, left=0.25, right=0.99)
835
- for lut, ax in enumerate(axes, start=1):
835
+ for lut, ax in enumerate(axes, start=1): # type: ignore
836
836
  pos = list(ax.get_position().bounds)
837
837
  ax.set_axis_off()
838
838
  ax.imshow(gradient, aspect=4, cmap=tol_cmap("rainbow_discrete", lut))
mergeron/gen/__init__.py CHANGED
@@ -7,28 +7,26 @@ from __future__ import annotations
7
7
 
8
8
  import enum
9
9
  from dataclasses import dataclass
10
- from importlib.metadata import version
11
- from typing import ClassVar, Protocol, TypeVar
10
+ from typing import ClassVar, Protocol
12
11
 
13
12
  import numpy as np
14
- from attrs import Attribute, define, field, frozen, validators
15
- from numpy.typing import NBitBase, NDArray
13
+ from attrs import Attribute, cmp_using, define, field, frozen, validators
14
+ from numpy.typing import NDArray
16
15
 
17
- from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
16
+ from .. import VERSION, RECConstants, UPPAggrSelector # noqa: TID252
18
17
  from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
19
18
 
20
- __version__ = version(_PKG_NAME)
19
+ __version__ = VERSION
21
20
 
22
21
 
23
22
  EMPTY_ARRAY_DEFAULT = np.zeros(2)
24
- FCOUNT_WTS_DEFAULT = ((_nr := np.arange(1, 6)[::-1]) / _nr.sum()).astype(np.float64)
25
-
26
- TF = TypeVar("TF", bound=NBitBase)
27
- TI = TypeVar("TI", bound=NBitBase)
23
+ FCOUNT_WTS_DEFAULT = np.divide(
24
+ (_nr := np.arange(1, 6)[::-1]), _nr.sum(), dtype=np.float64
25
+ )
28
26
 
29
27
 
30
28
  @enum.unique
31
- class PRIConstants(tuple[bool, str | None], enum.ReprEnum):
29
+ class PriceConstants(tuple[bool, str | None], enum.ReprEnum):
32
30
  """Price specification.
33
31
 
34
32
  Whether prices are symmetric and, if not, the direction of correlation, if any.
@@ -38,7 +36,7 @@ class PRIConstants(tuple[bool, str | None], enum.ReprEnum):
38
36
  ZERO = (False, None)
39
37
  NEG = (False, "negative share-correlation")
40
38
  POS = (False, "positive share-correlation")
41
- CSY = (False, "market-wide cost-symmetry")
39
+ # TODO: CSY = (False, "market-wide cost-symmetry")
42
40
 
43
41
 
44
42
  @enum.unique
@@ -46,7 +44,7 @@ class SHRConstants(enum.StrEnum):
46
44
  """Market share distributions."""
47
45
 
48
46
  UNI = "Uniform"
49
- """Uniform distribution over the 3-simplex"""
47
+ R"""Uniform distribution over :math:`s_1 + s_2 \leqslant 1`"""
50
48
 
51
49
  DIR_FLAT = "Flat Dirichlet"
52
50
  """Shape parameter for all merging-firm-shares is unity (1)"""
@@ -87,48 +85,56 @@ class ShareSpec:
87
85
  """
88
86
 
89
87
  recapture_form: RECConstants
90
- """see RECConstants"""
88
+ """See :class:`mergeron.RECConstants`"""
91
89
 
92
90
  recapture_rate: float | None
93
- """A value between 0 and 1.
91
+ """A value between 0 and 1, typically 0.8.
94
92
 
95
- None if market share specification requires direct generation of
96
- outside good choice probabilities (RECConstants.OUTIN).
93
+ :code:`None` if market share specification requires direct generation of
94
+ outside good choice probabilities (:attr:`mergeron.RECConstants.OUTIN`).
97
95
 
98
96
  The recapture rate is usually calibrated to the numbers-equivalent of the
99
- HHI threshold for the presumtion of harm from unilateral compoetitive effects
100
- in published merger guidelines. Accordingly, values for the recapture rate may be:
97
+ HHI threshold for the presumtion of harm from unilateral competitive effects
98
+ in published merger guidelines. Accordingly, the recapture rate rounded to
99
+ the nearest 5% is:
100
+
101
+ * 0.85, **7-to-6 merger from symmetry**; US Guidelines, 1992, 2023
102
+ * 0.80, **6-to-5 merger to symmetry**; EU Guidelines for horizontal mergers, 2004
103
+ * 0.80, 5-to-4 merger from symmetry
104
+ * 0.80, **5-to-4 merger to symmetry**; US Guidelines, 2010
101
105
 
102
- * 0.855, **6-to-5 merger from symmetry**; US Guidelines, 1992, 2023
103
- * 0.855, 6-to-5 merger from symmetry; EU Guidelines for horizontal mergers, 2004
104
- * 0.82, **6-to-5 merger to symmetry**; EU Guidelines for horizontal mergers, 2004
105
- * 0.80, 5-to-4 merger from symmetry; US Guidelines, 2010
106
- * 0.78, **5-to-4 merger to symmetry**; US Guidelines, 2010
106
+ Highlighting indicates hypothetical mergers in the neighborhood of (the boundary of)
107
+ the Guidelines presumption of harm. (In the EU Guidelines, concentration measures serve as
108
+ screens for further investigation, rather than as the basis for presumptions of harm or
109
+ presumptions no harm.)
107
110
 
108
- Highlighting indicates hypothetical mergers close to the boundary of the presumption.
109
111
  """
110
112
 
111
113
  dist_type: SHRConstants
112
- """see SHRConstants"""
114
+ """See :class:`mergeron.gen.SHRConstants`"""
113
115
 
114
- dist_parms: NDArray[np.float64] | None
116
+ dist_parms: NDArray[np.float64] | None = field(
117
+ default=None, eq=cmp_using(eq=np.array_equal)
118
+ )
115
119
  """Parameters for tailoring market-share distribution
116
120
 
117
121
  For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
118
- for Beta distribution, shape parameters, defaults to `(1, 1)`;
119
- for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
120
122
  for Dirichlet-type distributions, a vector of shape parameters of length
121
123
  no less than the length of firm-count weights below; defaults depend on
122
124
  type of Dirichlet-distribution specified.
123
125
 
124
126
  """
125
- firm_counts_weights: NDArray[np.float64 | np.int64] | None
126
- """relative or absolute frequencies of firm counts
127
+ firm_counts_weights: NDArray[np.float64 | np.int64] | None = field(
128
+ default=None, eq=cmp_using(eq=np.array_equal)
129
+ )
130
+ """Relative or absolute frequencies of firm counts
127
131
 
128
132
 
129
133
  Given frequencies are exogenous to generated market data sample;
130
- defaults to FCOUNT_WTS_DEFAULT, which specifies firm-counts of 2 to 6
131
- with weights in descending order from 5 to 1."""
134
+ for Dirichlet-type distributions, defaults to FCOUNT_WTS_DEFAULT, which specifies
135
+ firm-counts of 2 to 6 with weights in descending order from 5 to 1.
136
+
137
+ """
132
138
 
133
139
 
134
140
  @enum.unique
@@ -167,10 +173,10 @@ class PCMSpec:
167
173
  """
168
174
 
169
175
  firm2_pcm_constraint: FM2Constants
170
- """See FM2Constants"""
176
+ """See :class:`mergeron.gen.FM2Constants`"""
171
177
 
172
178
  dist_type: PCMConstants
173
- """See PCMConstants"""
179
+ """See :class:`mergeron.gen.PCMConstants`"""
174
180
 
175
181
  dist_parms: NDArray[np.float64] | None
176
182
  """Parameter specification for tailoring PCM distribution
@@ -179,6 +185,7 @@ class PCMSpec:
179
185
  for Beta distribution, shape parameters, defaults to `(1, 1)`;
180
186
  for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
181
187
  for empirical distribution based on Damodaran margin data, optional, ignored
188
+
182
189
  """
183
190
 
184
191
 
@@ -303,31 +310,31 @@ class MarketSpec:
303
310
 
304
311
  share_spec: ShareSpec = field(
305
312
  kw_only=True,
306
- default=ShareSpec(RECConstants.INOUT, 0.855, SHRConstants.UNI, None, None),
313
+ default=ShareSpec(RECConstants.INOUT, 0.85, SHRConstants.UNI, None, None),
307
314
  validator=[validators.instance_of(ShareSpec), _share_spec_validator],
308
315
  )
309
- """Market-share specification, see definition of ShareSpec"""
316
+ """Market-share specification, see :class:`mergeron.gen.ShareSpec`"""
310
317
 
311
318
  pcm_spec: PCMSpec = field(
312
319
  kw_only=True,
313
320
  default=PCMSpec(FM2Constants.IID, PCMConstants.UNI, None),
314
321
  validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
315
322
  )
316
- """Margin specification, see definition of PCMSpec"""
323
+ """Margin specification, see :class:`mergeron.gen.PCMSpec`"""
317
324
 
318
- price_spec: PRIConstants = field(
325
+ price_spec: PriceConstants = field(
319
326
  kw_only=True,
320
- default=PRIConstants.SYM,
321
- validator=validators.instance_of(PRIConstants),
327
+ default=PriceConstants.SYM,
328
+ validator=validators.instance_of(PriceConstants),
322
329
  )
323
- """Price specification, see PRIConstants"""
330
+ """Price specification, see :class:`mergeron.gen.PriceConstants`"""
324
331
 
325
332
  hsr_filing_test_type: SSZConstants = field(
326
333
  kw_only=True,
327
334
  default=SSZConstants.ONE,
328
335
  validator=validators.instance_of(SSZConstants),
329
336
  )
330
- """Method for modeling HSR filing threholds, see SSZConstants"""
337
+ """Method for modeling HSR filing threholds, see :class:`mergeron.gen.SSZConstants`"""
331
338
 
332
339
 
333
340
  @enum.unique
@@ -456,8 +463,8 @@ class UPPTestsRaw:
456
463
 
457
464
  A test success is a draw ("market") that meeets the
458
465
  specified test criterion, and a test failure is
459
- one that does not; test criteria are defined and
460
- evaluated in:code:`guidelines_stats.gen_upp_arrays`.
466
+ one that does not; test criteria are evaluated in
467
+ :func:`enforcement_stats.gen_upp_arrays`.
461
468
  """
462
469
 
463
470
  guppi_test_simple: NDArray[np.bool_]
@@ -479,7 +486,7 @@ class UPPTestsRaw:
479
486
  class UPPTestsCounts:
480
487
  """Counts of markets resolved as specified
481
488
 
482
- Resolution may be either "enforcement" or "clearance".
489
+ Resolution may be either :attr:`INVResolution.ENFT` or :attr:`INVResolution.CLRN`.
483
490
  """
484
491
 
485
492
  by_firm_count: NDArray[np.int64]
@@ -4,36 +4,33 @@ Non-public functions called in data_generation.py
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from importlib.metadata import version
8
7
  from typing import Literal
9
8
 
10
9
  import numpy as np
11
10
  from numpy.random import SeedSequence
12
11
  from numpy.typing import NDArray
13
12
 
14
- from .. import _PKG_NAME, RECConstants # noqa: TID252
15
- from ..core.damodaran_margin_data import resample_mgn_data # noqa: TID252
13
+ from .. import VERSION, RECConstants # noqa: TID252
14
+ from ..core.damodaran_margin_data import mgn_data_resampler # noqa: TID252
16
15
  from ..core.pseudorandom_numbers import ( # noqa: TID252
17
16
  DIST_PARMS_DEFAULT,
18
17
  MultithreadedRNG,
19
18
  prng,
20
19
  )
21
20
  from . import (
22
- EMPTY_ARRAY_DEFAULT,
23
21
  FCOUNT_WTS_DEFAULT,
24
- TF,
25
22
  FM2Constants,
26
23
  MarginDataSample,
27
24
  MarketSpec,
28
25
  PCMConstants,
26
+ PriceConstants,
29
27
  PriceDataSample,
30
- PRIConstants,
31
28
  ShareDataSample,
32
29
  SHRConstants,
33
30
  SSZConstants,
34
31
  )
35
32
 
36
- __version__ = version(_PKG_NAME)
33
+ __version__ = VERSION
37
34
 
38
35
 
39
36
  def _gen_share_data(
@@ -100,7 +97,7 @@ def _gen_share_data(
100
97
 
101
98
  # If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
102
99
  _frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
103
- _r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.855
100
+ _r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.85
104
101
  if _recapture_form == RECConstants.INOUT:
105
102
  _mkt_share_sample = ShareDataSample(
106
103
  _mkt_share_sample.mktshr_array,
@@ -114,7 +111,7 @@ def _gen_share_data(
114
111
 
115
112
  def _gen_market_shares_uniform(
116
113
  _s_size: int = 10**6,
117
- _dist_parms_mktshr: NDArray[np.floating[TF]] | None = DIST_PARMS_DEFAULT, # type: ignore
114
+ _dist_parms_mktshr: NDArray[np.float64] | None = DIST_PARMS_DEFAULT,
118
115
  _mktshr_rng_seed_seq: SeedSequence | None = None,
119
116
  _nthreads: int = 16,
120
117
  /,
@@ -139,8 +136,8 @@ def _gen_market_shares_uniform(
139
136
  """
140
137
 
141
138
  _frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
142
- _dist_parms_mktshr: NDArray[np.floating[TF]] = (
143
- DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr # type: ignore
139
+ _dist_parms_mktshr: NDArray[np.float64] = (
140
+ DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr
144
141
  )
145
142
  _mrng = MultithreadedRNG(
146
143
  _frmshr_array,
@@ -150,7 +147,7 @@ def _gen_market_shares_uniform(
150
147
  nthreads=_nthreads,
151
148
  )
152
149
  _mrng.fill()
153
- # Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 < 1
150
+ # Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 <= 1
154
151
  _frmshr_array = np.sort(_frmshr_array, axis=1)
155
152
  _frmshr_array = np.column_stack((
156
153
  _frmshr_array[:, 0],
@@ -179,8 +176,8 @@ def _gen_market_shares_dirichlet_multisample(
179
176
  _s_size: int = 10**6,
180
177
  _recapture_form: RECConstants = RECConstants.INOUT,
181
178
  _dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
182
- _dist_parms_dir: NDArray[np.floating[TF]] | None = None,
183
- _firm_count_wts: NDArray[np.floating[TF]] | None = None,
179
+ _dist_parms_dir: NDArray[np.float64] | None = None,
180
+ _firm_count_wts: NDArray[np.float64] | None = None,
184
181
  _fcount_rng_seed_seq: SeedSequence | None = None,
185
182
  _mktshr_rng_seed_seq: SeedSequence | None = None,
186
183
  _nthreads: int = 16,
@@ -218,7 +215,7 @@ def _gen_market_shares_dirichlet_multisample(
218
215
 
219
216
  """
220
217
 
221
- _firm_count_wts: NDArray[np.floating[TF]] = (
218
+ _firm_count_wts: NDArray[np.float64] = (
222
219
  FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
223
220
  )
224
221
 
@@ -314,7 +311,7 @@ def _gen_market_shares_dirichlet_multisample(
314
311
 
315
312
 
316
313
  def _gen_market_shares_dirichlet(
317
- _dir_alphas: NDArray[np.floating[TF]],
314
+ _dir_alphas: NDArray[np.float64],
318
315
  _s_size: int = 10**6,
319
316
  _recapture_form: RECConstants = RECConstants.INOUT,
320
317
  _mktshr_rng_seed_seq: SeedSequence | None = None,
@@ -412,18 +409,18 @@ def _gen_price_data(
412
409
 
413
410
  _pr_max_ratio = 5.0
414
411
  match _mkt_sample_spec.price_spec:
415
- case PRIConstants.SYM:
412
+ case PriceConstants.SYM:
416
413
  _nth_firm_price = np.ones((len(_frmshr_array), 1))
417
- case PRIConstants.POS:
414
+ case PriceConstants.POS:
418
415
  _price_array, _nth_firm_price = (
419
416
  np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
420
417
  )
421
- case PRIConstants.NEG:
418
+ case PriceConstants.NEG:
422
419
  _price_array, _nth_firm_price = (
423
420
  np.ceil((1 - _p) * _pr_max_ratio)
424
421
  for _p in (_frmshr_array, _nth_firm_share)
425
422
  )
426
- case PRIConstants.ZERO:
423
+ case PriceConstants.ZERO:
427
424
  _price_array_gen = prng(_seed_seq).choice(
428
425
  1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
429
426
  )
@@ -458,10 +455,10 @@ def _gen_price_data(
458
455
  _hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
459
456
  # del _rev_array, _rev_ratio
460
457
  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.
458
+ # To get around the 10-to-1 ratio restriction, specify that the nth firm test:
459
+ # if the smaller merging firm matches or exceeds the n-th firm in size, and
460
+ # the larger merging firm has at least 10 times the size of the nth firm,
461
+ # the size test is considered met.
465
462
  # Alternatively, if the smaller merging firm has 10% or greater share,
466
463
  # the value of transaction test is considered met.
467
464
  _rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
@@ -483,33 +480,31 @@ def _gen_price_data(
483
480
 
484
481
 
485
482
  def _gen_pcm_data(
486
- _frmshr_array: NDArray[np.floating[TF]],
483
+ _frmshr_array: NDArray[np.float64],
484
+ _price_array: NDArray[np.float64],
485
+ _aggregate_purchase_prob: NDArray[np.float64],
487
486
  _mkt_sample_spec: MarketSpec,
488
- _price_array: NDArray[np.floating[TF]],
489
- _aggregate_purchase_prob: NDArray[np.floating[TF]],
490
487
  _pcm_rng_seed_seq: SeedSequence,
491
488
  _nthreads: int = 16,
492
489
  /,
493
490
  ) -> MarginDataSample:
494
- _recapture_form = _mkt_sample_spec.share_spec.recapture_form
495
491
  _dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
496
492
  getattr(_mkt_sample_spec.pcm_spec, _f)
497
493
  for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
498
494
  )
499
- _dist_type: Literal["Beta", "Uniform"] = (
500
- "Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
501
- )
502
495
 
496
+ _dist_type: Literal["Beta", "Uniform"]
503
497
  _pcm_array = np.empty((len(_frmshr_array), 2), dtype=np.float64)
504
498
  _mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
505
499
 
506
500
  _beta_min, _beta_max = [None] * 2 # placeholder
507
501
  if _dist_type_pcm == PCMConstants.EMPR:
508
- _pcm_array = resample_mgn_data(
502
+ _pcm_array = mgn_data_resampler(
509
503
  _pcm_array.shape, # type: ignore
510
504
  seed_sequence=_pcm_rng_seed_seq,
511
505
  )
512
506
  else:
507
+ _dist_type = "Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
513
508
  if _dist_type_pcm == PCMConstants.BETA:
514
509
  if _dist_parms_pcm is None:
515
510
  _dist_parms_pcm = np.ones(2, np.float64)
@@ -562,66 +557,6 @@ def _gen_pcm_data(
562
557
  return MarginDataSample(_pcm_array, _mnl_test_array)
563
558
 
564
559
 
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
-
625
560
  def _beta_located(
626
561
  _mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
627
562
  ) -> NDArray[np.float64]:
@@ -647,7 +582,7 @@ def _beta_located(
647
582
  return np.array([_mu * _mul, (1 - _mu) * _mul], dtype=np.float64)
648
583
 
649
584
 
650
- def beta_located_bound(_dist_parms: NDArray[np.floating[TF]], /) -> NDArray[np.float64]:
585
+ def beta_located_bound(_dist_parms: NDArray[np.float64], /) -> NDArray[np.float64]:
651
586
  R"""
652
587
  Return shape parameters for a non-standard beta, given the mean, stddev, range
653
588