mergeron 2025.739355.0__py3-none-any.whl → 2025.739355.2__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 +2 -2
- mergeron/core/ftc_merger_investigations_data.py +22 -17
- mergeron/core/guidelines_boundary_functions.py +104 -48
- mergeron/core/guidelines_boundary_functions_extra.py +19 -21
- mergeron/gen/data_generation_functions.py +33 -36
- {mergeron-2025.739355.0.dist-info → mergeron-2025.739355.2.dist-info}/METADATA +6 -6
- {mergeron-2025.739355.0.dist-info → mergeron-2025.739355.2.dist-info}/RECORD +8 -8
- {mergeron-2025.739355.0.dist-info → mergeron-2025.739355.2.dist-info}/WHEEL +0 -0
mergeron/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ from ruamel import yaml
|
|
|
15
15
|
|
|
16
16
|
_PKG_NAME: str = Path(__file__).parent.name
|
|
17
17
|
|
|
18
|
-
VERSION = "2025.739355.
|
|
18
|
+
VERSION = "2025.739355.2"
|
|
19
19
|
|
|
20
20
|
__version__ = VERSION
|
|
21
21
|
|
|
@@ -39,7 +39,7 @@ NTHREADS = 2 * cpu_count()
|
|
|
39
39
|
|
|
40
40
|
PKG_ATTRS_MAP: dict[str, type] = {}
|
|
41
41
|
|
|
42
|
-
np.set_printoptions(precision=
|
|
42
|
+
np.set_printoptions(precision=28, floatmode="fixed", legacy=False)
|
|
43
43
|
|
|
44
44
|
type HMGPubYear = Literal[1992, 2010, 2023]
|
|
45
45
|
|
|
@@ -36,6 +36,8 @@ from . import (
|
|
|
36
36
|
|
|
37
37
|
__version__ = VERSION
|
|
38
38
|
|
|
39
|
+
# cspell: "includeRegExpList": ["strings", "comments", /( {3}['"]{3}).*?\\1/g]
|
|
40
|
+
|
|
39
41
|
WORK_DIR = globals().get("WORK_DIR", PKG_WORK_DIR)
|
|
40
42
|
"""Redefined, in case the user defines WORK_DIR betweeen module imports."""
|
|
41
43
|
|
|
@@ -61,7 +63,7 @@ CONC_HHI_DICT = {
|
|
|
61
63
|
"3,000 - 3,999": 3000,
|
|
62
64
|
"4,000 - 4,999": 4000,
|
|
63
65
|
"5,000 - 6,999": 5000,
|
|
64
|
-
"7,000
|
|
66
|
+
"7,000 - 10,000": 7000,
|
|
65
67
|
"TOTAL": TTL_KEY,
|
|
66
68
|
}
|
|
67
69
|
CONC_DELTA_DICT = {
|
|
@@ -72,7 +74,7 @@ CONC_DELTA_DICT = {
|
|
|
72
74
|
"500 - 800": 500,
|
|
73
75
|
"800 - 1,200": 800,
|
|
74
76
|
"1,200 - 2,500": 1200,
|
|
75
|
-
"2,500
|
|
77
|
+
"2,500 - 5,000": 2500,
|
|
76
78
|
"TOTAL": TTL_KEY,
|
|
77
79
|
}
|
|
78
80
|
CNT_FCOUNT_DICT = {
|
|
@@ -90,8 +92,8 @@ CNT_FCOUNT_DICT = {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
|
|
93
|
-
def
|
|
94
|
-
"""
|
|
95
|
+
def invert_map(_dict: Mapping[Any, Any]) -> Mapping[Any, Any]:
|
|
96
|
+
"""Invert mapping, mapping values to keys of the original mapping."""
|
|
95
97
|
return {_v: _k for _k, _v in _dict.items()}
|
|
96
98
|
|
|
97
99
|
|
|
@@ -432,7 +434,7 @@ def _parse_invdata() -> INVData:
|
|
|
432
434
|
" the source code as well as to install an additional package"
|
|
433
435
|
" not distributed with this package or identified as a requirement."
|
|
434
436
|
)
|
|
435
|
-
import pymupdf # type: ignore
|
|
437
|
+
import pymupdf # type: ignore # noqa: PLC0415
|
|
436
438
|
|
|
437
439
|
invdata_docnames = _download_invdata(FID_WORK_DIR)
|
|
438
440
|
|
|
@@ -610,41 +612,44 @@ def _process_table_blks_conc_type(
|
|
|
610
612
|
) -> ArrayBIGINT:
|
|
611
613
|
conc_row_pat = re.compile(r"((?:0|\d,\d{3}) (?:- \d+,\d{3}|\+)|TOTAL)")
|
|
612
614
|
|
|
613
|
-
|
|
614
|
-
col_totals: ArrayBIGINT = np.zeros(len(
|
|
615
|
+
col_titles = tuple(CONC_DELTA_DICT.values())
|
|
616
|
+
col_totals: ArrayBIGINT = np.zeros(len(col_titles), int)
|
|
615
617
|
invdata_array: ArrayBIGINT = np.array(None)
|
|
616
618
|
|
|
617
619
|
for tbl_blk in _table_blocks:
|
|
618
620
|
if conc_row_pat.match(_blk_str := tbl_blk[-3]):
|
|
619
621
|
row_list: list[str] = _blk_str.strip().split("\n")
|
|
620
622
|
row_title: str = row_list.pop(0)
|
|
621
|
-
row_key: int =
|
|
623
|
+
row_key: int = (
|
|
624
|
+
7000 if row_title.startswith("7,000") else CONC_HHI_DICT[row_title]
|
|
625
|
+
)
|
|
622
626
|
row_total = np.array(row_list.pop().replace(",", "").split("/"), int)
|
|
623
|
-
|
|
627
|
+
data_row_list: list[list[int]] = []
|
|
624
628
|
while row_list:
|
|
625
629
|
enfd_val, clsd_val = row_list.pop(0).split("/")
|
|
626
|
-
|
|
630
|
+
data_row_list += [
|
|
627
631
|
[
|
|
628
632
|
row_key,
|
|
629
|
-
|
|
633
|
+
col_titles[len(data_row_list)],
|
|
630
634
|
int(enfd_val),
|
|
631
635
|
int(clsd_val),
|
|
632
636
|
int(enfd_val) + int(clsd_val),
|
|
633
637
|
]
|
|
634
638
|
]
|
|
635
|
-
|
|
639
|
+
data_row_array = np.array(data_row_list, int)
|
|
640
|
+
del data_row_list
|
|
636
641
|
# Check row totals
|
|
637
|
-
assert_array_equal(row_total, np.einsum("ij->j",
|
|
642
|
+
assert_array_equal(row_total, np.einsum("ij->j", data_row_array[:, 2:4]))
|
|
638
643
|
|
|
639
644
|
if row_key == TTL_KEY:
|
|
640
|
-
col_totals =
|
|
645
|
+
col_totals = data_row_array
|
|
641
646
|
else:
|
|
642
647
|
invdata_array = (
|
|
643
|
-
np.vstack((invdata_array,
|
|
648
|
+
np.vstack((invdata_array, data_row_array))
|
|
644
649
|
if invdata_array.shape
|
|
645
|
-
else
|
|
650
|
+
else data_row_array
|
|
646
651
|
)
|
|
647
|
-
del
|
|
652
|
+
del data_row_array
|
|
648
653
|
else:
|
|
649
654
|
continue
|
|
650
655
|
|
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import decimal
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from types import ModuleType
|
|
5
6
|
from typing import Literal, TypedDict
|
|
6
7
|
|
|
7
8
|
import matplotlib as mpl
|
|
8
9
|
import matplotlib.axes as mpa
|
|
10
|
+
import matplotlib.offsetbox as mof
|
|
9
11
|
import matplotlib.patches as mpp
|
|
10
12
|
import matplotlib.pyplot as plt
|
|
11
13
|
import matplotlib.ticker as mpt
|
|
12
14
|
import numpy as np
|
|
13
15
|
from mpmath import mp, mpf # type: ignore
|
|
14
16
|
|
|
15
|
-
from .. import
|
|
17
|
+
from .. import ( # noqa: TID252
|
|
18
|
+
_PKG_NAME,
|
|
19
|
+
DEFAULT_REC_RATIO,
|
|
20
|
+
VERSION,
|
|
21
|
+
ArrayBIGINT,
|
|
22
|
+
ArrayDouble,
|
|
23
|
+
)
|
|
16
24
|
from . import GuidelinesBoundary, MPFloat
|
|
17
25
|
|
|
18
26
|
__version__ = VERSION
|
|
@@ -467,47 +475,46 @@ def diversion_share_boundary_xact_avg(
|
|
|
467
475
|
Array of share-pairs, area under boundary, area under boundary.
|
|
468
476
|
|
|
469
477
|
"""
|
|
470
|
-
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
471
478
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
472
|
-
_step_size =
|
|
479
|
+
_step_size = 10**-dps
|
|
473
480
|
|
|
474
481
|
_bdry_start = np.array([(_s_mid, _s_mid)])
|
|
475
|
-
_s_1 = np.
|
|
482
|
+
_s_1 = np.arange(_s_mid - _step_size, 0, -_step_size)
|
|
476
483
|
if recapture_form == "inside-out":
|
|
477
|
-
_s_intcpt =
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
2 *
|
|
484
|
+
_s_intcpt: float = (
|
|
485
|
+
2 * _delta_star * _r_val + 1 - np.abs(2 * _delta_star * _r_val - 1)
|
|
486
|
+
) / (2 * _r_val)
|
|
487
|
+
nr_t1: ArrayDouble = (
|
|
488
|
+
1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
482
489
|
)
|
|
483
|
-
nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
484
490
|
|
|
485
|
-
nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
486
|
-
nr_sqrt_mdr2 = nr_sqrt_mdr * _r_val
|
|
487
|
-
nr_sqrt_md2r2 = nr_sqrt_mdr2 * _delta_star
|
|
491
|
+
nr_sqrt_mdr: float = 4 * _delta_star * _r_val
|
|
492
|
+
nr_sqrt_mdr2: float = nr_sqrt_mdr * _r_val
|
|
493
|
+
nr_sqrt_md2r2: float = nr_sqrt_mdr2 * _delta_star
|
|
488
494
|
|
|
489
|
-
nr_sqrt_t1 = nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
490
|
-
nr_sqrt_t2 = nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
491
|
-
nr_sqrt_t3 = nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
492
|
-
nr_sqrt_t4 = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
493
|
-
nr_sqrt_t5 = _s_1 * (6 * _r_val - 2) + 1
|
|
495
|
+
nr_sqrt_t1: ArrayDouble = nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
496
|
+
nr_sqrt_t2: ArrayDouble = nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
497
|
+
nr_sqrt_t3: ArrayDouble = nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
498
|
+
nr_sqrt_t4: ArrayDouble = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
499
|
+
nr_sqrt_t5: ArrayDouble = _s_1 * (6 * _r_val - 2) + 1
|
|
494
500
|
|
|
495
|
-
nr_t2_mdr =
|
|
501
|
+
nr_t2_mdr: ArrayDouble = (
|
|
502
|
+
nr_sqrt_t1 + nr_sqrt_t2 + nr_sqrt_t3 + nr_sqrt_t4 + nr_sqrt_t5
|
|
503
|
+
)
|
|
496
504
|
|
|
497
505
|
# Alternative grouping of terms in np.sqrt
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
)
|
|
501
|
-
nr_sqrt_s1 = _s_1 * (
|
|
506
|
+
nr_sqrt_nos1: float = nr_sqrt_md2r2 - nr_sqrt_mdr + 1
|
|
507
|
+
nr_sqrt_s1: ArrayDouble = _s_1 * (
|
|
502
508
|
-2 * nr_sqrt_md2r2 - nr_sqrt_mdr2 + 2 * nr_sqrt_mdr + 6 * _r_val - 2
|
|
503
509
|
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
510
|
+
nr_sqrt_s1sq: ArrayDouble = (_s_1**2) * (
|
|
511
|
+
nr_sqrt_md2r2 + nr_sqrt_mdr2 - nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
512
|
+
)
|
|
513
|
+
nr_t2_s1: ArrayDouble = nr_sqrt_s1sq + nr_sqrt_s1 + nr_sqrt_nos1
|
|
507
514
|
|
|
508
515
|
if not np.isclose(
|
|
509
|
-
np.einsum("i->", nr_t2_mdr
|
|
510
|
-
np.einsum("i->", nr_t2_s1
|
|
516
|
+
np.einsum("i->", nr_t2_mdr),
|
|
517
|
+
np.einsum("i->", nr_t2_s1),
|
|
511
518
|
rtol=0,
|
|
512
519
|
atol=0.5 * dps,
|
|
513
520
|
):
|
|
@@ -516,25 +523,28 @@ def diversion_share_boundary_xact_avg(
|
|
|
516
523
|
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
517
524
|
)
|
|
518
525
|
|
|
519
|
-
s_2 = (nr_t1 -
|
|
526
|
+
s_2: ArrayDouble = (nr_t1 - nr_t2_s1**0.5) / (2 * _r_val)
|
|
520
527
|
|
|
521
528
|
else:
|
|
522
|
-
_s_intcpt =
|
|
523
|
-
s_2 = (
|
|
524
|
-
|
|
529
|
+
_s_intcpt: float = _delta_star + 1 / 2 - np.abs(_delta_star - 1 / 2)
|
|
530
|
+
s_2: ArrayDouble = (
|
|
531
|
+
0.5
|
|
525
532
|
+ _delta_star
|
|
526
533
|
- _delta_star * _s_1
|
|
527
|
-
-
|
|
528
|
-
(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
534
|
+
- (
|
|
535
|
+
(
|
|
536
|
+
((_delta_star**2) - 1) * (_s_1**2)
|
|
537
|
+
+ (-2 * (_delta_star**2) + _delta_star + 1) * _s_1
|
|
538
|
+
+ (_delta_star**2)
|
|
539
|
+
- _delta_star
|
|
540
|
+
+ (1 / 4)
|
|
541
|
+
)
|
|
542
|
+
** 0.5
|
|
533
543
|
)
|
|
534
544
|
)
|
|
535
545
|
|
|
536
546
|
bdry_inner = np.stack((_s_1, s_2), axis=1)
|
|
537
|
-
bdry_end = np.array([(
|
|
547
|
+
bdry_end = np.array([(0.0, _s_intcpt)], float)
|
|
538
548
|
|
|
539
549
|
bdry = np.vstack((
|
|
540
550
|
bdry_end,
|
|
@@ -542,14 +552,14 @@ def diversion_share_boundary_xact_avg(
|
|
|
542
552
|
_bdry_start,
|
|
543
553
|
bdry_inner[:, ::-1],
|
|
544
554
|
bdry_end[:, ::-1],
|
|
545
|
-
))
|
|
555
|
+
))
|
|
546
556
|
s_2 = np.concatenate((np.array([_s_mid], float), s_2))
|
|
547
557
|
|
|
548
558
|
bdry_ends = [0, -1]
|
|
549
559
|
bdry_odds = np.array(range(1, len(s_2), 2), int)
|
|
550
560
|
bdry_evns = np.array(range(2, len(s_2), 2), int)
|
|
551
561
|
|
|
552
|
-
# Double the
|
|
562
|
+
# Double the area under the curve, and subtract the double counted bit.
|
|
553
563
|
bdry_area_simpson = 2 * _step_size * (
|
|
554
564
|
(4 / 3) * np.sum(s_2.take(bdry_odds))
|
|
555
565
|
+ (2 / 3) * np.sum(s_2.take(bdry_evns))
|
|
@@ -779,6 +789,7 @@ def round_cust(
|
|
|
779
789
|
|
|
780
790
|
|
|
781
791
|
def boundary_plot(
|
|
792
|
+
_plt: ModuleType,
|
|
782
793
|
*,
|
|
783
794
|
mktshare_plot_flag: bool = True,
|
|
784
795
|
mktshare_axes_flag: bool = True,
|
|
@@ -791,11 +802,11 @@ def boundary_plot(
|
|
|
791
802
|
if backend == "pgf":
|
|
792
803
|
mpl.use("pgf")
|
|
793
804
|
|
|
794
|
-
|
|
805
|
+
mpl.rcParams.update({
|
|
806
|
+
"text.usetex": True,
|
|
795
807
|
"pgf.rcfonts": False,
|
|
796
808
|
"pgf.texsystem": "lualatex",
|
|
797
809
|
"pgf.preamble": "\n".join([
|
|
798
|
-
R"\pdfvariable minorversion=7",
|
|
799
810
|
R"\usepackage{fontspec}",
|
|
800
811
|
R"\usepackage{luacode}",
|
|
801
812
|
R"\begin{luacode}",
|
|
@@ -807,27 +818,40 @@ def boundary_plot(
|
|
|
807
818
|
R' "luaotfload.patch_font", embedfull, "embedfull"'
|
|
808
819
|
R")",
|
|
809
820
|
R"\end{luacode}",
|
|
810
|
-
R"\setmainfont{STIX Two Text}",
|
|
811
|
-
r"\setsansfont{Fira Sans Light}",
|
|
812
|
-
R"\setmonofont[Scale=MatchLowercase,]{Fira Mono}",
|
|
813
821
|
R"\defaultfontfeatures[\rmfamily]{",
|
|
814
822
|
R" Ligatures={TeX, Common},",
|
|
815
823
|
R" Numbers={Proportional, Lining},",
|
|
816
824
|
R" }",
|
|
817
|
-
R"\defaultfontfeatures[\sffamily]{",
|
|
825
|
+
R"\defaultfontfeatures[\sffamily, \dvsfamily]{",
|
|
818
826
|
R" Ligatures={TeX, Common},",
|
|
819
827
|
R" Numbers={Monospaced, Lining},",
|
|
820
828
|
R" LetterSpace=0.50,",
|
|
821
829
|
R" }",
|
|
830
|
+
R"\setmainfont{STIX Two Text}",
|
|
831
|
+
R"\setsansfont{Fira Sans Light}",
|
|
832
|
+
R"\setmonofont[Scale=MatchLowercase,]{Fira Mono}",
|
|
833
|
+
R"\newfontfamily\dvsfamily{DejaVu Sans}",
|
|
822
834
|
R"\usepackage{mathtools}",
|
|
823
835
|
R"\usepackage{unicode-math}",
|
|
824
|
-
R"\setmathfont[math-style=ISO
|
|
836
|
+
R"\setmathfont{STIX Two Math}[math-style=ISO,range={scr,bfscr},StylisticSet=01]",
|
|
825
837
|
R"\usepackage[",
|
|
826
838
|
R" activate={true, nocompatibility},",
|
|
827
839
|
R" tracking=true,",
|
|
828
840
|
R" ]{microtype}",
|
|
829
841
|
]),
|
|
830
842
|
})
|
|
843
|
+
else:
|
|
844
|
+
if backend:
|
|
845
|
+
mpl.use(backend)
|
|
846
|
+
mpl.rcParams.update({
|
|
847
|
+
"text.usetex": True,
|
|
848
|
+
"pgf.rcfonts": True,
|
|
849
|
+
"font.family": "sans-serif",
|
|
850
|
+
"font.sans-serif": ["DejaVu Sans", "sans-serif"],
|
|
851
|
+
"font.monospace": ["DejaVu Mono", "monospace"],
|
|
852
|
+
"font.serif": ["stix", "serif"],
|
|
853
|
+
"mathtext.fontset": "stix",
|
|
854
|
+
})
|
|
831
855
|
|
|
832
856
|
# Initialize a canvas with a single figure (set of axes)
|
|
833
857
|
fig_ = plt.figure(figsize=(5, 5), dpi=600)
|
|
@@ -907,6 +931,38 @@ def boundary_plot(
|
|
|
907
931
|
for _t in axl_[::2]:
|
|
908
932
|
_t.set_visible(False)
|
|
909
933
|
|
|
934
|
+
# package version badge
|
|
935
|
+
# https://futurile.net/2016/03/14/partial-colouring-text-in-matplotlib-with-latex/
|
|
936
|
+
badge_fmt_str = R"{{\dvsfamily {}}}" if backend == "pgf" else "{}"
|
|
937
|
+
badge_txt_list = [
|
|
938
|
+
badge_fmt_str.format(_s) for _s in [f"{_PKG_NAME}", f"v{VERSION}"]
|
|
939
|
+
]
|
|
940
|
+
|
|
941
|
+
badge_fmt_list = [
|
|
942
|
+
_btp := {"color": "#fff", "backgroundcolor": "#555", "size": 2},
|
|
943
|
+
_btp | {"backgroundcolor": "#007ec6"},
|
|
944
|
+
]
|
|
945
|
+
|
|
946
|
+
badge_box = mof.HPacker(
|
|
947
|
+
sep=2.6,
|
|
948
|
+
children=[
|
|
949
|
+
mof.TextArea(_t, textprops=_f)
|
|
950
|
+
for _t, _f in zip(badge_txt_list, badge_fmt_list)
|
|
951
|
+
],
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
ax0_.add_artist(
|
|
955
|
+
mof.AnnotationBbox(
|
|
956
|
+
badge_box,
|
|
957
|
+
xy=(0.5, 0.5),
|
|
958
|
+
xybox=(-0.05, -0.12),
|
|
959
|
+
xycoords="data",
|
|
960
|
+
boxcoords="data",
|
|
961
|
+
frameon=False,
|
|
962
|
+
pad=0,
|
|
963
|
+
)
|
|
964
|
+
)
|
|
965
|
+
|
|
910
966
|
# Axis labels
|
|
911
967
|
if mktshare_axes_flag:
|
|
912
968
|
# x-axis
|
|
@@ -122,9 +122,9 @@ def diversion_share_boundary_qdtr_wtd_avg(
|
|
|
122
122
|
Array of share-pairs, area under boundary.
|
|
123
123
|
|
|
124
124
|
"""
|
|
125
|
-
_delta_star = mpf(f"{_delta_star}")
|
|
125
|
+
_delta_star, _r_val = (mpf(_v) for _v in (f"{_delta_star}", f"{_r_val}"))
|
|
126
126
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
127
|
-
s_naught = 0
|
|
127
|
+
s_naught = mpf("0.0")
|
|
128
128
|
|
|
129
129
|
s_1, s_2 = symbols("s_1:3", positive=True)
|
|
130
130
|
|
|
@@ -143,19 +143,17 @@ def diversion_share_boundary_qdtr_wtd_avg(
|
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
_bdry_func = solve(_bdry_eqn, s_2)[0]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* (
|
|
146
|
+
|
|
147
|
+
if recapture_form == "inside-out":
|
|
148
|
+
s_naught = mpf(solve(simplify(_bdry_eqn.subs({s_2: 1 - s_1})), s_1)[0])
|
|
149
|
+
|
|
150
|
+
bdry_area = mp.fmul(
|
|
151
|
+
"2",
|
|
152
|
+
(
|
|
154
153
|
s_naught
|
|
155
154
|
+ mp.quad(lambdify(s_1, _bdry_func, "mpmath"), (s_naught, _s_mid))
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
)
|
|
155
|
+
),
|
|
156
|
+
) - (_s_mid**2 + s_naught**2)
|
|
159
157
|
|
|
160
158
|
case "cross-product-share":
|
|
161
159
|
mp.trap_complex = False
|
|
@@ -173,14 +171,14 @@ def diversion_share_boundary_qdtr_wtd_avg(
|
|
|
173
171
|
)
|
|
174
172
|
|
|
175
173
|
_bdry_func = solve(_bdry_eqn, s_2)[1]
|
|
176
|
-
bdry_area =
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
bdry_area = (
|
|
175
|
+
mp.fmul(
|
|
176
|
+
"2",
|
|
179
177
|
mp.quad(
|
|
180
178
|
lambdify(s_1, _bdry_func.subs({d_star: _delta_star}), "mpmath"),
|
|
181
179
|
(0, _s_mid),
|
|
182
|
-
)
|
|
183
|
-
)
|
|
180
|
+
).real,
|
|
181
|
+
)
|
|
184
182
|
- _s_mid**2
|
|
185
183
|
)
|
|
186
184
|
|
|
@@ -199,13 +197,13 @@ def diversion_share_boundary_qdtr_wtd_avg(
|
|
|
199
197
|
)
|
|
200
198
|
|
|
201
199
|
_bdry_func = solve(_bdry_eqn, s_2)[0]
|
|
202
|
-
bdry_area =
|
|
203
|
-
2
|
|
200
|
+
bdry_area = (
|
|
201
|
+
mp.fmul("2", mp.quad(lambdify(s_1, _bdry_func, "mpmath"), (0, _s_mid)))
|
|
204
202
|
- _s_mid**2
|
|
205
203
|
)
|
|
206
204
|
|
|
207
205
|
return GuidelinesBoundaryCallable(
|
|
208
|
-
lambdify(s_1, _bdry_func, "numpy"), bdry_area, s_naught
|
|
206
|
+
lambdify(s_1, _bdry_func, "numpy"), float(bdry_area), float(s_naught)
|
|
209
207
|
)
|
|
210
208
|
|
|
211
209
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Literal
|
|
6
6
|
|
|
7
|
+
import numexpr as ne
|
|
7
8
|
import numpy as np
|
|
8
9
|
from attrs import evolve
|
|
9
10
|
from numpy.random import SeedSequence
|
|
@@ -438,11 +439,15 @@ def gen_divr_array(
|
|
|
438
439
|
"""
|
|
439
440
|
divr_array: ArrayDouble
|
|
440
441
|
if _recapture_form == RECForm.FIXED:
|
|
441
|
-
|
|
442
|
+
_frmshr_array_rev = _frmshr_array[:, ::-1]
|
|
443
|
+
divr_array = ne.evaluate(
|
|
444
|
+
"_recapture_ratio * _frmshr_array_rev / (1 - _frmshr_array)"
|
|
445
|
+
)
|
|
442
446
|
|
|
443
447
|
else:
|
|
444
|
-
purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
445
|
-
|
|
448
|
+
purchprob_array = ne.evaluate("_aggregate_purchase_prob * _frmshr_array")
|
|
449
|
+
purchprob_array_rev = purchprob_array[:, ::-1] # noqa: F841
|
|
450
|
+
divr_array = ne.evaluate("purchprob_array_rev / (1 - purchprob_array)")
|
|
446
451
|
|
|
447
452
|
divr_assert_test = (
|
|
448
453
|
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
@@ -533,12 +538,12 @@ def gen_margin_price_data(
|
|
|
533
538
|
for _p in (_frmshr_array, _nth_firm_share)
|
|
534
539
|
)
|
|
535
540
|
case PriceSpec.RNG:
|
|
536
|
-
|
|
541
|
+
_price_array_gen = prng(_pr_rng_seed_seq).choice(
|
|
537
542
|
1 + np.arange(pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
538
543
|
)
|
|
539
|
-
price_array =
|
|
540
|
-
nth_firm_price =
|
|
541
|
-
|
|
544
|
+
price_array = _price_array_gen[:, :2]
|
|
545
|
+
nth_firm_price = _price_array_gen[:, [2]]
|
|
546
|
+
del _price_array_gen
|
|
542
547
|
case PriceSpec.CSY:
|
|
543
548
|
# TODO:
|
|
544
549
|
# evolve PCMRestriction (save running MNL test twice); evolve copy of _mkt_sample_spec=1q
|
|
@@ -559,26 +564,22 @@ def gen_margin_price_data(
|
|
|
559
564
|
getattr(margin_data, _f) for _f in ("pcm_array", "mnl_test_array")
|
|
560
565
|
)
|
|
561
566
|
|
|
562
|
-
price_array_here = 1 / (1 - pcm_array)
|
|
567
|
+
price_array_here = ne.evaluate("1 / (1 - pcm_array)")
|
|
563
568
|
price_array = price_array_here[:, :2]
|
|
564
|
-
nth_firm_price = price_array_here[:, [-1]]
|
|
569
|
+
nth_firm_price = price_array_here[:, [-1]] # noqa: F841
|
|
565
570
|
if _pcm_spec.pcm_restriction == PCMRestriction.MNL:
|
|
566
571
|
# Generate i.i.d. PCMs then take PCM0 and construct PCM1
|
|
567
572
|
# Regenerate MNL test
|
|
568
|
-
purchase_prob_array =
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
1 - purchase_prob_array[:, [1]],
|
|
579
|
-
)
|
|
580
|
-
),
|
|
581
|
-
1 + m1_nr,
|
|
573
|
+
purchase_prob_array = ne.evaluate(
|
|
574
|
+
"_aggregate_purchase_prob * _frmshr_array"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
_pr_0, _pi_0 = price_array_here[:, [0]], purchase_prob_array[:, [0]]
|
|
578
|
+
_pcm_0 = pcm_array[:, [0]]
|
|
579
|
+
_pr_1, _pi_1 = price_array_here[:, [1]], purchase_prob_array[:, [1]]
|
|
580
|
+
|
|
581
|
+
pcm_array[:, [1]] = ne.evaluate(
|
|
582
|
+
"_pcm_0 * (1 - _pi_0) / (1 - _pi_1 + _pcm_0 * (_pi_1 - _pi_0))"
|
|
582
583
|
)
|
|
583
584
|
mnl_test_array = (pcm_array[:, [1]] >= 0) & (pcm_array[:, [1]] <= 1)
|
|
584
585
|
|
|
@@ -599,8 +600,8 @@ def gen_margin_price_data(
|
|
|
599
600
|
)
|
|
600
601
|
|
|
601
602
|
# _price_array = _price_array.astype(np.float64)
|
|
602
|
-
rev_array = price_array * _frmshr_array
|
|
603
|
-
nth_firm_rev = nth_firm_price * _nth_firm_share
|
|
603
|
+
rev_array = ne.evaluate("price_array * _frmshr_array")
|
|
604
|
+
nth_firm_rev = ne.evaluate("nth_firm_price * _nth_firm_share")
|
|
604
605
|
|
|
605
606
|
# Although `_test_rev_ratio_inv` is not fixed at 10%,
|
|
606
607
|
# the ratio has not changed since inception of the HSR filing test,
|
|
@@ -616,7 +617,7 @@ def gen_margin_price_data(
|
|
|
616
617
|
# Revenue ratio has been 10-to-1 since inception
|
|
617
618
|
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
618
619
|
# ratio restriction on the merging firms' revenues
|
|
619
|
-
rev_ratio = (rev_array.min(axis=1)
|
|
620
|
+
rev_ratio = np.divide(rev_array.min(axis=1), rev_array.max(axis=1)).round(4)
|
|
620
621
|
hsr_filing_test = rev_ratio >= test_rev_ratio_inv
|
|
621
622
|
# del _rev_array, _rev_ratio
|
|
622
623
|
case SSZConstant.HSR_NTH:
|
|
@@ -721,16 +722,12 @@ def _gen_margin_data(
|
|
|
721
722
|
# Impose FOCs from profit-maximization with MNL demand
|
|
722
723
|
purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
723
724
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
),
|
|
731
|
-
np.einsum(
|
|
732
|
-
"ij,ij->ij", _price_array[:, [1]], 1 - purchase_prob_array[:, [1]]
|
|
733
|
-
),
|
|
725
|
+
_pr_0, _pi_0 = _price_array[:, [0]], purchase_prob_array[:, [0]]
|
|
726
|
+
_pcm_0 = pcm_array[:, [0]]
|
|
727
|
+
_pr_1, _pi_1 = _price_array[:, [1]], purchase_prob_array[:, [1]]
|
|
728
|
+
|
|
729
|
+
pcm_array[:, [1]] = ne.evaluate(
|
|
730
|
+
"_pr_0 * _pcm_0 * (1 - _pi_0) / (_pr_1 * (1 - _pi_1))"
|
|
734
731
|
)
|
|
735
732
|
|
|
736
733
|
mnl_test_array = (pcm_array[:, 1] >= 0) & (pcm_array[:, 1] <= 1)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mergeron
|
|
3
|
-
Version: 2025.739355.
|
|
3
|
+
Version: 2025.739355.2
|
|
4
4
|
Summary: Python for analyzing merger enforcement policy
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: merger enforcement policy,merger guidelines,merger screening,enforcement presumptions,concentration standards,diversion ratio,upward pricing pressure,GUPPI
|
|
7
7
|
Author: Murthy Kambhampaty
|
|
8
8
|
Author-email: smk@capeconomics.com
|
|
9
|
-
Requires-Python: >=3.12
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: End Users/Desktop
|
|
@@ -14,10 +14,9 @@ Classifier: Intended Audience :: Science/Research
|
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
20
|
Requires-Dist: aenum (>=3.1.15)
|
|
22
21
|
Requires-Dist: attrs (>=25.3.0)
|
|
23
22
|
Requires-Dist: beautifulsoup4 (>=4.13.3)
|
|
@@ -28,6 +27,7 @@ Requires-Dist: joblib (>=1.4.2)
|
|
|
28
27
|
Requires-Dist: lxml (>=5.3.2)
|
|
29
28
|
Requires-Dist: matplotlib (>=3.10.1)
|
|
30
29
|
Requires-Dist: mpmath (>=1.3.0)
|
|
30
|
+
Requires-Dist: numexpr (>=2.10.2,<3.0.0)
|
|
31
31
|
Requires-Dist: python-calamine (>=0.3.2)
|
|
32
32
|
Requires-Dist: ruamel-yaml (>=0.18.10)
|
|
33
33
|
Requires-Dist: scipy (>=1.15.2)
|
|
@@ -89,5 +89,5 @@ To install the package, use the following shell command:
|
|
|
89
89
|
Documentation
|
|
90
90
|
-------------
|
|
91
91
|
|
|
92
|
-
Usage guide and API reference
|
|
92
|
+
Usage guide and API reference are `available <https://capeconomics.github.io/mergeron/>`_.
|
|
93
93
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
mergeron/__init__.py,sha256=
|
|
1
|
+
mergeron/__init__.py,sha256=uuqwnqQy0tnwO6matahMTWWeMOKMSyDINJfakP7fd3Y,5787
|
|
2
2
|
mergeron/core/__init__.py,sha256=4Y_q-Qu7gXENVKHS-lNebn5mPZDy9oPHFwUV7fAW9Nw,3269
|
|
3
3
|
mergeron/core/empirical_margin_distribution.py,sha256=aqQ7JYpliHSjHpzyPRkYW9LhJfp-aAlSifRxYx3Dmbo,11623
|
|
4
|
-
mergeron/core/ftc_merger_investigations_data.py,sha256=
|
|
4
|
+
mergeron/core/ftc_merger_investigations_data.py,sha256=ccPN81wE6Z-jUguXtAr01JUJVovWzP8Vxlha9g3r8yI,28788
|
|
5
5
|
mergeron/core/guidelines_boundaries.py,sha256=noacM3NmhzqPKLPGm7HEvLKX2UlRI1DCw1kxDa2cFXk,15586
|
|
6
|
-
mergeron/core/guidelines_boundary_functions.py,sha256=
|
|
7
|
-
mergeron/core/guidelines_boundary_functions_extra.py,sha256=
|
|
6
|
+
mergeron/core/guidelines_boundary_functions.py,sha256=GHDNYXMZSFkkNz29x4JmdodBKyANAL1uocnTB8FGxV0,30339
|
|
7
|
+
mergeron/core/guidelines_boundary_functions_extra.py,sha256=hRhROaLJoRtTrtd-dNkpAGPOEeY1w_qXQHKIxfALaG4,22070
|
|
8
8
|
mergeron/core/pseudorandom_numbers.py,sha256=-mPveXjJJ446NrBMAmWIa2jI6j0Px0xcCJTGEEsn3bo,10149
|
|
9
9
|
mergeron/data/__init__.py,sha256=CbqheFSkXEe7NOfuAV-NLaaEiNzl9pVCndGjtUUOj9g,1846
|
|
10
10
|
mergeron/data/damodaran_margin_data_serialized.zip,sha256=Wc1v9buSrYTWWAravG8W9nPbgsU07zMtSAR2RvMQU5s,623482
|
|
11
11
|
mergeron/data/ftc_merger_investigations_data.zip,sha256=tiB2TLFyS9LMSFIv8DBA_oEEx12DU4MyjHni4NlsRMU,24002
|
|
12
12
|
mergeron/gen/__init__.py,sha256=bRGGIFBqIVw4-zZ4AKwcNbOTM9aHKegPj7w_7MJV9-I,23856
|
|
13
13
|
mergeron/gen/data_generation.py,sha256=L44YNtxso-Ya50YT71rnG-el4_PgGn4vtoA7rFDD194,17487
|
|
14
|
-
mergeron/gen/data_generation_functions.py,sha256=
|
|
14
|
+
mergeron/gen/data_generation_functions.py,sha256=GQaeIzdiBXrYWMb9-kO2QPBY7YAVtUxaz1o1bqgnbw0,26126
|
|
15
15
|
mergeron/gen/enforcement_stats.py,sha256=etTax-sBSn8DveF-IxuBJDdX0XSBD6oFU9vaZe6cYks,14387
|
|
16
16
|
mergeron/gen/upp_tests.py,sha256=gRJISQ2jGmIDmFOvaTIkvYooI4mK-QbgkfgL46RrRio,7445
|
|
17
17
|
mergeron/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
18
|
-
mergeron-2025.739355.
|
|
19
|
-
mergeron-2025.739355.
|
|
20
|
-
mergeron-2025.739355.
|
|
18
|
+
mergeron-2025.739355.2.dist-info/METADATA,sha256=dM_917MY6amh3JiasPM8cfk1xKu8N_75RYJmEyOX2Ok,3857
|
|
19
|
+
mergeron-2025.739355.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
20
|
+
mergeron-2025.739355.2.dist-info/RECORD,,
|
|
File without changes
|