mergeron 2024.738949.5__py3-none-any.whl → 2024.738949.6__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.

@@ -14,10 +14,9 @@ import numpy as np
14
14
  from attrs import define, field
15
15
  from mpmath import mp, mpf # type: ignore
16
16
  from numpy.typing import NDArray
17
- from scipy.spatial.distance import minkowski as distance_function
18
- from sympy import lambdify, simplify, solve, symbols
19
17
 
20
- from .. import _PKG_NAME # noqa: TID252
18
+ from .. import _PKG_NAME, UPPAggrSelector # noqa: TID252
19
+ from . import UPPBoundarySpec
21
20
 
22
21
  __version__ = version(_PKG_NAME)
23
22
 
@@ -28,6 +27,16 @@ mp.trap_complex = True
28
27
  HMGPubYear: TypeAlias = Literal[1992, 2010, 2023]
29
28
 
30
29
 
30
+ @dataclass(slots=True, frozen=True)
31
+ class HMGThresholds:
32
+ delta: float
33
+ rec: float
34
+ guppi: float
35
+ divr: float
36
+ cmcr: float
37
+ ipr: float
38
+
39
+
31
40
  @dataclass(slots=True, frozen=True)
32
41
  class GuidelinesBoundary:
33
42
  coordinates: NDArray[np.float64]
@@ -41,16 +50,6 @@ class GuidelinesBoundaryCallable:
41
50
  s_naught: float = 0
42
51
 
43
52
 
44
- @define(slots=True, frozen=True)
45
- class HMGThresholds:
46
- delta: float
47
- rec: float
48
- guppi: float
49
- divr: float
50
- cmcr: float
51
- ipr: float
52
-
53
-
54
53
  @define(slots=True, frozen=True)
55
54
  class GuidelinesThresholds:
56
55
  """
@@ -90,7 +89,7 @@ class GuidelinesThresholds:
90
89
  diversion ratio limit, CMCR, and IPR
91
90
  """
92
91
 
93
- def __attrs_post_init__(self, /):
92
+ def __attrs_post_init__(self, /) -> None:
94
93
  # In the 2023 Guidlines, the agencies do not define a
95
94
  # negative presumption, or safeharbor. Practically speaking,
96
95
  # given resource constraints and loss aversion, it is likely
@@ -541,11 +540,11 @@ def dh_area(_dh_val: float = 0.01, /, *, dh_dps: int = 9) -> float:
541
540
  """
542
541
 
543
542
  _dh_val = mpf(f"{_dh_val}")
544
- _s1_zero = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
545
- _s1_one = 1 - _s1_zero
543
+ _s_naught = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
546
544
 
547
545
  return round(
548
- float(_s1_zero + (_dh_val / 2) * (mp.ln(_s1_one) - mp.ln(_s1_zero))), dh_dps
546
+ float(_s_naught + (_dh_val / 2) * (mp.ln(1 - _s_naught) - mp.ln(_s_naught))),
547
+ dh_dps,
549
548
  )
550
549
 
551
550
 
@@ -571,17 +570,18 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, dh_dps: int = 9) -> float:
571
570
  """
572
571
 
573
572
  _dh_val = mpf(f"{_dh_val}")
574
- _s1_zero = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
575
- _s1_one = 1 - _s1_zero
573
+ _s_naught = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
576
574
 
577
575
  return round(
578
- float(_s1_zero + mp.quad(lambda x: _dh_val / (2 * x), [_s1_zero, _s1_one])),
576
+ float(
577
+ _s_naught + mp.quad(lambda x: _dh_val / (2 * x), [_s_naught, 1 - _s_naught])
578
+ ),
579
579
  dh_dps,
580
580
  )
581
581
 
582
582
 
583
583
  def delta_hhi_boundary(
584
- _dh_val: float = 0.01, /, *, dh_dps: int = 5
584
+ _dh_val: float = 0.01, /, *, prec: int = 5
585
585
  ) -> GuidelinesBoundary:
586
586
  """
587
587
  Generate the list of share combination on the ΔHHI boundary.
@@ -600,19 +600,19 @@ def delta_hhi_boundary(
600
600
  """
601
601
 
602
602
  _dh_val = mpf(f"{_dh_val}")
603
- _s1_zero = 1 / 2 * (1 - mp.sqrt(1 - 2 * _dh_val))
604
- _s1_one = 1 - _s1_zero
603
+ _s_naught = 1 / 2 * (1 - mp.sqrt(1 - 2 * _dh_val))
604
+ _s_mid = mp.sqrt(_dh_val / 2)
605
605
 
606
606
  _dh_step_sz = mp.power(10, -6)
