mergeron 2024.738973.0__py3-none-any.whl → 2024.739079.9__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 +32 -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.9.dist-info/METADATA +109 -0
  26. mergeron-2024.739079.9.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.9.dist-info}/WHEEL +0 -0
@@ -4,26 +4,20 @@ with a canvas on which to draw boundaries for Guidelines standards.
4
4
 
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  from dataclasses import dataclass
8
- from importlib.metadata import version
9
10
  from typing import Literal, TypeAlias
10
11
 
11
12
  import numpy as np
12
- from attrs import field, frozen
13
+ from attrs import Attribute, field, frozen, validators
13
14
  from mpmath import mp, mpf # type: ignore
15
+ from numpy.typing import NDArray
14
16
 
15
- from .. import _PKG_NAME, UPPAggrSelector # noqa: TID252
16
- from . import GuidelinesBoundary, UPPBoundarySpec
17
- from .guidelines_boundary_functions import (
18
- dh_area,
19
- round_cust,
20
- shrratio_boundary_max,
21
- shrratio_boundary_min,
22
- shrratio_boundary_wtd_avg,
23
- shrratio_boundary_xact_avg,
24
- )
17
+ from .. import VERSION, RECConstants, UPPAggrSelector # noqa: TID252
18
+ from . import guidelines_boundary_functions as gbfn
25
19
 
26
- __version__ = version(_PKG_NAME)
20
+ __version__ = VERSION
27
21
 
28
22
 
29
23
  mp.prec = 80
@@ -75,6 +69,14 @@ class GuidelinesThresholds:
75
69
  diversion ratio limit, CMCR, and IPR
76
70
  """
77
71
 
72
+ presumption: HMGThresholds = field(kw_only=True, default=None)
73
+ """
74
+ Presumption of harm defined in HMG
75
+
76
+ ΔHHI bound and corresponding default recapture rate, GUPPI bound,
77
+ diversion ratio limit, CMCR, and IPR
78
+ """
79
+
78
80
  imputed_presumption: HMGThresholds = field(kw_only=True, default=None)
79
81
  """
80
82
  Presumption of harm imputed from guidelines
@@ -84,14 +86,6 @@ class GuidelinesThresholds:
84
86
  GUPPI bound, diversion ratio limit, CMCR, and IPR
