mergeron 2024.739127.1__py3-none-any.whl → 2024.739145.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mergeron might be problematic. Click here for more details.
- mergeron/__init__.py +5 -8
- mergeron/core/__init__.py +5 -0
- mergeron/core/{damodaran_margin_data.py → empirical_margin_distribution.py} +6 -3
- mergeron/core/ftc_merger_investigations_data.py +23 -20
- mergeron/core/guidelines_boundaries.py +89 -89
- mergeron/core/guidelines_boundary_functions.py +203 -61
- mergeron/core/guidelines_boundary_functions_extra.py +19 -25
- mergeron/core/pseudorandom_numbers.py +3 -3
- mergeron/demo/visualize_empirical_margin_distribution.py +2 -3
- mergeron/gen/__init__.py +63 -45
- mergeron/gen/data_generation.py +17 -17
- mergeron/gen/data_generation_functions.py +68 -58
- mergeron/gen/upp_tests.py +6 -9
- {mergeron-2024.739127.1.dist-info → mergeron-2024.739145.0.dist-info}/METADATA +5 -5
- mergeron-2024.739145.0.dist-info/RECORD +24 -0
- mergeron-2024.739127.1.dist-info/RECORD +0 -24
- {mergeron-2024.739127.1.dist-info → mergeron-2024.739145.0.dist-info}/WHEEL +0 -0
|
@@ -6,10 +6,11 @@ import numpy as np
|
|
|
6
6
|
from mpmath import mp, mpf # type: ignore
|
|
7
7
|
|
|
8
8
|
from .. import DEFAULT_REC_RATE, VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
|
|
9
|
+
from . import MPFloat
|
|
9
10
|
|
|
10
11
|
__version__ = VERSION
|
|
11
12
|
|
|
12
|
-
mp.
|
|
13
|
+
mp.dps = 32
|
|
13
14
|
mp.trap_complex = True
|
|
14
15
|
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ class ShareRatioBoundaryKeywords(TypedDict, total=False):
|
|
|
17
18
|
"""Keyword arguments for functions generating share ratio boundaries."""
|
|
18
19
|
|
|
19
20
|
recapture_form: Literal["inside-out", "proportional"]
|
|
20
|
-
|
|
21
|
+
dps: int
|
|
21
22
|
agg_method: Literal["arithmetic mean", "geometric mean", "distance"]
|
|
22
23
|
weighting: Literal["own-share", "cross-product-share", None]
|
|
23
24
|
|
|
@@ -33,7 +34,7 @@ class GuidelinesBoundary:
|
|
|
33
34
|
"""Area under the boundary."""
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def dh_area(_dh_val: float = 0.01, /, *,
|
|
37
|
+
def dh_area(_dh_val: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
37
38
|
R"""
|
|
38
39
|
Area under the ΔHHI boundary.
|
|
39
40
|
|
|
@@ -56,7 +57,7 @@ def dh_area(_dh_val: float = 0.01, /, *, prec: int = 9) -> float:
|
|
|
56
57
|
----------
|
|
57
58
|
_dh_val
|
|
58
59
|
Change in concentration.
|
|
59
|
-
|
|
60
|
+
dps
|
|
60
61
|
Specified precision in decimal places.
|
|
61
62
|
|
|
62
63
|
Returns
|
|
@@ -70,13 +71,11 @@ def dh_area(_dh_val: float = 0.01, /, *, prec: int = 9) -> float:
|
|
|
70
71
|
|
|
71
72
|
return round(
|
|
72
73
|
float(_s_naught + (_dh_val / 2) * (mp.ln(1 - _s_naught) - mp.ln(_s_naught))),
|
|
73
|
-
|
|
74
|
+
dps,
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
def hhi_delta_boundary(
|
|
78
|
-
_dh_val: float = 0.01, /, *, prec: int = 5
|
|
79
|
-
) -> GuidelinesBoundary:
|
|
78
|
+
def hhi_delta_boundary(_dh_val: float = 0.01, /, *, dps: int = 5) -> GuidelinesBoundary:
|
|
80
79
|
"""
|
|
81
80
|
Generate the list of share combination on the ΔHHI boundary.
|
|
82
81
|
|
|
@@ -84,7 +83,7 @@ def hhi_delta_boundary(
|
|
|
84
83
|
----------
|
|
85
84
|
_dh_val:
|
|
86
85
|
Merging-firms' ΔHHI bound.
|
|
87
|
-
|
|
86
|
+
dps
|
|
88
87
|
Number of decimal places for rounding reported shares.
|
|
89
88
|
|
|
90
89
|
Returns
|
|
@@ -106,20 +105,13 @@ def hhi_delta_boundary(
|
|
|
106
105
|
np.column_stack((_s_1, _s_2)),
|
|
107
106
|
np.array([(mpf("0.0"), mpf("1.0"))]),
|
|
108
107
|
))
|
|
109
|
-
_dh_bdry_pts = np.vstack((
|
|
108
|
+
_dh_bdry_pts = np.vstack((_dh_half[::-1], _dh_half[1:, ::-1]))
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
return GuidelinesBoundary(
|
|
113
|
-
np.column_stack((
|
|
114
|
-
np.array(_s_1_pts, np.float64),
|
|
115
|
-
np.array(_s_2_pts, np.float64),
|
|
116
|
-
)),
|
|
117
|
-
dh_area(_dh_val, prec=prec),
|
|
118
|
-
)
|
|
110
|
+
return GuidelinesBoundary(_dh_bdry_pts, dh_area(_dh_val, dps=dps))
|
|
119
111
|
|
|
120
112
|
|
|
121
113
|
def hhi_pre_contrib_boundary(
|
|
122
|
-
_hhi_contrib: float = 0.03125, /, *,
|
|
114
|
+
_hhi_contrib: float = 0.03125, /, *, dps: int = 5
|
|
123
115
|
) -> GuidelinesBoundary:
|
|
124
116
|
"""
|
|
125
117
|
Share combinations on the premerger HHI contribution boundary.
|
|
@@ -128,7 +120,7 @@ def hhi_pre_contrib_boundary(
|
|
|
128
120
|
----------
|
|
129
121
|
_hhi_contrib:
|
|
130
122
|
Merging-firms' pre-merger HHI contribution bound.
|
|
131
|
-
|
|
123
|
+
dps
|
|
132
124
|
Number of decimal places for rounding reported shares.
|
|
133
125
|
|
|
134
126
|
Returns
|
|
@@ -139,19 +131,19 @@ def hhi_pre_contrib_boundary(
|
|
|
139
131
|
_hhi_contrib = mpf(f"{_hhi_contrib}")
|
|
140
132
|
_s_mid = mp.sqrt(_hhi_contrib / 2)
|
|
141
133
|
|
|
142
|
-
_bdry_step_sz = mp.power(10, -
|
|
134
|
+
_bdry_step_sz = mp.power(10, -dps)
|
|
143
135
|
# Range-limit is 0 less a step, which is -1 * step-size
|
|
144
136
|
_s_1 = np.array(mp.arange(_s_mid, -_bdry_step_sz, -_bdry_step_sz), np.float64)
|
|
145
137
|
_s_2 = np.sqrt(_hhi_contrib - _s_1**2).astype(np.float64)
|
|
146
138
|
_bdry_pts_mid = np.column_stack((_s_1, _s_2))
|
|
147
139
|
return GuidelinesBoundary(
|
|
148
|
-
np.vstack((
|
|
149
|
-
round(float(mp.pi * _hhi_contrib / 4),
|
|
140
|
+
np.vstack((_bdry_pts_mid[::-1], _bdry_pts_mid[1:, ::-1])),
|
|
141
|
+
round(float(mp.pi * _hhi_contrib / 4), dps),
|
|
150
142
|
)
|
|
151
143
|
|
|
152
144
|
|
|
153
145
|
def combined_share_boundary(
|
|
154
|
-
_s_intcpt: float = 0.0625, /, *,
|
|
146
|
+
_s_intcpt: float = 0.0625, /, *, dps: int = 10
|
|
155
147
|
) -> GuidelinesBoundary:
|
|
156
148
|
"""
|
|
157
149
|
Share combinations on the merging-firms' combined share boundary.
|
|
@@ -164,7 +156,7 @@ def combined_share_boundary(
|
|
|
164
156
|
----------
|
|
165
157
|
_s_intcpt:
|
|
166
158
|
Merging-firms' combined share.
|
|
167
|
-
|
|
159
|
+
dps
|
|
168
160
|
Number of decimal places for rounding reported shares.
|
|
169
161
|
|
|
170
162
|
Returns
|
|
@@ -181,12 +173,12 @@ def combined_share_boundary(
|
|
|
181
173
|
np.array(_s1_pts, np.float64),
|
|
182
174
|
np.array(_s1_pts[::-1], np.float64),
|
|
183
175
|
)),
|
|
184
|
-
round(float(_s_intcpt * _s_mid),
|
|
176
|
+
round(float(_s_intcpt * _s_mid), dps),
|
|
185
177
|
)
|
|
186
178
|
|
|
187
179
|
|
|
188
180
|
def hhi_post_contrib_boundary(
|
|
189
|
-
_hhi_contrib: float = 0.800, /, *,
|
|
181
|
+
_hhi_contrib: float = 0.800, /, *, dps: int = 10
|
|
190
182
|
) -> GuidelinesBoundary:
|
|
191
183
|
"""
|
|
192
184
|
Share combinations on the postmerger HHI contribution boundary.
|
|
@@ -198,7 +190,7 @@ def hhi_post_contrib_boundary(
|
|
|
198
190
|
----------
|
|
199
191
|
_hhi_contrib:
|
|
200
192
|
Merging-firms' pre-merger HHI contribution bound.
|
|
201
|
-
|
|
193
|
+
dps
|
|
202
194
|
Number of decimal places for rounding reported shares.
|
|
203
195
|
|
|
204
196
|
Returns
|
|
@@ -206,7 +198,7 @@ def hhi_post_contrib_boundary(
|
|
|
206
198
|
Array of share-pairs, area under boundary.
|
|
207
199
|
|
|
208
200
|
"""
|
|
209
|
-
return combined_share_boundary(np.sqrt(_hhi_contrib),
|
|
201
|
+
return combined_share_boundary(np.sqrt(_hhi_contrib), dps=dps)
|
|
210
202
|
|
|
211
203
|
|
|
212
204
|
def shrratio_boundary_wtd_avg(
|
|
@@ -219,7 +211,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
219
211
|
] = "arithmetic mean",
|
|
220
212
|
weighting: Literal["own-share", "cross-product-share", None] = "own-share",
|
|
221
213
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
222
|
-
|
|
214
|
+
dps: int = 5,
|
|
223
215
|
) -> GuidelinesBoundary:
|
|
224
216
|
"""
|
|
225
217
|
Share combinations on the share-weighted average diversion ratio boundary.
|
|
@@ -237,7 +229,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
237
229
|
recapture_form
|
|
238
230
|
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
239
231
|
value for both merging firms ("proportional").
|
|
240
|
-
|
|
232
|
+
dps
|
|
241
233
|
Number of decimal places for rounding returned shares and area.
|
|
242
234
|
|
|
243
235
|
Returns
|
|
@@ -309,7 +301,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
309
301
|
"""
|
|
310
302
|
|
|
311
303
|
_delta_star = mpf(f"{_delta_star}")
|
|
312
|
-
_s_mid = _delta_star
|
|
304
|
+
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
313
305
|
|
|
314
306
|
# initial conditions
|
|
315
307
|
_gbdry_points = [(_s_mid, _s_mid)]
|
|
@@ -317,7 +309,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
317
309
|
_s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0.0, 0.0
|
|
318
310
|
|
|
319
311
|
# parameters for iteration
|
|
320
|
-
_gbd_step_sz = mp.power(10, -
|
|
312
|
+
_gbd_step_sz = mp.power(10, -dps)
|
|
321
313
|
_theta = _gbd_step_sz * (10 if weighting == "cross-product-share" else 1)
|
|
322
314
|
for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
|
|
323
315
|
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
@@ -346,7 +338,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
346
338
|
)
|
|
347
339
|
|
|
348
340
|
match agg_method:
|
|
349
|
-
case "geometric":
|
|
341
|
+
case "geometric mean":
|
|
350
342
|
_delta_test = mp.expm1(lerp(mp.log1p(_de_1), mp.log1p(_de_2), _r))
|
|
351
343
|
case "distance":
|
|
352
344
|
_delta_test = mp.sqrt(lerp(_de_1**2, _de_2**2, _r))
|
|
@@ -407,14 +399,13 @@ def shrratio_boundary_wtd_avg(
|
|
|
407
399
|
# Area under boundary
|
|
408
400
|
_gbdry_area_total = float(2 * _gbd_prtlarea - mp.power(_s_mid, "2"))
|
|
409
401
|
|
|
410
|
-
_gbdry_points
|
|
411
|
-
|
|
412
|
-
)
|
|
402
|
+
_gbdry_points.append((mpf("0.0"), _s_intcpt))
|
|
403
|
+
_gbd_pts_arr = np.array(_gbdry_points).astype(np.float64)
|
|
413
404
|
|
|
414
405
|
# Points defining boundary to point-of-symmetry
|
|
415
406
|
return GuidelinesBoundary(
|
|
416
|
-
np.vstack((
|
|
417
|
-
round(float(_gbdry_area_total),
|
|
407
|
+
np.vstack((_gbd_pts_arr[::-1], _gbd_pts_arr[1:, ::-1])),
|
|
408
|
+
round(float(_gbdry_area_total), dps),
|
|
418
409
|
)
|
|
419
410
|
|
|
420
411
|
|
|
@@ -424,7 +415,7 @@ def shrratio_boundary_xact_avg(
|
|
|
424
415
|
/,
|
|
425
416
|
*,
|
|
426
417
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
427
|
-
|
|
418
|
+
dps: int = 5,
|
|
428
419
|
) -> GuidelinesBoundary:
|
|
429
420
|
"""
|
|
430
421
|
Share combinations for the simple average GUPPI boundary with symmetric
|
|
@@ -472,7 +463,7 @@ def shrratio_boundary_xact_avg(
|
|
|
472
463
|
recapture_form
|
|
473
464
|
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
474
465
|
value for both merging firms ("proportional").
|
|
475
|
-
|
|
466
|
+
dps
|
|
476
467
|
Number of decimal places for rounding returned shares.
|
|
477
468
|
|
|
478
469
|
Returns
|
|
@@ -483,7 +474,7 @@ def shrratio_boundary_xact_avg(
|
|
|
483
474
|
|
|
484
475
|
_delta_star = mpf(f"{_delta_star}")
|
|
485
476
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
486
|
-
_gbd_step_sz = mp.power(10, -
|
|
477
|
+
_gbd_step_sz = mp.power(10, -dps)
|
|
487
478
|
|
|
488
479
|
_gbdry_points_start = np.array([(_s_mid, _s_mid)])
|
|
489
480
|
_s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
|
|
@@ -523,7 +514,7 @@ def shrratio_boundary_xact_avg(
|
|
|
523
514
|
np.einsum("i->", _nr_t2_mdr.astype(np.float64)),
|
|
524
515
|
np.einsum("i->", _nr_t2_s1.astype(np.float64)),
|
|
525
516
|
rtol=0,
|
|
526
|
-
atol=0.5 *
|
|
517
|
+
atol=0.5 * dps,
|
|
527
518
|
):
|
|
528
519
|
raise RuntimeError(
|
|
529
520
|
"Calculation of sq. root term in exact average GUPPI"
|
|
@@ -552,7 +543,162 @@ def shrratio_boundary_xact_avg(
|
|
|
552
543
|
|
|
553
544
|
_gbdry_points = np.vstack((
|
|
554
545
|
_gbdry_points_end,
|
|
555
|
-
|
|
546
|
+
_gbdry_points_inner[::-1],
|
|
547
|
+
_gbdry_points_start,
|
|
548
|
+
_gbdry_points_inner[:, ::-1],
|
|
549
|
+
_gbdry_points_end[:, ::-1],
|
|
550
|
+
)).astype(np.float64)
|
|
551
|
+
_s_2 = np.concatenate((np.array([_s_mid], np.float64), _s_2))
|
|
552
|
+
|
|
553
|
+
_gbdry_ends = [0, -1]
|
|
554
|
+
_gbdry_odds = np.array(range(1, len(_s_2), 2), np.int64)
|
|
555
|
+
_gbdry_evns = np.array(range(2, len(_s_2), 2), np.int64)
|
|
556
|
+
|
|
557
|
+
# Double the are under the curve, and subtract the double counted bit.
|
|
558
|
+
_gbdry_area_simpson = 2 * _gbd_step_sz * (
|
|
559
|
+
(4 / 3) * np.sum(_s_2.take(_gbdry_odds))
|
|
560
|
+
+ (2 / 3) * np.sum(_s_2.take(_gbdry_evns))
|
|
561
|
+
+ (1 / 3) * np.sum(_s_2.take(_gbdry_ends))
|
|
562
|
+
) - np.power(_s_mid, 2)
|
|
563
|
+
|
|
564
|
+
return GuidelinesBoundary(_gbdry_points, round(float(_gbdry_area_simpson), dps))
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def shrratio_boundary_xact_avg_mp(
|
|
568
|
+
_delta_star: float = 0.075,
|
|
569
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
570
|
+
/,
|
|
571
|
+
*,
|
|
572
|
+
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
573
|
+
dps: int = 5,
|
|
574
|
+
) -> GuidelinesBoundary:
|
|
575
|
+
"""
|
|
576
|
+
Share combinations for the simple average GUPPI boundary with symmetric
|
|
577
|
+
merging-firm margins.
|
|
578
|
+
|
|
579
|
+
Notes
|
|
580
|
+
-----
|
|
581
|
+
An analytical expression for the exact average boundary is derived
|
|
582
|
+
and plotted from the y-intercept to the ray of symmetry as follows::
|
|
583
|
+
|
|
584
|
+
from sympy import latex, plot as symplot, solve, symbols
|
|
585
|
+
|
|
586
|
+
s_1, s_2 = symbols("s_1 s_2")
|
|
587
|
+
|
|
588
|
+
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
589
|
+
d_hat = g_val / (r_val * m_val)
|
|
590
|
+
|
|
591
|
+
# recapture_form = "inside-out"
|
|
592
|
+
sag = solve(
|
|
593
|
+
(s_2 / (1 - s_1))
|
|
594
|
+
+ (s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1)))
|
|
595
|
+
- 2 * d_hat,
|
|
596
|
+
s_2
|
|
597
|
+
)[0]
|
|
598
|
+
symplot(
|
|
599
|
+
sag,
|
|
600
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
601
|
+
ylabel=s_2
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
# recapture_form = "proportional"
|
|
605
|
+
sag = solve((s_2/(1 - s_1)) + (s_1/(1 - s_2)) - 2 * d_hat, s_2)[0]
|
|
606
|
+
symplot(
|
|
607
|
+
sag,
|
|
608
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
609
|
+
ylabel=s_2
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
Parameters
|
|
613
|
+
----------
|
|
614
|
+
_delta_star
|
|
615
|
+
Share ratio (:math:`\\overline{d} / \\overline{r}`).
|
|
616
|
+
_r_val
|
|
617
|
+
Recapture ratio
|
|
618
|
+
recapture_form
|
|
619
|
+
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
620
|
+
value for both merging firms ("proportional").
|
|
621
|
+
dps
|
|
622
|
+
Number of decimal places for rounding returned shares.
|
|
623
|
+
|
|
624
|
+
Returns
|
|
625
|
+
-------
|
|
626
|
+
Array of share-pairs, area under boundary, area under boundary.
|
|
627
|
+
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
_delta_star = mpf(f"{_delta_star}")
|
|
631
|
+
_s_mid = _delta_star / (1 + _delta_star)
|
|
632
|
+
_gbd_step_sz = mp.power(10, -dps)
|
|
633
|
+
|
|
634
|
+
_gbdry_points_start = np.array([(_s_mid, _s_mid)])
|
|
635
|
+
_s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
|
|
636
|
+
if recapture_form == "inside-out":
|
|
637
|
+
_s_intcpt = mp.fdiv(
|
|
638
|
+
mp.fsub(
|
|
639
|
+
2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
|
|
640
|
+
),
|
|
641
|
+
2 * mpf(f"{_r_val}"),
|
|
642
|
+
)
|
|
643
|
+
_nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
644
|
+
|
|
645
|
+
_nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
646
|
+
_nr_sqrt_mdr2 = _nr_sqrt_mdr * _r_val
|
|
647
|
+
_nr_sqrt_md2r2 = _nr_sqrt_mdr2 * _delta_star
|
|
648
|
+
|
|
649
|
+
_nr_sqrt_t1 = _nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
650
|
+
_nr_sqrt_t2 = _nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
651
|
+
_nr_sqrt_t3 = _nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
652
|
+
_nr_sqrt_t4 = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
653
|
+
_nr_sqrt_t5 = _s_1 * (6 * _r_val - 2) + 1
|
|
654
|
+
|
|
655
|
+
_nr_t2_mdr = _nr_sqrt_t1 + _nr_sqrt_t2 + _nr_sqrt_t3 + _nr_sqrt_t4 + _nr_sqrt_t5
|
|
656
|
+
|
|
657
|
+
# Alternative grouping of terms in np.sqrt
|
|
658
|
+
_nr_sqrt_s1sq = (_s_1**2) * (
|
|
659
|
+
_nr_sqrt_md2r2 + _nr_sqrt_mdr2 - _nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
660
|
+
)
|
|
661
|
+
_nr_sqrt_s1 = _s_1 * (
|
|
662
|
+
-2 * _nr_sqrt_md2r2 - _nr_sqrt_mdr2 + 2 * _nr_sqrt_mdr + 6 * _r_val - 2
|
|
663
|
+
)
|
|
664
|
+
_nr_sqrt_nos1 = _nr_sqrt_md2r2 - _nr_sqrt_mdr + 1
|
|
665
|
+
|
|
666
|
+
_nr_t2_s1 = _nr_sqrt_s1sq + _nr_sqrt_s1 + _nr_sqrt_nos1
|
|
667
|
+
|
|
668
|
+
if not np.isclose(
|
|
669
|
+
np.einsum("i->", _nr_t2_mdr.astype(np.float64)),
|
|
670
|
+
np.einsum("i->", _nr_t2_s1.astype(np.float64)),
|
|
671
|
+
rtol=0,
|
|
672
|
+
atol=0.5 * dps,
|
|
673
|
+
):
|
|
674
|
+
raise RuntimeError(
|
|
675
|
+
"Calculation of sq. root term in exact average GUPPI"
|
|
676
|
+
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
_s_2 = (_nr_t1 - np.sqrt(_nr_t2_s1)) / (2 * _r_val)
|
|
680
|
+
|
|
681
|
+
else:
|
|
682
|
+
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
683
|
+
_s_2 = (
|
|
684
|
+
(1 / 2)
|
|
685
|
+
+ _delta_star
|
|
686
|
+
- _delta_star * _s_1
|
|
687
|
+
- np.sqrt(
|
|
688
|
+
((_delta_star**2) - 1) * (_s_1**2)
|
|
689
|
+
+ (-2 * (_delta_star**2) + _delta_star + 1) * _s_1
|
|
690
|
+
+ (_delta_star**2)
|
|
691
|
+
- _delta_star
|
|
692
|
+
+ (1 / 4)
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
_gbdry_points_inner = np.column_stack((_s_1, _s_2))
|
|
697
|
+
_gbdry_points_end = np.array([(mpf("0.0"), _s_intcpt)], np.float64)
|
|
698
|
+
|
|
699
|
+
_gbdry_points = np.vstack((
|
|
700
|
+
_gbdry_points_end,
|
|
701
|
+
_gbdry_points_inner[::-1],
|
|
556
702
|
_gbdry_points_start,
|
|
557
703
|
np.flip(_gbdry_points_inner, 1),
|
|
558
704
|
np.flip(_gbdry_points_end, 1),
|
|
@@ -570,11 +716,7 @@ def shrratio_boundary_xact_avg(
|
|
|
570
716
|
+ (1 / 3) * np.sum(_s_2.take(_gbdry_ends))
|
|
571
717
|
) - np.power(_s_mid, 2)
|
|
572
718
|
|
|
573
|
-
|
|
574
|
-
return GuidelinesBoundary(
|
|
575
|
-
np.column_stack((np.array(_s_1_pts), np.array(_s_2_pts))),
|
|
576
|
-
round(float(_gbdry_area_simpson), prec),
|
|
577
|
-
)
|
|
719
|
+
return GuidelinesBoundary(_gbdry_points, round(float(_gbdry_area_simpson), dps))
|
|
578
720
|
|
|
579
721
|
|
|
580
722
|
def shrratio_boundary_min(
|
|
@@ -583,7 +725,7 @@ def shrratio_boundary_min(
|
|
|
583
725
|
/,
|
|
584
726
|
*,
|
|
585
727
|
recapture_form: str = "inside-out",
|
|
586
|
-
|
|
728
|
+
dps: int = 10,
|
|
587
729
|
) -> GuidelinesBoundary:
|
|
588
730
|
"""
|
|
589
731
|
Share combinations on the minimum GUPPI boundary, with symmetric
|
|
@@ -605,7 +747,7 @@ def shrratio_boundary_min(
|
|
|
605
747
|
recapture_form
|
|
606
748
|
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
607
749
|
value for both merging firms ("proportional").
|
|
608
|
-
|
|
750
|
+
dps
|
|
609
751
|
Number of decimal places for rounding returned shares.
|
|
610
752
|
|
|
611
753
|
Returns
|
|
@@ -640,12 +782,12 @@ def shrratio_boundary_min(
|
|
|
640
782
|
_s1_pts, _gbd_area = np.array((0, _s_mid, _s_intcpt), np.float64), _s_mid
|
|
641
783
|
|
|
642
784
|
return GuidelinesBoundary(
|
|
643
|
-
np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area),
|
|
785
|
+
np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area), dps)
|
|
644
786
|
)
|
|
645
787
|
|
|
646
788
|
|
|
647
789
|
def shrratio_boundary_max(
|
|
648
|
-
_delta_star: float = 0.075, _r_val: float = DEFAULT_REC_RATE, /, *,
|
|
790
|
+
_delta_star: float = 0.075, _r_val: float = DEFAULT_REC_RATE, /, *, dps: int = 10
|
|
649
791
|
) -> GuidelinesBoundary:
|
|
650
792
|
"""
|
|
651
793
|
Share combinations on the minimum GUPPI boundary with symmetric
|
|
@@ -657,7 +799,7 @@ def shrratio_boundary_max(
|
|
|
657
799
|
Share ratio (:math:`\\overline{d} / \\overline{r}`).
|
|
658
800
|
_r_val
|
|
659
801
|
Recapture ratio.
|
|
660
|
-
|
|
802
|
+
dps
|
|
661
803
|
Number of decimal places for rounding returned shares.
|
|
662
804
|
|
|
663
805
|
Returns
|
|
@@ -680,14 +822,14 @@ def shrratio_boundary_max(
|
|
|
680
822
|
np.array(_s1_pts, np.float64),
|
|
681
823
|
np.array(_s1_pts[::-1], np.float64),
|
|
682
824
|
)),
|
|
683
|
-
round(float(_s_intcpt * _s_mid),
|
|
825
|
+
round(float(_s_intcpt * _s_mid), dps), # simplified calculation
|
|
684
826
|
)
|
|
685
827
|
|
|
686
828
|
|
|
687
829
|
def _shrratio_boundary_intcpt(
|
|
688
830
|
_s_2_pre: float,
|
|
689
|
-
_delta_star:
|
|
690
|
-
_r_val:
|
|
831
|
+
_delta_star: MPFloat,
|
|
832
|
+
_r_val: MPFloat,
|
|
691
833
|
/,
|
|
692
834
|
*,
|
|
693
835
|
recapture_form: Literal["inside-out", "proportional"],
|
|
@@ -717,11 +859,11 @@ def _shrratio_boundary_intcpt(
|
|
|
717
859
|
|
|
718
860
|
|
|
719
861
|
def lerp(
|
|
720
|
-
_x1: int | float |
|
|
721
|
-
_x2: int | float |
|
|
722
|
-
_r: float |
|
|
862
|
+
_x1: int | float | MPFloat | ArrayDouble | ArrayBIGINT = 3,
|
|
863
|
+
_x2: int | float | MPFloat | ArrayDouble | ArrayBIGINT = 1,
|
|
864
|
+
_r: float | MPFloat = 0.25,
|
|
723
865
|
/,
|
|
724
|
-
) -> float |
|
|
866
|
+
) -> float | MPFloat | ArrayDouble:
|
|
725
867
|
"""
|
|
726
868
|
From the function of the same name in the C++ standard [2]_
|
|
727
869
|
|
|
@@ -17,16 +17,12 @@ from scipy.spatial.distance import minkowski as distance_function # type: ignor
|
|
|
17
17
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
18
18
|
|
|
19
19
|
from .. import DEFAULT_REC_RATE, VERSION, ArrayDouble # noqa: TID252
|
|
20
|
-
from .
|
|
21
|
-
GuidelinesBoundary,
|
|
22
|
-
_shrratio_boundary_intcpt,
|
|
23
|
-
lerp,
|
|
24
|
-
)
|
|
20
|
+
from . import guidelines_boundary_functions as gbfn
|
|
25
21
|
|
|
26
22
|
__version__ = VERSION
|
|
27
23
|
|
|
28
24
|
|
|
29
|
-
mp.
|
|
25
|
+
mp.dps = 32
|
|
30
26
|
mp.trap_complex = True
|
|
31
27
|
|
|
32
28
|
|
|
@@ -37,7 +33,7 @@ class GuidelinesBoundaryCallable:
|
|
|
37
33
|
s_naught: float = 0
|
|
38
34
|
|
|
39
35
|
|
|
40
|
-
def dh_area_quad(_dh_val: float = 0.01, /, *,
|
|
36
|
+
def dh_area_quad(_dh_val: float = 0.01, /, *, dps: int = 9) -> float:
|
|
41
37
|
"""
|
|
42
38
|
Area under the ΔHHI boundary.
|
|
43
39
|
|
|
@@ -49,7 +45,7 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, prec: int = 9) -> float:
|
|
|
49
45
|
----------
|
|
50
46
|
_dh_val
|
|
51
47
|
Merging-firms' ΔHHI bound.
|
|
52
|
-
|
|
48
|
+
dps
|
|
53
49
|
Specified precision in decimal places.
|
|
54
50
|
|
|
55
51
|
Returns
|
|
@@ -65,7 +61,7 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, prec: int = 9) -> float:
|
|
|
65
61
|
float(
|
|
66
62
|
_s_naught + mp.quad(lambda x: _dh_val / (2 * x), [_s_naught, 1 - _s_naught])
|
|
67
63
|
),
|
|
68
|
-
|
|
64
|
+
dps,
|
|
69
65
|
)
|
|
70
66
|
|
|
71
67
|
|
|
@@ -95,7 +91,7 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
|
|
|
95
91
|
|
|
96
92
|
_hhi_bdry_area = 2 * (
|
|
97
93
|
_s_nought
|
|
98
|
-
+ mp.quad(lambdify(_s_1, _hhi_bdry, "mpmath"), (_s_nought, 1 - _s_nought))
|
|
94
|
+
+ mp.quad(lambdify(_s_1, _hhi_bdry, "mpmath"), (_s_nought, 1 - _s_nought)) # pyright: ignore
|
|
99
95
|
)
|
|
100
96
|
|
|
101
97
|
return GuidelinesBoundaryCallable(
|
|
@@ -163,7 +159,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
163
159
|
2
|
|
164
160
|
* (
|
|
165
161
|
_s_naught
|
|
166
|
-
+ mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (_s_naught, _s_mid))
|
|
162
|
+
+ mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (_s_naught, _s_mid)) # pyright: ignore
|
|
167
163
|
)
|
|
168
164
|
- (_s_mid**2 + _s_naught**2)
|
|
169
165
|
)
|
|
@@ -193,7 +189,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
193
189
|
),
|
|
194
190
|
(0, _s_mid),
|
|
195
191
|
)
|
|
196
|
-
).real
|
|
192
|
+
).real # pyright: ignore
|
|
197
193
|
- _s_mid**2
|
|
198
194
|
)
|
|
199
195
|
|
|
@@ -213,7 +209,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
213
209
|
|
|
214
210
|
_bdry_func = solve(_bdry_eqn, _s_2)[0]
|
|
215
211
|
_bdry_area = float(
|
|
216
|
-
2 * (mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (0, _s_mid)))
|
|
212
|
+
2 * (mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (0, _s_mid))) # pyright: ignore
|
|
217
213
|
- _s_mid**2
|
|
218
214
|
)
|
|
219
215
|
|
|
@@ -230,8 +226,8 @@ def shrratio_boundary_distance(
|
|
|
230
226
|
agg_method: Literal["arithmetic mean", "distance"] = "arithmetic mean",
|
|
231
227
|
weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
232
228
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
233
|
-
|
|
234
|
-
) -> GuidelinesBoundary:
|
|
229
|
+
dps: int = 5,
|
|
230
|
+
) -> gbfn.GuidelinesBoundary:
|
|
235
231
|
"""
|
|
236
232
|
Share combinations for the GUPPI boundaries using various aggregators with
|
|
237
233
|
symmetric merging-firm margins.
|
|
@@ -255,7 +251,7 @@ def shrratio_boundary_distance(
|
|
|
255
251
|
recapture_form
|
|
256
252
|
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
257
253
|
value for both merging firms ("proportional").
|
|
258
|
-
|
|
254
|
+
dps
|
|
259
255
|
Number of decimal places for rounding returned shares and area.
|
|
260
256
|
|
|
261
257
|
Returns
|
|
@@ -274,7 +270,7 @@ def shrratio_boundary_distance(
|
|
|
274
270
|
|
|
275
271
|
# parameters for iteration
|
|
276
272
|
_weights_base = (mpf("0.5"),) * 2
|
|
277
|
-
_gbd_step_sz = mp.power(10, -
|
|
273
|
+
_gbd_step_sz = mp.power(10, -dps)
|
|
278
274
|
_theta = _gbd_step_sz * (10 if weighting == "cross-product-share" else 1)
|
|
279
275
|
for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
|
|
280
276
|
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
@@ -289,7 +285,7 @@ def shrratio_boundary_distance(
|
|
|
289
285
|
while True:
|
|
290
286
|
_de_1 = _s_2 / (1 - _s_1)
|
|
291
287
|
_de_2 = (
|
|
292
|
-
_s_1 / (1 - lerp(_s_1, _s_2, _r_val))
|
|
288
|
+
_s_1 / (1 - gbfn.lerp(_s_1, _s_2, _r_val))
|
|
293
289
|
if recapture_form == "inside-out"
|
|
294
290
|
else _s_1 / (1 - _s_2)
|
|
295
291
|
)
|
|
@@ -344,7 +340,7 @@ def shrratio_boundary_distance(
|
|
|
344
340
|
else:
|
|
345
341
|
_s_2_oddsum -= _s_1_pre
|
|
346
342
|
|
|
347
|
-
_s_intcpt = _shrratio_boundary_intcpt(
|
|
343
|
+
_s_intcpt = gbfn._shrratio_boundary_intcpt(
|
|
348
344
|
_s_1_pre,
|
|
349
345
|
_delta_star,
|
|
350
346
|
_r_val,
|
|
@@ -369,11 +365,9 @@ def shrratio_boundary_distance(
|
|
|
369
365
|
# Area under boundary
|
|
370
366
|
_gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, "2")
|
|
371
367
|
|
|
372
|
-
_gbdry_points
|
|
373
|
-
np.float64
|
|
374
|
-
)
|
|
368
|
+
_gbdry_points.append((mpf("0.0"), _s_intcpt))
|
|
375
369
|
# Points defining boundary to point-of-symmetry
|
|
376
|
-
return GuidelinesBoundary(
|
|
377
|
-
np.vstack((
|
|
378
|
-
round(float(_gbdry_area_total),
|
|
370
|
+
return gbfn.GuidelinesBoundary(
|
|
371
|
+
np.vstack((_gbdry_points[::-1], np.flip(_gbdry_points[1:], 1))),
|
|
372
|
+
round(float(_gbdry_area_total), dps),
|
|
379
373
|
)
|
|
@@ -19,7 +19,7 @@ from .. import VERSION, ArrayDouble # noqa: TID252
|
|
|
19
19
|
__version__ = VERSION
|
|
20
20
|
|
|
21
21
|
NTHREADS = 2 * cpu_count()
|
|
22
|
-
|
|
22
|
+
DEFAULT_DIST_PARMS = np.array([0.0, 1.0], np.float64)
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def prng(_s: SeedSequence | None = None, /) -> np.random.Generator:
|
|
@@ -139,7 +139,7 @@ class MultithreadedRNG:
|
|
|
139
139
|
dist_type: Literal[
|
|
140
140
|
"Beta", "Dirichlet", "Gaussian", "Normal", "Random", "Uniform"
|
|
141
141
|
] = "Uniform",
|
|
142
|
-
dist_parms: ArrayDouble | None =
|
|
142
|
+
dist_parms: ArrayDouble | None = DEFAULT_DIST_PARMS,
|
|
143
143
|
seed_sequence: SeedSequence | None = None,
|
|
144
144
|
nthreads: int = NTHREADS,
|
|
145
145
|
):
|
|
@@ -166,7 +166,7 @@ class MultithreadedRNG:
|
|
|
166
166
|
|
|
167
167
|
self.dist_type = dist_type
|
|
168
168
|
|
|
169
|
-
if dist_parms is None or np.array_equal(dist_parms,
|
|
169
|
+
if dist_parms is None or np.array_equal(dist_parms, DEFAULT_DIST_PARMS):
|
|
170
170
|
match dist_type:
|
|
171
171
|
case "Uniform":
|
|
172
172
|
self.dist_type = "Random"
|