607
- _s_1 = np.array(mp.arange(_s1_zero, _s1_one + _dh_step_sz, _dh_step_sz))
607
+ _s_1 = np.array(mp.arange(_s_mid, _s_naught - mp.eps, -_dh_step_sz))
608
608
  _s_2 = _dh_val / (2 * _s_1)
609
609
 
610
610
  # Boundary points
611
- _dh_bdry_pts = np.row_stack((
612
- np.array([(mpf("0.0"), mpf("1.0"))]),
611
+ _dh_half = np.row_stack((
613
612
  np.column_stack((_s_1, _s_2)),
614
- np.array([(mpf("1.0"), mpf("0.0"))]),
613
+ np.array([(mpf("0.0"), mpf("1.0"))]),
615
614
  ))
615
+ _dh_bdry_pts = np.row_stack((np.flip(_dh_half, 0), np.flip(_dh_half[1:], 1)))
616
616
 
617
617
  _s_1_pts, _s_2_pts = np.split(_dh_bdry_pts, 2, axis=1)
618
618
  return GuidelinesBoundary(
@@ -620,43 +620,7 @@ def delta_hhi_boundary(
620
620
  np.array(_s_1_pts, np.float64),
621
621
  np.array(_s_2_pts, np.float64),
622
622
  )),
623
- dh_area(_dh_val, dh_dps=dh_dps),
624
- )
625
-
626
-
627
- def delta_hhi_boundary_qdtr(_dh_val: float = 0.01) -> GuidelinesBoundaryCallable:
628
- """
629
- Generate the list of share combination on the ΔHHI boundary.
630
-
631
- Parameters
632
- ----------
633
- _dh_val:
634
- Merging-firms' ΔHHI bound.
635
- dh_dps
636
- Number of decimal places for rounding reported shares.
637
-
638
- Returns
639
- -------
640
- Callable to generate array of share-pairs, area under boundary.
641
-
642
- """
643
-
644
- _dh_val = mpf(f"{_dh_val}")
645
-
646
- _s_1, _s_2 = symbols("s_1, s_2", positive=True)
647
-
648
- _hhi_eqn = _s_2 - 0.01 / (2 * _s_1)
649
-
650
- _hhi_bdry = solve(_hhi_eqn, _s_2)[0]
651
- _s_nought = float(solve(_hhi_eqn.subs({_s_2: 1 - _s_1}), _s_1)[0])
652
-
653
- _hhi_bdry_area = 2 * (
654
- _s_nought
655
- + mp.quad(lambdify(_s_1, _hhi_bdry, "mpmath"), (_s_nought, 1 - _s_nought))
656
- )
657
-
658
- return GuidelinesBoundaryCallable(
659
- lambdify(_s_1, _hhi_bdry, "numpy"), _hhi_bdry_area, _s_nought
623
+ dh_area(_dh_val, dh_dps=prec),
660
624
  )
661
625
 
662
626
 
@@ -727,8 +691,60 @@ def hhi_pre_contrib_boundary(
727
691
  )
728
692
 
729
693
 
694
+ def shrratio_boundary(_bdry_spec: UPPBoundarySpec) -> GuidelinesBoundary:
695
+ match _bdry_spec.agg_method:
696
+ case UPPAggrSelector.AVG:
697
+ return shrratio_boundary_xact_avg(
698
+ _bdry_spec.share_ratio,
699
+ _bdry_spec.rec,
700
+ recapture_spec=_bdry_spec.recapture_spec.value, # type: ignore
701
+ prec=_bdry_spec.precision,
702
+ )
703
+ case UPPAggrSelector.MAX:
704
+ return shrratio_boundary_max(
705
+ _bdry_spec.share_ratio, _bdry_spec.rec, prec=_bdry_spec.precision
706
+ )
707
+ case UPPAggrSelector.MIN:
708
+ return shrratio_boundary_min(
709
+ _bdry_spec.share_ratio,
710
+ _bdry_spec.rec,
711
+ recapture_spec=_bdry_spec.recapture_spec.value, # type: ignore
712
+ prec=_bdry_spec.precision,
713
+ )
714
+ case UPPAggrSelector.DIS:
715
+ return shrratio_boundary_wtd_avg(
716
+ _bdry_spec.share_ratio,
717
+ _bdry_spec.rec,
718
+ agg_method="distance",
719
+ weighting=None,
720
+ recapture_spec=_bdry_spec.recapture_spec.value, # type: ignore
721
+ prec=_bdry_spec.precision,
722
+ )
723
+ case _:
724
+ _weighting = (
725
+ "cross-product-share"
726
+ if _bdry_spec.agg_method.value.startswith("cross-product-share")
727
+ else "own-share"
728
+ )
729
+
730
+ _agg_method = (
731
+ "arithmetic"
732
+ if _bdry_spec.agg_method.value.endswith("average")
733
+ else "distance"
734
+ )
735
+
736
+ return shrratio_boundary_wtd_avg(
737
+ _bdry_spec.share_ratio,
738
+ _bdry_spec.rec,
739
+ agg_method=_agg_method, # type: ignore
740
+ weighting=_weighting, # type: ignore
741
+ recapture_spec=_bdry_spec.recapture_spec.value, # type: ignore
742
+ prec=_bdry_spec.precision,
743
+ )
744
+
745
+
730
746
  def shrratio_boundary_max(
731
- _delta_star: float = 0.075, _r_val: float = 0.80, /, *, gbd_dps: int = 10
747
+ _delta_star: float = 0.075, _r_val: float = 0.80, /, *, prec: int = 10
732
748
  ) -> GuidelinesBoundary:
733
749
  """
734
750
  Share combinations on the minimum GUPPI boundary with symmetric
@@ -740,7 +756,7 @@ def shrratio_boundary_max(
740
756
  Margin-adjusted benchmark share ratio.
741
757
  _r_val
742
758
  Recapture ratio.
743
- gbd_dps
759
+ prec
744
760
  Number of decimal places for rounding returned shares.
745
761
 
746
762
  Returns
@@ -749,12 +765,6 @@ def shrratio_boundary_max(
749
765
 
750
766
  """
751
767
 
752
- if _delta_star > 1:
753
- raise ValueError(
754
- "Invalid combination specified; "
755
- "Margin-adjusted benchmark share ratio cannot exceed 1."
756
- )
757
-
758
768
  # _r_val is not needed for max boundary, but is specified for consistency
759
769
  # of function call with other shrratio_mgnsym_boundary functions
760
770
  del _r_val
@@ -769,7 +779,7 @@ def shrratio_boundary_max(
769
779
  np.array(_s1_pts, np.float64),
770
780
  np.array(_s1_pts[::-1], np.float64),
771
781
  )),
