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 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.0"
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=24, floatmode="fixed")
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 +": 7000,
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 +": 2500,
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 reverse_map(_dict: Mapping[Any, Any]) -> Mapping[Any, Any]:
94
- """Reverse a mapping."""
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
- col_titles_array = tuple(CONC_DELTA_DICT.values())
614
- col_totals: ArrayBIGINT = np.zeros(len(col_titles_array), int)
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 = CONC_HHI_DICT[row_title]
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
- row_array_list: list[list[int]] = []
627
+ data_row_list: list[list[int]] = []
624
628
  while row_list:
625
629
  enfd_val, clsd_val = row_list.pop(0).split("/")
626
- row_array_list += [
630
+ data_row_list += [
627
631
  [
628
632
  row_key,
629
- col_titles_array[len(row_array_list)],
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
- row_array = np.array(row_array_list, int)
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", row_array[:, 2:4]))
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 = row_array
645
+ col_totals = data_row_array
641
646
  else:
642
647
  invdata_array = (
643
- np.vstack((invdata_array, row_array))
648
+ np.vstack((invdata_array, data_row_array))
644
649
  if invdata_array.shape
645
- else row_array
650
+ else data_row_array
646
651
  )
647
- del row_array, row_array_list
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 DEFAULT_REC_RATIO, VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
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 = mp.power(10, -dps)
479
+ _step_size = 10**-dps
473
480
 
474
481
  _bdry_start = np.array([(_s_mid, _s_mid)])
475
- _s_1 = np.array(mp.arange(_s_mid - _step_size, 0, -_step_size))
482
+ _s_1 = np.arange(_s_mid - _step_size, 0, -_step_size)
476
483
  if recapture_form == "inside-out":
477
- _s_intcpt = mp.fdiv(
478
- mp.fsub(
479
- 2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
480
- ),
481
- 2 * mpf(f"{_r_val}"),
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 = nr_sqrt_t1 + nr_sqrt_t2 + nr_sqrt_t3 + nr_sqrt_t4 + nr_sqrt_t5
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
- nr_sqrt_s1sq = (_s_1**2) * (
499
- nr_sqrt_md2r2 + nr_sqrt_mdr2 - nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
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
- nr_sqrt_nos1 = nr_sqrt_md2r2 - nr_sqrt_mdr + 1
505
-
506
- nr_t2_s1 = nr_sqrt_s1sq + nr_sqrt_s1 + nr_sqrt_nos1
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.astype(float)),
510
- np.einsum("i->", nr_t2_s1.astype(float)),
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 - np.sqrt(nr_t2_s1)) / (2 * _r_val)
526
+ s_2: ArrayDouble = (nr_t1 - nr_t2_s1**0.5) / (2 * _r_val)
520
527
 
521
528
  else:
522
- _s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
523
- s_2 = (
524
- (1 / 2)
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
- - np.sqrt(
528
- ((_delta_star**2) - 1) * (_s_1**2)
529
- + (-2 * (_delta_star**2) + _delta_star + 1) * _s_1
530
- + (_delta_star**2)
531
- - _delta_star
532
- + (1 / 4)
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([(mpf("0.0"), _s_intcpt)], float)
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
- )).astype(float)
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 are under the curve, and subtract the double counted bit.
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
- plt.rcParams.update({
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]{STIX Two Math}",
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
- s_naught = (
147
- float(solve(simplify(_bdry_eqn.subs({s_2: 1 - s_1})), s_1)[0]) # type: ignore
148
- if recapture_form == "inside-out"
149
- else 0
150
- )
151
- bdry_area = float(
152
- 2
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
- - (_s_mid**2 + s_naught**2)
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 = float(
177
- 2
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
- ).real
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 = float(
203
- 2 * (mp.quad(lambdify(s_1, _bdry_func, "mpmath"), (0, _s_mid)))
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
- divr_array = _recapture_ratio * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
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
- divr_array = purchprob_array[:, ::-1] / (1 - purchprob_array)
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
- price_array_gen = prng(_pr_rng_seed_seq).choice(
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 = price_array_gen[:, :2]
540
- nth_firm_price = price_array_gen[:, [2]]
541
- # del _price_array_gen
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 = _aggregate_purchase_prob * _frmshr_array
569
- pcm_array[:, 1] = np.divide(
570
- (
571
- m1_nr := np.divide(
572
- np.einsum(
573
- "ij,ij,ij->ij",
574
- price_array[:, [0]],
575
- pcm_array[:, [0]],
576
- 1 - purchase_prob_array[:, [0]],
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) / rev_array.max(axis=1)).round(4)
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
- pcm_array[:, [1]] = np.divide(
725
- np.einsum(
726
- "ij,ij,ij->ij",
727
- _price_array[:, [0]],
728
- pcm_array[:, [0]],
729
- 1 - purchase_prob_array[:, [0]],
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.0
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 available `here <https://capeconomics.github.io/mergeron/>`_.
92
+ Usage guide and API reference are `available <https://capeconomics.github.io/mergeron/>`_.
93
93
 
@@ -1,20 +1,20 @@
1
- mergeron/__init__.py,sha256=qjmFjh6yCKnTzrgdmI7KamtTpqwtjZrAtJmc9piXGHo,5773
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=k4TDkP1rDBmN4uKOYF0SUvSRkYmyVhbsBvLUKDYJqOo,28537
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=ZTx4uaaR-3Qcc3Uh27S_shIu-4eyW8rLR2Lj13WvDX0,28513
7
- mergeron/core/guidelines_boundary_functions_extra.py,sha256=JompgWmwnwcWsodXrZbvzY_OXw-7ppb_H_Gsz9-fpgI,22080
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=f6aLBg6JNqOoBq57k3ovevgvtaLfIAfT-ATzZl11CEA,26076
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.0.dist-info/METADATA,sha256=DwSbyhgSZPnJ6V89zcswJC98ojEDui7Y3zJkrrZc0v4,3865
19
- mergeron-2025.739355.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
20
- mergeron-2025.739355.0.dist-info/RECORD,,
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,,