mergeron 2024.738936.0__py3-none-any.whl → 2024.738940.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/core/excel_helper.py +4 -2
- mergeron/core/guidelines_standards.py +368 -123
- mergeron/core/proportions_tests.py +9 -9
- mergeron/core/pseudorandom_numbers.py +1 -2
- mergeron/examples/concentration_as_diversion.py +96 -109
- mergeron/examples/investigations_stats_sim_tables.py +22 -29
- mergeron/examples/safeharbor_boundaries_for_mergers_with_asymmetric_shares.py +13 -13
- mergeron/examples/safeharbor_boundaries_for_symmetric_firm_mergers.py +3 -3
- mergeron/examples/sound_guppi_safeharbor.py +31 -28
- mergeron/examples/testIntrinsicClearanceRates.py +12 -12
- mergeron/examples/visualize_guidelines_tests.py +34 -29
- mergeron/gen/__init__.py +463 -0
- mergeron/gen/_data_generation_functions_nonpublic.py +626 -0
- mergeron/gen/data_generation.py +45 -992
- mergeron/gen/guidelines_tests.py +122 -99
- mergeron/gen/investigations_stats.py +10 -12
- mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +1 -1
- mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +1 -1
- {mergeron-2024.738936.0.dist-info → mergeron-2024.738940.0.dist-info}/METADATA +1 -1
- {mergeron-2024.738936.0.dist-info → mergeron-2024.738940.0.dist-info}/RECORD +21 -20
- {mergeron-2024.738936.0.dist-info → mergeron-2024.738940.0.dist-info}/WHEEL +0 -0
|
@@ -4,7 +4,6 @@ with a canvas on which to draw boundaries for Guidelines standards.
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from collections import namedtuple
|
|
8
7
|
from importlib.metadata import version
|
|
9
8
|
|
|
10
9
|
from .. import _PKG_NAME # noqa: TID252
|
|
@@ -12,19 +11,38 @@ from .. import _PKG_NAME # noqa: TID252
|
|
|
12
11
|
__version__ = version(_PKG_NAME)
|
|
13
12
|
|
|
14
13
|
import decimal
|
|
14
|
+
from dataclasses import dataclass
|
|
15
15
|
from typing import Any, Literal, TypeAlias
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
|
+
from attr import define, field
|
|
18
19
|
from mpmath import mp, mpf # type: ignore
|
|
19
20
|
from numpy.typing import NDArray
|
|
21
|
+
from scipy.spatial.distance import minkowski as distance_function
|
|
20
22
|
|
|
21
23
|
mp.prec = 80
|
|
22
24
|
mp.trap_complex = True
|
|
23
25
|
|
|
24
26
|
HMGPubYear: TypeAlias = Literal[1992, 2010, 2023]
|
|
25
|
-
GuidelinesSTD = namedtuple("GuidelinesSTD", "delta rec guppi divr cmcr ipr")
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
@dataclass(slots=True, frozen=True)
|
|
30
|
+
class GuidelinesBoundary:
|
|
31
|
+
coordinates: NDArray[np.float64]
|
|
32
|
+
area: float
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@define(slots=True, frozen=True)
|
|
36
|
+
class GuidelinesSTD:
|
|
37
|
+
delta: float
|
|
38
|
+
rec: float
|
|
39
|
+
guppi: float
|
|
40
|
+
divr: float
|
|
41
|
+
cmcr: float
|
|
42
|
+
ipr: float
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@define(slots=True, frozen=True)
|
|
28
46
|
class GuidelinesStandards:
|
|
29
47
|
"""
|
|
30
48
|
Guidelines standards by Guidelines publication year
|
|
@@ -32,76 +50,100 @@ class GuidelinesStandards:
|
|
|
32
50
|
Diversion ratio, GUPPI, CMCR, and IPR standards are constructed from
|
|
33
51
|
concentration standards.
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
"""
|
|
54
|
+
|
|
37
55
|
pub_year: HMGPubYear
|
|
38
|
-
Year of publication of the U.S. Horizontal Merger Guidelines (HMG);
|
|
39
|
-
1992, 2010, or 2023
|
|
40
|
-
safeharbor: GuidelinesSTD
|
|
41
|
-
ΔHHI safeharbor bound, default recapture rate, GUPPI bound and
|
|
42
|
-
diversion ratio limit at ΔHHI safeharbor
|
|
43
|
-
inferred_presumption: GuidelinesSTD
|
|
44
|
-
ΔHHI safeharbor bound, default recapture rate, GUPPI bound and
|
|
45
|
-
diversion ratio limit at enforcement margin for Guidelines
|
|
46
|
-
presumption of harm, interpreted strictly
|
|
47
|
-
presumption: GuidelinesSTD
|
|
48
|
-
ΔHHI safeharbor bound, default recapture rate, GUPPI bound and
|
|
49
|
-
diversion ratio limit at enforcement margin for Guidelines
|
|
50
|
-
presumption of harm, as typically interpreted in the literature
|
|
51
|
-
on merger enforcement
|
|
52
56
|
"""
|
|
57
|
+
Year of publication of the U.S. Horizontal Merger Guidelines (HMG)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
safeharbor: GuidelinesSTD = field(kw_only=True, default=None)
|
|
61
|
+
"""
|
|
62
|
+
Negative presumption defined on various measures
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
ΔHHI safeharbor bound, default recapture rate, GUPPI bound,
|
|
65
|
+
diversion ratio limit, CMCR, and IPR
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
inferred_presumption: GuidelinesSTD = field(kw_only=True, default=None)
|
|
69
|
+
"""
|
|
70
|
+
Inferred ΔHHI safeharbor presumption and related measures
|
|
71
|
+
|
|
72
|
+
ΔHHI bound inferred from strict numbers-equivalent
|
|
73
|
+
of (post-merger) HHI presumption, and corresponding default recapture rate,
|
|
74
|
+
GUPPI bound, diversion ratio limit, CMCR, and IPR
|
|
75
|
+
"""
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
presumption: GuidelinesSTD = field(kw_only=True, default=None)
|
|
78
|
+
"""
|
|
79
|
+
Guidelines ΔHHI safeharbor presumption and related measures
|
|
80
|
+
|
|
81
|
+
ΔHHI bound and corresponding default recapture rate, GUPPI bound,
|
|
82
|
+
diversion ratio limit, CMCR, and IPR
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __attrs_post_init__(self, /):
|
|
86
|
+
# In the 2023 Guidlines, the agencies do not define a
|
|
87
|
+
# negative presumption, or safeharbor. Practically speaking,
|
|
88
|
+
# given resource constraints and loss aversion, it is likely
|
|
89
|
+
# that staff only investigates mergers that meet the presumption;
|
|
90
|
+
# thus, here, the tentative delta safeharbor under
|
|
91
|
+
# the 2023 Guidelines is 100 points
|
|
62
92
|
_hhi_p, _dh_s, _dh_p = {
|
|
63
93
|
1992: (0.18, 0.005, 0.01),
|
|
64
94
|
2010: (0.25, 0.01, 0.02),
|
|
65
95
|
2023: (0.18, 0.01, 0.01),
|
|
66
|
-
}[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
}[self.pub_year]
|
|
97
|
+
|
|
98
|
+
object.__setattr__(
|
|
99
|
+
self,
|
|
100
|
+
"safeharbor",
|
|
101
|
+
GuidelinesSTD(
|
|
102
|
+
_dh_s,
|
|
103
|
+
_r := round_cust((_fc := int(np.ceil(1 / _hhi_p))) / (_fc + 1)),
|
|
104
|
+
_g_s := gbd_from_dsf(_dh_s, m_star=1.0, r_bar=_r),
|
|
105
|
+
_dr := round_cust(1 / (_fc + 1)),
|
|
106
|
+
_cmcr := 0.03, # Not strictly a Guidelines standard
|
|
107
|
+
_ipr := _g_s, # Not strictly a Guidelines standard
|
|
108
|
+
),
|
|
75
109
|
)
|
|
76
110
|
|
|
77
111
|
# inferred_presumption is relevant for 2010 Guidelines
|
|
78
|
-
|
|
112
|
+
object.__setattr__(
|
|
113
|
+
self,
|
|
114
|
+
"inferred_presumption",
|
|
115
|
+
(
|
|
116
|
+
GuidelinesSTD(
|
|
117
|
+
_dh_i := 2 * (0.5 / _fc) ** 2,
|
|
118
|
+
_r_i := round_cust((_fc - 1 / 2) / (_fc + 1 / 2)),
|
|
119
|
+
_g_i := gbd_from_dsf(_dh_i, m_star=1.0, r_bar=_r_i),
|
|
120
|
+
round_cust((1 / 2) / (_fc - 1 / 2)),
|
|
121
|
+
_cmcr,
|
|
122
|
+
_g_i,
|
|
123
|
+
)
|
|
124
|
+
if self.pub_year == 2010
|
|
125
|
+
else GuidelinesSTD(
|
|
126
|
+
_dh_i := 2 * (1 / (_fc + 1)) ** 2,
|
|
127
|
+
_r,
|
|
128
|
+
_g_i := gbd_from_dsf(_dh_i, m_star=1.0, r_bar=_r),
|
|
129
|
+
_dr,
|
|
130
|
+
_cmcr,
|
|
131
|
+
_g_i,
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
object.__setattr__(
|
|
137
|
+
self,
|
|
138
|
+
"presumption",
|
|
79
139
|
GuidelinesSTD(
|
|
80
|
-
|
|
140
|
+
_dh_p,
|
|
81
141
|
_r,
|
|
82
|
-
|
|
142
|
+
_g_p := gbd_from_dsf(_dh_p, m_star=1.0, r_bar=_r),
|
|
83
143
|
_dr,
|
|
84
144
|
_cmcr,
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
if _pub_year in (1992, 2023)
|
|
88
|
-
else GuidelinesSTD(
|
|
89
|
-
_dh_i := 2 * (_s := 0.5 / _fc) * _s,
|
|
90
|
-
_r_i := round_cust((_fc - 1 / 2) / (_fc + 1 / 2)),
|
|
91
|
-
_g_i := gbd_from_dsf(_dh_i, m_star=1.0, r_bar=_r_i),
|
|
92
|
-
round_cust((1 / 2) / (_fc - 1 / 2)),
|
|
93
|
-
_cmcr,
|
|
94
|
-
_g_i,
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
self.presumption = GuidelinesSTD(
|
|
99
|
-
_dh_p,
|
|
100
|
-
_r,
|
|
101
|
-
_g_p := gbd_from_dsf(_dh_p, m_star=1.0, r_bar=_r),
|
|
102
|
-
_dr,
|
|
103
|
-
_cmcr,
|
|
104
|
-
_ipr := _g_p,
|
|
145
|
+
_ipr := _g_p,
|
|
146
|
+
),
|
|
105
147
|
)
|
|
106
148
|
|
|
107
149
|
|
|
@@ -168,7 +210,7 @@ def lerp(
|
|
|
168
210
|
/,
|
|
169
211
|
) -> float | mpf | NDArray[np.float64]:
|
|
170
212
|
"""
|
|
171
|
-
From the
|
|
213
|
+
From the function of the same name in the C++ standard [2]_
|
|
172
214
|
|
|
173
215
|
Constructs the weighted average, :math:`w_1 x_1 + w_2 x_2`, where
|
|
174
216
|
:math:`w_1 = 1 - r` and :math:`w_2 = r`.
|
|
@@ -178,21 +220,35 @@ def lerp(
|
|
|
178
220
|
_x1, _x2
|
|
179
221
|
bounds :math:`x_1, x_2` to interpolate between.
|
|
180
222
|
_r
|
|
181
|
-
interpolation weight :math:`r` assigned to :math:`x_2
|
|
223
|
+
interpolation weight :math:`r` assigned to :math:`x_2`
|
|
182
224
|
|
|
183
225
|
Returns
|
|
184
226
|
-------
|
|
185
|
-
The linear interpolation, or weighted average,
|
|
227
|
+
The linear interpolation, or weighted average,
|
|
228
|
+
:math:`x_1 + r \\cdot (x_1 - x_2) \\equiv (1 - r) \\cdot x_1 + r \\cdot x_2`.
|
|
186
229
|
|
|
187
230
|
Raises
|
|
188
231
|
------
|
|
189
232
|
ValueError
|
|
190
233
|
If the interpolation weight is not in the interval, :math:`[0, 1]`.
|
|
191
234
|
|
|
235
|
+
References
|
|
236
|
+
----------
|
|
237
|
+
|
|
238
|
+
.. [2] C++ Reference, https://en.cppreference.com/w/cpp/numeric/lerp
|
|
239
|
+
|
|
192
240
|
"""
|
|
241
|
+
|
|
193
242
|
if not 0 <= _r <= 1:
|
|
194
243
|
raise ValueError("Specified interpolation weight must lie in [0, 1].")
|
|
195
|
-
|
|
244
|
+
elif _r == 0:
|
|
245
|
+
return _x1
|
|
246
|
+
elif _r == 1:
|
|
247
|
+
return _x2
|
|
248
|
+
elif _r == 0.5:
|
|
249
|
+
return 1 / 2 * (_x1 + _x2)
|
|
250
|
+
else:
|
|
251
|
+
return _r * _x2 + (1 - _r) * _x1
|
|
196
252
|
|
|
197
253
|
|
|
198
254
|
def gbd_from_dsf(
|
|
@@ -506,7 +562,7 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, dh_dps: int = 9) -> float:
|
|
|
506
562
|
|
|
507
563
|
def delta_hhi_boundary(
|
|
508
564
|
_dh_val: float = 0.01, /, *, dh_dps: int = 5
|
|
509
|
-
) ->
|
|
565
|
+
) -> GuidelinesBoundary:
|
|
510
566
|
"""
|
|
511
567
|
Generate the list of share combination on the ΔHHI boundary.
|
|
512
568
|
|
|
@@ -542,7 +598,7 @@ def delta_hhi_boundary(
|
|
|
542
598
|
_dh_bdry_pts = np.row_stack((np.flip(_dh_half, 0), np.flip(_dh_half[1:], 1)))
|
|
543
599
|
|
|
544
600
|
_s_1_pts, _s_2_pts = np.split(_dh_bdry_pts, 2, axis=1)
|
|
545
|
-
return (
|
|
601
|
+
return GuidelinesBoundary(
|
|
546
602
|
np.column_stack((
|
|
547
603
|
np.array(_s_1_pts, np.float64),
|
|
548
604
|
np.array(_s_2_pts, np.float64),
|
|
@@ -552,8 +608,8 @@ def delta_hhi_boundary(
|
|
|
552
608
|
|
|
553
609
|
|
|
554
610
|
def combined_share_boundary(
|
|
555
|
-
|
|
556
|
-
) ->
|
|
611
|
+
_s_intcpt: float = 0.0625, /, *, bdry_dps: int = 10
|
|
612
|
+
) -> GuidelinesBoundary:
|
|
557
613
|
"""
|
|
558
614
|
Share combinations on the merging-firms' combined share boundary.
|
|
559
615
|
|
|
@@ -563,7 +619,7 @@ def combined_share_boundary(
|
|
|
563
619
|
|
|
564
620
|
Parameters
|
|
565
621
|
----------
|
|
566
|
-
|
|
622
|
+
_s_intcpt:
|
|
567
623
|
Merging-firms' combined share.
|
|
568
624
|
bdry_dps
|
|
569
625
|
Number of decimal places for rounding reported shares.
|
|
@@ -573,22 +629,22 @@ def combined_share_boundary(
|
|
|
573
629
|
Array of share-pairs, area under boundary.
|
|
574
630
|
|
|
575
631
|
"""
|
|
576
|
-
|
|
577
|
-
_s_mid =
|
|
632
|
+
_s_intcpt = mpf(f"{_s_intcpt}")
|
|
633
|
+
_s_mid = _s_intcpt / 2
|
|
578
634
|
|
|
579
|
-
_s1_pts = (0, _s_mid,
|
|
580
|
-
return (
|
|
635
|
+
_s1_pts = (0, _s_mid, _s_intcpt)
|
|
636
|
+
return GuidelinesBoundary(
|
|
581
637
|
np.column_stack((
|
|
582
638
|
np.array(_s1_pts, np.float64),
|
|
583
639
|
np.array(_s1_pts[::-1], np.float64),
|
|
584
640
|
)),
|
|
585
|
-
round(float(
|
|
641
|
+
round(float(_s_intcpt * _s_mid), bdry_dps),
|
|
586
642
|
)
|
|
587
643
|
|
|
588
644
|
|
|
589
645
|
def hhi_pre_contrib_boundary(
|
|
590
646
|
_hhi_contrib: float = 0.03125, /, *, bdry_dps: int = 5
|
|
591
|
-
) ->
|
|
647
|
+
) -> GuidelinesBoundary:
|
|
592
648
|
"""
|
|
593
649
|
Share combinations on the premerger HHI contribution boundary.
|
|
594
650
|
|
|
@@ -612,15 +668,15 @@ def hhi_pre_contrib_boundary(
|
|
|
612
668
|
_s_1 = np.array(mp.arange(_s_mid, -_bdry_step_sz, -_bdry_step_sz), np.float64)
|
|
613
669
|
_s_2 = np.sqrt(_hhi_contrib - _s_1**2).astype(np.float64)
|
|
614
670
|
_bdry_pts_mid = np.column_stack((_s_1, _s_2))
|
|
615
|
-
return (
|
|
671
|
+
return GuidelinesBoundary(
|
|
616
672
|
np.row_stack((np.flip(_bdry_pts_mid, 0), np.flip(_bdry_pts_mid[1:], 1))),
|
|
617
673
|
round(float(mp.pi * _hhi_contrib / 4), bdry_dps),
|
|
618
674
|
)
|
|
619
675
|
|
|
620
676
|
|
|
621
|
-
def
|
|
677
|
+
def shrratio_boundary_max(
|
|
622
678
|
_delta_star: float = 0.075, _r_val: float = 0.80, /, *, gbd_dps: int = 10
|
|
623
|
-
) ->
|
|
679
|
+
) -> GuidelinesBoundary:
|
|
624
680
|
"""
|
|
625
681
|
Share combinations on the minimum GUPPI boundary with symmetric
|
|
626
682
|
merging-firm margins.
|
|
@@ -650,28 +706,28 @@ def shrratio_mgnsym_boundary_max(
|
|
|
650
706
|
# of function call with other shrratio_mgnsym_boundary functions
|
|
651
707
|
del _r_val
|
|
652
708
|
_delta_star = mpf(f"{_delta_star}")
|
|
653
|
-
|
|
709
|
+
_s_intcpt = _delta_star
|
|
654
710
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
655
711
|
|
|
656
|
-
_s1_pts = (0, _s_mid,
|
|
712
|
+
_s1_pts = (0, _s_mid, _s_intcpt)
|
|
657
713
|
|
|
658
|
-
return (
|
|
714
|
+
return GuidelinesBoundary(
|
|
659
715
|
np.column_stack((
|
|
660
716
|
np.array(_s1_pts, np.float64),
|
|
661
717
|
np.array(_s1_pts[::-1], np.float64),
|
|
662
718
|
)),
|
|
663
|
-
round(float(
|
|
719
|
+
round(float(_s_intcpt * _s_mid), gbd_dps), # simplified calculation
|
|
664
720
|
)
|
|
665
721
|
|
|
666
722
|
|
|
667
|
-
def
|
|
723
|
+
def shrratio_boundary_min(
|
|
668
724
|
_delta_star: float = 0.075,
|
|
669
725
|
_r_val: float = 0.80,
|
|
670
726
|
/,
|
|
671
727
|
*,
|
|
672
728
|
recapture_spec: str = "inside-out",
|
|
673
729
|
gbd_dps: int = 10,
|
|
674
|
-
) ->
|
|
730
|
+
) -> GuidelinesBoundary:
|
|
675
731
|
"""
|
|
676
732
|
Share combinations on the minimum GUPPI boundary, with symmetric
|
|
677
733
|
merging-firm margins.
|
|
@@ -710,7 +766,7 @@ def shrratio_mgnsym_boundary_min(
|
|
|
710
766
|
)
|
|
711
767
|
|
|
712
768
|
_delta_star = mpf(f"{_delta_star}")
|
|
713
|
-
|
|
769
|
+
_s_intcpt = mpf("1.00")
|
|
714
770
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
715
771
|
|
|
716
772
|
if recapture_spec == "inside-out":
|
|
@@ -725,19 +781,21 @@ def shrratio_mgnsym_boundary_min(
|
|
|
725
781
|
_smin_nr / _guppi_bdry_env_dr,
|
|
726
782
|
_s_mid,
|
|
727
783
|
_smax_nr / _guppi_bdry_env_dr,
|
|
728
|
-
|
|
784
|
+
_s_intcpt,
|
|
729
785
|
),
|
|
730
786
|
np.float64,
|
|
731
787
|
)
|
|
732
788
|
|
|
733
789
|
_gbd_area = (_smin_nr + (_smax_nr - _smin_nr) * _s_mid) / _guppi_bdry_env_dr
|
|
734
790
|
else:
|
|
735
|
-
_s1_pts, _gbd_area = np.array((0, _s_mid,
|
|
791
|
+
_s1_pts, _gbd_area = np.array((0, _s_mid, _s_intcpt), np.float64), _s_mid
|
|
736
792
|
|
|
737
|
-
return
|
|
793
|
+
return GuidelinesBoundary(
|
|
794
|
+
np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area), gbd_dps)
|
|
795
|
+
)
|
|
738
796
|
|
|
739
797
|
|
|
740
|
-
def
|
|
798
|
+
def shrratio_boundary_wtd_avg(
|
|
741
799
|
_delta_star: float = 0.075,
|
|
742
800
|
_r_val: float = 0.80,
|
|
743
801
|
/,
|
|
@@ -746,7 +804,7 @@ def shrratio_mgnsym_boundary_wtd_avg(
|
|
|
746
804
|
wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
747
805
|
recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
|
|
748
806
|
gbd_dps: int = 5,
|
|
749
|
-
) ->
|
|
807
|
+
) -> GuidelinesBoundary:
|
|
750
808
|
"""
|
|
751
809
|
Share combinations for the share-weighted average GUPPI boundary with symmetric
|
|
752
810
|
merging-firm margins.
|
|
@@ -842,10 +900,12 @@ def shrratio_mgnsym_boundary_wtd_avg(
|
|
|
842
900
|
_delta_star = mpf(f"{_delta_star}")
|
|
843
901
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
844
902
|
|
|
903
|
+
# initial conditions
|
|
845
904
|
_gbdry_points = [(_s_mid, _s_mid)]
|
|
846
905
|
_s_1_pre, _s_2_pre = _s_mid, _s_mid
|
|
847
906
|
_s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
|
|
848
907
|
|
|
908
|
+
# parameters for iteration
|
|
849
909
|
_gbd_step_sz = mp.power(10, -gbd_dps)
|
|
850
910
|
_theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
|
|
851
911
|
for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
|
|
@@ -883,12 +943,12 @@ def shrratio_mgnsym_boundary_wtd_avg(
|
|
|
883
943
|
_delta_test = lerp(_de_1, _de_2, _r)
|
|
884
944
|
|
|
885
945
|
if wgtng_policy == "cross-product-share":
|
|
886
|
-
|
|
946
|
+
_test_flag, _incr_decr = (_delta_test > _delta_star, -1)
|
|
887
947
|
else:
|
|
888
|
-
|
|
948
|
+
_test_flag, _incr_decr = (_delta_test < _delta_star, 1)
|
|
889
949
|
|
|
890
|
-
if
|
|
891
|
-
_s_2 +=
|
|
950
|
+
if _test_flag:
|
|
951
|
+
_s_2 += _incr_decr * _gbd_step_sz
|
|
892
952
|
else:
|
|
893
953
|
break
|
|
894
954
|
|
|
@@ -916,28 +976,34 @@ def shrratio_mgnsym_boundary_wtd_avg(
|
|
|
916
976
|
# Area under boundary
|
|
917
977
|
_gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, 2)
|
|
918
978
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
979
|
+
match wgtng_policy:
|
|
980
|
+
case "cross-product-share":
|
|
981
|
+
_s_intcpt = _delta_star
|
|
982
|
+
case "own-product-share":
|
|
983
|
+
_s_intcpt = mpf("1.0")
|
|
984
|
+
case None if avg_method == "distance":
|
|
985
|
+
_s_intcpt = _delta_star * mp.sqrt("2")
|
|
986
|
+
case _:
|
|
987
|
+
_s_intcpt = _s_2_pre
|
|
988
|
+
|
|
989
|
+
_gbdry_points = np.row_stack((_gbdry_points, (mpf("0.0"), _s_intcpt))).astype(
|
|
990
|
+
np.float64
|
|
991
|
+
)
|
|
926
992
|
# Points defining boundary to point-of-symmetry
|
|
927
|
-
return (
|
|
993
|
+
return GuidelinesBoundary(
|
|
928
994
|
np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
|
|
929
995
|
round(float(_gbdry_area_total), gbd_dps),
|
|
930
996
|
)
|
|
931
997
|
|
|
932
998
|
|
|
933
|
-
def
|
|
999
|
+
def shrratio_boundary_xact_avg(
|
|
934
1000
|
_delta_star: float = 0.075,
|
|
935
1001
|
_r_val: float = 0.80,
|
|
936
1002
|
/,
|
|
937
1003
|
*,
|
|
938
1004
|
recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
|
|
939
1005
|
gbd_dps: int = 5,
|
|
940
|
-
) ->
|
|
1006
|
+
) -> GuidelinesBoundary:
|
|
941
1007
|
"""
|
|
942
1008
|
Share combinations for the simple average GUPPI boundary with symmetric
|
|
943
1009
|
merging-firm margins.
|
|
@@ -1009,13 +1075,13 @@ def shrratio_mgnsym_boundary_xact_avg(
|
|
|
1009
1075
|
_gbdry_points_start = np.array([(_s_mid, _s_mid)])
|
|
1010
1076
|
_s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
|
|
1011
1077
|
if recapture_spec == "inside-out":
|
|
1012
|
-
|
|
1078
|
+
_s_intcpt = mp.fdiv(
|
|
1013
1079
|
mp.fsub(
|
|
1014
1080
|
2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
|
|
1015
1081
|
),
|
|
1016
1082
|
2 * mpf(f"{_r_val}"),
|
|
1017
1083
|
)
|
|
1018
|
-
_nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
1084
|
+
_nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val) # type: ignore
|
|
1019
1085
|
|
|
1020
1086
|
_nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
1021
1087
|
_nr_sqrt_mdr2 = _nr_sqrt_mdr * _r_val
|
|
@@ -1040,9 +1106,9 @@ def shrratio_mgnsym_boundary_xact_avg(
|
|
|
1040
1106
|
|
|
1041
1107
|
_nr_t2_s1 = _nr_sqrt_s1sq + _nr_sqrt_s1 + _nr_sqrt_nos1
|
|
1042
1108
|
|
|
1043
|
-
if not np.isclose(
|
|
1044
|
-
np.einsum("i->", _nr_t2_mdr.astype(np.float64)), # from mpf to float64
|
|
1045
|
-
np.einsum("i->", _nr_t2_s1.astype(np.float64)),
|
|
1109
|
+
if not np.isclose( # type: ignore
|
|
1110
|
+
np.einsum("i->", _nr_t2_mdr.astype(np.float64)), # type: ignore from mpf to float64
|
|
1111
|
+
np.einsum("i->", _nr_t2_s1.astype(np.float64)), # type: ignore from mpf to float64
|
|
1046
1112
|
rtol=0,
|
|
1047
1113
|
atol=0.5 * gbd_dps,
|
|
1048
1114
|
):
|
|
@@ -1054,7 +1120,7 @@ def shrratio_mgnsym_boundary_xact_avg(
|
|
|
1054
1120
|
_s_2 = (_nr_t1 - np.sqrt(_nr_t2_s1)) / (2 * _r_val)
|
|
1055
1121
|
|
|
1056
1122
|
else:
|
|
1057
|
-
|
|
1123
|
+
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
1058
1124
|
_s_2 = (
|
|
1059
1125
|
(1 / 2)
|
|
1060
1126
|
+ _delta_star
|
|
@@ -1069,7 +1135,7 @@ def shrratio_mgnsym_boundary_xact_avg(
|
|
|
1069
1135
|
)
|
|
1070
1136
|
|
|
1071
1137
|
_gbdry_points_inner = np.column_stack((_s_1, _s_2))
|
|
1072
|
-
_gbdry_points_end = np.array([(mpf("0.0"),
|
|
1138
|
+
_gbdry_points_end = np.array([(mpf("0.0"), _s_intcpt)], np.float64)
|
|
1073
1139
|
|
|
1074
1140
|
_gbdry_points = np.row_stack((
|
|
1075
1141
|
_gbdry_points_end,
|
|
@@ -1092,13 +1158,13 @@ def shrratio_mgnsym_boundary_xact_avg(
|
|
|
1092
1158
|
) - np.power(_s_mid, 2)
|
|
1093
1159
|
|
|
1094
1160
|
_s_1_pts, _s_2_pts = np.split(_gbdry_points, 2, axis=1)
|
|
1095
|
-
return (
|
|
1161
|
+
return GuidelinesBoundary(
|
|
1096
1162
|
np.column_stack((np.array(_s_1_pts), np.array(_s_2_pts))),
|
|
1097
1163
|
round(float(_gbdry_area_simpson), gbd_dps),
|
|
1098
1164
|
)
|
|
1099
1165
|
|
|
1100
1166
|
|
|
1101
|
-
def
|
|
1167
|
+
def shrratio_boundary_avg(
|
|
1102
1168
|
_delta_star: float = 0.075,
|
|
1103
1169
|
_r_val: float = 0.80,
|
|
1104
1170
|
/,
|
|
@@ -1106,11 +1172,17 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1106
1172
|
avg_method: Literal["arithmetic", "geometric", "distance"] = "arithmetic",
|
|
1107
1173
|
recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
|
|
1108
1174
|
gbd_dps: int = 5,
|
|
1109
|
-
) ->
|
|
1175
|
+
) -> GuidelinesBoundary:
|
|
1110
1176
|
"""
|
|
1111
1177
|
Share combinations along the average GUPPI boundary, with
|
|
1112
1178
|
symmetric merging-firm margins.
|
|
1113
1179
|
|
|
1180
|
+
Reimplements the unweighted average and distance estimations from function,
|
|
1181
|
+
`shrratio_boundary_wtd_avg`. This reimplementation
|
|
1182
|
+
is primarifly useful for testing the output of `shrratio_boundary_wtd_avg`
|
|
1183
|
+
as it tests considerably slower.
|
|
1184
|
+
|
|
1185
|
+
|
|
1114
1186
|
Parameters
|
|
1115
1187
|
----------
|
|
1116
1188
|
_delta_star
|
|
@@ -1118,7 +1190,7 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1118
1190
|
_r_val
|
|
1119
1191
|
Recapture ratio.
|
|
1120
1192
|
avg_method
|
|
1121
|
-
Whether "arithmetic", "geometric", or "
|
|
1193
|
+
Whether "arithmetic", "geometric", or "distance".
|
|
1122
1194
|
recapture_spec
|
|
1123
1195
|
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
1124
1196
|
value for both merging firms ("proportional").
|
|
@@ -1137,7 +1209,7 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1137
1209
|
"Margin-adjusted benchmark share ratio cannot exceed 1."
|
|
1138
1210
|
)
|
|
1139
1211
|
|
|
1140
|
-
if avg_method not in (_avgmthds := ("arithmetic", "geometric", "
|
|
1212
|
+
if avg_method not in (_avgmthds := ("arithmetic", "geometric", "distance")):
|
|
1141
1213
|
raise ValueError(
|
|
1142
1214
|
f"Averarging method, {f'"{avg_method}"'} is invalid. "
|
|
1143
1215
|
f"Must be one of, {_avgmthds!r}."
|
|
@@ -1151,18 +1223,21 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1151
1223
|
|
|
1152
1224
|
_delta_star = mpf(f"{_delta_star}")
|
|
1153
1225
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
1154
|
-
_gbd_step_sz = mp.power(10, -gbd_dps)
|
|
1155
1226
|
|
|
1227
|
+
# initial conditions
|
|
1156
1228
|
_s_2 = _s_mid
|
|
1157
1229
|
_s_2_oddval = True
|
|
1158
1230
|
_s_2_oddsum = 0
|
|
1159
1231
|
_s_2_evnsum = 0
|
|
1160
1232
|
_gbdry_points = [(_s_mid, _s_mid)]
|
|
1233
|
+
|
|
1234
|
+
# parameters for iteration
|
|
1235
|
+
_gbd_step_sz = mp.power(10, -gbd_dps)
|
|
1161
1236
|
for _s_1 in mp.arange(_s_mid, 0, -_gbd_step_sz):
|
|
1162
1237
|
_s_1 -= _gbd_step_sz
|
|
1163
1238
|
while True:
|
|
1164
|
-
|
|
1165
|
-
|
|
1239
|
+
_delta_12 = _s_2 / (1 - _s_1)
|
|
1240
|
+
_delta_21 = (
|
|
1166
1241
|
_s_1 / (1 - _s_2)
|
|
1167
1242
|
if recapture_spec == "proportional"
|
|
1168
1243
|
else _s_1 / (1 - lerp(_s_1, _s_2, _r_val))
|
|
@@ -1170,13 +1245,21 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1170
1245
|
|
|
1171
1246
|
match avg_method:
|
|
1172
1247
|
case "geometric":
|
|
1173
|
-
|
|
1248
|
+
_delta_test = mp.sqrt(_delta_12 * _delta_21)
|
|
1174
1249
|
case "distance":
|
|
1175
|
-
|
|
1250
|
+
# _delta_test = mp.sqrt(mp.fdiv((_delta_12**2 + _delta_21**2), "2"))
|
|
1251
|
+
_delta_test = mp.sqrt(
|
|
1252
|
+
mp.fdiv(
|
|
1253
|
+
mp.fsum(
|
|
1254
|
+
mp.power(f"{_g}", "2") for _g in (_delta_12, _delta_21)
|
|
1255
|
+
),
|
|
1256
|
+
"2",
|
|
1257
|
+
)
|
|
1258
|
+
)
|
|
1176
1259
|
case _:
|
|
1177
|
-
|
|
1260
|
+
_delta_test = mp.fdiv(_delta_12 + _delta_21, "2")
|
|
1178
1261
|
|
|
1179
|
-
if
|
|
1262
|
+
if _delta_test < _delta_star:
|
|
1180
1263
|
_s_2 += _gbd_step_sz
|
|
1181
1264
|
else:
|
|
1182
1265
|
break
|
|
@@ -1189,16 +1272,178 @@ def shrratio_mgnsym_boundary_avg(
|
|
|
1189
1272
|
|
|
1190
1273
|
# Starting at _s_id - _gbd_step_sz means _s_1 is not always
|
|
1191
1274
|
# an even multiple of _gbd_step_sz
|
|
1192
|
-
|
|
1275
|
+
_s_intcpt = _s_2
|
|
1193
1276
|
|
|
1194
1277
|
_gbd_prtlarea = 2 * _gbd_step_sz * (
|
|
1195
1278
|
mp.fmul(4 / 3, _s_2_oddsum)
|
|
1196
1279
|
+ mp.fmul(2 / 3, _s_2_evnsum)
|
|
1197
|
-
+ mp.fmul(1 / 3, _s_mid +
|
|
1280
|
+
+ mp.fmul(1 / 3, _s_mid + _s_intcpt)
|
|
1198
1281
|
) - mp.power(_s_mid, 2)
|
|
1199
1282
|
|
|
1200
1283
|
_gbdry_points = np.array(_gbdry_points, np.float64)
|
|
1201
|
-
return (
|
|
1284
|
+
return GuidelinesBoundary(
|
|
1202
1285
|
np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
|
|
1203
1286
|
round(float(_gbd_prtlarea), gbd_dps),
|
|
1204
1287
|
)
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
def shrratio_boundary_distance(
|
|
1291
|
+
_delta_star: float = 0.075,
|
|
1292
|
+
_r_val: float = 0.80,
|
|
1293
|
+
/,
|
|
1294
|
+
*,
|
|
1295
|
+
avg_method: Literal["arithmetic", "distance"] = "arithmetic",
|
|
1296
|
+
wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
1297
|
+
recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
|
|
1298
|
+
gbd_dps: int = 5,
|
|
1299
|
+
) -> GuidelinesBoundary:
|
|
1300
|
+
"""
|
|
1301
|
+
Share combinations for the GUPPI boundaries using various aggregators with
|
|
1302
|
+
symmetric merging-firm margins.
|
|
1303
|
+
|
|
1304
|
+
Reimplements the arithmetic-averages and distance estimations from function,
|
|
1305
|
+
`shrratio_boundary_wtd_avg`but uses the Minkowski-distance function,
|
|
1306
|
+
`scipy.spatial.distance.minkowski` for all aggregators. This reimplementation
|
|
1307
|
+
is primarifly useful for testing the output of `shrratio_boundary_wtd_avg`
|
|
1308
|
+
as it tests considerably slower.
|
|
1309
|
+
|
|
1310
|
+
Parameters
|
|
1311
|
+
----------
|
|
1312
|
+
_delta_star
|
|
1313
|
+
corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
|
|
1314
|
+
_r_val
|
|
1315
|
+
recapture ratio
|
|
1316
|
+
avg_method
|
|
1317
|
+
Whether "arithmetic", "geometric", or "distance".
|
|
1318
|
+
wgtng_policy
|
|
1319
|
+
Whether "own-share" or "cross-product-share".
|
|
1320
|
+
recapture_spec
|
|
1321
|
+
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
1322
|
+
value for both merging firms ("proportional").
|
|
1323
|
+
gbd_dps
|
|
1324
|
+
Number of decimal places for rounding returned shares and area.
|
|
1325
|
+
|
|
1326
|
+
Returns
|
|
1327
|
+
-------
|
|
1328
|
+
Array of share-pairs, area under boundary.
|
|
1329
|
+
|
|
1330
|
+
"""
|
|
1331
|
+
|
|
1332
|
+
if _delta_star > 1:
|
|
1333
|
+
raise ValueError(
|
|
1334
|
+
"Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
_delta_star = mpf(f"{_delta_star}")
|
|
1338
|
+
_s_mid = _delta_star / (1 + _delta_star)
|
|
1339
|
+
|
|
1340
|
+
# initial conditions
|
|
1341
|
+
_gbdry_points = [(_s_mid, _s_mid)]
|
|
1342
|
+
_s_1_pre, _s_2_pre = _s_mid, _s_mid
|
|
1343
|
+
_s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
|
|
1344
|
+
|
|
1345
|
+
# parameters for iteration
|
|
1346
|
+
_weights_base = (mpf("0.5"),) * 2
|
|
1347
|
+
_gbd_step_sz = mp.power(10, -gbd_dps)
|
|
1348
|
+
_theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
|
|
1349
|
+
for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
|
|
1350
|
+
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
1351
|
+
# increment _s_2 after each iteration in which our algorithm
|
|
1352
|
+
# finds (s1, s2) on the boundary
|
|
1353
|
+
_s_2 = _s_2_pre * (1 + _theta)
|
|
1354
|
+
|
|
1355
|
+
if (_s_1 + _s_2) > mpf("0.99875"):
|
|
1356
|
+
# 1: # We lose accuracy at 3-9s and up
|
|
1357
|
+
break
|
|
1358
|
+
|
|
1359
|
+
while True:
|
|
1360
|
+
_de_1 = _s_2 / (1 - _s_1)
|
|
1361
|
+
_de_2 = (
|
|
1362
|
+
_s_1 / (1 - lerp(_s_1, _s_2, _r_val))
|
|
1363
|
+
if recapture_spec == "inside-out"
|
|
1364
|
+
else _s_1 / (1 - _s_2)
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
_weights_i = (
|
|
1368
|
+
(
|
|
1369
|
+
_w1 := mp.fdiv(
|
|
1370
|
+
_s_2 if wgtng_policy == "cross-product-share" else _s_1,
|
|
1371
|
+
_s_1 + _s_2,
|
|
1372
|
+
),
|
|
1373
|
+
1 - _w1,
|
|
1374
|
+
)
|
|
1375
|
+
if wgtng_policy
|
|
1376
|
+
else _weights_base
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
match avg_method:
|
|
1380
|
+
case "arithmetic":
|
|
1381
|
+
_delta_test = distance_function(
|
|
1382
|
+
(_de_1, _de_2), (0.0, 0.0), p=1, w=_weights_i
|
|
1383
|
+
)
|
|
1384
|
+
case "distance":
|
|
1385
|
+
_delta_test = distance_function(
|
|
1386
|
+
(_de_1, _de_2), (0.0, 0.0), p=2, w=_weights_i
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
if wgtng_policy == "cross-product-share":
|
|
1390
|
+
_test_flag, _incr_decr = (_delta_test > _delta_star, -1)
|
|
1391
|
+
else:
|
|
1392
|
+
_test_flag, _incr_decr = (_delta_test < _delta_star, 1)
|
|
1393
|
+
|
|
1394
|
+
if _test_flag:
|
|
1395
|
+
_s_2 += _incr_decr * _gbd_step_sz
|
|
1396
|
+
else:
|
|
1397
|
+
break
|
|
1398
|
+
|
|
1399
|
+
# Build-up boundary points
|
|
1400
|
+
_gbdry_points.append((_s_1, _s_2))
|
|
1401
|
+
|
|
1402
|
+
# Build up area terms
|
|
1403
|
+
_s_2_oddsum += _s_2 if _s_2_oddval else 0
|
|
1404
|
+
_s_2_evnsum += _s_2 if not _s_2_oddval else 0
|
|
1405
|
+
_s_2_oddval = not _s_2_oddval
|
|
1406
|
+
|
|
1407
|
+
# Hold share points
|
|
1408
|
+
_s_2_pre = _s_2
|
|
1409
|
+
_s_1_pre = _s_1
|
|
1410
|
+
|
|
1411
|
+
_gbd_prtlarea = _gbd_step_sz * (
|
|
1412
|
+
(4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _delta_star) / 3
|
|
1413
|
+
if wgtng_policy == "cross-product-share"
|
|
1414
|
+
else (
|
|
1415
|
+
(4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _s_2_pre) / 3
|
|
1416
|
+
+ _s_1_pre * (1 + _s_2_pre) / 2
|
|
1417
|
+
)
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
# Area under boundary
|
|
1421
|
+
_gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, 2)
|
|
1422
|
+
|
|
1423
|
+
match wgtng_policy:
|
|
1424
|
+
case "cross-product-share":
|
|
1425
|
+
_s_intcpt = _delta_star
|
|
1426
|
+
case "own-product-share":
|
|
1427
|
+
_s_intcpt = mpf("1.0")
|
|
1428
|
+
case None if avg_method == "distance":
|
|
1429
|
+
_s_intcpt = _delta_star * mp.sqrt("2")
|
|
1430
|
+
case None if avg_method == "arithmetic" and recapture_spec == "inside-out":
|
|
1431
|
+
_s_intcpt = mp.fdiv(
|
|
1432
|
+
mp.fsub(
|
|
1433
|
+
2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
|
|
1434
|
+
),
|
|
1435
|
+
2 * mpf(f"{_r_val}"),
|
|
1436
|
+
)
|
|
1437
|
+
case None if avg_method == "arithmetic":
|
|
1438
|
+
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
1439
|
+
case _:
|
|
1440
|
+
_s_intcpt = _s_2_pre
|
|
1441
|
+
|
|
1442
|
+
_gbdry_points = np.row_stack((_gbdry_points, (mpf("0.0"), _s_intcpt))).astype(
|
|
1443
|
+
np.float64
|
|
1444
|
+
)
|
|
1445
|
+
# Points defining boundary to point-of-symmetry
|
|
1446
|
+
return GuidelinesBoundary(
|
|
1447
|
+
np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
|
|
1448
|
+
round(float(_gbdry_area_total), gbd_dps),
|
|
1449
|
+
)
|