772
- round(float(_s_intcpt * _s_mid), gbd_dps), # simplified calculation
782
+ round(float(_s_intcpt * _s_mid), prec), # simplified calculation
773
783
  )
774
784
 
775
785
 
@@ -779,7 +789,7 @@ def shrratio_boundary_min(
779
789
  /,
780
790
  *,
781
791
  recapture_spec: str = "inside-out",
782
- gbd_dps: int = 10,
792
+ prec: int = 10,
783
793
  ) -> GuidelinesBoundary:
784
794
  """
785
795
  Share combinations on the minimum GUPPI boundary, with symmetric
@@ -801,7 +811,7 @@ def shrratio_boundary_min(
801
811
  recapture_spec
802
812
  Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
803
813
  value for both merging firms ("proportional").
804
- gbd_dps
814
+ prec
805
815
  Number of decimal places for rounding returned shares.
806
816
 
807
817
  Returns
@@ -810,14 +820,6 @@ def shrratio_boundary_min(
810
820
 
811
821
  """
812
822
 
813
- if _delta_star > 1:
814
- raise ValueError("Margin-adjusted benchmark share ratio cannot exceed 1.")
815
-
816
- if recapture_spec not in (_recspecs := ("inside-out", "proportional")):
817
- raise ValueError(
818
- f"Recapture_spec value, {f'"{recapture_spec}"'} not in {_recspecs!r}"
819
- )
820
-
821
823
  _delta_star = mpf(f"{_delta_star}")
822
824
  _s_intcpt = mpf("1.00")
823
825
  _s_mid = _delta_star / (1 + _delta_star)