85
87
  """
86
88
 
87
- presumption: HMGThresholds = field(kw_only=True, default=None)
88
- """
89
- Presumption of harm defined in HMG
90
-
91
- ΔHHI bound and corresponding default recapture rate, GUPPI bound,
92
- diversion ratio limit, CMCR, and IPR
93
- """
94
-
95
89
  def __attrs_post_init__(self, /) -> None:
96
90
  # In the 2023 Guidlines, the agencies do not define a
97
91
  # negative presumption, or safeharbor. Practically speaking,
@@ -102,7 +96,7 @@ class GuidelinesThresholds:
102
96
  _hhi_p, _dh_s, _dh_p = {
103
97
  1992: (0.18, 0.005, 0.01),
104
98
  2010: (0.25, 0.01, 0.02),
105
- 2004: (0.20, 0.015, 0.015),
99
+ 2004: (0.20, 0.015, 0.025),
106
100
  2023: (0.18, 0.01, 0.01),
107
101
  }[self.pub_year]
108
102
 
@@ -112,15 +106,30 @@ class GuidelinesThresholds:
112
106
  HMGThresholds(
113
107
  _dh_s,
114
108
  _fc := int(np.ceil(1 / _hhi_p)),
115
- _r := round_cust(_fc / (_fc + 1)),
109
+ _r := gbfn.round_cust(_fc / (_fc + 1), frac=0.05),
116
110
  _g_s := guppi_from_delta(_dh_s, m_star=1.0, r_bar=_r),
117
- _dr := round_cust(1 / (_fc + 1)),
111
+ _dr := (1 - _r),
118
112
  _cmcr := 0.03, # Not strictly a Guidelines standard
119
113
  _ipr := _g_s, # Not strictly a Guidelines standard
120
114
  ),
121
115
  )
122
116
 
117
+ object.__setattr__(
118
+ self,
119
+ "presumption",
120
+ HMGThresholds(
121
+ _dh_p,
122
+ _fc,
123
+ _r,
124
+ _g_p := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
125
+ _dr,
126
+ _cmcr,
127
+ _ipr := _g_p,
128
+ ),
129
+ )
130
+
123
131
  # imputed_presumption is relevant for 2010 Guidelines
132
+ # merger to symmettry in numbers-equivalent of post-merger HHI
124
133
  object.__setattr__(
125
134
  self,
126
135
  "imputed_presumption",
@@ -128,18 +137,18 @@ class GuidelinesThresholds:
128
137
  HMGThresholds(
129
138
  _dh_i := 2 * (0.5 / _fc) ** 2,
130
139
  _fc,
131
- _r_i := round_cust((_fc - 1 / 2) / (_fc + 1 / 2)),
132
- _g_i := guppi_from_delta(_dh_i, m_star=1.0, r_bar=_r_i),
133
- round_cust((1 / 2) / (_fc + 1 / 2)),
140
+ _r_i := gbfn.round_cust((_fc - 1 / 2) / (_fc + 1 / 2), frac=0.05),
141
+ _g_i := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r_i),
142
+ 1 / 2 * (1 - _r_i),
134
143
  _cmcr,
135
144
  _g_i,
136
145
  )
137
- if self.pub_year == 2010
146
+ if self.pub_year in (2004, 2010)
138
147
  else HMGThresholds(
139
148
  _dh_i := 2 * (1 / (_fc + 1)) ** 2,
140
149
  _fc,
141
150
  _r,
142
- _g_i := guppi_from_delta(_dh_i, m_star=1.0, r_bar=_r),
151
+ _g_i := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
143
152
  _dr,
144
153
  _cmcr,
145
154
  _g_i,
@@ -147,30 +156,232 @@ class GuidelinesThresholds:
147
156
  ),
148
157
  )
149
158
 
150
- object.__setattr__(
151
- self,
152
- "presumption",
153
- HMGThresholds(
154
- _dh_p,
155
- _fc,
156
- _r,
157
- _g_p := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
158
- _dr,
159
- _cmcr,
160
- _ipr := _g_p,
161
- ),
159
+
160
+ def _concentration_threshold_validator(
161
+ _instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
162
+ ) -> None:
163
+ if not 0 <= _value <= 1:
164
+ raise ValueError("Concentration threshold must lie between 0 and 1.")
165
+
166
+
167
+ def _concentration_measure_name_validator(
168
+ _instance: ConcentrationBoundary, _attribute: Attribute[str], _value: str, /
169
+ ) -> None:
170
+ if _value not in ("ΔHHI", "Combined share", "Pre-merger HHI", "Post-merger HHI"):
171
+ raise ValueError(f"Invalid name for a concentration measure, {_value!r}.")
172
+
173
+
174
+ @frozen
175
+ class ConcentrationBoundary:
176
+ """Concentration parameters, boundary coordinates, and area under concentration boundary."""
177
+
178
+ threshold: float = field(
179
+ kw_only=False,
180
+ default=0.01,
181
+ validator=(validators.instance_of(float), _concentration_threshold_validator),
182
+ )
183
+ precision: int = field(
184
+ kw_only=False, default=5, validator=validators.instance_of(int)
185
+ )
186
+ measure_name: Literal[
187
+ "ΔHHI", "Combined share", "Pre-merger HHI", "Post-merger HHI"
188
+ ] = field(
189
+ kw_only=False,
190
+ default="ΔHHI",
191
+ validator=(validators.instance_of(str), _concentration_measure_name_validator),
192
+ )
193
+
194
+ coordinates: NDArray[np.float64] = field(init=False, kw_only=True)
195
+ """Market-share pairs as Cartesian coordinates of points on the concentration boundary."""
196
+
197
+ area: float = field(init=False, kw_only=True)
198
+ """Area under the concentration boundary."""
199
+
200
+ def __attrs_post_init__(self, /) -> None:
201
+ match self.measure_name:
202
+ case "ΔHHI":
203
+ _conc_fn = gbfn.hhi_delta_boundary
204
+ case "Combined share":
205
+ _conc_fn = gbfn.combined_share_boundary
206
+ case "Pre-merger HHI":
207
+ _conc_fn = gbfn.hhi_pre_contrib_boundary
208
+ case "Post-merger HHI":
209
+ _conc_fn = gbfn.hhi_post_contrib_boundary
210
+
211
+ _boundary = _conc_fn(self.threshold, prec=self.precision)
212
+ object.__setattr__(self, "coordinates", _boundary.coordinates)
213
+ object.__setattr__(self, "area", _boundary.area)
214
+
215
+
216
+ def _divr_value_validator(
217
+ _instance: DiversionRatioBoundary, _attribute: Attribute[float], _value: float, /
218
+ ) -> None:
219
+ if not 0 <= _value <= 1:
220
+ raise ValueError(
221
+ "Margin-adjusted benchmark share ratio must lie between 0 and 1."
162
222
  )
163
223
 
164
224
 
225
+ def _rec_spec_validator(
226
+ _instance: DiversionRatioBoundary,
227
+ _attribute: Attribute[RECConstants],
228
+ _value: RECConstants,
229
+ /,
230
+ ) -> None:
231
+ if _value == RECConstants.OUTIN and _instance.recapture_rate:
232
+ raise ValueError(
233
+ f"Invalid recapture specification, {_value!r}. "
234
+ "You may consider specifying `mergeron.RECConstants.INOUT` here, and "
235
+ 'assigning the default recapture rate as attribute, "recapture_rate" of '
236
+ "this `DiversionRatioBoundarySpec` object."
237
+ )
238
+ if _value is None and _instance.agg_method != UPPAggrSelector.MAX:
239
+ raise ValueError(
240
+ f"Specified aggregation method, {_instance.agg_method} requires a recapture specification."
241
+ )
242
+
243
+
244
+ @frozen
245
+ class DiversionRatioBoundary:
246
+ """
247
+ Diversion ratio specification, boundary coordinates, and area under boundary.
248
+
249
+ Along with the default diversion ratio and recapture rate,
250
+ a diversion ratio boundary specification includes the recapture form --
251
+ whether fixed for both merging firms' products ("proportional") or
252
+ consistent with share-proportionality, i.e., "inside-out";
253
+ the method of aggregating diversion ratios for the two products, and
254
+ the precision for the estimate of area under the divertion ratio boundary
255
+ (also defines the number of points on the boundary).
256
+
257
+ """
258
+
259
+ diversion_ratio: float = field(
260
+ kw_only=False,
261
+ default=0.065,
262
+ validator=(validators.instance_of(float), _divr_value_validator),
263
+ )
264
+
265
+ recapture_rate: float = field(
266
+ kw_only=False, default=0.85, validator=validators.instance_of(float)
267
+ )
268
+
269
+ recapture_form: RECConstants | None = field(
270
+ kw_only=True,
271
+ default=RECConstants.INOUT,
272
+ validator=(
273
+ validators.instance_of((type(None), RECConstants)),
274
+ _rec_spec_validator,
275
+ ),
276
+ )
277
+ """
278
+ The form of the recapture rate.
279
+
280
+ When :attr:`mergeron.RECConstants.INOUT`, the recapture rate for
281
+ he product having the smaller market-share is assumed to equal the default,
282
+ and the recapture rate for the product with the larger market-share is
283
+ computed assuming MNL demand. Fixed recapture rates are specified as
284
+ :attr:`mergeron.RECConstants.FIXED`. (To specify that recapture rates be
285
+ constructed from the generated purchase-probabilities for products in
286
+ the market and for the outside good, specify :attr:`mergeron.RECConstants.OUTIN`.)
287
+
288
+ The GUPPI boundary is a continuum of diversion ratio boundaries conditional on
289
+ price-cost margins, :math:`d_{ij} = g_i * p_i / (m_j * p_j)`,
290
+ with :math:`d_{ij}` the diverion ratio from product :math:`i` to product :math:`j`;
291
+ :math:`g_i` the GUPPI for product :math:`i`;
292
+ :math:`m_j` the margin for product :math:`j`; and
293
+ :math:`p_i, p_j` the prices of goods :math:`i, j`, respectively.
294
+
295
+ """
296
+
297
+ agg_method: UPPAggrSelector = field(
298
+ kw_only=True,
299
+ default=UPPAggrSelector.MAX,
300
+ validator=validators.instance_of(UPPAggrSelector),
301
+ )
302
+ """
303
+ Method for aggregating the distinct diversion ratio measures for the two products.
304
+
305
+ Distinct diversion ratio or GUPPI measures for the two merging-firms' products are
306
+ aggregated using the method specified by the `agg_method` attribute, which is specified
307
+ using the enum :class:`mergeron.UPPAggrSelector`.
308
+
309
+ """
310
+
311
+ precision: int = field(
312
+ kw_only=False, default=5, validator=validators.instance_of(int)
313
+ )
314
+ """
315
+ The number of decimal places of precision for the estimated area under the UPP boundary.
316
+
317
+ Leaving this attribute unspecified will result in the default precision,
318
+ which varies based on the `agg_method` attribute, reflecting
319
+ the limit of precision available from the underlying functions. The number of
320
+ boundary points generated is also defined based on this attribute.
321
+
322
+ """
323
+
324
+ coordinates: NDArray[np.float64] = field(init=False, kw_only=True)
325
+ """Market-share pairs as Cartesian coordinates of points on the diversion ratio boundary."""
326
+
327
+ area: float = field(init=False, kw_only=True)
328
+ """Area under the diversion ratio boundary."""
329
+
330
+ def __attrs_post_init__(self, /) -> None:
331
+ _share_ratio = critical_share_ratio(
332
+ self.diversion_ratio, r_bar=self.recapture_rate
333
+ )
334
+ _upp_agg_kwargs: gbfn.ShareRatioBoundaryKeywords = {
335
+ "recapture_form": getattr(self.recapture_form, "value", "inside-out"),
336
+ "prec": self.precision,
337
+ }
338
+ match self.agg_method:
339
+ case UPPAggrSelector.DIS:
340
+ _upp_agg_fn = gbfn.shrratio_boundary_wtd_avg
341
+ _upp_agg_kwargs |= {"agg_method": "distance", "weighting": None}
342
+ case UPPAggrSelector.AVG:
343
+ _upp_agg_fn = gbfn.shrratio_boundary_xact_avg # type: ignore
344
+ case UPPAggrSelector.MAX:
345
+ _upp_agg_fn = gbfn.shrratio_boundary_max # type: ignore
346
+ _upp_agg_kwargs = {"prec": 10} # replace here
347
+ case UPPAggrSelector.MIN:
348
+ _upp_agg_fn = gbfn.shrratio_boundary_min # type: ignore
349
+ _upp_agg_kwargs |= {"prec": 10} # update here
350
+ case _:
351
+ _upp_agg_fn = gbfn.shrratio_boundary_wtd_avg
352
+
353
+ _aggregator: Literal["arithmetic mean", "geometric mean", "distance"]
354
+ if self.agg_method.value.endswith("average"):
355
+ _aggregator = "arithmetic mean"
356
+ elif self.agg_method.value.endswith("geometric mean"):
357
+ _aggregator = "geometric mean"
358
+ else:
359
+ _aggregator = "distance"
360
+
361
+ _wgt_type: Literal["cross-product-share", "own-share", None]
362
+ if self.agg_method.value.startswith("cross-product-share"):
363
+ _wgt_type = "cross-product-share"
364
+ elif self.agg_method.value.startswith("own-share"):
365
+ _wgt_type = "own-share"
366
+ else:
367
+ _wgt_type = None
368
+
369
+ _upp_agg_kwargs |= {"agg_method": _aggregator, "weighting": _wgt_type}
370
+
371
+ _boundary = _upp_agg_fn(_share_ratio, self.recapture_rate, **_upp_agg_kwargs)
372
+ object.__setattr__(self, "coordinates", _boundary.coordinates)
373
+ object.__setattr__(self, "area", _boundary.area)
374
+
375
+
165
376
  def guppi_from_delta(
166
- _delta_bound: float = 0.01, /, *, m_star: float = 1.00, r_bar: float = 0.855
377
+ _delta_bound: float = 0.01, /, *, m_star: float = 1.00, r_bar: float = 0.8
167
378
  ) -> float:
168
379
  """