@@ -844,131 +846,7 @@ def shrratio_boundary_min(
844
846
  _s1_pts, _gbd_area = np.array((0, _s_mid, _s_intcpt), np.float64), _s_mid
845
847
 
846
848
  return GuidelinesBoundary(
847
- np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area), gbd_dps)
848
- )
849
-
850
-
851
- def shrratio_boundary_qdtr_wtd_avg(
852
- _delta_star: float = 0.075,
853
- _r_val: float = 0.80,
854
- /,
855
- *,
856
- wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
857
- recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
858
- ) -> GuidelinesBoundaryCallable:
859
- """
860
- Share combinations for the share-weighted average GUPPI boundary with symmetric
861
- merging-firm margins.
862
-
863
- Parameters
864
- ----------
865
- _delta_star
866
- corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
867
- _r_val
868
- recapture ratio
869
- wgtng_policy
870
- Whether "own-share" or "cross-product-share" (or None for simple, unweighted average)
871
- recapture_spec
872
- Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
873
- value for both merging firms ("proportional").
874
-
875
- Returns
876
- -------
877
- Array of share-pairs, area under boundary.
878
-
879
- """
880
-
881
- if _delta_star > 1:
882
- raise ValueError(
883
- "Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
884
- )
885
-
886
- _delta_star = mpf(f"{_delta_star}")
887
- _s_mid = _delta_star / (1 + _delta_star)
888
- _s_naught = 0
889
-
890
- _s_1, _s_2 = symbols("s_1:3", positive=True)
891
-
892
- match wgtng_policy:
893
- case "own-share":
894
- _bdry_eqn = (
895
- _s_1 * _s_2 / (1 - _s_1)
896
- + _s_2
897
- * _s_1
898
- / (
899
- (1 - (_r_val * _s_2 + (1 - _r_val) * _s_1))
900
- if recapture_spec == "inside-out"
901
- else (1 - _s_2)
902
- )
903
- - (_s_1 + _s_2) * _delta_star
904
- )
905
-
906
- _bdry_func = solve(_bdry_eqn, _s_2)[0]
907
- _s_naught = (
908
- float(solve(simplify(_bdry_eqn.subs({_s_2: 1 - _s_1})), _s_1)[0])
909
- if recapture_spec == "inside-out"
910
- else 0
911
- )
912
- _bdry_area = float(
913
- 2
914
- * (
915
- _s_naught
916
- + mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (_s_naught, _s_mid))
917
- )
918
- - (_s_mid**2 + _s_naught**2)
919
- )
920
-
921
- case "cross-product-share":
922
- mp.trap_complex = False
923
- _d_star = symbols("d", positive=True)
924
- _bdry_eqn = (
925
- _s_2 * _s_2 / (1 - _s_1)
926
- + _s_1
927
- * _s_1
928
- / (
929
- (1 - (_r_val * _s_2 + (1 - _r_val) * _s_1))
930
- if recapture_spec == "inside-out"
931
- else (1 - _s_2)
932
- )
933
- - (_s_1 + _s_2) * _d_star
934
- )
935
-
936
- _bdry_func = solve(_bdry_eqn, _s_2)[1]
937
- _bdry_area = float(
938
- 2
939
- * (
940
- mp.quad(
941
- lambdify(
942
- _s_1, _bdry_func.subs({_d_star: _delta_star}), "mpmath"
943
- ),
944
- (0, _s_mid),
945
- )
946
- ).real
947
- - _s_mid**2
948
- )
949
-
950
- case _:
951
- _bdry_eqn = (
952
- 1 / 2 * _s_2 / (1 - _s_1)
953
- + 1
954
- / 2
955
- * _s_1
956
- / (
957
- (1 - (_r_val * _s_2 + (1 - _r_val) * _s_1))
958
- if recapture_spec == "inside-out"
959
- else (1 - _s_2)
960
- )
961
- - _delta_star
962
- )
963
-
964
- _bdry_func = solve(_bdry_eqn, _s_2)[0]
965
- _bdry_area = float(
966
- 2 * (mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (0, _s_mid)))
967
- - _s_mid**2
968
- )
969
-
970
- return GuidelinesBoundaryCallable(
971
- lambdify(_s_1, _bdry_func, "numpy"), _bdry_area, _s_naught
849
+ np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area), prec)
972
850
  )
973
851
 
974
852
 
@@ -977,10 +855,10 @@ def shrratio_boundary_wtd_avg(
977
855
  _r_val: float = 0.80,
978
856
  /,
979
857
  *,
980
- avg_method: Literal["arithmetic", "geometric", "distance"] = "arithmetic",
981
- wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
858
+ agg_method: Literal["arithmetic", "geometric", "distance"] = "arithmetic",
859
+ weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
982
860
  recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
983
- gbd_dps: int = 5,
861
+ prec: int = 5,
984
862
  ) -> GuidelinesBoundary:
985
863
  """
986
864
  Share combinations for the share-weighted average GUPPI boundary with symmetric
@@ -992,14 +870,14 @@ def shrratio_boundary_wtd_avg(
992
870
  corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
993
871
  _r_val
994
872
  recapture ratio
995
- avg_method
873
+ agg_method
996
874
  Whether "arithmetic", "geometric", or "distance".
997
- wgtng_policy
875
+ weighting
998
876
  Whether "own-share" or "cross-product-share" (or None for simple, unweighted average).
999
877
  recapture_spec
1000
878
  Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1001
879
  value for both merging firms ("proportional").
1002
- gbd_dps
880
+ prec
1003
881
  Number of decimal places for rounding returned shares and area.
1004
882
 
1005
883
  Returns
@@ -1070,11 +948,6 @@ def shrratio_boundary_wtd_avg(
1070
948
 
1071
949
  """
1072
950
 
1073
- if _delta_star > 1:
1074
- raise ValueError(
1075
- "Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
1076
- )
1077
-
1078
951
  _delta_star = mpf(f"{_delta_star}")
1079
952
  _s_mid = _delta_star / (1 + _delta_star)
1080
953
 
@@ -1084,8 +957,8 @@ def shrratio_boundary_wtd_avg(
1084
957
  _s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
1085
958
 
1086
959
  # parameters for iteration
1087
- _gbd_step_sz = mp.power(10, -gbd_dps)
1088
- _theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
960
+ _gbd_step_sz = mp.power(10, -prec)
961
+ _theta = _gbd_step_sz * (10 if weighting == "cross-product-share" else 1)
1089
962
  for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
1090
963
  # The wtd. avg. GUPPI is not always convex to the origin, so we
1091
964
  # increment _s_2 after each iteration in which our algorithm
@@ -1106,13 +979,13 @@ def shrratio_boundary_wtd_avg(
1106
979
 
1107
980
  _r = (
1108
981
  mp.fdiv(
1109
- _s_1 if wgtng_policy == "cross-product-share" else _s_2, _s_1 + _s_2
982
+ _s_1 if weighting == "cross-product-share" else _s_2, _s_1 + _s_2
1110
983
  )
1111
- if wgtng_policy
984
+ if weighting
1112
985
  else 0.5
1113
986
  )
1114
987
 
1115
- match avg_method:
988
+ match agg_method:
1116
989
  case "geometric":
1117
990
  _delta_test = mp.expm1(lerp(mp.log1p(_de_1), mp.log1p(_de_2), _r))
1118
991
  case "distance":
@@ -1122,7 +995,7 @@ def shrratio_boundary_wtd_avg(
1122
995
 
1123
996
  _test_flag, _incr_decr = (
1124
997
  (_delta_test > _delta_star, -1)
1125
- if wgtng_policy == "cross-product-share"
998
+ if weighting == "cross-product-share"
1126
999
  else (_delta_test < _delta_star, 1)
1127
1000
  )
1128
1001
 
@@ -1153,11 +1026,11 @@ def shrratio_boundary_wtd_avg(
1153
1026
  _delta_star,
1154
1027
  _r_val,
1155
1028
  recapture_spec=recapture_spec,
1156
- avg_method=avg_method,
1157
- wgtng_policy=wgtng_policy,
1029
+ agg_method=agg_method,
1030
+ weighting=weighting,
1158
1031
  )
1159
1032
 
1160
- if wgtng_policy == "own-share":
1033
+ if weighting == "own-share":
1161
1034
  _gbd_prtlarea = (
1162
1035
  _gbd_step_sz * (4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _s_2_pre) / 3
1163
1036
  )
@@ -1181,7 +1054,7 @@ def shrratio_boundary_wtd_avg(
1181
1054
  # Points defining boundary to point-of-symmetry
1182
1055
  return GuidelinesBoundary(
1183
1056
  np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
1184
- round(float(_gbdry_area_total), gbd_dps),
1057
+ round(float(_gbdry_area_total), prec),
1185
1058
  )
1186
1059
 
1187
1060
 
@@ -1191,7 +1064,7 @@ def shrratio_boundary_xact_avg(
1191
1064
  /,
1192
1065
  *,
1193
1066
  recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
1194
- gbd_dps: int = 5,
1067
+ prec: int = 5,
1195
1068
  ) -> GuidelinesBoundary:
1196
1069
  """
1197
1070
  Share combinations for the simple average GUPPI boundary with symmetric
@@ -1239,7 +1112,7 @@ def shrratio_boundary_xact_avg(
1239
1112
  recapture_spec
1240
1113
  Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1241
1114
  value for both merging firms ("proportional").
1242
- gbd_dps
1115
+ prec
1243
1116
  Number of decimal places for rounding returned shares.
1244
1117
 
1245
1118
  Returns
@@ -1248,18 +1121,9 @@ def shrratio_boundary_xact_avg(
1248
1121
 
1249
1122
  """
1250
1123
 
1251
- if _delta_star > 1:
1252
- raise ValueError(
1253
- "Invalid combination specified; "
1254
- "Margin-adjusted benchmark share ratio cannot exceed 1."
1255
- )
1256
-
1257
- if recapture_spec not in (_recspecs := ("inside-out", "proportional")):
1258
- raise ValueError(f"Recapture spec must be one of {_recspecs:!}")
1259
-
1260
1124
  _delta_star = mpf(f"{_delta_star}")
1261
1125
  _s_mid = _delta_star / (1 + _delta_star)
1262
- _gbd_step_sz = mp.power(10, -gbd_dps)
1126
+ _gbd_step_sz = mp.power(10, -prec)
1263
1127
 
1264
1128
  _gbdry_points_start = np.array([(_s_mid, _s_mid)])
1265
1129
  _s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
@@ -1296,10 +1160,10 @@ def shrratio_boundary_xact_avg(
1296
1160
  _nr_t2_s1 = _nr_sqrt_s1sq + _nr_sqrt_s1 + _nr_sqrt_nos1
1297
1161
 
1298
1162
  if not np.isclose( # type: ignore
1299
- np.einsum("i->", _nr_t2_mdr.astype(np.float64)), # type: ignore from mpf to float64
1300
- np.einsum("i->", _nr_t2_s1.astype(np.float64)), # type: ignore from mpf to float64
1163
+ np.einsum("i->", _nr_t2_mdr.astype(np.float64)),
1164
+ np.einsum("i->", _nr_t2_s1.astype(np.float64)),
1301
1165
  rtol=0,
1302
- atol=0.5 * gbd_dps,
1166
+ atol=0.5 * prec,
1303
1167
  ):
1304
1168
  raise RuntimeError(
1305
1169
  "Calculation of sq. root term in exact average GUPPI"
@@ -1349,292 +1213,7 @@ def shrratio_boundary_xact_avg(
1349
1213
  _s_1_pts, _s_2_pts = np.split(_gbdry_points, 2, axis=1)
1350
1214
  return GuidelinesBoundary(
1351
1215
  np.column_stack((np.array(_s_1_pts), np.array(_s_2_pts))),
1352
- round(float(_gbdry_area_simpson), gbd_dps),
1353
- )
1354
-
1355
-
1356
- def shrratio_boundary_avg(
1357
- _delta_star: float = 0.075,
1358
- _r_val: float = 0.80,
1359
- /,
1360
- *,
1361
- avg_method: Literal["arithmetic", "geometric", "distance"] = "arithmetic",
1362
- recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
1363
- gbd_dps: int = 5,
1364
- ) -> GuidelinesBoundary:
1365
- """
1366
- Share combinations along the average GUPPI boundary, with
1367
- symmetric merging-firm margins.
1368
-
1369
- Reimplements the unweighted average and distance estimations from function,
1370
- `shrratio_boundary_wtd_avg`. This reimplementation
1371
- is primarifly useful for testing the output of `shrratio_boundary_wtd_avg`
1372
- as it tests considerably slower.
1373
-
1374
-
1375
- Parameters
1376
- ----------
1377
- _delta_star
1378
- Margin-adjusted benchmark share ratio.
1379
- _r_val
1380
- Recapture ratio.
1381
- avg_method
1382
- Whether "arithmetic", "geometric", or "distance".
1383
- recapture_spec
1384
- Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1385
- value for both merging firms ("proportional").
1386
- gbd_dps
1387
- Number of decimal places for rounding returned shares.
1388
-
1389
- Returns
1390
- -------
1391
- Array of share-pairs, area under boundary.
1392
-
1393
- """
1394
-
1395
- if _delta_star > 1:
1396
- raise ValueError(
1397
- "Invalid combination specified; "
1398
- "Margin-adjusted benchmark share ratio cannot exceed 1."
1399
- )
1400
-
1401
- if avg_method not in (_avgmthds := ("arithmetic", "geometric", "distance")):
1402
- raise ValueError(
1403
- f"Averarging method, {f'"{avg_method}"'} is invalid. "
1404
- f"Must be one of, {_avgmthds!r}."
1405
- )
1406
-
1407
- if recapture_spec not in (_recspecs := ("inside-out", "proportional")):
1408
- raise ValueError(
1409
- f"Recapture spec, {f'"{recapture_spec}"'} is invalid. "
1410
- f"Must be one of {_recspecs!r}."
1411
- )
1412
-
1413
- _delta_star = mpf(f"{_delta_star}")
1414
- _s_mid = _delta_star / (1 + _delta_star)
1415
-
1416
- # initial conditions
1417
- _s_2 = _s_mid
1418
- _s_2_oddval = True
1419
- _s_2_oddsum = 0
1420
- _s_2_evnsum = 0
1421
- _gbdry_points = [(_s_mid, _s_mid)]
1422
-
1423
- # parameters for iteration
1424
- _gbd_step_sz = mp.power(10, -gbd_dps)
1425
- for _s_1 in mp.arange(_s_mid, 0, -_gbd_step_sz):
1426
- _s_1 -= _gbd_step_sz
1427
- while True:
1428
- _delta_12 = _s_2 / (1 - _s_1)
1429
- _delta_21 = (
1430
- _s_1 / (1 - _s_2)
1431
- if recapture_spec == "proportional"
1432
- else _s_1 / (1 - lerp(_s_1, _s_2, _r_val))
1433
- )
1434
-
1435
- match avg_method:
1436
- case "geometric":
1437
- _delta_test = mp.sqrt(_delta_12 * _delta_21)
1438
- case "distance":
1439
- # _delta_test = mp.sqrt(mp.fdiv((_delta_12**2 + _delta_21**2), "2"))
1440
- _delta_test = mp.sqrt(
1441
- mp.fdiv(
1442
- mp.fsum(
1443
- mp.power(f"{_g}", "2") for _g in (_delta_12, _delta_21)
1444
- ),
1445
- "2",
1446
- )
1447
- )
1448
- case _:
1449
- _delta_test = mp.fdiv(_delta_12 + _delta_21, "2")
1450
-
1451
- if _delta_test < _delta_star:
1452
- _s_2 += _gbd_step_sz
1453
- else:
1454
- break
1455
-
1456
- _gbdry_points.append((_s_1, _s_2))
1457
-
1458
- _s_2_oddsum += _s_2 if _s_2_oddval else 0
1459
- _s_2_evnsum += _s_2 if not _s_2_oddval else 0
1460
- _s_2_oddval = not _s_2_oddval
1461
-
1462
- # Starting at _s_id - _gbd_step_sz means _s_1 is not always
1463
- # an even multiple of _gbd_step_sz
1464
- _s_intcpt = _s_2
1465
-
1466
- _gbd_prtlarea = 2 * _gbd_step_sz * (
1467
- mp.fmul(4, _s_2_oddsum)
1468
- + mp.fmul(2, _s_2_evnsum)
1469
- + mp.fmul(1, _s_mid + _s_intcpt)
1470
- ) / 3 - mp.power(_s_mid, 2)
1471
-
1472
- _gbdry_points = np.array(_gbdry_points, np.float64)
1473
- return GuidelinesBoundary(
1474
- np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
1475
- round(float(_gbd_prtlarea), gbd_dps),
1476
- )
1477
-
1478
-
1479
- def shrratio_boundary_distance(
1480
- _delta_star: float = 0.075,
1481
- _r_val: float = 0.80,
1482
- /,
1483
- *,
1484
- avg_method: Literal["arithmetic", "distance"] = "arithmetic",
1485
- wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
1486
- recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
1487
- gbd_dps: int = 5,
1488
- ) -> GuidelinesBoundary:
1489
- """
1490
- Share combinations for the GUPPI boundaries using various aggregators with
1491
- symmetric merging-firm margins.
1492
-
1493
- Reimplements the arithmetic-averages and distance estimations from function,
1494
- `shrratio_boundary_wtd_avg`but uses the Minkowski-distance function,
1495
- `scipy.spatial.distance.minkowski` for all aggregators. This reimplementation
1496
- is useful for testing the output of `shrratio_boundary_wtd_avg`
1497
- but runs considerably slower.
1498
-
1499
- Parameters
1500
- ----------
1501
- _delta_star
1502
- corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
1503
- _r_val
1504
- recapture ratio
1505
- avg_method
1506
- Whether "arithmetic", "geometric", or "distance".
1507
- wgtng_policy
1508
- Whether "own-share" or "cross-product-share".
1509
- recapture_spec
1510
- Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1511
- value for both merging firms ("proportional").
1512
- gbd_dps
1513
- Number of decimal places for rounding returned shares and area.
1514
-
1515
- Returns
1516
- -------
1517
- Array of share-pairs, area under boundary.
1518
-
1519
- """
1520
-
1521
- if _delta_star > 1:
1522
- raise ValueError(
1523
- "Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
1524
- )
1525
-
1526
- _delta_star = mpf(f"{_delta_star}")
1527
- _s_mid = _delta_star / (1 + _delta_star)
1528
-
1529
- # initial conditions
1530
- _gbdry_points = [(_s_mid, _s_mid)]
1531
- _s_1_pre, _s_2_pre = _s_mid, _s_mid
1532
- _s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
1533
-
1534
- # parameters for iteration
1535
- _weights_base = (mpf("0.5"),) * 2
1536
- _gbd_step_sz = mp.power(10, -gbd_dps)
1537
- _theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
1538
- for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
1539
- # The wtd. avg. GUPPI is not always convex to the origin, so we
1540
- # increment _s_2 after each iteration in which our algorithm
1541
- # finds (s1, s2) on the boundary
1542
- _s_2 = _s_2_pre * (1 + _theta)
1543
-
1544
- if (_s_1 + _s_2) > mpf("0.99875"):
1545
- # 1: # We lose accuracy at 3-9s and up
1546
- break
1547
-
1548
- while True:
1549
- _de_1 = _s_2 / (1 - _s_1)
1550
- _de_2 = (
1551
- _s_1 / (1 - lerp(_s_1, _s_2, _r_val))
1552
- if recapture_spec == "inside-out"
1553
- else _s_1 / (1 - _s_2)
1554
- )
1555
-
1556
- _weights_i = (
1557
- (
1558
- _w1 := mp.fdiv(
1559
- _s_2 if wgtng_policy == "cross-product-share" else _s_1,
1560
- _s_1 + _s_2,
1561
- ),
1562
- 1 - _w1,
1563
- )
1564
- if wgtng_policy
1565
- else _weights_base
1566
- )
1567
-
1568
- match avg_method:
1569
- case "arithmetic":
1570
- _delta_test = distance_function(
1571
- (_de_1, _de_2), (0.0, 0.0), p=1, w=_weights_i
1572
- )
1573
- case "distance":
1574
- _delta_test = distance_function(
1575
- (_de_1, _de_2), (0.0, 0.0), p=2, w=_weights_i
1576
- )
1577
-
1578
- _test_flag, _incr_decr = (
1579
- (_delta_test > _delta_star, -1)
1580
- if wgtng_policy == "cross-product-share"
1581
- else (_delta_test < _delta_star, 1)
1582
- )
1583
-
1584
- if _test_flag:
1585
- _s_2 += _incr_decr * _gbd_step_sz
1586
- else:
1587
- break
1588
-
1589
- # Build-up boundary points
1590
- _gbdry_points.append((_s_1, _s_2))
1591
-
1592
- # Build up area terms
1593
- _s_2_oddsum += _s_2 if _s_2_oddval else 0
1594
- _s_2_evnsum += _s_2 if not _s_2_oddval else 0
1595
- _s_2_oddval = not _s_2_oddval
1596
-
1597
- # Hold share points
1598
- _s_2_pre = _s_2
1599
- _s_1_pre = _s_1
1600
-
1601
- if _s_2_oddval:
1602
- _s_2_evnsum -= _s_2_pre
1603
- else:
1604
- _s_2_oddsum -= _s_1_pre
1605
-
1606
- _s_intcpt = _shrratio_boundary_intcpt(
1607
- _s_1_pre,
1608
- _delta_star,
1609
- _r_val,
1610
- recapture_spec=recapture_spec,
1611
- avg_method=avg_method,
1612
- wgtng_policy=wgtng_policy,
1613
- )
1614
-
1615
- if wgtng_policy == "own-share":
1616
- _gbd_prtlarea = (
1617
- _gbd_step_sz * (4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _s_2_pre) / 3
1618
- )
1619
- # Area under boundary
1620
- _gbdry_area_total = 2 * (_s_1_pre + _gbd_prtlarea) - (
1621
- mp.power(_s_mid, "2") + mp.power(_s_1_pre, "2")
1622
- )
1623
-
1624
- else:
1625
- _gbd_prtlarea = (
1626
- _gbd_step_sz * (4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _s_intcpt) / 3
1627
- )
1628
- # Area under boundary
1629
- _gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, "2")
1630
-
1631
- _gbdry_points = np.row_stack((_gbdry_points, (mpf("0.0"), _s_intcpt))).astype(
1632
- np.float64
1633
- )
1634
- # Points defining boundary to point-of-symmetry
1635
- return GuidelinesBoundary(
1636
- np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
1637
- round(float(_gbdry_area_total), gbd_dps),
1216
+ round(float(_gbdry_area_simpson), prec),
1638
1217
  )
1639
1218
 
1640
1219
 
@@ -1645,24 +1224,24 @@ def _shrratio_boundary_intcpt(
1645
1224
  /,
1646
1225
  *,
1647
1226
  recapture_spec: Literal["inside-out", "proportional"],
1648
- avg_method: Literal["arithmetic", "geometric", "distance"],
1649
- wgtng_policy: Literal["cross-product-share", "own-share"] | None,
1227
+ agg_method: Literal["arithmetic", "geometric", "distance"],
1228
+ weighting: Literal["cross-product-share", "own-share"] | None,
1650
1229
  ) -> float:
1651
- match wgtng_policy:
1230
+ match weighting:
1652
1231
  case "cross-product-share":
1653
- _s_intcpt = _delta_star
1232
+ _s_intcpt: float = _delta_star
1654
1233
  case "own-share":
1655
1234
  _s_intcpt = mpf("1.0")
1656
- case None if avg_method == "distance":
1235
+ case None if agg_method == "distance":
1657
1236
  _s_intcpt = _delta_star * mp.sqrt("2")
1658
- case None if avg_method == "arithmetic" and recapture_spec == "inside-out":
1237
+ case None if agg_method == "arithmetic" and recapture_spec == "inside-out":
1659
1238
  _s_intcpt = mp.fdiv(
1660
1239
  mp.fsub(
1661
1240
  2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
1662
1241
  ),
1663
1242
  2 * mpf(f"{_r_val}"),
1664
1243
  )
1665
- case None if avg_method == "arithmetic":
1244
+ case None if agg_method == "arithmetic":
1666
1245
  _s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
1667
1246
  case _:
1668
1247
  _s_intcpt = _s_2_pre