169
380
  Translate ∆HHI bound to GUPPI bound.
170
381
 
171
382
  Parameters
172
383
  ----------
173
- _deltasf
384
+ _delta_bound
174
385
  Specified ∆HHI bound.
175
386
  m_star
176
387
  Parametric price-cost margin.
@@ -182,7 +393,7 @@ def guppi_from_delta(
182
393
  GUPPI bound corresponding to ∆HHI bound, at given margin and recapture rate.
183
394
 
184
395
  """
185
- return round_cust(
396
+ return gbfn.round_cust(
186
397
  m_star * r_bar * (_s_m := np.sqrt(_delta_bound / 2)) / (1 - _s_m),
187
398
  frac=0.005,
188
399
  rounding_mode="ROUND_HALF_DOWN",
@@ -215,13 +426,13 @@ def critical_share_ratio(
215
426
  for given margin and recapture rate.
216
427
 
217
428
  """
218
- return round_cust(
429
+ return gbfn.round_cust(
219
430
  mpf(f"{_guppi_bound}") / mp.fmul(f"{m_star}", f"{r_bar}"), frac=frac
220
431
  )
221
432
 
222
433
 
223
434
  def share_from_guppi(
224
- _guppi_bound: float = 0.065, /, *, m_star: float = 1.00, r_bar: float = 0.855
435
+ _guppi_bound: float = 0.065, /, *, m_star: float = 1.00, r_bar: float = 0.8
225
436
  ) -> float:
226
437
  """
227
438
  Symmetric-firm share for given GUPPI, margin, and recapture rate.
@@ -243,197 +454,13 @@ def share_from_guppi(
243
454
 
244
455
  """
245
456
 
246
- return round_cust(
457
+ return gbfn.round_cust(
247
458
  (_d0 := critical_share_ratio(_guppi_bound, m_star=m_star, r_bar=r_bar))
248
459
  / (1 + _d0)
249
460
  )
250
461
 
251
462
 
252
- def hhi_delta_boundary(
253
- _dh_val: float = 0.01, /, *, prec: int = 5
254
- ) -> GuidelinesBoundary:
255
- """
256
- Generate the list of share combination on the ΔHHI boundary.
257
-
258
- Parameters
259
- ----------
260
- _dh_val:
261
- Merging-firms' ΔHHI bound.
262
- prec
263
- Number of decimal places for rounding reported shares.
264
-
265
- Returns
266
- -------
267
- Array of share-pairs, area under boundary.
268
-
269
- """
270
-
271
- _dh_val = mpf(f"{_dh_val}")
272
- _s_naught = 1 / 2 * (1 - mp.sqrt(1 - 2 * _dh_val))
273
- _s_mid = mp.sqrt(_dh_val / 2)
274
-
275
- _dh_step_sz = mp.power(10, -6)
276
- _s_1 = np.array(mp.arange(_s_mid, _s_naught - mp.eps, -_dh_step_sz))
277
- _s_2 = _dh_val / (2 * _s_1)
278
-
279
- # Boundary points
280
- _dh_half = np.row_stack((
281
- np.column_stack((_s_1, _s_2)),
282
- np.array([(mpf("0.0"), mpf("1.0"))]),
283
- ))
284
- _dh_bdry_pts = np.row_stack((np.flip(_dh_half, 0), np.flip(_dh_half[1:], 1)))
285
-
286
- _s_1_pts, _s_2_pts = np.split(_dh_bdry_pts, 2, axis=1)
287
- return GuidelinesBoundary(
288
- np.column_stack((
289
- np.array(_s_1_pts, np.float64),
290
- np.array(_s_2_pts, np.float64),
291
- )),
292
- dh_area(_dh_val, prec=prec),
293
- )
294
-
295
-
296
- def combined_share_boundary(
297
- _s_intcpt: float = 0.0625, /, *, bdry_dps: int = 10
298
- ) -> GuidelinesBoundary:
299
- """
300
- Share combinations on the merging-firms' combined share boundary.
301
-
302
- Assumes symmetric merging-firm margins. The combined-share is
303
- congruent to the post-merger HHI contribution boundary, as the
304
- post-merger HHI bound is the square of the combined-share bound.
305
-
306
- Parameters
307
- ----------
308
- _s_intcpt:
309
- Merging-firms' combined share.
310
- bdry_dps
311
- Number of decimal places for rounding reported shares.
312
-
313
- Returns
314
- -------
315
- Array of share-pairs, area under boundary.
316
-
317
- """
318
- _s_intcpt = mpf(f"{_s_intcpt}")
319
- _s_mid = _s_intcpt / 2
320
-
321
- _s1_pts = (0, _s_mid, _s_intcpt)
322
- return GuidelinesBoundary(
323
- np.column_stack((
324
- np.array(_s1_pts, np.float64),
325
- np.array(_s1_pts[::-1], np.float64),
326
- )),
327
- round(float(_s_intcpt * _s_mid), bdry_dps),
328
- )
329
-
330
-
331
- def hhi_pre_contrib_boundary(
332
- _hhi_contrib: float = 0.03125, /, *, bdry_dps: int = 5
333
- ) -> GuidelinesBoundary:
334
- """
335
- Share combinations on the premerger HHI contribution boundary.
336
-
337
- Parameters
338
- ----------
339
- _hhi_contrib:
340
- Merging-firms' pre-merger HHI contribution bound.
341
- bdry_dps
342
- Number of decimal places for rounding reported shares.
343
-
344
- Returns
345
- -------
346
- Array of share-pairs, area under boundary.
347
-
348
- """
349
- _hhi_contrib = mpf(f"{_hhi_contrib}")
350
- _s_mid = mp.sqrt(_hhi_contrib / 2)
351
-
352
- _bdry_step_sz = mp.power(10, -bdry_dps)
353
- # Range-limit is 0 less a step, which is -1 * step-size
354
- _s_1 = np.array(mp.arange(_s_mid, -_bdry_step_sz, -_bdry_step_sz), np.float64)
355
- _s_2 = np.sqrt(_hhi_contrib - _s_1**2).astype(np.float64)
356
- _bdry_pts_mid = np.column_stack((_s_1, _s_2))
357
- return GuidelinesBoundary(
358
- np.row_stack((np.flip(_bdry_pts_mid, 0), np.flip(_bdry_pts_mid[1:], 1))),
359
- round(float(mp.pi * _hhi_contrib / 4), bdry_dps),
360
- )
361
-
362
-
363
- def hhi_post_contrib_boundary(
364
- _hhi_contrib: float = 0.800, /, *, bdry_dps: int = 10
365
- ) -> GuidelinesBoundary:
366
- """
367
- Share combinations on the postmerger HHI contribution boundary.
368
-
369
- The post-merger HHI contribution boundary is identical to the
370
- combined-share boundary.
371
-
372
- Parameters
373
- ----------
374
- _hhi_contrib:
375
- Merging-firms' pre-merger HHI contribution bound.
376
- bdry_dps
377
- Number of decimal places for rounding reported shares.
378
-
379
- Returns
380
- -------
381
- Array of share-pairs, area under boundary.
382
-
383
- """
384
- return combined_share_boundary(np.sqrt(_hhi_contrib), bdry_dps=bdry_dps)
385
-
386
-
387
- def diversion_ratio_boundary(_bdry_spec: UPPBoundarySpec) -> GuidelinesBoundary:
388
- _share_ratio = critical_share_ratio(
389
- _bdry_spec.diversion_ratio, r_bar=_bdry_spec.rec
463
+ if __name__ == "__main__":
464
+ print(
465
+ "This module defines classes with methods for generating boundaries for concentration and diversion-ratio screens."
390
466
  )
391
- match _bdry_spec.agg_method:
392
- case UPPAggrSelector.AVG:
393
- return shrratio_boundary_xact_avg(
394
- _share_ratio,
395
- _bdry_spec.rec,
396
- recapture_form=_bdry_spec.recapture_form.value, # type: ignore
397
- prec=_bdry_spec.precision,
398
- )
399
- case UPPAggrSelector.MAX:
400
- return shrratio_boundary_max(
401
- _share_ratio, _bdry_spec.rec, prec=_bdry_spec.precision
402
- )
403
- case UPPAggrSelector.MIN:
404
- return shrratio_boundary_min(
405
- _share_ratio,
406
- _bdry_spec.rec,
407
- recapture_form=_bdry_spec.recapture_form.value, # type: ignore
408
- prec=_bdry_spec.precision,
409
- )
410
- case UPPAggrSelector.DIS:
411
- return shrratio_boundary_wtd_avg(
412
- _share_ratio,
413
- _bdry_spec.rec,
414
- agg_method="distance",
415
- weighting=None,
416
- recapture_form=_bdry_spec.recapture_form.value, # type: ignore
417
- prec=_bdry_spec.precision,
418
- )
419
- case _:
420
- _weighting = (
421
- "cross-product-share"
422
- if _bdry_spec.agg_method.value.startswith("cross-product-share")
423
- else "own-share"
424
- )
425
-
426
- _agg_method = (
427
- "arithmetic"
428
- if _bdry_spec.agg_method.value.endswith("average")
429
- else "distance"
430
- )
431
-
432
- return shrratio_boundary_wtd_avg(
433
- _share_ratio,
434
- _bdry_spec.rec,
435
- agg_method=_agg_method, # type: ignore
436
- weighting=_weighting, # type: ignore
437
- recapture_form=_bdry_spec.recapture_form.value, # type: ignore
438
- prec=_bdry_spec.precision,
439
- )